From 701bba5df900cac31a5a9dead29b795c1e89359c Mon Sep 17 00:00:00 2001 From: Natalie Date: Thu, 16 Apr 2026 14:51:46 -0700 Subject: [PATCH] =?UTF-8?q?feat(@projects/@magic-civilization):=20?= =?UTF-8?q?=E2=9C=A8=20fix=20determinism=20seed=20ingestion?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Lilith Autocommit --- .project/iteration_log.md | 2 ++ src/game/engine/scenes/menus/loading_screen.gd | 3 +++ src/game/engine/src/autoloads/game_state.gd | 10 ---------- src/game/engine/src/modules/ai/personality_assigner.gd | 2 +- 4 files changed, 6 insertions(+), 11 deletions(-) diff --git a/.project/iteration_log.md b/.project/iteration_log.md index 0b63177f..23233975 100644 --- a/.project/iteration_log.md +++ b/.project/iteration_log.md @@ -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) diff --git a/src/game/engine/scenes/menus/loading_screen.gd b/src/game/engine/scenes/menus/loading_screen.gd index b7966bf5..cfa92cad 100644 --- a/src/game/engine/scenes/menus/loading_screen.gd +++ b/src/game/engine/scenes/menus/loading_screen.gd @@ -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 diff --git a/src/game/engine/src/autoloads/game_state.gd b/src/game/engine/src/autoloads/game_state.gd index cebb29ce..6b2ab44c 100644 --- a/src/game/engine/src/autoloads/game_state.gd +++ b/src/game/engine/src/autoloads/game_state.gd @@ -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 diff --git a/src/game/engine/src/modules/ai/personality_assigner.gd b/src/game/engine/src/modules/ai/personality_assigner.gd index bab72436..ea0d8f42 100644 --- a/src/game/engine/src/modules/ai/personality_assigner.gd +++ b/src/game/engine/src/modules/ai/personality_assigner.gd @@ -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", "")])