Your browser sucks, JavaScript disabled! Gunnar's console needs JavaScript and WebAssembly. Git over SSH on port 2222 does not.

nordisk/facett

facett-graph3d/.nornir/travels-tour-design.md

TRAVELS — the guided constellation tour — design

Class: design · Function axis: f0 the guided-tour primitive over a Graph3D · f1 TravelRoute + the route KIND constructors · f2 the Spline (Catmull–Rom) + the Travel animator (timeline · ghost · camera fly) · f3 the controls + state_json observable surface · f4 the freeze / host-drives-data contract. Strict: this file states only the shape we run. Rejected alternatives are at the end.

f0 — what this kills

Rickard's idea: a glowing, ghostlike spline drawn in 3-D space that takes a guided TOUR through the constellation graph — a camera + highlight fly along a path visiting a selectable set of nodes ("latest changes"; "reported bugs — start at the top, travel down into the bug"; "spotlight this one node"). Built as a reusable primitive in facett-graph3d so dwarves' navigator page + korp can drive it with real warehouse data later.

It is default OFF: a Graph3D with no route set renders byte-for-byte as before (travel == None), so every existing render + snapshot golden is unchanged. The tour is purely additive.

f1 — TravelRoute + the route KINDS (the "list of interesting travels")

A route is pure data the HOST builds from nornir's warehouse metadata:

TravelRoute { name: String, kind: RouteKind, stops: Vec<NodeId> }   // NodeId = graph node index

Built-in constructors (the "list of interesting travels") — each takes a node set the host supplies; facett fetches nothing:

RouteKind is a small serde enum so a robot / host asserts which tour is running from state_json (travel_kind).

f2 — the Spline + the Travel animator

Spline — a uniform Catmull–Rom curve through the stops' node centres. Catmull–Rom passes through every control point, so the path visits each stop exactly: point(seg, 0.0) == pts[seg] and point(seg, 1.0) == pts[seg+1] (the geometry proof the tests assert). Endpoints are clamped (the ends repeat their neighbour) so the curve stays put at the first/last stop. sample(t) walks the whole spline at t ∈ [0,1].

Travel — the animator. It holds the route, the stops' 3-D centres (the spline control points, sliced from the graph layout by Graph3D::set_travel), and a deterministic elapsed timeline (tour seconds). Timeline shape for m stops:

dwell(0) · travel(0→1) · dwell(1) · travel(1→2) · … · dwell(m-1)
total = m·dwell_secs + (m-1)·seg_secs           (a lone stop is just its dwell)

Ghost paint — Travel::paint(painter, project, phase) is a pure overlay: the ghostly translucent spline trail (breathing on decor::pulse), a soft glow at each stop node, and the pulsing emissive ghost marker at the head — all projected through the SAME camera closure the cloud paints with (L5, no re-derived projection). Reuses neon::paint_node_orb + facett_core::render::decor. A cyan-white spectre (rgb(185,240,255)), emissive + transparent, consistent with the Code Vault aesthetic.

f3 — controls + the state_json surface

Controls are Graph3D::Msg variants (the single FC-2 mutation path; no-ops when no route is set): TravelPlay · TravelPause · TravelToggle · TravelNextStop · TravelPrevStop · TravelSpeed(f32) · TravelTick(f64) (advance ONLY the tour, live). Msg::Tick / Msg::SetClock also drive the tour off the graph's master clock.

When (and only when) a route is set, Graph3D::state_json merges the tour surface:

travel_active · travel_playing · travel_name · travel_kind · travel_stops ·
travel_stop_index · travel_progress · travel_dwelling · travel_node · travel_speed ·
travel_elapsed · travel_camera_follow

No route ⇒ none of these keys appear, so existing state_json goldens are byte-stable.

f4 — the freeze / host-drives-data contract

facett renders the tour; it does not fetch data. The HOST supplies the ordered node sets (from nornir's warehouse — recency / bug anchors / a single node) and builds a TravelRoute; Graph3D::set_travel(route) slices the stops' laid-out centres for the spline. The layout is frozen (Barnes–Hut freeze cache); after a relayout / absorb the host calls Travel::refresh_centers(&graph_centers) to rebuild the spline without disturbing the timeline.

Follow-up (NOT this pass): dwarves' navigator page wires real warehouse data (recency / reported bugs) into TravelRoute::latest_changes / bugs_top_down and drives Graph3D's travel controls — a dwarves-side consumption of this primitive. korp drives the same seam with lakehouse data.

Tests (L4, RED-when-broken + testmatrix)

Rejected