Draw the mission map and let the player point at it #18

Merged
icub3d merged 1 commit from 3-mission-rendering-and-camera into main 2026-08-07 17:17:05 +00:00
Owner

Puts the proving ground on screen and lets the cursor resolve to a tile. Coloured
primitives only; no art, no glTF.

Closes #3

What to look at when you run it

There is no display here, so nothing visual is claimed — only that it builds, passes
cargo test (67, up from 45), cargo clippy --all-targets -- -D warnings, and
cargo fmt --check. Deploy from the main menu, then:

  • Framing. The whole map should be on screen when the mission opens, centred, with
    the raised platform and pillars readable as elevation.
  • Camera. Right-drag orbits, middle-drag pans, wheel zooms. WASD pans, Q/E
    turn, R/F tilt, -/= zoom. It should be hard to lose the map: pitch cannot go
    under the floor or straight down, zoom is bounded, and the focus is held near the
    map's extent.
  • Hover. An outline with four corner risers should sit on the tile under the cursor
    and follow it up onto the raised platform and the pillars. The thing worth stress
    testing is a shallow angle across a pillar — the cue should land on the pillar, not
    the floor behind it.
  • Escape returns to the menu, and the menu should look exactly as it did before.

Two things to scrutinise

Scope I added: an entry point. #3 says the mission view appears on entering
GameState::Mission, but nothing in the game entered that state, so the work would have
been unreviewable. The root menu gains a "Deploy" button and Escape leaves a mission.
Both are placeholders — the contract board and a real victory check are #12 — and both
are called that in the code and in CLAUDE.md.

Camera ordering touches the menu. The UI camera now draws above the mission camera
and stops clearing while a mission is beneath it, otherwise the HUD would wipe the map.
Outside a mission it goes back to clearing, so the menu and the loading frames behave
exactly as before. This is the one change I cannot verify without a display, and it is
the change most likely to show up as a smear if I have it wrong.

Notes on the shape

MapLayout is the only thing that knows where the grid sits in world space. Rendering
draws blocks from it, picking casts rays at the same blocks — one function, so the
highlight cannot end up a tile away from the cursor. It touches no ECS, which is what
makes oblique picking over varying heights testable: the tests build rays by hand and
assert that a wall is picked instead of the floor it hides.

The camera is four numbers (focus, yaw, pitch, distance) with a Transform derived from
them each frame, rather than a Transform mutated in place. Its clamps are multiples of
the map's framing radius, so a larger map frames correctly without retuning, and the
opening distance is checked against the projection's field of view rather than eyeballed.

World-space dimensions live in theme::scale, next to the space ladder, so the
"no size literal outside theme" rule holds for 3D as it does for UI. Terrain, hover,
and lighting colours are new semantic tokens; the assignment table in CLAUDE.md has
the new rows.

The headless test drives a real state transition with no window and no renderer, and
asserts the view is built on entry and gone on exit. I checked it is not vacuous by
dropping the teardown system and confirming it fails.

Two things I deliberately did not use: Bevy's mesh picking backend (an analytic ray test
is deterministic and testable headlessly, which the backend is not), and diagonal
neighbours (movement's business, not the coordinate type's).

Puts the proving ground on screen and lets the cursor resolve to a tile. Coloured primitives only; no art, no glTF. Closes #3 ## What to look at when you run it There is no display here, so nothing visual is claimed — only that it builds, passes `cargo test` (67, up from 45), `cargo clippy --all-targets -- -D warnings`, and `cargo fmt --check`. Deploy from the main menu, then: - **Framing.** The whole map should be on screen when the mission opens, centred, with the raised platform and pillars readable as elevation. - **Camera.** Right-drag orbits, middle-drag pans, wheel zooms. `WASD` pans, `Q`/`E` turn, `R`/`F` tilt, `-`/`=` zoom. It should be hard to lose the map: pitch cannot go under the floor or straight down, zoom is bounded, and the focus is held near the map's extent. - **Hover.** An outline with four corner risers should sit on the tile under the cursor and follow it up onto the raised platform and the pillars. The thing worth stress testing is a shallow angle across a pillar — the cue should land on the pillar, not the floor behind it. - **Escape** returns to the menu, and the menu should look exactly as it did before. ## Two things to scrutinise **Scope I added: an entry point.** #3 says the mission view appears on entering `GameState::Mission`, but nothing in the game entered that state, so the work would have been unreviewable. The root menu gains a "Deploy" button and Escape leaves a mission. Both are placeholders — the contract board and a real victory check are #12 — and both are called that in the code and in `CLAUDE.md`. **Camera ordering touches the menu.** The UI camera now draws above the mission camera and stops clearing while a mission is beneath it, otherwise the HUD would wipe the map. Outside a mission it goes back to clearing, so the menu and the loading frames behave exactly as before. This is the one change I cannot verify without a display, and it is the change most likely to show up as a smear if I have it wrong. ## Notes on the shape `MapLayout` is the only thing that knows where the grid sits in world space. Rendering draws blocks from it, picking casts rays at the same blocks — one function, so the highlight cannot end up a tile away from the cursor. It touches no ECS, which is what makes oblique picking over varying heights testable: the tests build rays by hand and assert that a wall is picked instead of the floor it hides. The camera is four numbers (focus, yaw, pitch, distance) with a `Transform` derived from them each frame, rather than a `Transform` mutated in place. Its clamps are multiples of the map's framing radius, so a larger map frames correctly without retuning, and the opening distance is checked against the projection's field of view rather than eyeballed. World-space dimensions live in `theme::scale`, next to the `space` ladder, so the "no size literal outside `theme`" rule holds for 3D as it does for UI. Terrain, hover, and lighting colours are new semantic tokens; the assignment table in `CLAUDE.md` has the new rows. The headless test drives a real state transition with no window and no renderer, and asserts the view is built on entry and gone on exit. I checked it is not vacuous by dropping the teardown system and confirming it fails. Two things I deliberately did not use: Bevy's mesh picking backend (an analytic ray test is deterministic and testable headlessly, which the backend is not), and diagonal neighbours (movement's business, not the coordinate type's).
Entering a mission builds terrain blocks, a key light, and an orbit camera;
leaving takes them with it. Coloured PBR primitives only — the map is the thing
being read, and geometry that matches the rules exactly is worth more than
geometry that looks like a building.

`MapLayout` is the single place that knows where the grid sits in world space.
Rendering draws blocks from it and picking casts rays against the same blocks,
so the highlight cannot drift a tile away from the cursor. It touches no ECS,
which makes the part most likely to be quietly wrong at an odd camera angle —
oblique picking over varying tile heights — testable with a hand-built ray.

The camera's state is four numbers rather than a `Transform`, with the transform
derived from them each frame; reading a yaw back out of a rotation matrix to add
a degree to it is how orbit controls drift and gimbal-flip. Every mouse gesture
has a keyboard equivalent.

World-space dimensions join the theme as `theme::scale`, alongside terrain,
hover, and lighting tokens. The hover cue is an outline with corner risers, so
it does not rely on colour alone.

The main menu gains a "Deploy" button and Escape leaves a mission — placeholders
so the view is reachable now, replaced by the contract board and a real
victory check in #12.

Closes #3
icub3d merged commit b31976293e into main 2026-08-07 17:17:05 +00:00
icub3d deleted branch 3-mission-rendering-and-camera 2026-08-07 17:17:06 +00:00
Sign in to join this conversation.
No description provided.