-
Notifications
You must be signed in to change notification settings - Fork 0
Pack Validator Design
Auto-generated from the repo docs by
tools/sync_wiki.sh— edit the source Markdown in the repo, not this wiki page.
📁 ARCHIVED NOTE — frozen 2026-08-18, not maintained. The design note; the validator was built and drilled. Current reference: the Validate button in Editor-Tools.md and
haf validatein Headless-CLI.md. Kept for the rule catalogue and the fault-injection drill record.
Status: BUILT + DRILLED 2026-08-18 (Phases 1 + 2), per this design and the drill ADR. The fault-injection
drill (a "Turrret" bone typo, a misspelled WAV, a volume of 5, planted in the live pack) caught a real defect
before passing: the editor's Validate button could fail silently (no try/catch — "validate detects nothing"
while a headless run of the same core on the same file found the fault). Fixed — the button now reports
exceptions loudly, names the registry path it validated even when clean, and shows its results in a dialog
(drill feedback). Drill passed: all three faults named with field, entry, and reason. The pure rule core lives in the shared schema DLL
(Haf.Schema.PackValidator — one rule set, ~30 rules over files/bones/pawn/formats/ranges/exclusions, 19 unit
tests) with the tri-state IValidationContext exactly as specified (null = "this host can't check" → skipped,
never guessed). The two thin hosts: the Model Factory's "Validate pack" button (editor context: the Pick
list's pawn names, deployed-pack + legacy file dirs, bones from each entry's baked skeleton asset) and the
plugin's boot-time pass (UniversalInject.RunPreflight, once per process after registration — bones against
the LOADED skeleton, files on the player's disk, plus the plugin-only authored-GUID-didn't-resolve checks —
appending ## Pre-flight to haf_load_report.txt). Severity semantics as designed: warnings explain, nothing is
blocked. Two user-designed surfaces beyond the original two (same day): pre-bake (every Bake runs the rules
on the current form first — the mistake is named while the model is on screen) and the mod build
(HAF.Cli.BuildMod validates the full registry before the SDK build steps; -strict makes any issue fail the
build with exit 4 — the CI-able stop-ship gate). Phase 3 (vertex-budget hints, JSON-schema file) remains open.
Original design below.
Today a HAF pack fails in two very different ways depending on the mistake:
-
Structure mistakes are already loud and human-readable. Malformed pack JSON, duplicate
modId, missingdependsOn, ordering cycles, and same-pawn conflicts all produce plain-language warnings in the BepInEx log and thehaf_load_report.txt(implemented in the 07-14 / 07-19 multi-mod work). Bad input fails soft — never a crash. -
Content mistakes fail silently. A wrong bone name (
muzzleBone: "Turrret"), an unresolvable asset GUID, or a missing texture/WAV path doesn't warn — the feature just doesn't happen. For someone authoring a pack on their own machine, "the flash is in the wrong place and I don't know why" is exactly the friction that stops third-party adoption.
The validator turns silent content failures into named, actionable messages before (or at) load.
Grounded in the fields a ModelEntry / district / prop / projectile entry actually references:
| Class | Fields | Check | Where it's checkable |
|---|---|---|---|
| Asset GUIDs |
skeleton, atlas, clip, moveClip/idleClip/… , district FxMesh, prop MeshCollection |
the GUID resolves to a loaded asset of the right kind | boot-time (needs the game's asset DB) |
| Bone names |
muzzleBone, turretBone, animateBones, hand-prop bone |
the named bone exists in the entry's skeleton | editor (baker knows the model's bones) and boot-time (against the loaded skeleton) |
| File paths |
soundFile, soundStart/Stop/Idle/Attack/Death/Battle, PNG skins |
the file exists in the pack folder | editor and boot-time (plain file existence) |
| Target unit | pawnDescription |
matches a real game unit descriptor | editor (pawn dropdown) and boot-time (unit catalog) |
| Schema | field names, types, enums (materialMode), numeric ranges |
keys are known, types/enums valid, ranges sane | editor (pre-ship) |
Note the split: file/bone/pawn/schema checks are doable in the editor (before the pack ever ships — the best DX), while GUID resolution and bone-against-loaded-skeleton need the running game, so they belong to a boot-time pass on the end user's machine.
Factor the rules into one pure ValidateEntry(entry, context) returning a list of (severity, message) — mirrors the
existing FormationOverridePatch.Validate(Entry) pattern and stays unit-testable (it's pure logic over data + a small
lookup interface). Two thin callers:
- Editor button — "Validate pack" in the pack/Factory window. Runs the checks it can without the game (files exist, bones exist in the inspected model, pawn is real, schema/enums/ranges). This is the pre-ship gate — an author sees problems before distributing.
-
Boot-time pre-flight pass in the plugin, right after pack resolution and asset registration. Runs the checks that
need the loaded game (GUID resolution, bone-against-actual-skeleton, file existence on the user's disk) and appends
a
## Pre-flightsection tohaf_load_report.txtplus a summary log line.
One line per problem, always naming pack + entry + the specific fault + (where cheap) the valid options:
[Preflight] pack 'coolmod' entry 'PanzerIV' (pawn 'AttackHelicopter'):
bone 'Turrret' not found in skeleton — available: Root, Body, Turret, Barrel, Muzzle
texture 'skins/panzer.png' not found in pack folder
clip GUID 3f2a… did not resolve to a ClipCollection (was it baked?)
-
Severity:
warning= the feature degrades but the pack still loads (today's fail-soft behaviour, now explained);error= the entry is unusable and skipped. Default towarning— never regress the "bad input never crashes" rule. - Summary line:
[Preflight] pack 'coolmod': 2 warning(s), 0 error(s) — see haf_load_report.txt.
- Phase 1 (cheap, most of the value): the editor "Validate pack" button — file existence, bone-in-inspected-model, pawn-exists, schema/enum/range. Catches the common typos before a pack ever ships, with zero runtime risk.
- Phase 2: the boot-time pass — GUID resolution + bone-against-loaded-skeleton + on-disk file existence → load report. Catches what only the end user's machine can (a bad shipped GUID, a case-wrong path).
- Phase 3 (optional): vertex-budget hints (warn when a pack's model types approach the mesh-buffer ceiling — see Vertex-Budget) and a JSON-schema file authors can validate against in their own editor.
-
Not
check_schema_parity.sh— that's a dev-side guard againstModelDef↔ModelEntrysource drift across the two repos. This validator is author-side, against a concrete pack's data. - Not a replacement for fail-soft — it explains failures; the runtime still degrades gracefully on anything it misses.
- Fits the "guided, not guessy" design goal and the Multi-Mod pack contract; it's the author-facing bookend to the load report.
Get started
- Getting Started
- Installation
- Troubleshooting
- Authoring State and Deployment
- Mod Editor version.xml Recovery
- Building
- Backup
Author models and behavior
- Editor Tools
- Factory Manual
- Vehicle Lab Quickstart
- Animated Models
- Animation Pitfalls
- Textures
- Unit Size
- Unit Combat Behavior
- Formations
- Pawn Props
- Projectiles
- Game Sound Lab
- Firing on Attack
- Turn Ease
- Facing Persistence
- Donor Clip Flight
Districts and wonders
Ship and operate
Internals and project