Follow-up to the TechWeb fix: research advanced to 109 techs but every city
still spawned tier-1 dwarf_warrior. Five stacked defects severed the
tech→unit→production chain; all are fixed here. Proven in a 599-turn duel
self-play (seed 42): slot 0 fields a 222-strong tier-8 dwarf_adamantine_champion
army; both clans now CHOOSE tier-2..8 units by tech (was tier-1 only).
1. units_catalog (#[serde(skip)]) was never stamped onto the dispatch
GdPlayerApi — only GdGameState. The harness comment claimed 'both must be
re-stamped at boot' but only did GdGameState. Added
GdPlayerApi::set_units_runtime_catalog_json + a post-load harness re-stamp.
Without it apply_queue_production/try_spawn_unit ran catalog-blind.
2. apply_queue_production classified queued ids as unit-vs-building by a
starts_with("dwarf_") prefix. Replaced with an authoritative
units_catalog.get() lookup (prefix kept only as empty-catalog fallback).
The prefix leaked dwarf_-prefixed BUILDINGS (dwarf_deep_forge) onto the map
as units and misfiled every non-dwarf unit.
3. project_tactical_player hardcoded race_id: None, filtering out every
race_required:dwarf unit. Added PlayerState.race_id (race gates production →
it is sim state, not pure presentation per p2-72a), stamped it in
set_player_presentation_json + the headless harness (sourced from
setup.json::default_race), and projected it.
4. build_unit_catalog loaded faction:"wild" monsters (ancient_hydra, …) and
freepeople into the AI production catalog. With race_required:null they
passed every race filter and, being high-tier, the picker preferred them.
Excluded non-player factions from the production catalog (runtime catalog
still carries them for encounters).
5. Generic warrior/spearmen/archer carried clan_affinity to ALL FIVE clans at
tier 1, so clan_affinity_score=2 dominated tier and every named clan locked
onto tier-1 'warrior'. Cleared their affinity (they are neutral baselines,
not clan signatures).
mc-player-api 132 + mc-state 12 tests green. Known next layer (not this fix):
production VOLUME — try_spawn_unit's empty-queue dwarf_warrior auto-spawn still
floods tier-1, and the loser snowballs; the picker is correct, army composition
tuning is separate.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
100 lines
2.1 KiB
JSON
100 lines
2.1 KiB
JSON
[
|
|
{
|
|
"id": "warrior",
|
|
"name": "Warrior",
|
|
"description": "A basic melee fighter armed with sword and shield.",
|
|
"unit_type": "melee",
|
|
"school": null,
|
|
"domain": "land",
|
|
"keywords": [],
|
|
"attributes": [
|
|
"light",
|
|
"biological"
|
|
],
|
|
"attack_type": "blade",
|
|
"hp": 80,
|
|
"attack": 14,
|
|
"defense": 8,
|
|
"ranged_attack": 0,
|
|
"range": 0,
|
|
"movement": 2,
|
|
"vision": 2,
|
|
"cost": 40,
|
|
"tier": 1,
|
|
"tech_required": null,
|
|
"upgradeable_from": null,
|
|
"race_required": null,
|
|
"faction": "dwarf",
|
|
"mana_cost": null,
|
|
"sprite": "sprites/units/warrior.png",
|
|
"gender": {
|
|
"male": {
|
|
"name": "Warrior",
|
|
"sprite": "sprites/units/warrior_dwarf_male.png"
|
|
},
|
|
"female": {
|
|
"name": "Warrior",
|
|
"sprite": "sprites/units/warrior_dwarf_female.png"
|
|
}
|
|
},
|
|
"maintenance": 2,
|
|
"auto_join": true,
|
|
"encyclopedia": {
|
|
"category": "combat",
|
|
"entry_type": "unit",
|
|
"detail_route": "/military/units",
|
|
"tags": [
|
|
"land",
|
|
"military",
|
|
"blade"
|
|
]
|
|
},
|
|
"flavor": "The backbone of any early military.",
|
|
"archetype": "light_melee",
|
|
"promotion_tree": "melee",
|
|
"clan_affinity": [],
|
|
"logistics": {
|
|
"composition": {
|
|
"captain": 1,
|
|
"sergeant": 1,
|
|
"soldier": 13,
|
|
"horn_blower": 1
|
|
},
|
|
"inventory": {
|
|
"rations": 160,
|
|
"water": 128,
|
|
"tool_durability": 2
|
|
},
|
|
"stats": {
|
|
"str": 70,
|
|
"con": 100,
|
|
"end": 60
|
|
},
|
|
"terrain_movement": {
|
|
"road": 0.5,
|
|
"dirt_road": 0.65,
|
|
"wagon_track": 0.85,
|
|
"trail": 0.8,
|
|
"grass": 1.0,
|
|
"hills": 1.5,
|
|
"forest_open": 1.5,
|
|
"forest_dense": 2.5,
|
|
"mountain": 3.0,
|
|
"snow": 2.0,
|
|
"tundra": 1.5,
|
|
"desert": 2.5,
|
|
"marsh": 3.0,
|
|
"river_ford": 2.0,
|
|
"river_unfordable": "blocked",
|
|
"deep_water": "blocked"
|
|
},
|
|
"carriers": {
|
|
"foot_runners": 2
|
|
},
|
|
"supply": {
|
|
"range_turns": 4,
|
|
"decline_rate": 1.2
|
|
}
|
|
}
|
|
}
|
|
]
|