You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
First post-v1.0 sketch. Filed during the #238 cluster-cutout review session on 2026-05-17 as an aside; capturing here so the idea doesn't drift.
Motivation
The pancake experience today uses OrbitControls — drag-to-orbit around SURFACE_CENTER, scroll to zoom, soft min/max distance clamps to keep the user inside the cluster envelope. It works, but a first-person scheme (WASD translation + PointerLockControls mouse-look) would be worth offering as a mode: it more closely matches the "step into the museum exhibit and look around" framing the v1.0 staged-exhibit vocabulary (#221) is reaching for, and center-reticle aim + click is conceptually identical to VR controller-aim picking — so a first-person pancake unifies the cross-mode interaction model in a way OrbitControls structurally can't.
Why this is architecturally cheap
The v0.9 pancake work already put the right seams in place:
Pointer interface (infra: Pointer abstraction + VR adapter (pancake step 2) #190) abstracts the input layer; a new FirstPersonPointer variant slots in next to DesktopPointer / MobilePointer / VRPointer without touching the Slider / Preset / SceneTab consumers.
Estimated knee-jerk scope: a weekend of code plus iteration on the feel. PointerLockControls + WASD translation in the camera's local XZ plane + a FirstPersonPointer (screen-center ray, not cursor-screen ray) + Esc-to-release-lock handling + soft bounding-sphere clamp around the cluster envelope.
Open design questions (defer to plan-doc phase)
Default vs toggle. Whether FPS becomes the pancake default, the OrbitControls default stays put with FPS opt-in (?mode=fps), or both modes coexist with a runtime UI toggle. Audience-fit (APPM 2350 cohort) is the load-bearing input here and not pre-decided — could go either way.
WASD relative to what. Camera-local horizontal motion (W = forward in camera-XZ-projected direction) is the standard FPS idiom; world-relative motion is the alternative if pedagogy benefits from "W always means +X math-frame" or similar. Lean: standard FPS.
Whether to support running / jump / crouch. Probably not — museum-walk pacing matches the math-exhibit framing. Documented out-of-scope until smoke proves otherwise.
Interaction with railing once it lands. Should the player be able to walk through the illusory railing (since it's illusory), or does the railing become a soft soft-wall clamp? Design call at the plan-doc phase.
Out of scope (initial)
Collision detection beyond a soft cluster-envelope clamp.
Stamina / sprint / jump / crouch.
Head bob, footstep audio, or anything that adds "game feel" beyond a museum-walk pace.
Mobile pancake-FPS — MobilePointer is touch-based; FPS-on-mobile is a separate UX problem (virtual joystick + tap-to-look). Out of scope for the initial pass; revisit if mobile usage warrants.
Replacing OrbitControls. Even if FPS becomes the default, OrbitControls stays as a mode — it's the strictly-better interaction for "I want to inspect this surface from outside" workflows.
First post-v1.0 sketch. Filed during the #238 cluster-cutout review session on 2026-05-17 as an aside; capturing here so the idea doesn't drift.
Motivation
The pancake experience today uses OrbitControls — drag-to-orbit around
SURFACE_CENTER, scroll to zoom, soft min/max distance clamps to keep the user inside the cluster envelope. It works, but a first-person scheme (WASD translation + PointerLockControls mouse-look) would be worth offering as a mode: it more closely matches the "step into the museum exhibit and look around" framing the v1.0 staged-exhibit vocabulary (#221) is reaching for, and center-reticle aim + click is conceptually identical to VR controller-aim picking — so a first-person pancake unifies the cross-mode interaction model in a way OrbitControls structurally can't.Why this is architecturally cheap
The v0.9 pancake work already put the right seams in place:
Pointerinterface (infra:Pointerabstraction + VR adapter (pancake step 2) #190) abstracts the input layer; a newFirstPersonPointervariant slots in next toDesktopPointer/MobilePointer/VRPointerwithout touching the Slider / Preset / SceneTab consumers.cameraControlswrapper (infra: desktop camera-controls module (pancake step 4) #192) isolates OrbitControls behind a small wrapper; swapping inTHREE.PointerLockControlsis a near-drop-in.?mode=URL-param parsing (feat(shell): ?mode= URL-param parsing (#189 pancake step 1) #202) already exists; adding?mode=fps(or similar) follows the same pattern.Estimated knee-jerk scope: a weekend of code plus iteration on the feel. PointerLockControls + WASD translation in the camera's local XZ plane + a
FirstPersonPointer(screen-center ray, not cursor-screen ray) + Esc-to-release-lock handling + soft bounding-sphere clamp around the cluster envelope.Open design questions (defer to plan-doc phase)
?mode=fps), or both modes coexist with a runtime UI toggle. Audience-fit (APPM 2350 cohort) is the load-bearing input here and not pre-decided — could go either way.SURFACE_CENTER, floor-extent clamp (now thatStageFloor.outerHalfExtentis per-scene per feat(staging): apply StageFloor to tangent-planes / gradient-levels / saddle-extrema (cutout UX decision) #238), or something else entirely. Worth re-eyeballing once E1.2 railing (feat(staging): illusory railing around floor periphery #223) and E1.3 environment surround (feat(staging): environment surround — skybox + baked lighting + distant detail #224) define the visual "room boundary."Out of scope (initial)
MobilePointeris touch-based; FPS-on-mobile is a separate UX problem (virtual joystick + tap-to-look). Out of scope for the initial pass; revisit if mobile usage warrants.References
Pointerabstraction + VR adapter (pancake step 2) #190 (PR feat(shell): Pointer interface + VRPointer adapter (#190) #203).DesktopPointer+ desktop boot wiring (pancake step 5) #193 (PR feat(shell): DesktopPointer + desktop boot wiring (#193) #206).?mode=URL routing: feat(shell): ?mode= URL-param parsing (#189 pancake step 1) #202.PointerLockControls.