# Saved recording display profile · 22 September 2026 ## Operator contract The existing Display window now offers cloud decimation, an accumulation-slider range in minutes and the current accumulation duration. New session profiles default to a three-minute slider range, zero decimation and the existing 12 s current window. Exact value inputs accept fractional percentages and custom durations; the 30-minute drag range is not a duration-validation ceiling. 0% retains the original point count, 50% retains half per frame (rounded), and 100% hides points without hiding the trajectory or grid. These are presentation controls, not scanner, registration, reconstruction or evidence controls. Recorded Display edits preview while the modeless window remains open. Changes coalesce for 750 ms or flush on a control's commit; 0%/100% decimation bypass that delay. Closing atomically saves the complete display settings in `display-profile.json` inside that session's catalog-validated directory. Schema is `missioncore.session-display-profile/v1`. Sealed `method.json`, raw transport, corrected geometry and original RRD are not rewritten. Missing profiles use defaults. Reads/writes are fenced by session identity and edit revision; writes are serialized. Save failure leaves the window closable and reports an error. ## Implementation / Rerun upgrade inventory - `packages/spatial-ui/src/{sceneSettings,ObservationTimeline,SceneDisplayControls}`: shared RangeControl, three-minute default, per-profile timeline maximum. - `apps/control-station/src/core/observation/{sessionDisplayProfile,useSessionDisplayProfile}` and App: JSON mapping, validation, live preview and close-to-save session-bound persistence. - `src/k1link/sessions/display_profile.py`, SessionStore and session_api: confined GET/PUT display-profile endpoint, atomic fsync/replace sidecar. - Plugin contribution `point_display_renderer`, K1 `recorded_point_display.py`: read-only full timeline traversal, deterministic ranked sampling, full-frame palette before selecting rows, corrected map-frame ownership retained. - Follow-up fix `prepared_point_display.py`: intensity/Turbo decimation reads exact positions and packed colors from the already prepared operator RRD via `RrdReaderInternal`/Arrow instead of re-normalizing raw transport. The client supplies the source SHA-256; the server validates and pins that exact ready preparation and releases it on completion, startup failure or cancellation. A stale generation fails with 412. The hook also fences applied banks by source generation. Neither a browser-supplied filesystem path nor a second recording is admitted. Other color modes retain the raw decoding fallback. - `recordedPointDisplay.ts`, `useRecordedPointDisplay.ts`, RerunViewport and viewer/recorded.py: framed RRD batches into the existing receiver, unique generation entity below `/world/display_points`, then blueprint activation. Inactive generations are excluded before streaming, including old cached RRDs. Unique paths avoid accumulating old samples at the same temporal entity. Previous representation remains visible during preparation or failure. 0% uses the base entity; 100% uses visibility, without rebuilding an empty recording. - The same recording, iframe, clock and native camera remain active. Blueprint activation carries actual native eye. Changing only accumulation or size does not regenerate points. Native SDK/WebViewer remain 0.36.3 with the previously accepted navigation patch; this increment adds no Rust/WASM patch. The HTTP envelope is NPD1 + repeated little-endian u32 length / standalone RRF2 payload + a terminal zero length. Native `send_rrd` expects a complete RRD per call, not arbitrary HTTP chunks. Browser QA caught this distinction: an earlier prototype appeared thinned but logged decoder errors and was rejected. The replacement frames transport explicitly, validates every batch and refuses activation without the success terminator. A multi-batch RrdReader regression decodes each payload independently and counts every input frame. Upgrade checks: complete RRF2 batch admission, same recording ID, temporal query and generation include/exclude precedence, point-size overrides on alternate entity, current eye preservation, retained pause/seek and teardown/abort. Repeat profile roundtrip and failure tests independently of browser local storage. ## Verification and limits Frontend complete suite: 900 passed; production TypeScript/Vite build passed. Final backend display/profile/colors/session/blueprint/plugin-boundary suites: 65 distinct tests passed, including atomic-write failure, source-generation pin/release, independently decodable batches and exact prepared-RRD attributes. Sampling tests cover 0/5/49/49.5/50/99.9/100%, exact count and nested subsets; stream cancellation releases the preparation slot. Frontend tests cover fragmented RRF2 headers, cancellation, cross-origin rejection, profile identity, range override and recorded-only control exposure. Canonical port 8000 browser QA on corrected JA-STROITEL-SUN-RING-002: staged 95% decimation, 600 s range and 60 s current window persisted on close. The prototype visibly thinned the cloud, but its decoder errors invalidate that as evidence of complete delivery. Pause remained at 01:45.173 and grid/camera framing stayed unchanged. 100% removed only points; 0% restored full cloud without decoding it again. This tests display, not geometric accuracy or localization. The earlier framed-protocol browser trials admitted multiple batches without codec errors, but service restarts interrupted the requests before their success terminator. The previous representation remained visible and the UI reported the failure; it did not activate the incomplete generation. Full real-recording completion was NOT browser-qualified at that point. The watchdog journal has no restart-requested event for these restarts; their initiator has not been established. No watchdog setting was weakened. The small ring's separate replay preparation was also interrupted by a service restart. That earlier test must not be cited as a passed large-recording test. After a page reload, the recording restored its exact 0.5 px point size, 0% decimation, 600 s custom range and 60 s accumulation from the JSON profile. The QA session was subsequently returned to 0% decimation, a 180 s range and 47 s window. The final retry-dependency fix passed production build and the nine focused frontend profile/camera tests: unrelated size/time edits do not cancel preparation of the same sampling key, while closing Display retries a failure. Follow-up browser acceptance on canonical 8000, 22 September: persisted 86.2% was reproduced in JA-STROITEL-SUN-RING-002. The old renderer was still decoding raw transport; retaining the old representation until completion made it look inert. After installing the prepared-RRD path, the complete generated bank was activated. At the same paused cursor 01:38.386 and accumulation 49 s, switching 86.2% -> 0% visibly restored the dense cloud; 100% removed points while keeping grid/trajectory, and returning to 86.2% restored the sparse cloud. Camera framing and the paused cursor remained unchanged. Expanded mode and host Escape return passed. Browser error log was empty (only known native web viewport-command warnings). The source had no decoder errors. This closes the earlier real-file activation acceptance item. The test leaves the user's 86.2%, 0.5 px, 180 s range and 49 s current window intact. It does not claim exact GPU counts or physical gesture coverage. The synthetic RRD test proves 138/1000 points per frame at 86.2%, exact XYZ/RGBA and original timestamps without raw decoding. OPS registry #74 is updated only after this follow-up browser qualification; the long-lived upgrade registry itself is not moved to Done. Performance limitation: the old raw path took several minutes on the 24-minute/49-million-point recording. The intensity/Turbo path now traverses the prepared RRD; other palettes/modes still use the slower raw path. A new percentage still needs asynchronous preparation, not instantaneous GPU sampling. The server streams bounded batches (one preparation at a time), and the browser avoids a whole-cloud JS blob. Decimation reduces visible geometry/draw work; it does NOT evict the base recording or guarantee lower total native store RAM. Inactive generations remain subject to native store retention until viewer disposal. Repeated-percentage memory stress and a persistent preview cache are not claimed as qualified. Raw/corrected source integrity and navigation are not traded for speed. No extra backend, Docker runtime or alternate renderer was introduced. Host acceptance limitation: the disk had about 300 MiB free during final QA, below the service's 2 GiB reserve. `/api/health` reported recording-cache capacity-pressure while the single canonical service continued serving 8000; 8765 had no listener. No recordings were deleted and the reserve was not weakened. New materialization/load stress is not safe until disk headroom is recovered; the prepared-RRD fix does not add a new persistent cloud cache. The product UI skill kept the controls in the admitted Display window and reused canonical RangeControl/ToastStack rather than creating another workspace. ## Follow-up: open-inspector preview regression The operator's 07:57 screenshots exposed a second, independent defect after the prepared-RRD fix. `stageDisplayPatch` returned early whenever recorded playback and Display were both open. Both the inspector and the bottom accumulation slider used this callback: their labels showed the draft, while Rerun continued to receive the previous scene settings. The preceding browser acceptance only tested changes after closing Display and did not cover this interaction. The inspector-open suppression is removed. Preview is independent of JSON persistence; close still saves the latest shared draft, including edits made from the bottom slider. Cheap 0%/100% changes apply immediately. Intermediate percentage edits remain coalesced and asynchronous, with the previous complete bank retained until the replacement succeeds. Returning to a cached bank or 0% now clears an obsolete preparation status. No raw/corrected data, camera navigation or native viewer build is changed by this follow-up. Preparation status is placed upper-right on the source-button vertical axis. Camera windows default to bottom-left, still clear of the timeline. Explicit saved/user-moved window rectangles retain priority over the default placement. The product UI skill preserves the existing window and range primitives. Follow-up validation: architecture 4/4, full frontend 902/902, TypeScript and production Vite build passed; `git diff --check` passed. The regression test executes the actual App callbacks with controlled timers: open inspector, 100% -> 0%, 180 s -> 10 s, fractional 49.5% preview, then close-to-save. Existing lower-right/left-status layout assertions were updated to the requested layout. Visual acceptance of this follow-up remains PENDING. The prepared large ring loaded after the rebuild, but browser control was interrupted before the open-inspector comparison. After the host restart the canonical LaunchAgent failed before application startup: launchd reported `posix_spawn(uv): Operation not permitted`, exit 78, with no listener on 8000. Kickstart and re-registration of the identical existing plist did not recover it. No permissions, plist, data, raw recording, or other service were changed. No alternate backend was started. This startup failure is not proof of a Rerun failure; its exact OS-level cause is not established. OPS is not updated for this unfinished acceptance. ### Resumed acceptance and fractional cursor regression The owner requested coordination with Mission Core - SIM. That task owns the canonical service restart; this task does not launch another backend. SIM recovered the existing LaunchAgent by moving only its startup log out of Downloads to the user's Library/Logs directory. Canonical health subsequently reported operational=true, one listener on 8000, none on 8765. Browser QA ran with one prepared recording, no build/test overlap, and 53–75% reported free memory. Original per-session settings (86.2%, 49 s, 180 s maximum) were restored and verified through the profile API before closing the temporary viewer for SIM's next explicitly coordinated restart. Open-inspector 100% removed points and 0% restored the dense cloud; the camera window appeared bottom-left. However, after seek and Follow on/off, 180 s and 10 s produced an unchanged view and later display requests stopped reaching the blueprint endpoint. This is NOT accepted as working accumulation. An additional concrete boundary defect was reproduced in a focused test: `fetchRecordedBlueprintRrd` rejected a native fractional-nanosecond cursor such as 536460021972.65625 before making the request. Pinned Rerun 0.36.3 `crates/viewer/re_viewer/src/web.rs::get_time_for_timeline` returns TimeReal as f64, not an integer. A failed following-eye transition remains pending, so later display-only updates retry that same invalid pose-query timestamp. The request boundary now rounds to the nearest integer nanosecond (at most 0.5 ns); the native playback cursor is untouched. Negative, non-finite and unsafe timestamps still fail. Aborted requests stay quiet; other blueprint failures now produce a console warning instead of disappearing silently. The new regression failed before the change and passed after it. Full gates and resumed end-to-end accumulation verification are still pending; OPS remains unchanged until they pass. ### Final acceptance after the fractional cursor fix The sequential gates now pass: architecture 4/4, TypeScript, full frontend 903/903 (zero skipped), production Vite build and `git diff --check`. The build retains only the existing large-chunk warning. Served asset `app-Bm1TJUff.js` was confirmed before the final browser pass. No native/WASM rebuild or further service restart was needed. On JA-STROITEL-SUN-RING-002, with Display open and the same paused cursor/camera, 180 s showed the longer cloud history and 10 s visibly removed older geometry. This also passed after seeking to 01:00.780 and toggling Follow on/off. 100% removed only points; 0% restored them. 99.9% yielded a near-empty cloud while keeping grid/trajectory, and returning to 86.2% restored the prepared subset. During that replacement the preparation pill was visibly upper-right, aligned with the source/expand controls. Camera default was bottom-left. Expanded view and Escape returned to the same paused frame and accumulation. Console error entries and the new recorded-scene failure warnings were both empty. Original session profile values were restored and read back after closing Display: 86.2% decimation, 49 s accumulation, 180 s scale maximum, 0.5 point size, intensity/Turbo. Source recordings and sealed method.json were not rewritten. No exact GPU point-count claim or physical trackpad-device qualification is made by this browser pass. The temporary viewer was then closed rather than left resident: the explicitly coordinated Mission Core - SIM task needed the next single-viewer acceptance window, and the host had recently exhausted RAM. SIM was notified that the frontend gates had already passed and must not be duplicated unnecessarily. Final local checks: operational=true, one canonical listener on 8000, none on 8765, 62% reported free memory and about 16.5 GB available disk. No background build, second backend or large-recording stress test was started. ### Operator cleanup and repository packaging At the owner's request the persistent LMB/RMB/wheel hint was removed from the shared SpatialScene and both recorded/live callers, with its unused CSS and readiness prop. Native camera input and renderer code were not changed. Architecture/layout checks 9/9, frontend 904/904, typecheck and production build passed. Lightweight browser inspection confirmed the hint was absent; the large recording was not reloaded solely for this copy removal after host swap growth. The canonical service remained healthy and the temporary browser was closed. The accepted 0.36.3 native WASM is packaged through Git LFS, matching the existing binary-artifact convention for the archived viewer. The source patch, paired JS/types, licenses and hash-bound build manifest remain normal versioned files. Raw captures, runtime state and simulation work-in-progress are outside this publication scope. The long-lived Ops customization registry is not marked done. Before publication, the selected Git index was exported into an isolated temporary source snapshot, excluding every pending SIM change (including the SIM hunks in App, dependency manifests and web/app). Against that snapshot, 154 focused frontend tests, TypeScript typecheck and 230 focused backend tests passed. Dependencies were reused from the existing local installation; this is not a clean dependency-install qualification. The only backend warning was the existing Starlette/httpx deprecation. First-party staged diff checks passed; generated vendor JS and source-patch context retain their hash-bound whitespace. The staged WASM is a Git LFS pointer to the manifest-verified 50,469,154-byte artifact. Working files were unchanged by staging. No service restart, new physical scan, repeat full build or large-recording replay was needed for this publication gate; canonical 8000 returned operational=true.