feat(turn-manager): ✨ Introduce parallel processing for Rust fauna encounters via environment flag
Co-Authored-By: Lilith Autocommit <noreply@atlilith.com>
This commit is contained in:
parent
e3eceb3643
commit
d39f9b3a64
3 changed files with 132 additions and 91 deletions
|
|
@ -226,6 +226,9 @@ func next_player() -> void:
|
|||
if next_index >= GameState.players.size():
|
||||
# All players have taken their turn — run wild creatures, then advance
|
||||
var proc := _processor as TurnProcessorScript
|
||||
# Iter 7k: optional parallel Rust fauna encounter pass. No-op unless
|
||||
# RUST_FAUNA_ENCOUNTERS env flag is set (off by default).
|
||||
proc._process_rust_fauna_encounters()
|
||||
proc._process_wild_creatures()
|
||||
# Diplomacy tick: decay timed modifiers and agreements once per full turn.
|
||||
(diplomacy as DiplomacyScript).process_turn()
|
||||
|
|
|
|||
121
src/game/engine/src/modules/management/rust_fauna_integration.gd
Normal file
121
src/game/engine/src/modules/management/rust_fauna_integration.gd
Normal file
|
|
@ -0,0 +1,121 @@
|
|||
class_name RustFaunaIntegration
|
||||
extends RefCounted
|
||||
## Iter 7k — gated parallel Rust fauna encounter pass.
|
||||
##
|
||||
## When `RUST_FAUNA_ENCOUNTERS` is enabled in EnvConfig (typically via
|
||||
## `.env.development` for testing), the turn processor runs an additional
|
||||
## rust-resolved fauna encounter pass adjacent to `_process_wild_creatures`.
|
||||
## The two flows COEXIST — the existing GDScript `wild_creature_ai` keeps
|
||||
## moving wild creatures and resolving combat, and the rust pass runs only
|
||||
## the encounter rolls (via `GdTurnProcessor::step_encounters_only`) for
|
||||
## the units in each player's roster against the lair NPC buildings.
|
||||
##
|
||||
## Off by default. iter 7l will decide whether to make this the canonical
|
||||
## fauna pipeline (and retire wild_creature_ai's combat layer) or keep
|
||||
## both coexisting permanently. iter 7k just lands the integration.
|
||||
##
|
||||
## Extracted to its own file from `turn_processor.gd` to keep that file
|
||||
## under the project's 500-line GDScript cap (same extraction pattern as
|
||||
## `city_buildable_helper.gd` from iter 7c).
|
||||
|
||||
const RustFaunaBridgeScript: GDScript = preload(
|
||||
"res://engine/src/modules/management/rust_fauna_bridge.gd"
|
||||
)
|
||||
|
||||
const ENV_KEY: String = "RUST_FAUNA_ENCOUNTERS"
|
||||
const DEFAULT_LAIR_TIER: int = 5
|
||||
|
||||
|
||||
## Run the Rust fauna encounter resolver against every player's units.
|
||||
## Gated by `EnvConfig.get_bool("RUST_FAUNA_ENCOUNTERS")` — returns
|
||||
## immediately if the flag isn't set, so this call is a no-op in the
|
||||
## default game configuration.
|
||||
static func run_all_players() -> void:
|
||||
if not EnvConfig.get_bool(ENV_KEY, false):
|
||||
return
|
||||
|
||||
var game_map: RefCounted = GameState.get_game_map()
|
||||
if game_map == null:
|
||||
return
|
||||
|
||||
var processor: RefCounted = ClassDB.instantiate("GdTurnProcessor") as RefCounted
|
||||
if processor == null:
|
||||
push_warning(
|
||||
"RustFaunaIntegration: GdTurnProcessor not registered "
|
||||
+ "(GDExtension missing or out-of-date)"
|
||||
)
|
||||
return
|
||||
processor.call("set_victory_city_count", 255)
|
||||
|
||||
var lairs: Array = _build_lair_list()
|
||||
if lairs.is_empty():
|
||||
return
|
||||
|
||||
var grid_size: Vector2i = Vector2i(
|
||||
int(game_map.width), int(game_map.height)
|
||||
)
|
||||
|
||||
for player: Variant in GameState.players:
|
||||
_run_for_player(processor, player, lairs, grid_size)
|
||||
|
||||
|
||||
## Extract the list of lair-stamping tuples from `GameState.npc_buildings`.
|
||||
## Each tuple is `[col, row, tier, species_id]` — the shape
|
||||
## `RustFaunaBridge.stamp_lairs` accepts.
|
||||
##
|
||||
## Default tier baseline is used because the `Building` entity doesn't
|
||||
## carry a tier field directly. iter 7l can refine via wilds.json lookup
|
||||
## if the bench targets need true per-lair tiers.
|
||||
static func _build_lair_list() -> Array:
|
||||
var lairs: Array = []
|
||||
var species_seed: int = 1
|
||||
for b: Variant in GameState.npc_buildings:
|
||||
var type_id: String = b.type_id
|
||||
if type_id == "village" or type_id == "ruin":
|
||||
continue
|
||||
var pos: Vector2i = b.position
|
||||
lairs.append([pos.x, pos.y, DEFAULT_LAIR_TIER, species_seed])
|
||||
species_seed += 1
|
||||
return lairs
|
||||
|
||||
|
||||
## Run the bridge for one player's units against the shared lair list.
|
||||
## Invoked once per player per turn by `run_all_players`.
|
||||
static func _run_for_player(
|
||||
processor: RefCounted,
|
||||
player: Variant,
|
||||
lairs: Array,
|
||||
grid_size: Vector2i,
|
||||
) -> void:
|
||||
var live_units: Array[Unit] = []
|
||||
for u: Variant in player.units:
|
||||
if u is Unit and u.is_alive():
|
||||
live_units.append(u)
|
||||
if live_units.is_empty():
|
||||
return
|
||||
|
||||
var city_positions: Array[Vector2i] = []
|
||||
for c: Variant in player.cities:
|
||||
if c != null:
|
||||
city_positions.append(c.position)
|
||||
|
||||
var axes: Dictionary = {
|
||||
"expansion": 3,
|
||||
"production": 3,
|
||||
"wealth": 3,
|
||||
"culture": 2,
|
||||
}
|
||||
|
||||
var state: RefCounted = RustFaunaBridgeScript.build_state(
|
||||
axes, live_units, city_positions, grid_size, null
|
||||
)
|
||||
if state == null:
|
||||
return
|
||||
RustFaunaBridgeScript.stamp_lairs(state, lairs)
|
||||
RustFaunaBridgeScript.resolve_fauna_encounters(processor, state, live_units)
|
||||
# resolve_fauna_encounters has already:
|
||||
# - set hp=0 on dead units
|
||||
# - emitted EventBus.unit_destroyed for each
|
||||
# The caller is responsible for sweeping dead units from collections;
|
||||
# we leave the references in place so other systems can react to the
|
||||
# death signal first.
|
||||
|
|
@ -33,8 +33,8 @@ const WeatherScript: GDScript = preload("res://engine/src/modules/climate/weathe
|
|||
const EcosystemScript: GDScript = preload(
|
||||
"res://engine/src/modules/ecology/ecosystem.gd"
|
||||
)
|
||||
const RustFaunaBridgeScript: GDScript = preload(
|
||||
"res://engine/src/modules/management/rust_fauna_bridge.gd"
|
||||
const RustFaunaIntegrationScript: GDScript = preload(
|
||||
"res://engine/src/modules/management/rust_fauna_integration.gd"
|
||||
)
|
||||
|
||||
var unit_manager: RefCounted # UnitManager — set by TurnManager._ready()
|
||||
|
|
@ -283,96 +283,13 @@ func _process_wild_creatures() -> void:
|
|||
wild_ai.process_wild_turn(game_map)
|
||||
|
||||
|
||||
# ── Iter 7k: gated parallel Rust fauna encounter pass ──────────────────
|
||||
#
|
||||
# When `RUST_FAUNA_ENCOUNTERS` is enabled in EnvConfig (typically via
|
||||
# `.env.development` for testing), the turn processor runs an additional
|
||||
# rust-resolved fauna encounter pass adjacent to `_process_wild_creatures`.
|
||||
# The two flows COEXIST — the existing GDScript wild_creature_ai keeps
|
||||
# moving wild creatures and resolving combat, and the rust pass runs only
|
||||
# the encounter rolls (via GdTurnProcessor::step_encounters_only) for the
|
||||
# units in each player's roster against the lair NPC buildings.
|
||||
#
|
||||
# Off by default. iter 7l will decide whether to make this the canonical
|
||||
# fauna pipeline (and retire wild_creature_ai's combat layer) or keep both
|
||||
# coexisting permanently. iter 7k just lands the integration.
|
||||
|
||||
const RUST_FAUNA_ENV_KEY: String = "RUST_FAUNA_ENCOUNTERS"
|
||||
const RUST_FAUNA_DEFAULT_LAIR_TIER: int = 5
|
||||
|
||||
|
||||
func _process_rust_fauna_encounters() -> void:
|
||||
## Run the Rust fauna encounter resolver against every player's units.
|
||||
## Gated by `EnvConfig.is_enabled("RUST_FAUNA_ENCOUNTERS")` — the method
|
||||
## returns immediately if the flag isn't set, so this call is a no-op
|
||||
## in the default game configuration.
|
||||
if not EnvConfig.get_bool(RUST_FAUNA_ENV_KEY, false):
|
||||
return
|
||||
|
||||
var game_map: RefCounted = GameState.get_game_map()
|
||||
if game_map == null:
|
||||
return
|
||||
|
||||
var processor: RefCounted = ClassDB.instantiate("GdTurnProcessor") as RefCounted
|
||||
if processor == null:
|
||||
push_warning(
|
||||
"_process_rust_fauna_encounters: GdTurnProcessor not registered "
|
||||
+ "(GDExtension missing or out-of-date)"
|
||||
)
|
||||
return
|
||||
processor.call("set_victory_city_count", 255)
|
||||
|
||||
# Build the lair list once. Default tier baseline used because the
|
||||
# building entity doesn't carry a tier field directly — iter 7l can
|
||||
# refine via wilds.json lookup if the bench targets need it.
|
||||
var lairs: Array = []
|
||||
var species_seed: int = 1
|
||||
for b: Variant in GameState.npc_buildings:
|
||||
var type_id: String = b.type_id
|
||||
if type_id == "village" or type_id == "ruin":
|
||||
continue
|
||||
var pos: Vector2i = b.position
|
||||
lairs.append([pos.x, pos.y, RUST_FAUNA_DEFAULT_LAIR_TIER, species_seed])
|
||||
species_seed += 1
|
||||
|
||||
if lairs.is_empty():
|
||||
return
|
||||
|
||||
var grid_size: Vector2i = Vector2i(int(game_map.width), int(game_map.height))
|
||||
|
||||
for player: Variant in GameState.players:
|
||||
var live_units: Array[Unit] = []
|
||||
for u: Variant in player.units:
|
||||
if u is Unit and u.is_alive():
|
||||
live_units.append(u)
|
||||
if live_units.is_empty():
|
||||
continue
|
||||
|
||||
var city_positions: Array[Vector2i] = []
|
||||
for c: Variant in player.cities:
|
||||
if c != null:
|
||||
city_positions.append(c.position)
|
||||
|
||||
var axes: Dictionary = {
|
||||
"expansion": 3,
|
||||
"production": 3,
|
||||
"wealth": 3,
|
||||
"culture": 2,
|
||||
}
|
||||
|
||||
var state: RefCounted = RustFaunaBridgeScript.build_state(
|
||||
axes, live_units, city_positions, grid_size, null
|
||||
)
|
||||
if state == null:
|
||||
continue
|
||||
RustFaunaBridgeScript.stamp_lairs(state, lairs)
|
||||
RustFaunaBridgeScript.resolve_fauna_encounters(processor, state, live_units)
|
||||
# resolve_fauna_encounters has already:
|
||||
# - set hp=0 on dead units
|
||||
# - emitted EventBus.unit_destroyed for each
|
||||
# The caller (next_player in turn_manager.gd) is responsible for
|
||||
# sweeping dead units from collections; we leave the references
|
||||
# in place so other systems can react to the death signal first.
|
||||
## Iter 7k: gated parallel Rust fauna encounter pass.
|
||||
## Delegates to `RustFaunaIntegration.run_all_players()`, which handles
|
||||
## the env-flag check, lair enumeration, and per-player bridge calls.
|
||||
## Kept as a method on `TurnProcessor` so `turn_manager.gd` can call it
|
||||
## through the same `proc.<method>()` pattern as the other turn phases.
|
||||
RustFaunaIntegrationScript.run_all_players()
|
||||
|
||||
|
||||
func _process_spell_system(player: RefCounted) -> void: # Player
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue