neon-sprawl/docs/plans/NEON-25-implementation-plan.md

6.9 KiB
Raw Blame History

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 (smooth follow via lerp/damped tracking unless profiling suggests snap — document the chosen behavior in code/README).
  • Player-facing controls: no camera rotate / orbit; yaw stays at default (0 rad) for normal play.
  • CameraState (dedicated script type): follow target reference, yaw (always 0 in prototype UX), default distance or zoom index (single band / scalar distance until NEON-26).
  • Seam: @export or 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.
  • CameraState exposes 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.md implementation snapshot when merged.

Technical approach

  1. Introduce CameraState as a small RefCounted (or equivalent) script under res://scripts/ with explicit fields: follow target (Node3D reference or NodePath resolved each frame), yaw (float, default 0), distance or zoom index placeholder for NEON-26, and room for future flags. Avoid class_name so headless CI matches godot-client-script-organization guidance; tests can preload the script.
  2. Isometric rig — add a Node3D (e.g. IsometricFollowCamera or CameraRig) with script that:
    • Owns or references the active Camera3D (current = true).
    • Each _process (or _physics_process if we want lockstep with player; default _process for smooth view): read player global_position, compute desired eye position from fixed pitch + effective yaw (forced to 0 when allow_yaw is false), optionally lerp rig origin toward target+offset for stability.
    • Updates a CameraState instance each frame (or on change) for future consumers.
  3. Scene wiring — replace the static World/Camera3D transform in main.tscn with a rig under World (camera as child), @export follow target = Player, distances matching current prototype readability (~existing diagonal framing).
  4. main.gd — keep thin: obtain rig/camera reference for ground_pick.fallback_camera (unchanged contract); no camera math in main.gd.
  5. Yaw seam — when allow_yaw is false: ignore any future input hook and set state yaw to 0; when true (dev-only): clamp by max_yaw_deg and apply to basis (implementation stub can leave input unbound).
  6. Docs — add a short Implementation snapshot subsection to E1_M2_IsometricCameraController.md after 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 rigs 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

  • Smooth vs snap: Default to damped follow; if lag is noticeable at 120 Hz physics, reduce smoothing or align update phase — document final choice.
  • player.gd _snap_capsule_upright: Identity basis today; camera yaw 0 matches world-aligned capsule. Future facing yaw must stay consistent with E1.M2 policy (not this storys 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_state tests.