fix(creation-model): migrate stray class_id/allowed_classes copies
The canon-log schema and NewGame code already migrated to race_id/ calling_id + allowed_callings, but two copies of the roster outside that schema were missed, and no parity test guards them: - docs/canon-log.md (the cross-boundary contract doc) still documented the OLD player shape and allowed_classes. Anyone building a player block from the doc would get a 422. - .claude/skills/world-building's schema reference still emitted allowed_classes with two dead calling ids (assassin, priest). Author a new origin with that skill and it fails origin.schema.json (additionalProperties: false, allowed_callings required) AND, if that ever loosened, silently allows zero callings at runtime. Also: - add schema tests rejecting a dead class_id field and an unknown calling_id (priest) - guard NewGame._validate's container types (spend/skills) so a malformed JSON round-trip (null spend, string skills) produces a front-loaded error list instead of a GDScript runtime crash - extend the no-mechanics-in-content test to db.races, not just db.callings - rewrite the roadmap's M4 bullet: the two contract migrations landed; only the creation screen and title->creation->shell flow remain client: 250/250. api: 74 passed, 2 skipped. content_build --check: clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QYa9u7Kdxv5gX4AnwWexy8
This commit is contained in:
@@ -27,7 +27,7 @@ Full field rules live in `canon-log.schema.json`. Summary:
|
||||
```json
|
||||
{
|
||||
"schema_version": 1,
|
||||
"player": { "name": "…", "class_id": "sellsword|assassin|priest", "luck_descriptor": "…" },
|
||||
"player": { "name": "…", "race_id": "human|elf|dwarf|beastfolk", "calling_id": "sellsword|reaver|cutpurse|trapper|hedge_mage|bonesetter|bloodsworn", "luck_descriptor": "…" },
|
||||
"location": { "id": "…", "name": "…" },
|
||||
"party": [ { "id": "…", "name": "…", "disposition": -100..100 } ],
|
||||
"recent_events": ["… (≤5, rolling)"],
|
||||
@@ -39,9 +39,13 @@ Full field rules live in `canon-log.schema.json`. Summary:
|
||||
|
||||
**The §7 boundary (enforced structurally).** The schema sets
|
||||
`additionalProperties: false` on the root and on `player`, and `player` admits
|
||||
only `name`, `class_id`, `luck_descriptor`. **Numeric Luck, stats, HP/MP, and
|
||||
inventory cannot be represented in the canon log** — the AI never sees a number
|
||||
it could use to calculate Luck. Only `luck_descriptor` (e.g. "Fortune spits on
|
||||
only `name`, `race_id`, `calling_id`, `luck_descriptor`. `race_id` and
|
||||
`calling_id` are there so the Narrator and NPCs can *describe* the player (a
|
||||
"beastfolk cutpurse" reads different scene text than a "dwarf bonesetter") —
|
||||
they are identity for prose, not mechanics. **Numeric Luck, stats, HP/MP, and
|
||||
inventory cannot be represented in the canon log** — the mechanical sheet stays
|
||||
in `GameState` and never reaches the log, and the AI never sees a number it
|
||||
could use to calculate Luck. Only `luck_descriptor` (e.g. "Fortune spits on
|
||||
you") crosses the boundary.
|
||||
|
||||
All string ids match `^[a-z0-9_]+$`.
|
||||
@@ -59,7 +63,7 @@ Full rules in `origin.schema.json`. An origin seeds the initial log:
|
||||
"disposition_overrides": { "<npc_id>": -100..100 },
|
||||
"inventory_grants": [ { "item_id": "…", "qty": 1.. } ],
|
||||
"start_quest_id": "…|null",
|
||||
"build_constraints": { "allowed_classes": ["sellsword",…], "luck_modifier": 0 }
|
||||
"build_constraints": { "allowed_callings": ["sellsword",…], "luck_modifier": 0 }
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
Reference in New Issue
Block a user