feat(kiosk): graphics tiers + Settings knob; network map kiosk mode
Demo images / Build & push demo images (push) Successful in 3m51s

The animated federation map froze the framework-pt 4K TV: the launcher
held every machine to the HD 5500-era choppy-audio flags (single raster
thread, GpuRasterization banned) while the map wrote SVG attrs at 60fps.

- Launcher: two flag tiers. legacy = the proven conservative set; modern
  (Intel gen8+, 'NNth Gen' models, AMD Ryzen) = default raster threads +
  GPU rasterization. Classified from /proc/cpuinfo (11 model strings
  covered by tests in-session); KIOSK_GRAPHICS=performance|quality in
  kiosk-display.conf overrides; headless unchanged. Reaches deployed
  kiosks via the include_str! self-heal, same as the vsync fix.
- system.kiosk-display.get/set: carries a 'graphics' field alongside
  'preset'; setting one no longer clobbers the other.
- Settings → Display: Graphics picker (Auto / Compatibility / Quality).
- NetworkMap3D: kiosks default to the 2D projection (remembered toggle
  still works) and tick at half rate with carried-over deltas — same
  spin speed, half the paint cost.
- Changelog: curated Unreleased notes for all of the above + the gate
  frame-embedding fix.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
archipelago
2026-08-13 09:23:07 -04:00
co-authored by Claude Fable 5
parent 6672d978f7
commit 9243babcdb
5 changed files with 203 additions and 30 deletions
@@ -853,7 +853,25 @@ function render() {
}
}
/** Kiosk TVs run the kiosk image's conservative rasterizer (single raster
* thread, GPU raster off — the June choppy-audio tuning), so a 60fps
* SVG-attribute animation at 4K froze the browser outright (framework-pt,
* 2026-08-12). On kiosks the ticker renders every OTHER frame (~30fps at a
* 60Hz panel) with the skipped frame's delta carried over so spin speed is
* unchanged — at couch distance the half rate is invisible, the paint cost
* halves. */
const kioskLowPower =
typeof document !== 'undefined' && document.documentElement.classList.contains('kiosk-mode')
let lowPowerPhase = 0
let carriedDeltaMS = 0
function tick(_time: number, deltaMS: number) {
if (kioskLowPower) {
carriedDeltaMS += deltaMS
if ((lowPowerPhase++ & 1) === 1) return
deltaMS = carriedDeltaMS
carriedDeltaMS = 0
}
elapsed += deltaMS / 1000
if (!dragging) cam.rotY += cam.spin * (deltaMS / 1000)
render()
@@ -1169,11 +1187,14 @@ function initialBuild() {
applyGraph()
// First measurement decides the default projection: saved preference wins,
// otherwise portrait containers (phone/companion) open in the flat 2D view.
// otherwise portrait containers (phone/companion) AND kiosk TVs open in the
// flat 2D view — the depth view stays one tap away and is remembered. The
// kiosk default exists for the same reason as the half-rate ticker above.
modeInitialized = true
let saved: string | null = null
try { saved = localStorage.getItem('federation-map-projection') } catch { /* private mode */ }
viewMode.value = saved === '2d' || saved === '3d' ? saved : (height > width ? '2d' : '3d')
viewMode.value =
saved === '2d' || saved === '3d' ? saved : (kioskLowPower || height > width ? '2d' : '3d')
applyMode(viewMode.value, false)
if (staticMode) {