magicciv/public/games/age-of-dwarves/data/sim-scenarios/sim-scenarios.schema.json
Natalie b6e365c95d feat(sim-scenarios): full scenario catalog + schema + docs (pre-calibration spec)
Declarative simulation-test scenarios for horizontal proving on the DO fleet.
Two kinds: combat_setpiece (hand-authored tactical board, known outcome) and
fullgame (seeded full-game, invariant/liveness/determinism/balance assertions).

- 10 combat set-pieces (data/sim-scenarios/combat/): rush/walls/pyrrhic, ranged
  kite, fortified hill, castle vs double-rush, siege catapult, last-stand,
  flanking, formation-vs-loose.
- 10 fullgame (data/sim-scenarios/fullgame/): smoke, determinism, expansion,
  time-to-tier, economy invariant, no-soft-lock, trade, culture borders, clan
  fairness band, broad 150t systems run.
- sim-scenarios.schema.json validates both kinds; assertion vocab enumerated,
  each mapped to a real engine signal (cities_captured, pvp_kills, surviving
  units, gold/pop, traded_luxuries, tech tier).
- All clan personalities are the REAL 8 (balanced/boom/expansionist/merchant/
  militarist/rusher/tech_rusher/turtle); the prior draft's ironhold/goldvein
  were fabricated.
- SIM_SCENARIOS.md: S3->fleet pipeline, full catalog, schema, calibration rule
  (assertion values calibrated against real runs, never invented). Router wired.

Removed the two old fake-schema drafts (smoke_duel_30t, game1_headless_systems_150t)
whose assertions rode on fabricated metrics. Runner + calibration follow.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-28 14:48:24 -04:00

118 lines
4.3 KiB
JSON

{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "sim-scenarios",
"title": "Simulation Test Scenario",
"description": "A declarative scenario run headless by the `sim_scenario` bin against the real mc-turn/mc-combat resolver, on the DO fleet. Two kinds: combat_setpiece (hand-authored tactical board with a known outcome) and fullgame (seeded full-game run with statistical / invariant assertions). Assertion `value`s are CALIBRATED against real runs, never invented.",
"type": "object",
"required": ["id", "kind", "description", "expect"],
"properties": {
"id": { "type": "string", "pattern": "^[a-z0-9_]+$" },
"kind": { "enum": ["combat_setpiece", "fullgame"] },
"version": { "type": "integer", "minimum": 1 },
"description": { "type": "string" },
"map": {
"type": "object",
"properties": {
"size": { "type": "integer", "minimum": 8 },
"evolution_ticks": { "type": "integer", "minimum": 0 },
"seed_base": { "type": "integer" }
}
},
"terrain_overrides": {
"type": "array",
"items": {
"type": "object",
"required": ["at", "biome"],
"properties": {
"at": { "type": "array", "items": { "type": "integer" }, "minItems": 2, "maxItems": 2 },
"biome": { "type": "string" }
}
}
},
"defender": { "$ref": "#/$defs/side_combat" },
"attacker": { "$ref": "#/$defs/side_combat" },
"players": {
"type": "array",
"items": {
"type": "object",
"required": ["personality"],
"properties": {
"personality": { "type": "string" },
"seed_luxuries": { "type": "array", "items": { "type": "string" } }
}
}
},
"rules": {
"type": "object",
"properties": {
"max_turns": { "type": "integer", "minimum": 1 },
"victory_city_count": { "type": "integer" },
"victory_disabled": { "type": "boolean" }
}
},
"max_turns": { "type": "integer", "minimum": 1 },
"seeds": {
"oneOf": [
{ "type": "array", "items": { "type": "integer" } },
{ "type": "string", "pattern": "^sweep:[0-9]+\\.\\.[0-9]+$" }
]
},
"expect": { "type": "array", "items": { "$ref": "#/$defs/assertion" }, "minItems": 1 }
},
"$defs": {
"stack_entry": {
"type": "object",
"required": ["unit", "count"],
"properties": {
"unit": { "type": "string" },
"count": { "type": "integer", "minimum": 1 },
"at": { "type": "array", "items": { "type": "integer" }, "minItems": 2, "maxItems": 2 },
"fortified": { "type": "boolean" },
"formation": { "type": "boolean" }
}
},
"side_combat": {
"type": "object",
"properties": {
"player": { "type": "string" },
"approach_from": { "type": "array", "items": { "type": "integer" }, "minItems": 2, "maxItems": 2 },
"flank": { "type": "boolean" },
"capital": {
"type": "object",
"required": ["col", "row"],
"properties": {
"col": { "type": "integer" },
"row": { "type": "integer" },
"population": { "type": "integer", "minimum": 1 },
"is_last_city": { "type": "boolean" }
}
},
"buildings": { "type": "array", "items": { "type": "string" } },
"garrison": { "type": "array", "items": { "$ref": "#/$defs/stack_entry" } },
"stack": { "type": "array", "items": { "$ref": "#/$defs/stack_entry" } }
}
},
"assertion": {
"type": "object",
"required": ["type"],
"properties": {
"type": {
"enum": [
"capital_captured", "capital_held", "attacker_survivors", "defender_survivors",
"attacker_losses", "pvp_kills", "capture_by_turn",
"final_turn", "terminates", "turn_monotonic", "no_nan_economy",
"population_non_negative", "deterministic_end_hash", "more_cities",
"city_count", "total_pvp_combats", "median_tier_peak", "trades_formed",
"border_growth", "clan_winrate_max"
]
},
"op": { "enum": [">=", ">", "==", "<=", "<"] },
"value": { "type": "number" },
"by": { "type": "string" },
"player": { "type": "integer" },
"than": { "type": "integer" },
"min_margin": { "type": "integer" }
}
}
}
}