Skip to content

docs(renderer): renderer-unification design spec#385

Open
rulkens wants to merge 8 commits into
mainfrom
spec/renderer-unification
Open

docs(renderer): renderer-unification design spec#385
rulkens wants to merge 8 commits into
mainfrom
spec/renderer-unification

Conversation

@rulkens

@rulkens rulkens commented Jun 29, 2026

Copy link
Copy Markdown
Owner

What

Adds the design spec for unifying skymap's renderer paths.

The recent zoom-to-Earth work added a third render path (opaque, depth-tested, f64 near-field bodies) bolted onto a frame loop that previously had two (additive HDR scene + premultiplied-OVER UI overlay). This spec proposes one compositor model that subsumes background, foreground, and pick:

  • Three independent axes un-braided: slab (which view-projection + depth range), target (which texture), blend (additive / opaque / over). Today which Pass[] array a renderer lives in implicitly fixes all four of projection, precision, blend, and target.
  • Compositor primitive replaces three byte-identical tone-map / composite implementations (postProcess tonemap, foregroundComposite, volumeUpsample blit).
  • Flat ContentLayer registry + a data FrameStep program replace HDR_PASSES / UI_PASSES / the hand-wired foreground. Frame order becomes data (and therefore unit-testable).
  • Pick becomes a parallel program over the same registry: per-slab pick targets + a pure-CPU frontmost-non-zero resolve. Space-aware, shipping at N=1 (cosmological).
  • 3-phase plan: Compositor primitive → registry + program → pick fold-in. Each phase is an independently shippable, behaviour-neutral superset.

Essential differences (precision, frustum, blend) are preserved; accidental duplication is erased.

Status

Draft — under review. No code changes; this is the design doc that will drive a future writing-plans.

Spec: docs/superpowers/specs/2026-06-29-renderer-unification-design.md

🤖 Generated with Claude Code

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Jun 29, 2026

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Preview URL Updated (UTC)
✅ Deployment successful!
View logs
skymap 5ba7c11 Commit Preview URL

Branch Preview URL
Jul 06 2026, 05:24 PM

rulkens and others added 2 commits June 30, 2026 02:59
…acklog

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…clarification

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
rulkens added a commit that referenced this pull request Jul 3, 2026
…leted/

/feature-done audit verdict: READY.
Tests 3765/3765 green (606 files), typecheck clean (both configs),
all checkboxes ticked (visual gates user-attested), zero new TODO/FIXME
markers, no unexpected files vs the plans' File Structure sections,
no backlog entries to sweep. Deferred (documented in the spec's
"Later" section): main-app adapter behind GalaxyEngineHandle after
renderer unification (#385), catalog-driven parameterisation,
render-on-demand/pick/mobile budgets.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
rulkens added a commit that referenced this pull request Jul 3, 2026
…tool (#402)

* docs(spec): galaxy-renderer tool — port the Claude Design spike as a dev tool

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* docs(plan): galaxy-renderer tool — three-part TDD implementation plan

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* feat(galaxy-renderer): tool scaffold — vite app on port 5400 with tool-local WESL root

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* feat(utils): makeValueNoise — seeded trilinear 3D value noise

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* feat(galaxy-renderer): model type vocabulary + tempColor/hiiPalette helpers

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* feat(galaxy-renderer): classifyHubbleType + splitStarBudget table dispatch

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(galaxy-renderer): bulgeSize falsy fallback matches the spike (|| not ??)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* feat(galaxy-renderer): stride-8 star/dust writers — overflow throws

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* feat(galaxy-renderer): makeWarpOffset — S-shaped, twist-precessing outer-disk warp

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* feat(galaxy-renderer): GalaxyBuildContext — explicit closure env, RNG draw-order contract

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* feat(galaxy-renderer): population builders I — bulge, bar geometry, bar, disk

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* feat(galaxy-renderer): population builders II — spiral arms, irregular clumps, halo, globulars

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* feat(galaxy-renderer): dust field + arm/bar/lenticular/irregular dust builders

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* feat(galaxy-renderer): generateGalaxy orchestrator + worker entry — determinism battery

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* docs(plan): tick plan-01 checkboxes — all tasks executed and reviewed

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* feat(galaxy-renderer): engine type contracts — handle surface, render/lod split

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* feat(galaxy-renderer): pure camera helpers — orbitEye, panAxes, lensShift

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* feat(galaxy-renderer): packCameraUniforms — 112-byte camera UBO layout

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* feat(galaxy-renderer): bakeExtraTransform — rigid transform baked into instance buffers

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* feat(galaxy-renderer): seven WESL shaders — stars, dust, dual-filter bloom, tonemap composite

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* feat(galaxy-renderer): createGalaxyEngine — five-pass HDR chain, worker-backed regen, orbit input

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* feat(galaxy-renderer): defaults + Viewport + live main.tsx — first pixels

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* docs(galaxy-renderer): plan-02 gate — fix upsample header, README controls + pass chain

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* feat(galaxy-renderer): store factory + galaxy/render/lod slices

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* feat(galaxy-renderer): compare/extras/ui slices — six-slice store with AppState trip-wire

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* feat(galaxy-renderer): data tables — paramSpec, stage patches, reference galaxies

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* feat(galaxy-renderer): rng-injected randomizer + background-galaxy spec builder

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* feat(galaxy-renderer): preset wire format — spike-compatible flat r-bag, split at the seam

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* feat(galaxy-renderer): descriptor pipeline — rotation/scale-invariant galaxy fingerprints

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* docs(galaxy-renderer): dominantArms docstring states the true flat-spectrum behaviour

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* feat(galaxy-renderer): fitPlan + autoFit coordinate descent + image descriptor loader

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* feat(galaxy-renderer): engineBridge — the single store→engine imperative boundary

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* test(galaxy-renderer): pin the bridge's fire-time fit suppression

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* feat(galaxy-renderer): runCompareFit — auto-fit orchestration over store + engine

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(galaxy-renderer): fitStarted precedes the photo load; error path preserves null score

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* feat(galaxy-renderer): UI foundation — shared vocabulary + section/slider/tonemap primitives

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(galaxy-renderer): TonemapSelect composes a form-control vocabulary class, not btnGhost

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* feat(galaxy-renderer): control panel — type picker, sliders, extras, presets

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(galaxy-renderer): restore hii + dust-ring sliders via PARAM_SPEC extension

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(galaxy-renderer): randomizer skips slider-only PARAM_SPEC keys — spike-faithful sampling

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* feat(galaxy-renderer): Hud + ComparePanel + App assembly — feature-complete tool

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* chore(galaxy-renderer): final-review fix wave — dispose completeness, comments, README, plan ticks

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* docs(plan): mark galaxy-renderer tool complete — plans + spec to completed/

/feature-done audit verdict: READY.
Tests 3765/3765 green (606 files), typecheck clean (both configs),
all checkboxes ticked (visual gates user-attested), zero new TODO/FIXME
markers, no unexpected files vs the plans' File Structure sections,
no backlog entries to sweep. Deferred (documented in the spec's
"Later" section): main-app adapter behind GalaxyEngineHandle after
renderer unification (#385), catalog-driven parameterisation,
render-on-demand/pick/mobile budgets.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
rulkens and others added 2 commits July 3, 2026 13:46
The 2026-07-02 powers-of-ten architecture review re-checked this spec's
premises against current main (post #394/#396) and found four stale spots:

- Migration table: add clipPathDebugPass (UI_PASSES member since the tour
  work) and note drawPickDebugOverlay as a debug composite step — main
  already has three hand-rolled composites before #386 adds its fourth.
- FrameStep program: the volume prepass + flow compute are nested inside
  BOTH HDR encoder branches today; the program lifts them, and the
  executor owns the single/split timing fork as an execution strategy
  (TIMED_SLOT_NAMES then derives from the program).
- Pick: new prerequisite — camera-for-pick becomes a value from the slab
  table; the pointSpritesPass byte-snapshot side channel
  (lastFrameUniformBytes) makes per-slab pick unimplementable.
- Phase 2: renderer factories declare targetFormat explicitly; three
  coexisting initGpu idioms named; GpuContext.format = swap format only.

Also: state PR #386 (foreground path) is a draft, not merged; phase-1
merge-order handling; cite the real tone-map curve test.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@rulkens

rulkens commented Jul 3, 2026

Copy link
Copy Markdown
Owner Author

Re-verified the spec against current main (post #394/#396) and fixed four stale spots (commit 7fe76ef):

  1. Migration table — added clipPathDebugPass (in UI_PASSES since the tour work) and a note on drawPickDebugOverlay as a debug composite step. Main already has three hand-rolled composites (postProcess tonemap, volumeUpsample blit, pickDebugOverlay blit) before feat(zoom-to-Earth): true-scale foreground renderer (Plan 01 + polish) #386 adds foregroundComposite as the fourth.
  2. FrameStep program — the volume prepass + flow compute are nested inside both HDR encoder branches today (shared via encodeVolumePrepass/encodeFlowCompute); the spec now says the program lifts them, and that the executor owns the single/split timing fork as an execution strategy (with TIMED_SLOT_NAMES deriving from the program).
  3. Pick prerequisite — camera-for-pick must become a value from the slab table; today it's a byte snapshot of pointSpritesPass's uniforms (state.picking.lastFrameUniformBytes), which makes per-slab pick targets unimplementable. Phase 3 now starts there.
  4. Phase 2 target-format idiom — renderer factories declare targetFormat explicitly; named the three coexisting initGpu idioms (positional arg, repurposed GpuContext.format, ctx-bag + positional override). GpuContext.format = swap format, always.

Also corrected the framing that the foreground path is on main (it's draft PR #386), added phase-1 merge-order handling for #386, and cited the actual tone-map curve test. Branch merged with origin/main (no history rewrite). Full audit context: docs/audits/2026-07-02-powers-of-ten-architecture-review.md (on grand-tour).

🤖 Generated with Claude Code

Iteration outcome (all five locked with the user):

- FrameStep render steps carry an explicit slab: the (target, slab) pair
  selects each step's layer set by construction; buildFrame() generates
  the same shape when slabs go dynamic.
- ContentSpace dropped — layers carry a plain slab: number.
- New SlabView, built once per render step by the executor and threaded
  into draw/drawPick; no vpFor on ReadyFrameContext, layers/renderers
  stay slab-ignorant, the per-pass ctx.vp casts retire.
- No deps: PassDeps on layers — layers read state.gpu.* directly;
  PassDeps deleted in phase 2 (gpu-handle-nullability end-state).
- flow compute stays a FRAME step via a COMPUTE name→fn table.

Adds the executor contract sketch (strategy axis + derived timing slots)
and updates the file inventory. No open questions remain.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@rulkens rulkens marked this pull request as ready for review July 6, 2026 09:17
Plans (one per spec phase, executed via subagent-driven-development):
- 01-compositor: Compositor primitive (all three blends) + the postProcess
  tonemap repoint; shaders/toneMap/ deleted; curve mirror moves in.
- 02-registry-and-program: eight @types/engine/frame types, slabs.ts
  (NEAR0/COSMO + slabViewOf), ContentLayer conversion of all 14 passes,
  frameProgram(tone) + strategy executor + derived timing slots, target
  table + consolidated resize, targetFormat factory cleanup, PassDeps
  deleted. 11 tasks, green suite at every commit, flip isolated in Task 7.
- 03-pick: pick camera becomes a value (lastFrameUniformBytes deleted,
  full 176-byte re-home table), drawPick rows, per-slab pickProgram,
  frontmostPick. Also dissolves proceduralDiskRenderer's second camera
  stash.
- 00-index: ordering, locked cross-plan seams, #386 merge-order.

Spec correction from plan-time shader verification: volumeUpsamplePass
stays a content layer (its 4-tap rotated-grid low-pass is essential grain
suppression) and drawPickDebugOverlay stays a bespoke debug renderer
(r32uint textureLoad visualisation) — neither is a Compositor consumer.
The FRAME program loses the volume→hdr composite step; the volume
double-gate knot is fixed by one shared deriveVolumeLiveness projection.
Also recorded: the blend→dstFormat constructor mapping (a pass encoder
cannot be queried for its attachment format).

Backlog: render-graph-restructure item removed (index line + detail file)
per the spec's acceptance note — the FrameStep program supersedes it.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@rulkens

rulkens commented Jul 6, 2026

Copy link
Copy Markdown
Owner Author

Implementation plans added to this PR (commit 417ad8b) — three phase plans + an index under docs/superpowers/plans/2026-07-06-renderer-unification-*:

  • 01-compositor (5 tasks): the Compositor primitive with all three blends + the postProcess tonemap repoint. shaders/toneMap/ is deleted; the JS-mirror curve cluster moves to compositor.ts; PostProcess.draw's signature is unchanged so renderFrame is untouched.
  • 02-registry-and-program (11 tasks): the eight frame types, slabs.ts (stable NEAR0/COSMO indices), mechanical conversion of all 14 passes to ContentLayer rows, frameProgram(tone) + the strategy executor with derived timing slots, the render-target table with consolidated resize, the targetFormat factory cleanup, and PassDeps deletion. Green suite at every commit; the flip is isolated in Task 7 with a mid-plan visual check.
  • 03-pick (12 tasks): pick camera becomes a value (the full 176-byte lastFrameUniformBytes layout is re-homed field-by-field, nothing unrebuildable), drawPick on the four pickable rows, the per-slab pickProgram + frontmostPick. Bonus finding folded in: proceduralDiskRenderer had a second camera byte-stash (cachedViewProj, replayed by pickDisks) — same braid, dissolved in the same phase.

One spec correction fell out of plan-time shader verification (spec updated in the same commit): volumeUpsamplePass's fragment is a deliberate 4-tap rotated-grid low-pass (grain suppression for the raymarch) and drawPickDebugOverlay's is an r32uint textureLoad visualisation — both are essential specializations, not generic composites, so neither joins the Compositor and the FRAME program has no volume→hdr composite step. The volume double-gate knot is fixed by a shared deriveVolumeLiveness projection instead. Also recorded: dstFormat can't be recovered from a pass encoder, so phase 1 maps blend→dstFormat in the constructor and phase 2 dissolves the mapping.

The superseded render-graph-restructure backlog item is removed in this PR per the spec's acceptance note.

🤖 Generated with Claude Code

…ab model

The fold plan executes on the #386 branch after plans 01+02 land: the
bespoke foreground wiring (encodeForegroundPass/Over, foregroundLabelsPass,
foregroundComposite + shaders, foregroundOffscreen, four ReadyFrameContext
fields) dissolves into two ContentLayer rows, one depth-bearing
RenderTargetSpec row, the activated NEAR0 slab (origin-relative f64
computeForegroundViewProj), and three appended FRAME steps. #386's deferred
visual gate becomes the plan's final gate.

Two contract amendments the fold surfaced, both fixed pre-execution:

- Compositor alpha semantics are now a column of the blend table
  (replace → 1.0; over/additive → preserve src alpha, packed as a
  preserveAlpha uniform derived from the blend). Plan 01's 'over' was
  premultiplied with a forced alpha-1.0 emit — folding #386's
  straight-alpha composite onto that would have painted the foreground
  offscreen over the whole screen.
- Depth attachments become target-table + executor data in plan 04
  (foreground:0 is the first row to exercise RenderTargetSpec.depth).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@rulkens

rulkens commented Jul 6, 2026

Copy link
Copy Markdown
Owner Author

Plan 04 added: fold PR #386 (zoom-to-earth) onto the layer/slab/program model (docs/superpowers/plans/2026-07-06-renderer-unification-04-fold-zoom-to-earth.md, 8 tasks).

Strategy: plans 01+02 execute on main without #386, then plan 04 executes on the #386 branch (merge main in, convert, run the visual gate #386 has been waiting on, un-draft). What dissolves: encodeForegroundPass/encodeForegroundOver/foregroundLabelsPass, foregroundComposite (+3 WESL files), foregroundOffscreen, and the four ReadyFrameContext foreground fields → two ContentLayer rows, one foreground:0 target row, the activated NEAR0 slab (origin-relative f64 computeForegroundViewProj), and three appended FRAME steps. All of #386's substance (f64 compose-before-narrow path, sphere renderer, scale units, scalebar ladder, MW approach-fade) rides along untouched.

Two contract gaps the fold surfaced, fixed pre-execution in this commit:

  1. Compositor alpha semantics (spec + plan 01): alpha is now a column of the blend table — replace emits 1.0, over/additive preserve source alpha via a preserveAlpha uniform derived from the blend; over is straight-alpha Porter-Duff matching feat(zoom-to-Earth): true-scale foreground renderer (Plan 01 + polish) #386's composite byte-for-byte. Plan 01 as previously written (premultiplied over + forced alpha-1.0) would have painted the foreground offscreen over the entire screen on fold.
  2. Depth attachments as data (plan 04, Task 2): foreground:0 is the first row to exercise RenderTargetSpec.depth; renderTargets + executeFrame extend the first-touch-clear rule to depth.

Behaviour deltas the fold accepts (all named in the plan): foreground offscreen renders after the hdr→swap tonemap (no data dependency), pass+composite skipped entirely when no body is enabled (touched-set rule — the branch always opened both), and the foreground gains dev-only per-layer timing slots.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant