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:
+26
-3
@@ -7,6 +7,12 @@
|
||||
* Micro-Behaviors). A USER gesture (movestart carrying originalEvent) flips
|
||||
* to free — a programmatic move never does (T-03-16: the fix update must
|
||||
* never read as the user having panned).
|
||||
*
|
||||
* Phase 6 (D-10): follow() takes optional pose overrides so the guidance
|
||||
* camera rides the SAME easing discipline and the SAME unlock law with the
|
||||
* guidance pose (padding {320,64,64,64}, pitch 35, zoom 16, 400ms — the
|
||||
* constants live in ../guidance/camera.ts). Absent fields fall back to the
|
||||
* Phase 3/5 defaults — existing callers stay byte-identical (RS13).
|
||||
*/
|
||||
import type { Map as MapLibreMap } from "maplibre-gl";
|
||||
import type { Fix } from "../location/types.ts";
|
||||
@@ -16,6 +22,16 @@ export const RECENTER_DURATION_MS = 400;
|
||||
|
||||
export type FollowMode = "following" | "free";
|
||||
|
||||
/** Optional camera-pose overrides (D-10's guidance pose rides these).
|
||||
* Padding accepts MapLibre's number | per-edge form; pitch/zoom are only
|
||||
* passed when provided (the no-opts call keeps its exact key set). */
|
||||
export interface FollowOptions {
|
||||
padding?: number | { top: number; bottom: number; left: number; right: number };
|
||||
pitch?: number;
|
||||
zoom?: number;
|
||||
durationMs?: number;
|
||||
}
|
||||
|
||||
export function createFollowController(map: MapLibreMap, onModeChange: (m: FollowMode) => void) {
|
||||
let mode: FollowMode = "following";
|
||||
let lock = false;
|
||||
@@ -33,10 +49,17 @@ export function createFollowController(map: MapLibreMap, onModeChange: (m: Follo
|
||||
get mode(): FollowMode {
|
||||
return mode;
|
||||
},
|
||||
follow(fix: Fix): void {
|
||||
follow(point: Fix | { lat: number; lng: number }, opts: FollowOptions = {}): void {
|
||||
lock = true;
|
||||
map.flyTo({ center: [fix.lng, fix.lat], padding: CAMERA_PADDING_PX, duration: RECENTER_DURATION_MS });
|
||||
set("following");
|
||||
map.flyTo({
|
||||
center: [point.lng, point.lat],
|
||||
padding: opts.padding ?? CAMERA_PADDING_PX,
|
||||
duration: opts.durationMs ?? RECENTER_DURATION_MS,
|
||||
...(opts.pitch !== undefined ? { pitch: opts.pitch } : {}),
|
||||
...(opts.zoom !== undefined ? { zoom: opts.zoom } : {}),
|
||||
});
|
||||
mode = "following";
|
||||
onModeChange(mode); // the re-lock is announced (the recenter notch's return signal)
|
||||
lock = false;
|
||||
},
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user