feat(@projects/@magic-civilization): fix determinism seed ingestion

Co-Authored-By: Lilith Autocommit <noreply@atlilith.com>
This commit is contained in:
Natalie 2026-04-16 14:51:46 -07:00
parent 0b7a4b291e
commit 701bba5df9
4 changed files with 6 additions and 11 deletions

View file

@ -48,3 +48,5 @@
2026-04-16 14:29 Task #10 COMBAT BALANCE DIAL-BACK (no-op verdict): tuned wall_penalty 0.70→0.75, melee_fraction 0.50→0.55, HEAL_PER_TURN 20→15 across 3 cumulative batches (option_a, option_ab, option_abc). All 3/3/3 produced 0 captures despite 260-342 combats and p1 10x kill ratio. Combat math NOT the bottleneck. Reverted all 3 to baseline (0.70/0.50/20), 103/103 mc-combat+mc-city tests pass. Handoff to #11 (AI capture commit in simple_heuristic_ai.gd). (balance-dev)
2026-04-16 14:36 Task #11 AI CAPTURE COMMIT complete (64403f888): simple_heuristic_ai.gd +41/-3 in _decide_military_action. Three behaviors: (1) Adjacent-city attack fires BEFORE retreat/chase logic; (2) Retreat-on-low-HP suppressed when within 4 hexes of enemy city (commitment); (3) When own_mil ≥ 2×enemy_mil AND enemy city closer than nearest stray, skip chase to press city. Batch: 70/121/114 city attacks per game (was 0), 45/64/43 killed=true attacks. Victories STILL 0/3 because HP resets to 380 every turn (net-zero bug in Rust). AI side done. (capture-ai-dev)
2026-04-16 14:36 Task #12 MCTS FOUNDATION complete: new src/simulator/crates/mc-ai/src/mcts_tree.rs (138 lines) + tests (78 lines). Arena-allocated tree with UCB1 select/expand/simulate/backpropagate. Existing mcts.rs bandit left untouched. 19/19 tests pass. Not wired to GDExtension yet — foundation only. Future work: connect to game state + define Action type from actual game decisions. (mcts-dev)
2026-04-16 14:47 Task #17 T50 DETERMINISM — CRITICAL BREAKTHROUGH: root cause was SEED INGESTION bug in game_state.gd, NOT mc-combat. `game_settings["seed"]` was read but never written to `GameState.map_seed`. RNG fell through to hash(Time.get_unix_time_from_system()) → wall-clock seed each run. Prior "byte-identical" reports from tasks #6/#9 were ILLUSIONS (same-second wall-clock coincidence, OR self-comparison). 3-line fix in game_state.gd:113-115 ingests settings_seed if nonzero. Verified: 2x seed=1 52-turn runs truly byte-identical. T1 rng_seed=1 both (was 3059205916/2794811774). T50 save equal. Combat counts match at T50. (t50-determinism-dev)
2026-04-16 14:47 Task #18 PLAYER GUIDE UPDATE complete: new Personality Axes page at /empire/personality in guide web app. PersonalityAxesPage.tsx renders 6-axis explainer + race strategic axes grid. Pulls from @resources/races/strategic_axes.json (no hardcoded data). Wired through lazy-pages.ts, App.tsx route, nav.tsx sidebar. pnpm typecheck clean. Visual verification blocked by WASM not built on macOS (environment, not code). (guide-dev)

View file

@ -7,6 +7,9 @@ const WORLD_MAP_SCENE: String = "res://engine/scenes/world_map/world_map.tscn"
const MapGeneratorScript: GDScript = preload("res://engine/src/generation/map_generator.gd")
const PlayerScript: GDScript = preload("res://engine/src/entities/player.gd")
const WildCreatureAIScript: GDScript = preload("res://engine/src/modules/ai/wild_creature_ai.gd")
const PersonalityAssignerScript: GDScript = preload(
"res://engine/src/modules/ai/personality_assigner.gd"
)
const TICK_DELAY: float = 0.04

View file

@ -171,16 +171,6 @@ func create_player(
var tier: String = str(race_data.get("growth_tier", ""))
if tier != "":
player.growth_tier = tier
if not is_human:
var clans: Dictionary = DataLoader.get_data("ai_personalities")
if not clans.is_empty():
var ids: Array = clans.keys()
ids.sort()
var pick: Dictionary = clans.get(ids[game_rng.randi() % ids.size()], {})
var axes: Dictionary = pick.get("strategic_axes", {})
if not axes.is_empty():
player.strategic_axes = axes.duplicate()
print("Player %s personality: %s" % [player_name, pick.get("name", "")])
add_player(player)
PersonalityAssignerScript.assign(player, game_rng)
return player

View file

@ -20,4 +20,4 @@ static func assign(player: RefCounted, rng: RandomNumberGenerator) -> void:
if axes.is_empty():
return
player.strategic_axes = axes.duplicate()
print("%s personality: %s" % [player.player_name, pick.get("name", "")])
print("Player %s personality: %s" % [player.player_name, pick.get("name", "")])