de48afcbd9
ci / verify (push) Successful in 46s
The aim bug was collateral from the camera work. "Mouse relative to the centre of the screen" WAS the cursor's world position while the world was drawn fixed at the origin, so subtracting the player position gave the right vector. Once the camera scrolled, that expression became the aim vector itself, and subtracting the player position again made the ship aim at a fixed world location -- walking around swung the crosshair with the mouse held still. Fixed by inverting the transform the view actually draws with (world = screen - world_view.position, published by the game scene each frame) rather than assuming the player is centred, so it still holds if the camera later clamps at map edges or gets shake or look-ahead. tests/unit/test_aim.gd pins it, including the regression directly: moving the player must not move the crosshair. Documentation, for other sessions picking this up cold: - docs/ROADMAP.md rewritten as the status map -- every feature in the brief against its state and the file implementing it, the known gaps called out (actor interest management is the notable one), and the ten design questions that are genuinely unspecified and should not be guessed at. - docs/DECISIONS.md, new: settled decisions with their reasoning, so a session does not re-litigate or re-ask. Several are not the obvious default -- no i-frames, no contact damage, non-interruptible escape, and never sending the map seed. - CLAUDE.md and README point at both. 137 tests; check.sh, test.sh and smoke.sh pass.
62 lines
2.7 KiB
GDScript
62 lines
2.7 KiB
GDScript
extends GutTest
|
|
## Cursor-to-world conversion.
|
|
##
|
|
## This broke the moment the camera started scrolling: the old code treated
|
|
## "mouse relative to the centre of the screen" as a world position, which was
|
|
## true only while the world was drawn fixed at the origin. Afterwards the ship
|
|
## aimed at a fixed world location regardless of where the cursor was.
|
|
|
|
const SCREEN := Vector2(1280.0, 720.0)
|
|
const CENTRE := Vector2(640.0, 360.0)
|
|
|
|
|
|
## The camera the game actually uses: world_view.position = centre - player.
|
|
func _offset_for(player: Vector2) -> Vector2:
|
|
return CENTRE - player
|
|
|
|
|
|
func _aim_from(mouse_screen: Vector2, player: Vector2) -> Vector2:
|
|
var world_mouse := ClientRuntime.screen_to_world(mouse_screen, _offset_for(player))
|
|
return world_mouse - player
|
|
|
|
|
|
func test_screen_to_world_inverts_the_draw_transform() -> void:
|
|
# The view draws at screen = world + offset, so this must be its inverse or
|
|
# everything derived from the cursor is off by the camera position.
|
|
var offset := _offset_for(Vector2(900.0, -400.0))
|
|
var world := Vector2(123.0, -456.0)
|
|
var screen := world + offset
|
|
assert_eq(ClientRuntime.screen_to_world(screen, offset), world)
|
|
|
|
|
|
func test_aim_follows_the_cursor_not_a_world_location() -> void:
|
|
# Same cursor position, two very different player positions: the aim
|
|
# direction must be identical, because the player is drawn centred.
|
|
var mouse := CENTRE + Vector2(100.0, 0.0)
|
|
var near_origin := _aim_from(mouse, Vector2.ZERO)
|
|
var far_away := _aim_from(mouse, Vector2(4000.0, -2500.0))
|
|
assert_almost_eq(near_origin.angle(), far_away.angle(), 0.0001,
|
|
"aim must depend on the cursor, not on where in the map you stand")
|
|
assert_almost_eq(near_origin.angle(), 0.0, 0.0001, "cursor right of centre aims right")
|
|
|
|
|
|
func test_the_four_cardinal_directions() -> void:
|
|
var player := Vector2(3000.0, 1500.0)
|
|
assert_almost_eq(_aim_from(CENTRE + Vector2(50.0, 0.0), player).angle(), 0.0, 0.001)
|
|
assert_almost_eq(_aim_from(CENTRE + Vector2(0.0, 50.0), player).angle(), PI * 0.5, 0.001)
|
|
assert_almost_eq(_aim_from(CENTRE + Vector2(0.0, -50.0), player).angle(), -PI * 0.5, 0.001)
|
|
assert_almost_eq(absf(_aim_from(CENTRE + Vector2(-50.0, 0.0), player).angle()), PI, 0.001)
|
|
|
|
|
|
## The regression itself, stated directly: with the old expression the aim
|
|
## vector picked up the player's position, so walking around the map swung the
|
|
## crosshair even with the mouse held still.
|
|
func test_walking_does_not_swing_the_aim() -> void:
|
|
var mouse := CENTRE + Vector2(0.0, -120.0)
|
|
var angles: Array[float] = []
|
|
for step in 5:
|
|
angles.append(_aim_from(mouse, Vector2(step * 500.0, step * -300.0)).angle())
|
|
for a in angles:
|
|
assert_almost_eq(a, angles[0], 0.0001,
|
|
"the crosshair must not move when only the player moves")
|