feat(engine): route live unit spawn/death through the presentation_units slot
Rail-1 Phase-1 increment 1 (wiring) — every site that brings a Unit into or out of the world now flips the authoritative Rust slot alongside the player/layer lists, so the proxy resolves a live MapUnit. Spawn sites → `spawn_into_slot()`: - world_map_units.register_unit (the central chokepoint: starting units via spawn_starting_units, the prologue tribe via _on_prologue_tribe_converged, and any future caller) - turn_processor._spawn_unit (city-built unit) - wild_creature_ai (lair spawns → wilds row; now constructs via the populating ctor so stats come from JSON) Death / consumption sites → `remove_from_slot()` (index-shift-safe; snapshots final pos/hp into the local mirror first so unit_destroyed subscribers — loot, chronicle — still read the unit as it died): - world_map_units.remove_unit - combat_utils.handle_unit_death + _destroy_high_archon - economy upkeep disband - ai_turn_bridge_dispatch settler-consumed-on-found - prologue_driver tribe-consumed-into-capital No live unit-CAPTURE path exists in Game 1 (units die, they are not captured); `transfer_to_owner` is wired on the proxy for parity but no site converts to it. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
2ad4b7bed6
commit
b28e25f554
7 changed files with 32 additions and 3 deletions
|
|
@ -38,6 +38,12 @@ func create_unit(type_id: String, owner_index: int, pos: Vector2i) -> RefCounted
|
|||
|
||||
|
||||
func register_unit(unit: RefCounted, player: RefCounted) -> void:
|
||||
# Rail-1: a unit enters the world here — push it into the authoritative
|
||||
# `presentation_units` slot before it lands in the player/layer lists, so all
|
||||
# downstream position/hp/movement/posture reads route to Rust. Idempotent if
|
||||
# the unit was already spawned (e.g. a load path).
|
||||
if unit is UnitScript:
|
||||
unit.spawn_into_slot()
|
||||
player.units.append(unit)
|
||||
var primary: Dictionary = GameState.get_primary_layer()
|
||||
var layer_units: Array = primary.get("units", [])
|
||||
|
|
@ -46,6 +52,10 @@ func register_unit(unit: RefCounted, player: RefCounted) -> void:
|
|||
|
||||
|
||||
func remove_unit(unit: RefCounted, player: RefCounted) -> void:
|
||||
# Rail-1: drop the unit's entry from the authoritative slot (index-shift-safe)
|
||||
# before unlinking it from the player/layer lists.
|
||||
if unit is UnitScript:
|
||||
unit.remove_from_slot()
|
||||
player.units.erase(unit)
|
||||
var primary: Dictionary = GameState.get_primary_layer()
|
||||
var layer_units: Array = primary.get("units", [])
|
||||
|
|
|
|||
|
|
@ -228,6 +228,9 @@ static func dispatch_found_city(
|
|||
GameState.turn_number,
|
||||
)
|
||||
player.cities.append(city)
|
||||
# Rail-1: the settler is consumed founding the city — drop its slot entry.
|
||||
if settler is Unit:
|
||||
settler.remove_from_slot()
|
||||
player.units.erase(settler)
|
||||
var primary: Dictionary = GameState.get_primary_layer()
|
||||
primary.get("units", []).erase(settler)
|
||||
|
|
|
|||
|
|
@ -72,15 +72,16 @@ func spawn_initial_creatures(game_map: RefCounted) -> void:
|
|||
continue
|
||||
|
||||
var b_pos: Vector2i = _building_pos(b)
|
||||
var unit: RefCounted = UnitScript.new()
|
||||
var unit: RefCounted = UnitScript.new(unit_type_id, -1, b_pos)
|
||||
unit.id = "wild_%d" % _rng.randi()
|
||||
unit.type_id = unit_type_id
|
||||
unit.owner = -1
|
||||
unit.position = b_pos
|
||||
unit.max_hp = unit_data.get("hp", 8)
|
||||
unit.hp = unit.max_hp
|
||||
unit.movement_remaining = unit_data.get("movement", 2)
|
||||
unit.has_attacked = false
|
||||
# Rail-1: wild creatures land in the dedicated wilds row of the
|
||||
# authoritative `presentation_units` slot (owner -1 → `wilds_pi()`).
|
||||
unit.spawn_into_slot()
|
||||
|
||||
primary_layer.get("units", []).append(unit)
|
||||
EventBus.wild_creature_spawned.emit(unit, b_pos)
|
||||
|
|
|
|||
|
|
@ -89,6 +89,12 @@ static func handle_unit_death(unit: RefCounted, killer: RefCounted, all_units: A
|
|||
if unit.owner == -1 and killer != null and killer is UnitScript and killer.owner >= 0:
|
||||
_roll_wild_creature_loot(unit, killer)
|
||||
|
||||
# Rail-1: drop the dead unit's entry from the authoritative `presentation_units`
|
||||
# slot. `remove_from_slot` snapshots its final position/hp into the unit's
|
||||
# local mirror first, so the `unit_destroyed` subscribers (loot, chronicle)
|
||||
# still read the unit as it died rather than safe defaults.
|
||||
unit.remove_from_slot()
|
||||
|
||||
all_units.erase(unit)
|
||||
|
||||
if unit.owner >= 0 and unit.owner < GameState.players.size():
|
||||
|
|
@ -195,6 +201,7 @@ static func _destroy_high_archon(player: RefCounted, all_units: Array) -> void:
|
|||
for unit: RefCounted in player.units:
|
||||
if unit is UnitScript and unit.type_id == "high_archon":
|
||||
unit.hp = 0
|
||||
unit.remove_from_slot()
|
||||
all_units.erase(unit)
|
||||
player.units.erase(unit)
|
||||
var primary_layer: Dictionary = GameState.get_primary_layer()
|
||||
|
|
|
|||
|
|
@ -170,6 +170,8 @@ static func _disband_cheapest(player: RefCounted, count: int) -> void:
|
|||
break
|
||||
if victim == null:
|
||||
return
|
||||
# Rail-1: drop the disbanded unit from the authoritative slot.
|
||||
victim.remove_from_slot()
|
||||
player.units.erase(victim)
|
||||
var primary: Dictionary = GameState.get_primary_layer()
|
||||
primary.get("units", []).erase(victim)
|
||||
|
|
|
|||
|
|
@ -184,6 +184,9 @@ func found_capital_for_tribe(
|
|||
city.owner = pid
|
||||
city.found_with_population(city_name, q, r, true, 1, pop)
|
||||
player.cities.append(city)
|
||||
# Rail-1: the tribe unit is consumed into the capital — drop its slot entry.
|
||||
if tribe_unit is Unit:
|
||||
tribe_unit.remove_from_slot()
|
||||
player.units.erase(tribe_unit)
|
||||
var primary: Dictionary = GameState.get_primary_layer()
|
||||
var layer_units: Array = primary.get("units", [])
|
||||
|
|
|
|||
|
|
@ -230,6 +230,9 @@ func _spawn_unit(type_id: String, player: RefCounted, pos: Vector2i) -> UnitScri
|
|||
unit.id = "unit_p%d_%d_%d_%d" % [player.index, pos.x, pos.y, GameState.turn_number]
|
||||
var data: Dictionary = DataLoader.get_unit(type_id)
|
||||
unit.display_name = data.get("name", type_id)
|
||||
# Rail-1: push the unit into the authoritative `presentation_units` slot. All
|
||||
# subsequent reads/writes of position/hp/movement/posture route to Rust.
|
||||
unit.spawn_into_slot()
|
||||
player.units.append(unit)
|
||||
var primary: Dictionary = GameState.get_primary_layer()
|
||||
var layer_units: Array = primary.get("units", [])
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue