From 31955b1adab79f728a7034c9cdfe6cb150515dca Mon Sep 17 00:00:00 2001 From: autocommit Date: Mon, 27 Apr 2026 05:47:10 -0700 Subject: [PATCH] =?UTF-8?q?perf(management-specific):=20=E2=9A=A1=20Migrat?= =?UTF-8?q?e=20research=20processing=20logic=20from=20GDScript=20to=20Rust?= =?UTF-8?q?=20(Rail-1)=20while=20delegating=20spell/tech=20checks=20and=20?= =?UTF-8?q?preserving=20GDScript=20wrapper=20for=20signals?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Lilith Autocommit --- .../src/modules/management/turn_processor.gd | 95 +++++++++++-------- .../engine/src/modules/tech/knowledge_web.gd | 10 ++ src/game/engine/src/modules/tech/tech_web.gd | 8 ++ 3 files changed, 72 insertions(+), 41 deletions(-) diff --git a/src/game/engine/src/modules/management/turn_processor.gd b/src/game/engine/src/modules/management/turn_processor.gd index d072f6ec..ea746d6b 100644 --- a/src/game/engine/src/modules/management/turn_processor.gd +++ b/src/game/engine/src/modules/management/turn_processor.gd @@ -141,58 +141,71 @@ func _process_production(player: RefCounted) -> void: # Player func _process_research(player: RefCounted) -> void: # Player + ## Rail-1: science accumulation + spell/tech completion check delegated + ## to Rust `GdTechWeb::process_research` (warcouncil p1-39 port, + ## 2026-04-27). GDScript only assembles input JSON + applies + ## completion side-effects (school_locked emit, _form_high_archon, + ## tech_researched signal, resource reveals). if player.researching.is_empty(): return - # Debug: instantly complete any queued research/spell. - if EnvConfig.get_bool("FORCE_UNLIMITED_RESEARCH"): - player.research_progress = 999999 - - # Effective per-yield mult composes static difficulty handicap + - # linear-per-turn growth (warcouncil p1-29 H4, 2026-04-27). Per-player - # overrides for batch testing still take precedence inside the helper. - # Full Rail-1 port to GdTechWeb::process_research is queued under p1-31 — - # requires wrapper-layer plumbing (tech_web.gd → KnowledgeWebScript → GdTechWeb). + # Per-yield difficulty multiplier (composed by GameState). var sci_modifier: float = GameState.get_effective_yield_mult(player, "research") - player.research_progress += int(player.science_per_turn * sci_modifier) - - # Add science from cities - var game_map: RefCounted = GameState.get_game_map() # GameMap + # Per-city science yields the Rust side will sum. + var game_map: RefCounted = GameState.get_game_map() + var yields_arr: Array = [] if game_map != null: for city: Variant in player.cities: - if city is CityScript: - var tile_json: String = BuildableHelperScript.build_tile_yields_json( - city as CityScript, game_map - ) - var yields: Dictionary = city.get_yields(tile_json) - var building_sci: int = _sum_city_building_effect(city as CityScript, "science") - var sci_pct: float = _sum_city_building_effect_float( - city as CityScript, "science_percent" - ) - player.research_progress += int( - (yields.get("science", 0) + building_sci) * (1.0 + sci_pct) * sci_modifier - ) + if not city is CityScript: + continue + var c: CityScript = city as CityScript + var tile_json: String = BuildableHelperScript.build_tile_yields_json(c, game_map) + var ys: Dictionary = c.get_yields(tile_json) + yields_arr.append({ + "science": int(ys.get("science", 0)), + "building_science": _sum_city_building_effect(c, "science"), + "science_percent": _sum_city_building_effect_float(c, "science_percent"), + }) - # Check if researching a spell (not a tech) + # Player input. spell_cost is set when researching a spell so Rust runs + # the cheap counter branch (no TechWeb lookup). var spell_data: Dictionary = DataLoader.get_spell(player.researching) - if not spell_data.is_empty(): - var spell_cost: int = spell_data.get("research_cost", 999999) - if player.research_progress >= spell_cost: - var completed_spell: String = player.researching - player.research_progress = 0 - player.researching = "" - var sys: SpellSystemScript = spell_system as SpellSystemScript - sys.research_spell(player.index, completed_spell) + var researching_spell: bool = not spell_data.is_empty() + var researched_arr: Array = Array(player.researched_techs) if player.researched_techs != null else [] + var player_dict: Dictionary = { + "researching": str(player.researching), + "research_progress": int(player.research_progress), + "science_per_turn": int(player.science_per_turn), + "researched_techs": researched_arr, + "instant_complete": EnvConfig.get_bool("FORCE_UNLIMITED_RESEARCH"), + } + if researching_spell: + player_dict["spell_cost"] = int(spell_data.get("research_cost", 999999)) + + var tw: RefCounted = TurnManager.get_tech_web() + if tw == null: + return + var result: Dictionary = tw.process_research( + JSON.stringify(player_dict), JSON.stringify(yields_arr), sci_modifier + ) + if result.is_empty(): + return + var err: String = str(result.get("error", "")) + if not err.is_empty(): + push_warning("p1-39 _process_research: " + err) + return + player.research_progress = int(result.get("new_progress", 0)) + player.researching = str(result.get("new_researching", "")) + + var completed_spell: String = str(result.get("completed_spell", "")) + if not completed_spell.is_empty(): + var sys: SpellSystemScript = spell_system as SpellSystemScript + sys.research_spell(player.index, completed_spell) return - var tech_data: Dictionary = DataLoader.get_tech(player.researching) - var tech_cost: int = tech_data.get("cost", 999999) - - if player.research_progress >= tech_cost: - var completed_tech: String = player.researching - player.research_progress = 0 - player.researching = "" + var completed_tech: String = str(result.get("completed_tech", "")) + if not completed_tech.is_empty(): var old_school_count: int = player.schools.size() player.add_tech(completed_tech) # Arcane Lore completion: transform leader into High Archon diff --git a/src/game/engine/src/modules/tech/knowledge_web.gd b/src/game/engine/src/modules/tech/knowledge_web.gd index 09b94f99..44f5668c 100644 --- a/src/game/engine/src/modules/tech/knowledge_web.gd +++ b/src/game/engine/src/modules/tech/knowledge_web.gd @@ -141,6 +141,16 @@ func start_research(node_id: String, player_index: int) -> bool: return true +## Direct passthrough to the underlying GDExtension's `process_research` +## (warcouncil p1-39 Rail-1 port, 2026-04-27). Caller composes the input +## JSON; Rust owns sci_modifier application + spell-vs-tech branching. +## Returns `{}` if the bridge is unavailable so callers can no-op safely. +func process_research(player_json: String, yield_json: String, sci_modifier: float) -> Dictionary: + if _bridge == null: + return {} + return _bridge.call("process_research", player_json, yield_json, sci_modifier) as Dictionary + + # ── Internals ─────────────────────────────────────────────────────────── func _get_player(player_index: int) -> PlayerScript: diff --git a/src/game/engine/src/modules/tech/tech_web.gd b/src/game/engine/src/modules/tech/tech_web.gd index 44cc0394..9222dd7c 100644 --- a/src/game/engine/src/modules/tech/tech_web.gd +++ b/src/game/engine/src/modules/tech/tech_web.gd @@ -37,6 +37,14 @@ func build(_techs: Array) -> void: _web.build() +## Delegate to GdTechWeb::process_research (warcouncil p1-39 Rail-1 port). +## Caller assembles the player + yield JSON and supplies the difficulty +## sci_modifier. Rust owns: per-city science sum, sci_modifier +## application, instant-complete branch, spell-vs-tech branching. +func process_research(player_json: String, yield_json: String, sci_modifier: float) -> Dictionary: + return _web.process_research(player_json, yield_json, sci_modifier) + + func apply_heritage_tech(player: RefCounted) -> void: ## Grant the player's race origin tech (cost 0 by convention). if player == null: