## SandboxSpace golden conversions (T-1088 design §2, §10.4): tile -> world metres, ## wire-float floori recovery (live tile-centers vs TestHarness integers), and the ## verified octant -> yaw table (yaw = PI/2 - theta; South 0, East +90, West -90, ## North 180 degrees). ## ## The East/West cells are the REGRESSION GUARD against character_visual.gd's ## mirrored 2D table (east=-90/west=+90, character_visual.gd:184-193) — copying that ## table makes the model face west while walking east. Never relax these two tests. class_name TestSandboxSpace extends GdUnitTestSuite const EPS := 0.000001 # -- tile_to_world --------------------------------------------------------------- func test_tile_to_world_origin_center() -> void: # Tile (0,0) center: subtile 0.5 m, center at +0.25 m on each axis. var w := SandboxSpace.tile_to_world(Vector3i(0, 0, 0)) assert_float(w.x).is_equal_approx(0.25, EPS) assert_float(w.y).is_equal_approx(0.0, EPS) assert_float(w.z).is_equal_approx(0.25, EPS) func test_tile_to_world_hub_spawn() -> void: # Gauntlet Hub spawn (50, 58): x = (50 + 0.5) * 0.5 = 25.25, z = (58 + 0.5) * 0.5 = 29.25. var w := SandboxSpace.tile_to_world(Vector3i(50, 58, 0)) assert_float(w.x).is_equal_approx(25.25, EPS) assert_float(w.y).is_equal_approx(0.0, EPS) assert_float(w.z).is_equal_approx(29.25, EPS) func test_tile_to_world_sim_south_is_local_plus_z() -> void: # Sim +y (South, Y-down) maps to local +Z: one southward tile step moves +0.5 m in Z only. var a := SandboxSpace.tile_to_world(Vector3i(10, 20, 0)) var b := SandboxSpace.tile_to_world(Vector3i(10, 21, 0)) assert_float(b.x - a.x).is_equal_approx(0.0, EPS) assert_float(b.z - a.z).is_equal_approx(0.5, EPS) func test_tile_to_world_sim_east_is_local_plus_x() -> void: # Sim +x (East) maps to local +X: one eastward tile step moves +0.5 m in X only. var a := SandboxSpace.tile_to_world(Vector3i(10, 20, 0)) var b := SandboxSpace.tile_to_world(Vector3i(11, 20, 0)) assert_float(b.x - a.x).is_equal_approx(0.5, EPS) assert_float(b.z - a.z).is_equal_approx(0.0, EPS) # -- wire-float recovery (floori, never round) ------------------------------------- func test_wire_to_tile_live_tile_center_floats() -> void: # Live wire sends tile-center floats (N + 0.5) — floori recovers N. # round(50.5) would land on 51: the half-a-tile-off failure mode. assert_object(SandboxSpace.wire_to_tile(50.5, 58.5)).is_equal(Vector3i(50, 58, 0)) func test_wire_to_tile_harness_integer_floats() -> void: # TestHarness sends integer floats — floori is a no-op, byte-identical both modes. assert_object(SandboxSpace.wire_to_tile(50.0, 58.0)).is_equal(Vector3i(50, 58, 0)) func test_wire_to_world_recenters() -> void: # Wire float -> tile index -> recomputed center: 50.5 -> tile 50 -> 25.25 m. var w := SandboxSpace.wire_to_world(50.5, 58.5) assert_float(w.x).is_equal_approx(25.25, EPS) assert_float(w.z).is_equal_approx(29.25, EPS) func test_wire_to_world_identical_across_modes() -> void: # The same tile must resolve to the same world point from either wire spelling. var live := SandboxSpace.wire_to_world(50.5, 58.5) var harness := SandboxSpace.wire_to_world(50.0, 58.0) assert_float(live.x).is_equal_approx(harness.x, EPS) assert_float(live.z).is_equal_approx(harness.z, EPS) func test_world_to_tile_round_trip() -> void: # Gauntlet corners + spawn: world_to_tile inverts tile_to_world exactly. for tile: Vector3i in [ Vector3i(0, 0, 0), Vector3i(50, 58, 0), Vector3i(116, 124, 0), Vector3i(3, 7, 0) ]: assert_object(SandboxSpace.world_to_tile(SandboxSpace.tile_to_world(tile))).is_equal(tile) # -- octant -> yaw (all 8; E/W are the mirrored-table regression guard) -------------- func test_octant_yaw_south_is_zero() -> void: assert_float(SandboxSpace.octant_to_yaw("South")).is_equal_approx(0.0, EPS) func test_octant_yaw_southeast_is_plus_45() -> void: assert_float(SandboxSpace.octant_to_yaw("Southeast")).is_equal_approx(PI / 4.0, EPS) func test_octant_yaw_east_is_plus_90() -> void: # REGRESSION GUARD: East = +PI/2 (model turns to +X). character_visual.gd's # mirrored table says east = -90 deg — copying it faces the model west while # walking east (the fatal flaw of both rejected design candidates). assert_float(SandboxSpace.octant_to_yaw("East")).is_equal_approx(PI / 2.0, EPS) func test_octant_yaw_northeast_is_plus_135() -> void: assert_float(SandboxSpace.octant_to_yaw("Northeast")).is_equal_approx(3.0 * PI / 4.0, EPS) func test_octant_yaw_north_is_180() -> void: assert_float(SandboxSpace.octant_to_yaw("North")).is_equal_approx(PI, EPS) func test_octant_yaw_northwest_is_minus_135() -> void: assert_float(SandboxSpace.octant_to_yaw("Northwest")).is_equal_approx(-3.0 * PI / 4.0, EPS) func test_octant_yaw_west_is_minus_90() -> void: # REGRESSION GUARD: West = -PI/2 (model turns to -X) — the mirror of the # character_visual.gd 2D table (west = +90 deg there). See East guard above. assert_float(SandboxSpace.octant_to_yaw("West")).is_equal_approx(-PI / 2.0, EPS) func test_octant_yaw_southwest_is_minus_45() -> void: assert_float(SandboxSpace.octant_to_yaw("Southwest")).is_equal_approx(-PI / 4.0, EPS) # -- derivation internals ------------------------------------------------------------ func test_sim_angle_convention_matches_input_mapper() -> void: # theta: 0 = East, +PI/2 = South, -PI/2 = North (Y-down radians — server # vision_cone.rs and InputMapper.facing_angle share this convention). assert_float(SandboxSpace.octant_to_sim_angle("East")).is_equal_approx(0.0, EPS) assert_float(SandboxSpace.octant_to_sim_angle("South")).is_equal_approx(PI / 2.0, EPS) assert_float(SandboxSpace.octant_to_sim_angle("North")).is_equal_approx(-PI / 2.0, EPS) func test_sim_angle_to_yaw_wraps_into_signed_pi_range() -> void: # Northwest: theta = -3PI/4 -> PI/2 - theta = 5PI/4 -> wrapped to -3PI/4. var nw := SandboxSpace.sim_angle_to_yaw(-3.0 * PI / 4.0) assert_float(nw).is_equal_approx(-3.0 * PI / 4.0, EPS) # North stays +PI (range (-PI, PI]), matching the golden table's 180 deg. assert_float(SandboxSpace.sim_angle_to_yaw(-PI / 2.0)).is_equal_approx(PI, EPS) func test_unknown_octant_defaults_to_north() -> void: # Defensive default mirrors GameState.player_facing's "North" default. assert_float(SandboxSpace.octant_to_yaw("Sideways")).is_equal_approx(PI, EPS)