Skip to content

feat(zoom-to-Earth): true-scale foreground renderer (Plan 01 + polish)#386

Draft
rulkens wants to merge 24 commits into
mainfrom
feat/zoom-to-earth-true-scale
Draft

feat(zoom-to-Earth): true-scale foreground renderer (Plan 01 + polish)#386
rulkens wants to merge 24 commits into
mainfrom
feat/zoom-to-earth-true-scale

Conversation

@rulkens

@rulkens rulkens commented Jun 29, 2026

Copy link
Copy Markdown
Owner

What

The zoom-to-Earth true-scale foreground renderer — Plan 01 (precision slice) plus follow-on polish.

  • f64 foreground view-projection (computeForegroundViewProj, composeBodyMvp): composes proj·view·model in double precision and narrows at the GPU-upload boundary, so a body at ~1 AU inside an Mpc-scale VP doesn't lose its position to catastrophic cancellation.
  • Foreground offscreen (rgba16float + depth32float) with an OVER composite, so opaque bodies depth-test against themselves and occlude the galaxy-level UI labels behind them.
  • Debug Sun/Earth spheres at the focus point, with named captions.
  • Milky Way approach fade — the impostor fades out as the camera dives inside the disc.
  • Scalebar distance ladder extended to pc / AU / km for solar-system scale.
  • Foreground composited over the UI so it occludes labels (the foundation for a future translucent atmosphere).

Status

Draft — awaiting visual confirmation at solar-system zoom. Plans 02 (Earth + anchors) and 03 (LOD + polish) are not started. Do not merge until the visual gate passes.

🤖 Generated with Claude Code

rulkens and others added 23 commits June 29, 2026 01:47
Kept foundation (not a throwaway spike): continuous per-object floating
origin (f64 truth on CPU, narrow to f32 at the GPU boundary) so the camera
can fly from the cosmic-web view down to a true-scale textured Earth past a
few anchors. Refines ADR 0001's discrete-shell stance for the interactive
free-zoom case.

Captures the readiness audit: the main scene is depthless/additive (opaque
bodies need a new depth-tested foreground pass composited via the volume
offscreen template), gl-matrix is f32-only (needs f64 mat helpers), and
star/planet/earth slot in as new SOURCE_REGISTRY data types seeded like the
famous/structures pattern. Precision slice is task 1, kept if green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The gl-matrix → wgpu-matrix migration (#382) provides f64 matrix
composition via mat4d/vec3d, so §8 no longer needs hand-written
lookAt64/perspectiveZO64/multiply64/translate64/scale64 helpers. Only
narrowMat4 (f64 → f32 for upload) remains bespoke. Updated §2, §3, §8,
§9, §10, §11, and references accordingly.

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

Three sequential plans for the zoom-to-Earth-at-true-scale feature, split by
the spec's phases with a shared cross-plan contract so type names/seams stay
consistent:

- 01 precision slice (Phase 1, the de-risk): scaleUnits + narrowMat4 + f64
  MVP compose via mat4d + opaque foreground depth pass + lowered min-distance
  + debug sphere at Earth's true scale.
- 02 earth & anchors (Phases 2-3): star/planet/earth data types + stores +
  sphere renderers on shared infra (Blue Marble Earth, Sun/Moon/Jupiter/Proxima).
- 03 LOD & polish (Phases 4-5): adaptive foreground frustum, apparent-size
  point<->sphere promotion, fly-to-Earth debug key, ADR 0008, entanglement-radar.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Unit conversions (km/AU/pc/kpc/Mpc/Gpc + ly for copy) derived from a single
parsec-in-km base, and the fixed Sun render origin the per-object f64 matrix
math is expressed relative to.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…enderOrigin (zoom-to-Earth T3)

Composes proj*view in wgpu-matrix mat4d (f64), with eye/target taken relative
to renderOrigin before lookAt. With renderOrigin=[0,0,0] it narrows to the
existing f32 computeViewProj; the f64 path is what survives Earth-at-1-AU scale.

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

Composes the full per-body MVP in wgpu-matrix mat4d (unit sphere translated to
bodyPos-renderOrigin and scaled by radiusMpc) and narrows the product to f32.
Guard tests prove the de-risk: at parsec scale the f64 compose-then-narrow holds
a body-radius feature to ~7e-7 radii while narrowing view and model separately
loses ~376 radii. (Negative-test geometry corrected from 1 AU to parsec scale,
where cancellation actually bites — see plan note.)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…s (zoom-to-Earth T5)

Unit-radius positions, equirectangular uvs (seam column duplicated for clean
texture wrap), CCW outward-facing triangle-list indices. Shared by the debug
sphere now and the Earth/planet/star renderers later.

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

Full-res rgba16float color + depth32float depth target for the opaque foreground,
and a fullscreen pass compositing it OVER the additive HDR backdrop (src-alpha /
one-minus-src-alpha), unlike volumeUpsample's additive blend. Not yet wired into
the frame loop (Task 10/11).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Shared SphereUniforms { mvp } + clip_from_local(mvp, localPos) helper (mvp
passed as a param, not captured), plus a flat lat-long-shaded debug-sphere
vertex/fragment emitting straight opaque alpha for the OVER composite.
SphereUniforms at @group(0)@binding(0); position @location(0) vec3 stride 12.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…h pass (zoom-to-Earth T8)

Uploads uvSphereMesh once (pos VBO + index IBO), explicit group0/binding0
SphereUniforms layout, depth32float depth-test (write on, less), rgba16float
opaque colour, CCW/back-cull. draw(pass, mvp) writes the f32 MVP and draws
indexed. satisfies Renderer.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ng (zoom-to-Earth T9)

deriveFrameContext now composes the f64 foregroundVp (computeForegroundViewProj
relative to RENDER_ORIGIN_MPC) once per frame and carries foregroundNear/Far
(a coarse Plan-01 cam.distance heuristic; Plan 03 swaps in foregroundFrustum)
+ renderOrigin. Existing ready-ctx test fixtures updated for the four new
required fields (neutral placeholders; the foreground pass isn't wired yet).

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

EngineGpuHandles gains foregroundOffscreen / foregroundComposite /
debugSphereRenderer (nullable). initGpu builds them after the existing
renderers ('rgba16float' color + 'depth32float' depth); runFrame resizes the
offscreen alongside postProcess/volumeOffscreen; engine.destroy() releases all
three. Not yet drawn (encodeForegroundPass is the next task). vite build green:
the foreground WESL links cleanly now that it is in the import graph.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ody seed (zoom-to-Earth T12)

MIN_DISTANCE_MPC 0.05 -> 1e-17 Mpc so the camera can sit just off Earth's
surface; the focus-on tween end (0.15 Mpc) sits far above the new floor so
clampDistance never ratchets it. DEBUG_SPHERE_BODY seeds Earth at true size
(6371 km) and a 1-AU position for the foreground pass to draw (Plan-01 stand-in;
Plan 02 replaces it with BodyStore.earth).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…to the frame (zoom-to-Earth T10)

Two-step pass on the shared encoder: render DEBUG_SPHERE_BODY into the foreground
offscreen (own depth, cleared) via composeBodyMvp, then OVER-composite it onto
ctx.postProcess.view (loadOp 'load', preserving the additive backdrop). Slotted
into both renderFrame branches after the HDR mega-pass and before tonemap, so
Earth stays inside the HDR/tonemap pipeline. Self-gates on the nullable handles.
destroyReachability test gains vi.mocks for the three foreground factories.

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

Teaching comments in the f64/f32 seam stated 1 AU as ~4.8e-9 Mpc (off by 1000x;
actual ~4.85e-12) and one conflated the AU distance with Earth's radius
(~2.06e-16 Mpc). Also: narrowMat4 docblock said row-major (wgpu-matrix/WGSL are
column-major); renderFrame's pass-order list omitted the foreground composite;
the cancellation-guard test comment conflated the raw VP-translation error with
the bounded ndcErrorMpc metric. Comment-only; suite + build unchanged.

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

All 13 tasks checked off through the automated gate; records the parsec-scale
correction to the catastrophic-cancellation guard test and the reorder note
(T11/T12 before T10). Visual gate (Task 13) remains open pending on-screen review.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The camera focuses on the Sun (render origin), but the only debug sphere was
Earth — seeded ~1 AU off-axis and 109x smaller — so nothing rendered where the
camera lands. Seed a Sun sphere at the origin as a visible landmark + scale
reference. Drawing two spheres surfaced a latent bug: debugSphereRenderer wrote
one shared MVP uniform per draw, which the queue.writeBuffer/submit ordering
collapses onto the last body. Switched to per-sphere dynamic uniform offsets so
each renders with its own matrix in one submit.

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

formatDistance bottomed out at kpc, so zooming to the Sun/Earth showed a
useless "0.0000000048 kpc" label. Continue the ladder below a kiloparsec:
pc/ly (parsec family), then a bare AU (solar-system scale), then a bare km
(planetary surface) once inside an astronomical unit. The light-year
companion is dropped below 1 pc, where a fraction of a light-year is no
more intuitive than a fraction of a parsec.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The true-scale foreground spheres are sub-pixel at almost every zoom, so
locating Earth by eye is hopeless. Add 'Sun'/'Earth' captions anchored to
each body's world position.

They can't ride the existing label path: the main projection's near plane
is pinned at 0.01 Mpc, and at solar-system zoom the bodies sit ~1e-13 Mpc
from the camera — inside that near plane, so the normal labels would clip
them. A label renderer draws with one view-projection, so a second
LabelRenderer instance (foregroundLabelRenderer) drawn with the adaptive
foregroundVp in its own UI pass is the natural split. Gated on
cam.distance < 1 kpc so the captions only appear during the descent and
don't clutter the galaxy view. This foreground-label seam is reused by
Plan 02's real planet labels.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The Sun/Earth captions used a fully-opaque black outline, which the
premultiplied-OVER composite renders as a hard black ring rather than a
shadow. Match the shared label convention (10%-alpha black, em-frac 0.16).

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

The procedural impostor is calibrated to be viewed from outside the disc.
Flying down toward the Sun left it hanging in front of the solar-system
view like a billboard you'd walked behind. Add milkyWayApproachFadeAlpha —
a near-side smoothstep (full beyond ~40 kpc, gone by ~8 kpc, the Sun's
galactocentric radius) — and multiply it into the existing fadeAlpha
channel, mirroring how clusters dim once you're inside their radius. The
far-side milkyWayFadeAlpha is unchanged; the pass now gates and draws on
the product of the two.

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

Galaxy-level labels drew on top of the Sun disc because the foreground
spheres composited into HDR before tone-map, while labels draw post-tone-map
on the swap chain. Move the foreground composite to AFTER the UI overlay:
encodeForegroundPass now only fills the foreground offscreen, and a new
encodeForegroundOver tone-maps it (same curve as the scene, via shared
toneMapDefaults + lib/tonemap.wesl) and OVER-composites it onto the swap
chain, then draws the Sun/Earth captions in the same pass so they land on
top. Galaxy labels / marker-lines / selection rings behind an opaque body
are now truly occluded by it, and a future translucent atmosphere will tint
them rather than hard-mask them. foregroundLabelsPass leaves UI_PASSES (it
moves into the new pass); foregroundComposite retargets to the swap format
and gains the tone-map uniform.

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 0b184eb Commit Preview URL

Branch Preview URL
Jun 30 2026, 12:57 AM

rulkens added a commit that referenced this pull request Jul 3, 2026
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 added a commit that referenced this pull request Jul 6, 2026
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 added a commit that referenced this pull request Jul 6, 2026
…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>
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