PURPOSE
Dev tool screen for previewing tileable procedural backgrounds and baking them to multi-resolution flipbook PNGs for runtime use. Live preview uses GPU-side REPEAT wrap on the layer-stack composite so the screen fills with infinitely tiled output of the currently selected BackgroundDef. Parallax toggle and seam-debug overlay help verify tile correctness before running the bake. Mounted at route /backgrounds-viewer.
OWNS
- Local React state for selection (
idx),parallaxfactor,seamDebugflag,staticModeflag,tilesAcrosscount,bakingflag, andbakeStatusstring. - A
containerRefdiv hosting the live preview canvas. - A
previewRefholding theBackgroundLivePreviewinstance, plus arafReffor the render loop handle. - Ref mirrors (
defRef,parallaxRef,seamRef,staticRef,tilesRef) so the long-lived RAF closure reads current values without resubscribe. - Tile size constant
TILE_SIZE = 1024. - Local helper functions
readUrlIndex,writeUrlIndex,downloadPngBytes, and the styling helperspillStyleandnavBtn. - The bake handler
onBake, which iterates baked size results and triggers PNG downloads named<id>-<size>.png.
READS FROM
ALL_BACKGROUNDSfrom@starship-survivors/engine/backgrounds-workbench/background-loaderfor the selectable list.BackgroundLivePreviewclass from@starship-survivors/engine/backgrounds-workbench/live-previewfor GPU rendering.bakeBackgroundAllSizesfrom@starship-survivors/engine/backgrounds-workbench/bake-backgroundfor offline PNG baking.BackgroundDeftype from@starship-survivors/engine/backgrounds-workbench/background-schema.window.location.searchandwindow.location.hrefforn=<idx>query-param state.window.devicePixelRatiocapped at 2 for canvas sizing.
PUSHES TO
BackgroundLivePreviewinstance fields each frame:parallax,seamDebug,staticMode,tilesAcross; then callspreview.render(def).window.history.replaceStateto keep?n=<idx>in sync with the selected background.- Browser download via a transient
<a>element with object-URLBlobof PNG bytes. - DOM: appends the preview canvas to
containerRef.currenton mount; removes on unmount.
DOES NOT
- Does not persist selection beyond the URL query param (no localStorage, no Zustand).
- Does not modify any
BackgroundDefdata; bake is read-only against the loaded defs. - Does not upload baked PNGs anywhere; the user receives them as browser downloads.
- Does not handle keyboard input while focus is inside an
HTMLInputElement. - Does not render game UI, HUD, hub, or any non-dev surface.
- Does not run when
ALL_BACKGROUNDSis empty beyond a fallback message div.
Signals
- Arrow keys (Left/Right) step previous/next background.
Ptoggles parallax between0and0.5.Stoggles seam-debug overlay.- Top-bar buttons toggle static/animated mode, cycle
tilesAcrossthrough1 -> 2 -> 4 -> 1, toggle parallax, toggle seams, and triggerBAKE. - Bottom selector row sets
idxdirectly to any background inALL_BACKGROUNDS. ResizeObserveron the container andwindowresizelistener drivepreview.resize.
Entry points
- Default export
BackgroundsViewerScreen(React component) — mounted by the app router at/backgrounds-viewer. - Initial
idxread from URL viareadUrlIndex, clamped into[0, ALL_BACKGROUNDS.length). - Mount effect constructs
BackgroundLivePreview({ tileSize: TILE_SIZE }), attaches its canvas, performs initial resize plus a deferred resize on the next animation frame (StrictMode first-paint workaround), and starts the RAF render loop. - Keyboard effect attaches a
keydownlistener at thewindowlevel.
Pattern notes
- Long-lived RAF closure pattern: the tick callback reads from ref mirrors instead of stale state captured at mount.
- Mount effect runs once with empty dependency array; all live tunables are mirrored to refs and applied to the preview instance every frame.
- Index navigation uses modulo-based wrapping in
setIdxClampedso prev/next wrap at the ends. - DPR sizing is capped at
2to avoid oversized framebuffers on high-DPI displays. - StrictMode-double-mount workaround: extra
requestAnimationFrame(resize)after the synchronous initial resize, in caseclientWidth/clientHeightare stale on the first run. - Cleanup tears down
ResizeObserver, RAF, resize listener, canvas DOM node, and callspreview.destroy(). - The
bakeStatusstring is the single user-facing surface for bake progress and errors; bake errors are caught and rendered, not thrown. - Inline styles only; no CSS modules. Pill-style buttons share a
pillStyle(active)helper; nav buttons share thenavBtnconstant. - Empty-state fallback renders a black screen with a monospace hint pointing to
src/starship-survivors/data/backgrounds/.