7.6 KiB
7.6 KiB
NEON-25 — Implementation plan
Story reference
| Field | Value |
|---|---|
| Key | NEON-25 |
| Title | E1.M2: Isometric follow camera (fixed yaw prototype; CameraState seam) |
| Jira | NEON-25 |
| Parent | NEON-1 — Epic 1 — Core Player Runtime |
| Module | E1.M2 — IsometricCameraController; umbrella NEON-10 |
Goal, scope, and out-of-scope
Goal: Client-local isometric follow camera that tracks the E1.M1 player anchor with fixed framing for prototype play, while modeling yaw in state for future consumers and optional orbit.
In scope
- Godot:
Camera3D(on a small rig) for isometric presentation; follow the player / follow target each frame per locked defaults below (smooth follow + teleport snap). - Player-facing controls: no camera rotate / orbit; yaw stays at default (
0rad) for normal play. CameraState(dedicated script type): follow target reference, yaw (always0in prototype UX), default distance or zoom index (single band / scalar distance until NEON-26).- Seam:
@exportor config fields (e.g.allow_yaw,max_yaw_deg) default off; short comment where orbit input would apply. - Semantics: movement and picking remain world-anchored (existing
ground_pick+ nav); camera does not redefine aim axes. - Authority: camera pose stays client-only (see
docs/decomposition/modules/client_server_authority.md).
Out of scope
- Binding rotate input or shipping orbit UX.
- Multiple zoom bands (NEON-26).
- Occlusion policy (NEON-27).
- Server use of camera pose.
Acceptance criteria checklist
- While the player moves via E1.M1, the avatar stays readably framed without manual camera control.
- Pitch/roll do not change from user input; yaw remains at configured default (
0) for prototype UX — document how enforced. CameraStateexposes yaw (even when always zero) so dependents do not assume yaw is impossible.- Seam for optional yaw (config/flags) documented in code or module implementation snapshot.
- Short note in client or
E1_M2_IsometricCameraController.mdimplementation snapshot when merged.
Locked defaults (agreed before implementation)
| Topic | Choice |
|---|---|
| Follow motion | Damped / lerped toward the desired rig position; teleport snap when error exceeds a threshold (e.g. after snap_to_server or huge separation) so the camera never eases across the map. |
| Framing | Match the current static World/Camera3D framing in main.tscn as the starting distance / pitch / FOV; fine-tune in the inspector after follow is wired. |
CameraState refresh |
Rig owns one instance and updates it every frame after computing pose (simplest contract for future readers). |
| Follow target wiring | @export NodePath (resolve to Node3D each tick); tests may assign path or set target via code as needed. |
| Update phase | _process (not _physics_process) for the rig. |
Technical approach
- Introduce
CameraStateas a small RefCounted (or equivalent) script underres://scripts/with explicit fields: follow target path (or resolved node identity in state for debug), yaw (float, default0), distance or zoom index placeholder for NEON-26, and room for future flags. Avoidclass_nameso headless CI matches godot-client-script-organization guidance; tests canpreloadthe script. - Isometric rig — add a
Node3D(e.g.IsometricFollowCameraorCameraRig) with script that:- Owns or references the active
Camera3D(current = true). - Each
_process: resolveNodePathfollow target, readglobal_position, compute desired eye position from fixed pitch + effective yaw (forced to0whenallow_yawis false), lerp rig toward target+offset; snap rig/camera when separation exceeds a configured threshold. - Updates the owned
CameraStateinstance every frame after pose is finalized.
- Owns or references the active
- Scene wiring — replace the static
World/Camera3Dtransform inmain.tscnwith a rig underWorld(camera as child),@exportfollowNodePath→Player, initial exports chosen to match the previous static camera readability. main.gd— keep thin: obtain rig/camera reference forground_pick.fallback_camera(unchanged contract); no camera math inmain.gd.- Yaw seam — when
allow_yawis false: ignore any future input hook and set state yaw to0; when true (dev-only): clamp bymax_yaw_degand apply to basis (implementation stub can leave input unbound). - Docs — add a short Implementation snapshot subsection to
E1_M2_IsometricCameraController.mdafter merge (or README pointer + module snapshot — satisfy AC).
Files to add
| Path | Purpose |
|---|---|
client/scripts/camera_state.gd |
RefCounted (or similar) holding follow target ref/path, yaw, distance/zoom index placeholder, future flags; no class_name. |
client/scripts/isometric_follow_camera.gd |
Node3D rig: positions/orients Camera3D, updates CameraState, exports distance/pitch/yaw seam. |
Godot may add companion .uid files when scripts are first touched in the editor; commit them if the toolchain creates them.
Files to modify
| Path | Rationale |
|---|---|
client/scenes/main.tscn |
Replace fixed Camera3D transform with rig + follow target wiring; keep current camera and layers consistent with picking. |
client/scripts/main.gd |
Point _camera (and ground_pick.fallback_camera) at the rig’s Camera3D; minimal diff. |
client/README.md |
One short paragraph: follow camera behavior, fixed yaw prototype, where to tune distance/pitch. |
docs/decomposition/modules/E1_M2_IsometricCameraController.md |
Implementation snapshot (date + NEON-25): rig script names, yaw seam, single-band distance until NEON-26. |
Tests
| File | What to cover |
|---|---|
client/test/camera_state_test.gd |
New — default yaw is 0; fields survive assign/read; optional: follow target path assignment (if exposed). |
client/test/isometric_follow_camera_test.gd |
New — if pure helpers are extracted (e.g. static offset from pitch/distance/yaw), unit-test them; otherwise minimal suite: instantiate rig + dummy Node3D target, advance one frame, assert camera global position moves toward expected quadrant (tolerance-based). If full follow is too scene-heavy, document manual verification as primary and keep camera_state tests as the automated baseline. |
Manual (required): Run main scene with server per README: click-move across floor and NEON-7 bumps; confirm avatar stays framed, no user-driven rotation, ground pick still hits walkable surfaces from the moving camera.
Open questions / risks
- Smoothing tuning: If follow feels floaty or laggy after the locked defaults, adjust lerp speed or snap threshold; document chosen constants in script or README.
player.gd_snap_capsule_upright: Identity basis today; camera yaw0matches world-aligned capsule. Future facing yaw must stay consistent with E1.M2 policy (not this story’s implementation unless trivial).- gdUnit rig tests: May require small refactor (static math helpers) for stable headless asserts; acceptable follow-up within the same story if the first PR ships manual AC +
camera_statetests.