|
|
|
@@ -24,6 +24,7 @@ var _flow: String = ""
|
|
|
|
|
var _interval: float = 3.0
|
|
|
|
|
var _list_mode: bool = false
|
|
|
|
|
var _config: Dictionary = {}
|
|
|
|
|
var _is_live: bool = false
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func _init():
|
|
|
|
@@ -73,6 +74,19 @@ func _run():
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
var main_node = main_scene.instantiate()
|
|
|
|
|
|
|
|
|
|
# Live mode: configure server port BEFORE main.gd._ready() calls connect_to_sim()
|
|
|
|
|
_is_live = OS.get_environment("SR_LIVE") == "1"
|
|
|
|
|
if _is_live:
|
|
|
|
|
var sim_bridge := root.get_node("/root/SimBridge")
|
|
|
|
|
var port_env := OS.get_environment("SR_PORT")
|
|
|
|
|
if port_env.is_empty():
|
|
|
|
|
push_error("visual_capture: SR_LIVE=1 but SR_PORT not set")
|
|
|
|
|
quit(1)
|
|
|
|
|
return
|
|
|
|
|
sim_bridge.server_port = int(port_env)
|
|
|
|
|
print("visual_capture: live mode — server port %d" % sim_bridge.server_port)
|
|
|
|
|
|
|
|
|
|
root.add_child(main_node)
|
|
|
|
|
|
|
|
|
|
# Wait for NoiseTexture2D async generation
|
|
|
|
@@ -92,6 +106,36 @@ func _run():
|
|
|
|
|
for i in range(settle_count):
|
|
|
|
|
await process_frame
|
|
|
|
|
|
|
|
|
|
# Live mode: wait for server connection and first snapshot
|
|
|
|
|
if _is_live:
|
|
|
|
|
var sim_bridge := root.get_node("/root/SimBridge")
|
|
|
|
|
var game_state := root.get_node("/root/GameState")
|
|
|
|
|
print("visual_capture: waiting for server connection...")
|
|
|
|
|
var max_frames := 300 # 5 seconds at 60fps
|
|
|
|
|
var waited := 0
|
|
|
|
|
while sim_bridge.state != sim_bridge.ConnectionState.CONNECTED:
|
|
|
|
|
if sim_bridge.state == sim_bridge.ConnectionState.ERROR:
|
|
|
|
|
push_error("visual_capture: server connection failed")
|
|
|
|
|
quit(1)
|
|
|
|
|
return
|
|
|
|
|
await process_frame
|
|
|
|
|
waited += 1
|
|
|
|
|
if waited >= max_frames:
|
|
|
|
|
push_error("visual_capture: connection timeout after %d frames" % waited)
|
|
|
|
|
quit(1)
|
|
|
|
|
return
|
|
|
|
|
print("visual_capture: connected after %d frames" % waited)
|
|
|
|
|
# Wait for first snapshot from server
|
|
|
|
|
waited = 0
|
|
|
|
|
while game_state.current_tick == 0:
|
|
|
|
|
await process_frame
|
|
|
|
|
waited += 1
|
|
|
|
|
if waited >= max_frames:
|
|
|
|
|
push_error("visual_capture: no snapshot after %d frames" % waited)
|
|
|
|
|
quit(1)
|
|
|
|
|
return
|
|
|
|
|
print("visual_capture: first snapshot tick=%d (%d frames)" % [game_state.current_tick, waited])
|
|
|
|
|
|
|
|
|
|
if not _scenario.is_empty():
|
|
|
|
|
await _run_scenario(main_node)
|
|
|
|
|
elif not _flow.is_empty():
|
|
|
|
@@ -123,6 +167,40 @@ func _run_scenario(_main_node: Node) -> void:
|
|
|
|
|
# Post-tick setup (e.g. zone tint patching)
|
|
|
|
|
_scenarios.post_setup(_scenario, root)
|
|
|
|
|
|
|
|
|
|
# Replay snapshot: inject a real server snapshot through the FULL client pipeline.
|
|
|
|
|
# Loads MessagePack bytes (exact wire format from server), decodes via Protocol.gd,
|
|
|
|
|
# then applies through GameState → FogState → shader — same path as live game.
|
|
|
|
|
var replay_path: String = scenario_cfg.get("replay_snapshot", "")
|
|
|
|
|
if not replay_path.is_empty():
|
|
|
|
|
var project_root := ProjectSettings.globalize_path("res://")
|
|
|
|
|
var repo_root := project_root.rstrip("/").get_base_dir()
|
|
|
|
|
var abs_path := repo_root.path_join(replay_path)
|
|
|
|
|
var rf := FileAccess.open(abs_path, FileAccess.READ)
|
|
|
|
|
if rf == null:
|
|
|
|
|
push_error("visual_capture: cannot open replay snapshot %s" % abs_path)
|
|
|
|
|
quit(1)
|
|
|
|
|
return
|
|
|
|
|
var replay_bytes := rf.get_buffer(rf.get_length())
|
|
|
|
|
rf.close()
|
|
|
|
|
# Decode through Protocol.decode_snapshot() — same as live IPC receive path.
|
|
|
|
|
# This exercises: msgpack decode → entity decode → tile_kind→type mapping → etc.
|
|
|
|
|
var replay_data: Variant = Protocol.decode_snapshot(replay_bytes)
|
|
|
|
|
if replay_data == null or not replay_data is Dictionary:
|
|
|
|
|
push_error("visual_capture: Protocol.decode_snapshot failed for %s" % abs_path)
|
|
|
|
|
quit(1)
|
|
|
|
|
return
|
|
|
|
|
print("visual_capture: replaying %s (%d bytes, tick=%s, %d tiles)" % [
|
|
|
|
|
replay_path, replay_bytes.size(),
|
|
|
|
|
str(replay_data.get("tick", "?")),
|
|
|
|
|
replay_data.get("visible_tiles", []).size()])
|
|
|
|
|
var game_state := root.get_node("/root/GameState")
|
|
|
|
|
var fog_state := root.get_node("/root/FogState")
|
|
|
|
|
game_state.apply_snapshot(replay_data)
|
|
|
|
|
fog_state.update_from_state()
|
|
|
|
|
# Extra frames for fog uniform propagation
|
|
|
|
|
for i in range(4):
|
|
|
|
|
await process_frame
|
|
|
|
|
|
|
|
|
|
# Extra frames for state propagation + viewport texture lag
|
|
|
|
|
await process_frame
|
|
|
|
|
await process_frame
|
|
|
|
|