feat(06-06): the D-10 locked-follow guidance camera + the context-aware BackButton resolver (GREEN)
- guidance/camera.ts: GUIDANCE_CAMERA_PADDING {320,64,64,64} / ZOOM 16 /
EASE_MS 400 / PITCH 35; easeToSnapped rides the follow controller (ONE
easing discipline) and never passes bearing — north-up, recorded call;
onRerouteApplied is the documented camera no-op (setData lives in the
route layer)
- map/follow.ts: follow(point, opts?) pose overrides spread over the
Phase 3/5 defaults — no-opts calls stay byte-identical (RS13); the
re-lock announces the mode (the recenter notch's return signal, RS12);
the originalEvent unlock law untouched
- telegram/backbutton.ts: BackAction resolver ({hidden|close|callback})
consulted per click + wire.sync() visibility re-apply (visible iff
actionable); the legacy (hasOverlay, walkBack) shape composes internally
so Phase 5 call sites and RS7 stay valid; close() only at the true root
This commit is contained in:
@@ -0,0 +1,79 @@
|
||||
/*
|
||||
* camera.ts — the D-10 locked-follow guidance camera.
|
||||
* -----------------------------------------------------------------------------
|
||||
* Guidance's camera rides the SNAPPED line (one authority — D-05): every
|
||||
* accepted fix eases to the puck's foot on the route, never the raw GPS
|
||||
* fix. The pose is the UI-SPEC camera table, pinned here as named
|
||||
* constants (RS11 asserts the values):
|
||||
*
|
||||
* padding {top 320, bottom 64, left 64, right 64} — map-space px;
|
||||
* T − B = 256 ≈ 0.30 × 844 reference viewport → center ≈65% down-screen
|
||||
* pitch 35 — the standing pitch; guidance never changes it (D-10)
|
||||
* zoom 16 — GUIDANCE_ZOOM, urban detail (replay-tunable)
|
||||
* 400 ms — GUIDANCE_EASE_MS, the RECENTER_DURATION_MS easing family
|
||||
*
|
||||
* BEARING IS NEVER PASSED — north-up is a recorded call (UI-SPEC §Camera
|
||||
* Behavior): the heading cone already communicates direction and a rotating
|
||||
* map contradicts the calm-instrument posture. RS11 pins the absence of the
|
||||
* bearing key on every ease.
|
||||
*
|
||||
* The ease rides the Phase 3 follow controller (ONE easing discipline, ONE
|
||||
* unlock law): follow.ts's originalEvent rule is the whole mechanism — a
|
||||
* programmatic ease never reads as the user panning; a user gesture unlocks
|
||||
* and the recenter control's notch clears (RS12). onRerouteApplied is a
|
||||
* DOCUMENTED camera no-op: the rerouted line snaps in via setData in the
|
||||
* route layer and the next per-fix ease settles the viewport — no camera
|
||||
* jump (UI-SPEC camera table, "Reroute snaps in" row).
|
||||
*
|
||||
* Node-testable: maplibre appears only as erased types; the ease is a call
|
||||
* on the injected follow controller (the route-surface fakeMap pattern).
|
||||
*/
|
||||
import type { Map as MapLibreMap } from "maplibre-gl";
|
||||
import type { RoutePoint } from "@araharah/routing/src/index.ts";
|
||||
import type { FollowOptions } from "../map/follow.ts";
|
||||
|
||||
/** The UI-SPEC guidance padding — center ≈65% down-screen (D-10). */
|
||||
export const GUIDANCE_CAMERA_PADDING: NonNullable<FollowOptions["padding"]> = {
|
||||
top: 320,
|
||||
bottom: 64,
|
||||
left: 64,
|
||||
right: 64,
|
||||
};
|
||||
/** Urban-detail zoom (the UI-SPEC camera table's GUIDANCE_ZOOM). */
|
||||
export const GUIDANCE_ZOOM = 16;
|
||||
/** The one easing discipline — 400 ms (the follow.ts family). */
|
||||
export const GUIDANCE_EASE_MS = 400;
|
||||
/** The standing pitch — guidance never changes it (D-10). */
|
||||
export const GUIDANCE_CAMERA_PITCH = 35;
|
||||
|
||||
export interface GuidanceCamera {
|
||||
/** Ease to the SNAPPED foot on the route line at the guidance pose. */
|
||||
easeToSnapped(point: RoutePoint): void;
|
||||
/** The reroute snap-in: a documented camera no-op — the new line's setData
|
||||
* happens in the route layer; the next per-fix ease settles the viewport. */
|
||||
onRerouteApplied(): void;
|
||||
}
|
||||
|
||||
/** The pose's executor is the follow controller (one easing discipline; the
|
||||
* `map` parameter names the viewport the pose applies to and keeps the
|
||||
* constructor symmetric with createFollowController). */
|
||||
export function createGuidanceCamera(
|
||||
map: MapLibreMap | null,
|
||||
followController: { follow(point: { lat: number; lng: number }, opts?: FollowOptions): void },
|
||||
): GuidanceCamera {
|
||||
return {
|
||||
easeToSnapped(point: RoutePoint): void {
|
||||
followController.follow(point, {
|
||||
padding: GUIDANCE_CAMERA_PADDING,
|
||||
pitch: GUIDANCE_CAMERA_PITCH,
|
||||
zoom: GUIDANCE_ZOOM,
|
||||
durationMs: GUIDANCE_EASE_MS,
|
||||
});
|
||||
},
|
||||
onRerouteApplied(): void {
|
||||
// camera no-op by contract — RS11 pins zero moves (map motion settles
|
||||
// naturally on the next per-fix ease; UI-SPEC "Reroute snaps in").
|
||||
void map;
|
||||
},
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user