using AcDream.Content.CharGen; using AcDream.Core.CharGen; using DatReaderWriter; using DatReaderWriter.Options; namespace AcDream.Content.Tests.CharGen; /// /// Installed-DAT gate for : proves the real /// CharGen table (portal.dat 0x0E000002) loads through the SAME /// production uses and lands in a /// plausible shape. Env-gated like the rest of Content.Tests' installed-DAT /// suite () — skips /// cleanly (with a console note) when no DAT directory is configured, /// matching e.g. PakEquivalenceTests / RetailDatLoaderTests. /// public sealed class ChargenTableReaderInstalledDatTests { // ACE ACE.Entity.Enum.HeritageGroup — the four heritages CC1's spec // calls out by name. private const uint AluvianId = 1u; private const uint GharundimId = 2u; private const uint ShoId = 3u; private const uint ViamontianId = 4u; private const uint OlthoiId = 12u; private const uint OlthoiAcidId = 13u; [Fact] public void InstalledCharGenTable_LoadsAndHasThirteenHeritageGroups() { string? datDir = ContentConformanceDats.ResolveDatDir(); if (datDir is null) { Console.WriteLine("SKIP: installed retail DAT directory is unavailable."); return; } using var dats = new DatCollection(datDir, DatAccessType.Read); using var adapter = new DatCollectionAdapter(dats); ChargenOptions options = ChargenTableReader.Load(adapter); // ACE's loader comment: "11 standard player races and 2 Olthoi". Assert.Equal(13, options.HeritagesById.Count); Assert.NotEmpty(options.StarterAreas); } [Fact] public void InstalledCharGenTable_HasTheFourNamedHeritagesWithRetailDisplayNames() { string? datDir = ContentConformanceDats.ResolveDatDir(); if (datDir is null) { Console.WriteLine("SKIP: installed retail DAT directory is unavailable."); return; } using var dats = new DatCollection(datDir, DatAccessType.Read); using var adapter = new DatCollectionAdapter(dats); ChargenOptions options = ChargenTableReader.Load(adapter); Assert.True(options.TryGetHeritage(AluvianId, out ChargenHeritageOptions aluvian)); Assert.Equal("Aluvian", aluvian.Name); Assert.True(options.TryGetHeritage(GharundimId, out ChargenHeritageOptions gharundim)); Assert.Equal("Gharu'ndim", gharundim.Name); Assert.True(options.TryGetHeritage(ShoId, out ChargenHeritageOptions sho)); Assert.Equal("Sho", sho.Name); Assert.True(options.TryGetHeritage(ViamontianId, out ChargenHeritageOptions viamontian)); Assert.Equal("Viamontian", viamontian.Name); Assert.True(options.TryGetHeritage(OlthoiId, out ChargenHeritageOptions olthoi)); Assert.True(olthoi.IsOlthoi); Assert.True(options.TryGetHeritage(OlthoiAcidId, out ChargenHeritageOptions olthoiAcid)); Assert.True(olthoiAcid.IsOlthoi); } [Fact] public void InstalledHeritages_EachHasAtLeastOneGenderWithNonEmptyAppearanceOptions() { string? datDir = ContentConformanceDats.ResolveDatDir(); if (datDir is null) { Console.WriteLine("SKIP: installed retail DAT directory is unavailable."); return; } using var dats = new DatCollection(datDir, DatAccessType.Read); using var adapter = new DatCollectionAdapter(dats); ChargenOptions options = ChargenTableReader.Load(adapter); Assert.NotEmpty(options.HeritagesById); foreach (ChargenHeritageOptions heritage in options.HeritagesById.Values) { Assert.NotEmpty(heritage.GendersByKey); Assert.Contains( heritage.GendersByKey.Values, gender => gender.HasAnyAppearanceOptions); } } /// /// Every template's six attributes fall within retail's 10..100 range, /// and no template's total exceeds its heritage's AttributeCredits /// budget. NOT every template fully spends the budget — each of the /// four human heritages (and Olthoi Acid) ships an "Adventurer" template /// sitting at the floor (60 of a 330 budget; confirmed against the /// installed DAT), which is retail's "Custom" starting point delivered /// as a real TemplateCG entry rather than a special-cased UI-only /// option. Every OTHER named human template (Bow Hunter, Swashbuckler, /// Life Caster, War Mage, Wayfarer, Soldier) exactly exhausts its /// heritage's credits, and Olthoi's single "Ripper" template exactly /// exhausts its (much smaller, non-customizable) 60-credit budget — so /// SOME template somewhere fully spends its budget, even though no /// single heritage is guaranteed to (Olthoi Acid's only template is the /// unspent "Adventurer" one). /// [Fact] public void InstalledHeritages_EveryTemplateAttributeSpreadFitsTheAttributeBudget() { string? datDir = ContentConformanceDats.ResolveDatDir(); if (datDir is null) { Console.WriteLine("SKIP: installed retail DAT directory is unavailable."); return; } using var dats = new DatCollection(datDir, DatAccessType.Read); using var adapter = new DatCollectionAdapter(dats); ChargenOptions options = ChargenTableReader.Load(adapter); int templatesChecked = 0; bool anyFullySpentAnywhere = false; foreach (ChargenHeritageOptions heritage in options.HeritagesById.Values) { foreach (ChargenTemplate template in heritage.Templates) { templatesChecked++; Assert.True( ChargenAttributeMath.AreAllWithinRange(template.Attributes), $"{heritage.Name}/{template.Name}: an attribute fell outside " + $"{ChargenAttributeMath.AttributeMin}..{ChargenAttributeMath.AttributeMax} " + $"({template.Attributes})."); Assert.True( template.Attributes.Total <= heritage.AttributeCredits, $"{heritage.Name}/{template.Name}: attribute spread totals " + $"{template.Attributes.Total}, exceeding the {heritage.AttributeCredits}-credit budget."); anyFullySpentAnywhere |= ChargenAttributeMath.IsFullySpent(heritage.AttributeCredits, template.Attributes); } } Assert.True(templatesChecked > 0, "Expected at least one profession template across all heritages."); Assert.True( anyFullySpentAnywhere, "Expected at least one named preset template to fully spend its heritage's credit budget."); } [Fact] public void InstalledHeritages_PrimaryAndSecondaryStartAreaIndicesResolveIntoTheSharedList() { string? datDir = ContentConformanceDats.ResolveDatDir(); if (datDir is null) { Console.WriteLine("SKIP: installed retail DAT directory is unavailable."); return; } using var dats = new DatCollection(datDir, DatAccessType.Read); using var adapter = new DatCollectionAdapter(dats); ChargenOptions options = ChargenTableReader.Load(adapter); int indicesChecked = 0; foreach (ChargenHeritageOptions heritage in options.HeritagesById.Values) { foreach (int index in heritage.PrimaryStartAreaIndices.Concat(heritage.SecondaryStartAreaIndices)) { indicesChecked++; Assert.True( options.TryGetStarterArea(index, out ChargenStarterArea area), $"{heritage.Name}: start-area index {index} does not resolve into the shared StarterAreas list."); Assert.False(string.IsNullOrEmpty(area.Name)); } } Assert.True(indicesChecked > 0, "Expected at least one heritage start-area reference."); } [Fact] public void InstalledHeritages_SkillCostsResolveToKnownWireSkillIds() { string? datDir = ContentConformanceDats.ResolveDatDir(); if (datDir is null) { Console.WriteLine("SKIP: installed retail DAT directory is unavailable."); return; } using var dats = new DatCollection(datDir, DatAccessType.Read); using var adapter = new DatCollectionAdapter(dats); ChargenOptions options = ChargenTableReader.Load(adapter); Assert.True(options.TryGetHeritage(AluvianId, out ChargenHeritageOptions aluvian)); Assert.NotEmpty(aluvian.SkillCostsBySkillId); foreach (var pair in aluvian.SkillCostsBySkillId) { Assert.Equal(pair.Key, pair.Value.SkillId); Assert.InRange(pair.Key, 1u, 54u); Assert.True(pair.Value.NormalCost >= 0); Assert.True(pair.Value.PrimaryCost >= 0); } } }