spec 2: camera pivot — body-fixed navigation behind the pose seam - #647
Merged
Merged
Conversation
…parison Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…pose seam Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Deploying with
|
| Status | Name | Latest Commit | Preview URL | Updated (UTC) |
|---|---|---|---|---|
| ✅ Deployment successful! View logs |
skymap | 4ce5f95 | Commit Preview URL Branch Preview URL |
Sep 11 2026, 02:13 AM |
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…he frame loop Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…n, returns-pose deferred Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Phase 1 data delta from the camera-pivot spec (§3): PoseFrame, BodyFixedPose, FramedCameraPose types plus the SURFACE_REGIME hysteresis/tilt constants. No consumers yet. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Task 3 of the camera-pivot plan: `toBodyArm` / `toWorldArm`, provider B's storage seam. Entering re-expresses the orbit pose in the body's fixed axes in SI metres and captures nothing time-dependent — no epoch, no orientation snapshot — so an accelerated clock cannot move the engaged pose (spec §5.1). It derives the world eye and camera basis exactly as `frameContext` does (`updatePosition`'s decode through `poseBasis`, then `imagePlaneBasis` over `frameUp(upBasis)`) and hands them to `bodyRelativePose`, the existing Mpc→m seam, rather than repeating the subtract-then-scale ordering the precision depends on. Leaving bakes the rotation back out and re-derives the orbit parameterization: the target is the nearest sphere hit ahead of the eye (a miss, or a body behind, falls back to the body centre), the range is measured in body-fixed metres, and `roll` carries the screen-up residual the eye position alone cannot express (spec §12-R1). Round-trips eye, forward and screen-up to ~1 nm — three orders under provider A's ~14 µm floor — over Earth, over a tilted-pole body with a non-identity orientation, through a rolled pose, and at a moon's radius. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
poseFrameConversion.ts (task 3) is a second legitimate SCALE_UNITS seam alongside bodyRelativePose.ts. Extend oneMpcSeam's sweep to services/engine/camera, services/camera, and utils/camera, exclude both seams explicitly, and allow-list the three verified radius->Mpc framing-bridge uses (bodyLikeFraming.ts, cameraDrivers.ts:332, pivotRadiusMpc.ts) so the gate covers the camera path the same way it already covers the body-slab render path. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Descending smoothstep ramp (SURFACE_REGIME.disengageHR -> tiltFullHR) so the look-mode tilt ceiling opens to pi near the ground and closes to 0 at the surface-arm disengage altitude (spec §6, task 5). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Review fixes for the world ↔ body conversions. `DIR_FLOOR` was 20× looser than its own derivation claimed: tightened to 1e-10 (eye bound over the shortest fixture range, doubled), still 130× above the measured 7.4e-13. `toBodyArm` re-composes `updatePosition`'s two steps out of the same sub-utils rather than calling it, and no round-trip can catch drift there — both halves would share it. One new test runs `updatePosition` on an equivalent `OrbitCamera` and asserts the module's world eye equals its output exactly. The fixture's target sits near the origin deliberately: at heliocentric magnitude the f64 grid (~25 µm) swallows any composition difference below a grid step and the equality goes blind — a 1e-15 relative perturbation passed until the fixture moved. Spec: the "~14 µm" round-trip bar was really the 2-ulp-at-1-AU figure, ≈50 µm. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Moves a BodyFixedPose's anchor toward the eye, quantized to the ulp grid of the anchor's own magnitude, so anchor + eyeRel stays bit-identical while both stored magnitudes shrink (spec §5.3). Not exercised by the shipped centre anchor yet — built now because deep-zoom anchors want it. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…basis Builds the CSS-pixel pick ray directly from basisLocal's columns and FOV (spec §6, task 7) rather than inverting the slab's view-projection matrix, so it cannot drift from what the renderer actually draws. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…iew round 1) C1: add two exact-value fixtures to reanchoredPose.test.ts that pin quantization to ulp(|anchorLocalM|) specifically — verified by substitution to fail when the grid instead comes from the range or a fixed constant, closing the gap the prior bit-identity tests left blind to. I1: tighten reanchoredPose.ts's three comment blocks (module header, TRIGGER_FRACTION, ulpAt doc) to 14 comment lines / 30 code lines, back inside the ≤half budget, without losing the ulp-floor or log2-landmine rationale. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Task 9 of the camera-pivot SDD plan: the metre-space descent floor reads SURFACE_STANDOFF_RADII from its single existing declaration (clampDistance.ts) so the Mpc-space distance clamp and the metre-space surface descent gesture can never disagree about where the ground is. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
A pure geometric h/R read over the SCENE_BODIES ∩ bodyStates roster, body-blind (Sun and Sgr A* included) and hysteretic purely through the PoseFrame it is handed back — no stored regime boolean anywhere, which noStoredRegimeFlag.test.ts gates as an import-graph scan. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…-centre ENU Derives standpoint/heading/tilt/range/altitude from a BodyFixedPose (spec §3, §14): altitudeM stays eye-based (FW-A) and never reads the sightline; tiltRad is measured from local nadir per the type's datum; heading falls back to the basis's up column within ~0.08° of vertical, and the East/North construction stays finite through the pole singularity. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Review finding: the fallback (eye's nadir footprint) does NOT reduce continuously to the exact-hit case as tilt crosses the tangency angle (arccos(R/(R+h)) from nadir) — standpoint snaps discontinuously there. The fallback choice stands (this readout is display currency; engaged-path gesture/enforcement code derives its own eye-anchored ENU) but the comment must not claim continuity it doesn't have. No behaviour change. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The 1:1 drag of spec §6a (FW-I): intersect the previous and current cursor rays with the frozen pick sphere and rotate the pose — position and basis together — by the quaternion carrying the current pick back onto the grabbed point, so that point re-projects onto the current pixel exactly. Null on a miss, on a hit behind the eye, or at grazing incidence, where a rotation would be a teleport; the caller degrades the gesture. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Stateless per-tick zoom toward an anchor (spec §6b): approaching with a cursor hit anchors on the cursor; zoom-out and any cursor miss always fall back to the body centre (FW-H), so the cursor can never become an accumulating pivot. Per-notch factor is clamped on both signs and the result is floored via surfaceFloorM. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…honormalise handedness trap The pose rotates with its rays, so it turns by the INVERSE of the quaternion carrying the grabbed pick to the current one; the spec sentence read the other way and would have been "fixed" back. `reorthonormalise` rebuilds column 2 as c0 × c1, so an image-plane (right, up, forward) triple comes back mirrored — that now lives in its header rather than only at the one call site. Also exports MIN_INCIDENCE_COS as the controller's single home for the grazing threshold, drops an authority-order claim that does not hold, and documents the unit-`dir` precondition. No behaviour change. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…-level const Two tests asserted call-vs-call equality only, which a latched-anchor or cursor-ignoring implementation would also satisfy; pin them to hand-derived closed-form eye positions instead. Delete the module-level CENTRE const (dead weight in the one module whose contract is statelessness) and inline the literal at its single use site. Trim the header comment to the algebra one-liner for the tangent-plane invariant and note the caller's contract (factor derives from the centre-measured range, never |eye - anchor|). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…task 13) Behaviour-identical: no body arm is constructible until the fold lands, so every gate added here reads `frame === 'absolute'` and is trivially true. - `camera.base`, `cameraRuntime.lastPose` and `CameraDriver.pose` carry the arm; `commitCameraPose` takes a `FramedCameraPose` and world-arm producers wrap with the one `absoluteArm` constructor. - `resting` stays arm-agnostic; `orbitDrag` / `autoRotate` / `followBody` gate `isActive` on the absolute arm (spec §7). `applyWheelZoom` and `applyFocusedBodyPivot` return early on a body arm rather than leaving the rule to a caller. - `runFrame` resolves the world arm exactly once, below every pose writer and above step 4 — the slot the regime fold takes next. `liveWorldPose` is the single off-frame resolution site (pick, demand, gesture seed, getLivePose, the follow driver's `from` capture). - `eyeMpcOf` extracts `updatePosition`'s eye derivation, allocation-free, so the regime predicate and the camera read one eye. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The header claimed the helper serves callers OUTSIDE the frame loop and always reflects the last RENDERED epoch. `followBody.pose` calls it from inside the produce step, after `runFrame` has advanced `lastRenderedSimDays`, so at that site the field is THIS frame's epoch — which is what the approach ease wants. Name both call-site classes and the epoch each actually gets. Comment-only. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Adds poseFromBodyArm (the anchor fold: eyeRelBodyM = anchorLocalM + eyeRelAnchorM, basisM = basisLocal) and wires it into deriveFrameContext's bodyPose closure: the engaged body's pose comes from provider B, every other body keeps provider A (spec §5.2, ruled S1). deriveFrameContext grows an `arm: FramedCameraPose` parameter threaded in beside the already-resolved world pose by both callers (runFrame, pickFrameContext) rather than re-resolving it. Tested and inert: nothing constructs a body arm until Task 15 flips the fold, so no rendered pixel moves. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…log-zoom The settle was `clamp(0.25·residual, ±0.1)` spent once per input STEP, and `inputAggregator` folds a frame's wheel events into one step — so a deltaY-1 trackpad event (factor ≈ 1.001) cost the same 25 % of the heading as a deltaY-100 mouse notch (factor ≈ 1.105). Continuous trackpad scrolling norths the view in ~0.2 s with essentially no altitude change (diagnosis §F1). Every zoom-driven settle amount is now a function of the notch's log-zoom magnitude `u = |ln factor|`: the residual multiplier is `e^(−k·u)` (so N notches decay exactly as much as the one notch whose factor they multiply to) and the cap is `capRadPerLogZoom·u` (so a burst of small events cannot re-flatten the rate). Calibrated on the deltaY-100 notch — `u = 100 · WHEEL_ZOOM_K = 0.1` exactly — to the legacy 25 % / 0.1 rad, giving `perLogZoom = −ln(0.75)/0.1 = 2.8768…` and `capRadPerLogZoom = 1`. `u` threads from `surfaceZoomStep`'s factor through `settledZoomPose` into `orientStepRad` (dive heading), `riddenOrientStepRad` (recession heading and the tilt-deviation decay) and `levelledPose`'s cap, and from `replayInput`'s zoom step into `frameAlignedRoll`. Drag-driven settles are out of scope and spend `ORIENT_DECAY.notchLogZoom` — the reference notch, i.e. what they always spent. `rideBoundRad` is unchanged: the reference's own move already scales with the notch. Two consequences worth naming. `u = 0` is inert, so a factor-1 notch settles nothing and a park no longer converges — several tests that used factor-1 (or deltaY-0.0001) notches to "converge in place" now dither in and out at one altitude instead. And a large folded notch buys a large settle: a factor-0.5 tick spends ~0.69 rad, which is what seven mouse notches spend. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Ruled behaviour change (see the previous commit): re-recorded with `SETTLE_GOLDEN_RECORD=1`. `driverGoldenTrace` did NOT move — it passes unmodified, so `DRIVER_GOLDEN_RECORD` was not run. Parse-compare, old vs new, both cases (`northUp`, `northUpOff`): - keys identical (`northUp`, `northUpOff`); step count 26 → 26 in both; every `label` identical in order; per-step field keys unchanged (`label`, `arm`, `hr`, `displayed`, `register`, `memory`); no `arm` changed. - first differing step: 3 (`dive 39`), in both cases. - 23 of 26 steps carry some changed value, in both cases. The script's deltaY-100 legs move only in the last digits — that is the calibration point, and it holds. The movement is the deltaY-300 legs, where a notch is now 3 e-folds' worth: `dive brisk 0` moves `register[5]` by 446 m out of 254 km (1.8e-3 relative, the largest single-value change), `hr` by 2.3e-5 relative, and the displayed roll by at most 4.4e-2 rad (`recede brisk 0`, −2.879 → −2.836). `northUpOff` moves too — the tilt-deviation decay is not behind that gate — but only by 5e-3 relative at most. `memory` is untouched beyond 4e-7 relative. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The body arm read `u` off the folded notch, not the one `anchoredZoomStep` is allowed to spend: a 20-event trackpad flick folds to factor e², moves the eye by the clamped x2, and spent u = 2.0 — up to 2.0 rad of heading in one frame, worse than the per-step defect the previous commit removed. The [0.5, 2] clamp moves into `spentZoomFactor` so both users read one home, and the settle prices off it: one frame now spends at most u = ln 2. The world arm keeps its threaded `logZoom`. Deriving it from the pre/post distances is not possible there: `zoomedDistance` scales the ALTITUDE above the pivot, so a distance ratio under-prices a focused notch ~20x, while the altitude ratio over-prices when the pivot has no radius — which of the two holds is `pivot.radiusMpc`, and only the caller has it. Its header now says so, and states that nothing above `rideBoundRad + capRad·u` bounds one notch there. `ORIENT_DECAY` derives from `WHEEL_ZOOM_K` instead of restating it as a 17-digit literal (bit-identical: -ln(0.75)/0.1 === 2.8768207245178088), so the calibration cannot silently decouple from the aggregator. Golden traces do not move — the settle trace's notches are u <= 0.3, inside the clamp. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
A folded burst is bounded by `spentZoomFactor`, so one frame spends at most u = ln 2 whatever arrives in it. Asserted through the real driver on the diagnosis's 20-event flick: the turn is at most capRadPerLogZoom*ln 2 and the heading retained at least exp(-perLogZoom*ln 2) of what it started with. Mutation-checked — pricing off the raw factor fails it at 2.0 rad vs 0.693. `rememberedTilt`'s "zoom must not move a just-set tilt" device was a factor-1 notch, which the per-log-zoom settle makes inert: it could no longer fail on the un-map bug it names. Now a +-notch dither, tolerance set above the anchored pair's own walk (1.0e-3) and below the erosion `remembered = display` spends (1.25e-2), both measured. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…e comments Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The wall was a third altitude ramp over the same scalar the tilt blend band already owns (F1 of the orientation-unbraid design), reconciled against it by a `Math.max` whose branch is unreachable at the shipped band. Ruling 12 made display tilt a pure function of the memory, so the memory needs one constant cap, not a returning curve: `TILT_BAND.maxRad = Math.PI` at the one `surfaceStep` memory clamp. `tilt = 0` at the arm flip is now carried by `TILT_BAND.zeroHR <= SURFACE_REGIME.disengageHR` alone. Deletes `walledTiltPose.ts`, `maxTiltRad.ts` and their tests, drops `SURFACE_REGIME.tiltMaxRad` / `tiltFullHR` and the `ceilingRad` debug row, and amends spec §6's ceiling paragraph and the Q4 identity in the DoD to the ruled shape. User rulings B1/B2/B3. B4/B5/B6 (the trial knobs) are deferred. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
`SurfaceMemory.{gesture, pointerDown}` stored one three-state lifecycle
(idle → pressed-unlatched → latched) as two independent fields, so it
could spell "latched with the pointer up" and three readers each had to
re-establish the invariant against it (F4 of the orientation-unbraid
design). One field, `SurfaceGesture | 'down' | null`, makes the
impossible state unrepresentable and collapses both guards to a
narrowing. `surfaceGestureEdge` stays the only writer of the boundary.
Also drops `noteBody`'s redundant conjunct (T-D/F7): the early return
already establishes `bodyId !== prev.memoryBodyId`.
No behaviour change — both golden traces byte-identical.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
`levelledPose` derived nothing from `logZoom` but the cap, so a drag — which carries no zoom — had to claim it spent a mouse notch, and `capRadPerLogZoom` was a false single home for two independent knobs (F5 of the orientation-unbraid design). The function now takes `capRad`; zoom callers price it per log-zoom spent, the drag passes its own `ORIENT_DECAY.dragLevelCapRad`. `notchLogZoom` keeps only its calibration role. Same numbers on both paths — both golden traces byte-identical. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…tale wall prose Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…bug snapshot The panel polls at 4 Hz, which averages ~15 frames into one reading — the shape that let F1 (a north-up decay keyed per frame instead of per zoom unit) run live without the readout showing it. `runFrame` now records each DOF's frame-to-frame motion, wrapped to ±π, plus the largest step since the last clear, and the snapshot carries it. The three DOFs move to one derivation home (`cameraDofAnglesOf`) as current/target/residual rows, so a Δ is always a Δ of the number on the row: heading against north of the band-blended reference, tilt against ruling 12's `remembered × w(h/R)`, roll against the band ride target. The two endpoint roll numbers (spin-axis roll and its residual) go with it — grill ruling Q4. Delta state is module-level, not `cameraRuntime`: that has one writer by gate, and this is not camera mechanism. (re-fitted onto camera-pivot after the wall deletion) Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
… a band bar Five parallel text groups read as a system dump, not an answer. The section is now organised by the questions it answers, in priority order: one header line for authorship (arm · driver · gesture, with a badge only on an arm or epoch mismatch), three DOF rows carrying current / target / residual / Δ / peak with one clear button, and the regime band as a drawn log ruler with its four edge sliders directly underneath, so dragging an edge moves its own tick. Degrees at one decimal on screen, h/R at three; `copy all` still dumps everything off the same model at full-precision radians. Anchor, cursor hit, eye-to-anchor, sim days and distance-Mpc move behind a collapsed `raw`. `OrientationTuning` grows the ruler and loses nothing; the band group is one visual object rather than a slider block and a text block to cross-reference. (re-fitted onto camera-pivot after the wall deletion — the tilt-ceiling readout goes with it) Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…mounted The Δ/peak derivation walked the body roster every frame (20–40 µs) for every user, including those who never open the DebugPanel and every pre-boot frame. The panel's mount is now the gate: `watchOrientDeltas()` from the section's poll effect, one boolean read in `runFrame` otherwise. `prevRad` resets on subscribe, so the first frame back does not report the whole closed period as one Δ. Rides along: the band readouts come off the one panel model like the DOF rows (they were formatted inline off the snapshot), and the record's header no longer claims Δ is body-rotation-free — heading and tilt are measured body-fixed. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…op the mirrors The Q8 test asserted the heading CURRENT cell (30.0°, which its zero target makes the residual too), so blanking both target columns left it green. It now asserts the roll row, whose target is independent of its current. The snapshot test's heading assertions restated the literal 0 the source passes and re-derived the residual from that same 0; the roll residual was the same mirror shape. None could fail on a real bug. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The four band edges and two feel toggles as ONE value on the camera slice: `CameraTuning`, its defaults/limits in `data/camera/cameraTuning.ts`, and `clampCameraTuning` — the single producer, folding `setSurfaceBand`'s and `setTiltBand`'s two settles (and their mutual re-settle) into one ordered pass over one record. Nothing reads it yet; the three module records stay until the threading lands. The two record tests are replaced by the clamp's cross-edge policy test, which also asserts the fixed point `bodyUpWeight`'s load-time `throw` stood for. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…ete the three mutable records `camera.tuning` reaches the math as an argument: one read per frame in `stepCameraRuntime` (off the pre-replay snapshot — no action the replay emits writes it), then down the two existing bags (`replayInput` ctx, `projectFramePose` args, `SurfaceStepCtx`) to the eleven readers. Eleven functions that advertised purity become pure. `surfaceRegime.ts`, `tiltBand.ts` and `orientTuning.ts` are deleted, so their import cycle is unrepresentable rather than merely broken, and the load-time `throw` it forced into a third file (`bodyUpWeight`) is gone — that invariant is now a property of `clampCameraTuning`, asserted in its test. `TILT_BAND.maxRad` becomes `MAX_REMEMBERED_TILT_RAD`; `SurfaceBandKnob` and `TiltBandKnob` die with the setters' return channel, so the panel's hysteresis readout keeps `AT FLOOR` without the "(x yielded)" clause. Both golden traces are byte-identical: same defaults, same clamp order, same values. Ten `*_AT_LOAD` save/restore rituals go with the records — the frame-level fixtures seed via `setCameraTuning`, the direct-call ones pass a tuning literal. The DebugPanel is wired minimally to compile and work against the new API (`CameraStateSection` owns the one selector read and passes the value down); its own task takes the readout ruling and the visual pass. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The spec and plan still named `SURFACE_REGIME` / `TILT_BAND` / `ORIENT_TUNING` as the live home of the band edges; they are `CameraTuning`, clamped by `clampCameraTuning` and written by `setCameraTuning`. Two comments in code carried the same dead names. No test-tuning helper: the seven per-case literals are one-line spreads, so `tuningWith(patch)` would save nothing and cost a file plus six imports. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…e plan's ceiling lines Applies the tuning-review's F1-F4 fix wave: engageFlipPop drops its local tuning literal in favour of the store it already seeds, makeSurfaceDriver's tuning param is now required with defaults only on each suite's own wrapper (closing the raiseTiltTo silent-'log'-default hazard), the two clamp expectations are hand-computed rather than restating the source's own division, and CameraState.tuning's readonly modifier now names its reason. Also restates the plan's five stale maxTiltRad/SURFACE_REGIME mentions against the live bodyUpWeight mechanism, present tense. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> # Conflicts: # src/services/engine/camera/bodyHomePose.ts # src/services/engine/camera/cameraFraming.ts # src/services/engine/engine.ts # src/services/engine/frame/runFrame.ts # src/services/engine/helpers/shouldKeepTicking.ts # src/services/engine/phases/wireInput.ts # src/state/selection/watchGoHomeSaga.ts
…g lines All 43 checkboxes (Tasks 1-22 + the Definition-of-Done inventory and spec §11 acceptance criteria) are backed by the SDD ledger: per-task 'complete' lines, T21 NEUTRAL with the user's LAND ruling, T22 attested 7/7 on 2026-09-11, and the debug-panel/tuning visual pass. Task 5's subject (maxTiltRad) was deleted by the ruled un-braid; the three sites that still described the ceiling as live now say the ceiling is gone and display tilt = remembered x bodyUpWeight(h/R, tuning). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
DoD audit READY after the controller's three rulings: main merged, all 43 checkboxes ticked from the ledger, Task 5's retired-ceiling prose restated. Plan + spec → completed/; the 2026-09-09 single-writer plan (shipped on this branch, ledger already archived) rides along as the housekeeping straggler — it shares this feature's spec, so there is no separate spec to move. SDD ledger copied verbatim to plans/completed/2026-09-01-camera-pivot.ledger.md. Backlog: nothing this plan shipped was left behind; the seven 2026-09-11 adjacent findings and the wheel-notch item are deferred work and stay. Their index lines are trimmed to the convention — title, tag, one clause, details link. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…icated test fixtures
rulkens
marked this pull request as ready for review
September 11, 2026 02:10
rulkens
added a commit
that referenced
this pull request
Sep 11, 2026
…as-mesh-bodies engine.ts: kept the new meshBodyRenderer / meshBodies asset-slot entries alongside main's comment-audit deletions around ringRenderer and bodyTextureAtlas. frameContext.ts: kept both import sides — the attached-mesh-bodies map helpers and #647's poseFromBodyArm, both live below. slabs.ts: kept the attached-mesh-body near-plane widening (hostNear + attachedBodiesByHostId) on top of main's tightened near-plane comments. Also fixed a semantic break the textual merge could not see: #682 routes orbit trails through sceneOccluderSpheres -> sceneBodyPartition, which reads this branch's state.data.bodies.meshBodies, so two of main's fixtures needed the field (and one branch test an explicit canvasSize). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Spec 2: camera pivot — body-fixed navigation behind the pose seam
Near a body the authoritative camera state stops being the heliocentric Mpc orbit camera and becomes a body-fixed, anchor-relative pose in SI metres. Google-Earth navigation follows from that storage instead of from corrections layered on a world camera: the ground under the cursor stays under the cursor, the horizon stays level at every latitude, the sky is reachable, and a fast sim clock cannot slide the ground. Outside the engage band the incumbent Mpc orbit camera is unchanged; the crossing is one lossless conversion at one site.
Spec 1 (#634) built the
BodyPoseProviderseam and moved every body's rendering into its own metre frame. This PR swaps provider B in behind that seam and does not change the renderer. Prep (#648: roll plumbing, orbitControls recognizer split,drainInput) shipped separately.docs/superpowers/specs/completed/2026-09-01-camera-pivot.md(ruling record:docs/grill-sessions/globe-camera-pivot-2026-08-24.md)docs/superpowers/plans/completed/2026-09-01-camera-pivot.md(22 tasks, all ticked; ledger beside it)docs/superpowers/plans/completed/2026-09-09-camera-runtime-single-writer.mdWhat landed
FramedCameraPose={ frame: 'absolute' | { body }, pose }; one arm authoritative, fold-last conversion at the regime edge.LookAtsemantics), descent floor in metres, anchored drag rotation, anchored zoom step, regime predicate with arm-dependent hysteresis.camera.basemigration across every reader, provider B behind the pose seam, the fold inrunFrame, the gesture latch and remembered tilt.cameraRuntimeis written byrunFrameandwireInputonly, pinned by an allow-list test.display = remembered × bodyUpWeight(h/R)), tilt floor at nadir with a dead stop, Google-Maps drag directions, authored vs displayed pose two-box, body-switch pose reset, eye-preserving followBody capture.CameraTuningrecord on the camera slice, threaded into the math; one clamp, no mutable module records, no import cycle.lonLatFocusPoseas a body-arm constructor.settleGoldenTraceanddriverGoldenTracepin the settle mechanisms and the driver stack through the frame loop; re-records carry a parse-compared diff in the commit body.Deferred (backlog,
docs/backlog/2026-09-11-*)Arm entry adopting the arriving tilt; world-arm wheel burst unclamped;
deriveBodyStatesscene-body-id type; clip per-endpoint authoring validation; f32 half-ulp sightline nudge at the arm flip;bodyLikeFraming⇄focusFramingimport cycle; three radar residuals. Alsodocs/backlog/2026-09-10-wheel-notch-route-by-last-winner.mdand the smooth-zoom / flick-coast item.Landmines for reviewers
camera.baseis a union. Readers narrow onframe; the world-Mpc view is derived (liveWorldPose/authoredWorldPose), never stored twice.cameraRuntimedisplayed pose. Pick, demand, debug and clip/tween seams read displayed; the fold reads authored.rememberedTiltRadis state.clampCameraTuningis the one write path for the band edges (hysteresis window ×1.1,tiltZeroHR ≤ disengageHR); the debug sliders dispatch through it.Verification
npm run typecheckclean;npm test1277 files / 8846 tests green on the merged HEAD.