Repository navigation
feat: add the Species root type (HXML-37) - #13
Conversation
World data defines the species. Each species has a key that saves and content use, a wire id, its name forms, its origin class, a player option, an optional parent (BranchOf), its element affinity and resistance, and its bodies. Each body has a key, a wire id that is unique across all species, an optional display name, a default flag and a retail skin colour (0 to 9). - Species.xsd, with xs:unique rules for body keys and body ids in a species. Pin the two new enums, SpeciesOrigin and SpeciesPlayerOption, in tools/enum-values.json. - Species loads on start from the species directory. "species" is an invariant plural, so WorldDataDirectory.NameFor returns it as it is. - Helpers: DefaultBody, TryGetBody, GetParent, and GetAffinity and GetResistance, which give a branch its parent's values. - ValidateAll checks unique species ids and body ids across all species, and Validate checks each species: bodies for a playable species, one default body, body keys and ids, and the branch rules. - Validate(ElementTable) checks Affinity and Resistance against a table. The server calls it with the active table. - A shared SchemaValidation helper for the tests. The element table tests use it. - Regenerate src/Objects. Each generated header lists the schema files, so the header of every generated file changes. Refs: HXML-37, HS-1520
- Pin SpeciesPlayerOption.Locked to 0. The loader does not check files against the schema (HXML-35), so a species without PlayerOption gets the value 0. With Starter at 0, players could pick it. - ValidateAll reports species keys that differ only in case. They share one lower-case index key. - Validate reports a species without Names, and a branch that is ByBranch. - Use ToLowerInvariant for the index key. - Validate(ElementTable) rejects a null table. - SpeciesKey derives from ElementKey, so the two key forms stay the same. - Move the Duplicates helper of ElementTable into EnumerableExtension, and use it in Species. Refs: HXML-37
- ValidateAll checks the three rules across species (keys that differ only in case, species ids, body ids) with one local helper. The case check is marked as a stopgap until HXML-34. - TryGetBody is a loop, as ElementTable.TryGetElement is. - Validate counts the default bodies once. - ElementTable calls Duplicates with its default comparer. - Tests: Starter and Playable constants, Kobold and Vosk built with Make, and a comment on each theory row. Refs: HXML-37
Validate rejects a species key or a body key that is empty, blank, or has a space at the start or the end. Keys are saved and the loader does not trim them, so a padded key is a different key. Validate rejects it instead of trimming it. Found in the review of the Trait type (xml PR 12), which has the same check for names. Refs: HXML-37
- Species.TryGet finds a loaded species by key without case, as TryGetBody does for bodies. The store compares primary keys with case, so the server uses TryGet, not the store key. GetParent uses it. - The BranchOf comment and the GetParent summary say that a branch takes only Affinity and Resistance from its parent. - The trait tests use the shared SchemaValidation helper. Refs: HXML-37
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #13 +/- ##
==========================================
+ Coverage 69.27% 70.97% +1.69%
==========================================
Files 44 45 +1
Lines 1660 1757 +97
Branches 396 432 +36
==========================================
+ Hits 1150 1247 +97
Misses 402 402
Partials 108 108 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
imbas-forosnai
left a comment
There was a problem hiding this comment.
Review from Imbas (an AI collaborator working with @baughj), posting from its own account. This was my own pass only, with no Codex round. It's a comment review, not an approval: @baughj is the requested reviewer.
Nothing blocking. The schema, the model and the validation match HXML-37 and the registry plan, and each stopgap names its ticket. There's one low and two notes.
Low
Validate checks that Names exists, but not the forms inside it. A species with <Names Singular="Gnoll" /> deserializes with Plural and Adjective null, and Validate returns no errors. Singular="" and Plural=" " also pass. The loader doesn't apply the schema (HXML-35), and the plan sends the name forms to Brigid in the registry message, so a missing form reaches the client as null. #12 settled the same question for trait names by rejecting empty, blank and padded values. For display text, the part that matters is rejecting an empty or blank form. Padding matters less here, because these aren't keys.
Notes
- A
ByBranchparent that no species branches from validates clean. Nobody can pick it, so it's harmless, but it's probably a content mistake: if a branch has a typo inBranchOf, the branch gets an error and its intended parent says nothing.ValidateAllalready has every species in hand if you want to report it. Your call. - A species file with no
Keyattribute fails to load withCannot perform runtime binding on a null reference, becauseXmlStorecallskey.ToString()on a null primary key. SoValidate's "Key is empty" message is reachable only for""or whitespace. Every type with a nullablePrimaryKeybehaves this way, so it isn't this PR's to fix. It's one more required attribute for HXML-35, and today it has an opaque error.
Verified
- 259/259 tests pass against ceridwen
main(6052cae). tools/regenerate.shon Linux reproducessrc/Objects/byte for byte. The 255 one-line files change only thexscgenheader line, which gainsSpecies.xsd.- I ran the
Namescases above and the missingKeyon a copy of the PR head; they weren't only read from source. A branch whoseBranchOfnames itself gets one error, andGetAffinityreturns null without recursing. - The package version is the maintainers' call. The plan calls this step a minor release.
Validate checked that Names exists, but not the forms in it. A species with no Plural, or with Singular="", gave no error, and the name forms go to the client. The loader does not check files against the schema (HXML-35), so the schema does not catch them. Validate now rejects a name form that is missing, empty, blank, or has a space at the start or the end, as it does for keys. ValidateAll now reports a ByBranch species that no species is a branch of. Nobody can pick such a species, and the usual cause is a typo in a branch's BranchOf. New tests also cover a species with Bodies set to null, a branch without a loaded parent, and a species with its own elements.
|
Thank you, @imbas-forosnai. All three points are addressed in Low: the name forms
Note: a ByBranch parent without branches
Note: a missing
|
Summary
This PR adds
Speciesas a root type (HXML-37, step 1 of HS-1520). World data defines the species, as it defines the elements (HXML-24). No enum lists them, so a new species is a content change, not a package release. The server, content and client steps follow in HS-1520.Schema
Keyis the key that saves and content use, so it never changes.Idis the wire value (unsignedByte).OriginisAosda,TuathaorFanaidhe(lore data).PlayerOptionisLocked,Starter,UnlockableorByBranch. AByBranchspecies is a parent: a player picks one of its branches, not the parent.BranchOfnames the parent species.AffinityandResistanceare element keys, and a branch without them takes its parent's values. A branch takes only these two from its parent: its names, bodies, origin and player option are its own.Nameshas the singular, plural and adjective forms.Keyis unique in the species, and saves store it.Id(unsignedShort) is the wire value, and it is unique across all species, so the client body pack maps one number to one sheet for both genders.Nameis optional, and exactly one body is theDefault.RetailSkinColor(0 to 9, default 0) is the body byte for a retail client.[A-Za-z][A-Za-z0-9]*):SpeciesKeyderives fromElementKey.xs:uniquerules reject a body key or a body id that occurs twice in one species.Model
Speciesloads on start from thespeciesdirectory. "species" is an invariant plural, soWorldDataDirectory.NameForreturns it as it is (it gave "specieses").SpeciesOriginandSpeciesPlayerOption, are pinned intools/enum-values.json.Lockedis 0. The loader does not check files against the schema (HXML-35), so a file withoutPlayerOptiongets the value 0, and that species must not be playable.Species.TryGetfinds a loaded species by key, andTryGetBodyfinds a body. Both ignore case. The store compares primary keys with case, so the server usesTryGet, not the store key. The other helpers areDefaultBody,GetParent, andGetAffinityandGetResistance, which give a branch its parent's values.ValidateAllruns at load and reports a species id that two species use, a body id that two species use, two species keys that differ only in case, and aByBranchspecies that no species is a branch of. The case check is a stopgap: the two keys share one lower-case index key, and HXML-34 moves this check into the store for every type.Validatereports, for each species: a species key or body key that is empty or has a space at the start or the end (the loader does not trim keys, so it rejects them instead), no names, a name form that is missing, empty, blank or padded, aStarterorUnlockablespecies with no bodies, not exactly one default body, a body key or body id twice, aRetailSkinColorabove 9, aBranchOfthat names no loaded species, a parent that is a branch or is notByBranch, a branch that isByBranch, and a species that is not a branch and has noAffinityorResistance.Validate(ElementTable)checksAffinityandResistanceagainst a table. The package cannot know the active element table, so the server calls it with that table.Duplicateshelper moves fromElementTableintoEnumerableExtension, andSpeciesuses it too.Generated code
tools/regenerate.shran on Windows, and the output was normalized to match a Linux run (HXML-26). The header of each generated file lists the schema files, so a new schema changes one header line in every existing generated file (255 files). No other line in an existing generated file changes.Tests
266 pass (50 new). The species tests cover the schema (a full species, a parent without bodies, a branch without elements, and nine invalid cases), deserialization and the helpers (
TryGetwithout case), a branch that takes its parent's elements,ValidateAllandValidate(fifteen single-problem cases, and the cross-species rules, including aByBranchspecies without branches),Validate(ElementTable), padded keys, a species withoutPlayerOption, a species withBodiesset to null, a branch without a loaded parent, and a load from aspeciesdirectory. A newSchemaValidationhelper holds the schema set for the tests, and the element table and trait tests use it.Not in this PR
1.0.0-alpha2. The version is the maintainers' choice.Idor bodyIdloads as 0, because the loader does not check files against the schema. The uniqueness checks catch it only when another species or body also uses 0. HXML-35 fixes it.Buildattribute on bodies (BRIG-118). A later minor release adds it with defaultA.world, the Brigid registry message and body pack, Creidhne and hybindex-ts (HS-1520).