extends SceneTree # Deterministic server/training-path trace for Phase 4's client-only # guarantee. The external parity command copies this file into a `git archive # HEAD` tree and compares its output byte-for-byte against the worktree. # # It drives the real Ship scene through a fixed controller, records every # physics-step pose/velocity and the observation vector, and deliberately # avoids NetworkedMatch/client code. Any accidental change to shared forces, # integration, drag, rotation, or observations changes the trace. const SHIP_SCENE = preload("res://objects/ship.tscn") const BALL_SCENE = preload("res://objects/ball.tscn") const ARENA_BOUNDARY_SCENE = preload("res://objects/arena_boundary.tscn") const ShipObservations = preload("res://scripts/ship_observations.gd") const ShipControllerScript = preload("res://scripts/ship_controller.gd") const ShipActionScript = preload("res://scripts/ship_action.gd") class FixedController extends ShipControllerScript: var tick := 0 func get_action(): tick += 1 var action := ShipActionScript.new() # Three deterministic segments exercise forward/vertical/strafe force, # yaw/pitch/roll torque, drag, and action transitions. if tick < 121: action.thrust = Vector3(0.25, 0.50, 1.0) action.rotation = Vector3(0.10, 0.35, -0.15) elif tick < 241: action.thrust = Vector3(-0.40, -0.20, 0.65) action.rotation = Vector3(-0.25, -0.20, 0.30) else: action.thrust = Vector3(0.15, 0.10, -0.35) action.rotation = Vector3(0.0, 0.15, 0.0) return action func _init() -> void: call_deferred("_run") func _run() -> void: # Use the same arena, ball and two-ship roster shape as a real match. This # deliberately exercises collision resources, scene setup and the complete # padded observation layout rather than a synthetic isolated rigid body. root.add_child(ARENA_BOUNDARY_SCENE.instantiate()) var ship = SHIP_SCENE.instantiate() ship.team = 0 ship.spawn_index = 0 root.add_child(ship) ship.global_position = Vector3(-4.0, 5.0, 4.0) ship.set_controller(FixedController.new()) var opponent = SHIP_SCENE.instantiate() opponent.team = 1 opponent.spawn_index = 0 root.add_child(opponent) opponent.global_position = Vector3(5.0, 6.0, -5.0) opponent.set_controller(FixedController.new()) var observation_ball = BALL_SCENE.instantiate() root.add_child(observation_ball) observation_ball.global_position = Vector3(0.0, 5.5, 0.0) observation_ball.linear_velocity = Vector3(0.7, 0.0, -0.4) var team0: Array[Ship] = [ship] var team1: Array[Ship] = [opponent] var trace: Array[String] = [] for tick in 360: await physics_frame var observation: Array = ShipObservations.build(ship, [], team1, observation_ball, Vector3(0.0, 0.0, -27.0)) var opponent_observation: Array = ShipObservations.build(opponent, [], team0, observation_ball, Vector3(0.0, 0.0, 27.0)) var observation_values: Array[String] = [] for value in observation: observation_values.append(str(roundi(float(value) * 100000.0))) for value in opponent_observation: observation_values.append(str(roundi(float(value) * 100000.0))) trace.append("%d:%d,%d,%d:%d,%d,%d:%d,%d,%d:%d,%d,%d:%d,%d,%d:%s" % [ tick, roundi(ship.global_position.x * 100000.0), roundi(ship.global_position.y * 100000.0), roundi(ship.global_position.z * 100000.0), roundi(ship.linear_velocity.x * 100000.0), roundi(ship.linear_velocity.y * 100000.0), roundi(ship.linear_velocity.z * 100000.0), roundi(ship.angular_velocity.x * 100000.0), roundi(ship.angular_velocity.y * 100000.0), roundi(ship.angular_velocity.z * 100000.0), roundi(opponent.global_position.x * 100000.0), roundi(opponent.global_position.y * 100000.0), roundi(opponent.global_position.z * 100000.0), roundi(observation_ball.global_position.x * 100000.0), roundi(observation_ball.global_position.y * 100000.0), roundi(observation_ball.global_position.z * 100000.0), ",".join(observation_values), ]) print("PHASE4_SERVER_PARITY ", "|".join(trace)) quit()