9.7 KiB
CT.M1 — ContentValidationPipeline
Summary
| Field | Value |
|---|---|
| Module ID | CT.M1 |
| Track | Content tooling — internal authoring (cross-cutting; not E1–E9) |
| Stage target | Prototype |
| Status | Planned (see dependency register) |
Purpose
Own content schema artifacts, automated validation in CI and locally, and (when wired) server-side smoke loading so file-backed catalogs cannot drift from enforced contracts.
Responsibilities
- Publish and version JSON Schema (or agreed equivalent) for catalogs consumed by E2.M1, E3.M2–M3, E4.M1, E7.M1.
- Run structural and cross-reference validation before merge; expose the same rules to CT.M2.
- Optional: C# deserialize test or boot check aligned with
ContentCatalogRegistry(or successor).
Key contracts
| Contract | Role |
|---|---|
| Content schema files | $id + versioning per contracts.md |
| Validation CLI | Stable exit codes and machine-readable errors for CI and studio |
Module dependencies
- Soft: Gameplay modules above must define or stabilize the shapes being validated; pipeline can ship minimal schemas first.
Dependents (by design)
- CT.M2 — AuthoringStudioApplication
- CT.M3 — ContentReferenceAndBundleWorkflows
Related implementation slices
Content tooling Slice 1 — schemas + CI; Slice 4 — server parity hardening.
CI (prototype)
Skill catalogs: the repo PR gate workflow (.github/workflows/pr-gate.yml) runs scripts/validate_content.py on every push and pull request. It validates each object in content/skills/*.json under the top-level skills array against content/schemas/skill-def.schema.json (Python jsonschema, Draft 2020-12), rejects duplicate id across files, and (Slice 1 / NEO-33) enforces the frozen prototype trio ids plus gather + tech + (process or make) category coverage.
Mastery catalogs (NEO-45): the same script validates content/mastery/*_mastery.json against content/schemas/mastery-catalog.schema.json, cross-checks track skillId against loaded SkillDef ids, rejects duplicate perkId, enforces branch integrity (tier branch sets match; perkIds reference defined perks), and (Slice 4) requires exactly one track for salvage only.
Item catalogs (NEO-50): the same script validates each row in content/items/*_items.json against content/schemas/item-def.schema.json, rejects duplicate id across files, and (Slice 1) enforces the frozen six-item id set plus exactly one row per prototypeRole (material through equip_stub).
Resource node catalogs (NEO-57): the same script validates content/resource-nodes/*_resource_nodes.json rows against content/schemas/resource-node-def.schema.json and *_resource_yields.json rows against content/schemas/resource-yield-row.schema.json; rejects duplicate nodeDefId; (Slice 2) enforces the frozen four-node id set, one row per gatherLens, exactly one yield row per node, yield itemId cross-check against item catalogs, and only scrap_metal_bulk as yield item in prototype.
Recipe catalogs (NEO-65): the same script validates each row in content/recipes/*_recipes.json against content/schemas/recipe-def.schema.json (I/O rows via recipe-io-row.schema.json), rejects duplicate id across files, cross-checks every input/output itemId against item catalogs and every requiredSkillId against skill catalogs, and (Slice 3) enforces the frozen eight-recipe id set, at least one process and one make row, and requiredSkillId: refine on every row.
Ability catalogs (NEO-76): the same script validates each row in content/abilities/*_abilities.json against content/schemas/ability-def.schema.json, rejects duplicate id across files, and (E5 Slice 1) enforces the frozen four-ability id set (prototype_pulse, prototype_guard, prototype_dash, prototype_burst). Extend the script when additional catalogs and schemas ship.
NPC behavior catalogs (NEO-87): the same script validates each row in content/npc-behaviors/*_npc_behaviors.json against content/schemas/npc-behavior-def.schema.json, rejects duplicate id across files, requires leashRadius > aggroRadius per row, and (E5 Slice 2) enforces the frozen three-behavior id set (prototype_melee_pressure, prototype_ranged_control, prototype_elite_mini_boss).
Reward table catalogs (NEO-100): the same script validates each row in content/reward-tables/*_reward_tables.json against content/schemas/reward-table.schema.json (grant rows via reward-grant-row.schema.json), rejects duplicate id across files and duplicate itemId within a table's fixedGrants, cross-checks every fixedGrants[].itemId against item catalogs, and (E5 Slice 3) enforces the frozen one-table id set (prototype_combat_pocket_clear) with fixed grant quantities.
Encounter catalogs (NEO-100): the same script validates each row in content/encounters/*_encounters.json against content/schemas/encounter-def.schema.json, rejects duplicate id across files, cross-checks rewardTableId against reward table catalogs, and (E5 Slice 3) enforces the frozen one-encounter id set (prototype_combat_pocket) with requiredNpcInstanceIds matching the three E5.M2 NPC instance ids.
Quest catalogs (NEO-112): the same script validates each row in content/quests/*_quests.json against content/schemas/quest-def.schema.json (steps via quest-step-def.schema.json, objectives via quest-objective-def.schema.json), rejects duplicate id across files and duplicate step/objective ids within a quest, cross-checks objective itemId / recipeId / encounterId against frozen item, recipe, and encounter catalogs, enforces acyclic prerequisiteQuestIds, and (E7 Slice 1) enforces the frozen four-quest id set with operator chain terminal inventory_has_item contract_handoff_token ×1.
Quest completion bundles (NEO-124): optional completionRewardBundle on each quest row via quest-reward-bundle.schema.json (itemGrants reuses reward-grant-row.schema.json; skillXpGrants via quest-skill-xp-grant.schema.json). E7 Slice 2 requires bundles on all four frozen quest ids with exact freeze-table contents; cross-checks itemGrants[].itemId and skillXpGrants[].skillId; each skill XP target must allow mission_reward in allowedXpSourceKinds.
Contract template catalogs (NEO-144): the same script validates each row in content/contracts/*_contract_templates.json against content/schemas/contract-template.schema.json (completionRewardBundle via quest-reward-bundle.schema.json; minFactionStanding via faction-gate-rule.schema.json), rejects duplicate id across files, cross-checks encounterTemplateId against encounter catalogs and bundle item/skill/faction refs against frozen catalogs, enforces the frozen one-template id set (prototype_contract_clear_combat_pocket) with exact freeze-table contents, and enforces band-1 economy caps (item qty ≤ 10, skill XP ≤ 25, rep ≤ 10 per grant row). Issue/audit shape: contract-seed.schema.json.
Decomposition vocabulary: the same workflow runs scripts/check_decomposition_language.py (stdlib only) over docs/decomposition/**/*.md to catch wording that treats combat as a leveled SkillDef line instead of gig progression (see script docstring for patterns). Adjust the script if a future doc needs a documented exception.