Skip to content

WGSL → WESL conversion + shared shader library (in progress)#36

Closed
rulkens wants to merge 28 commits intomainfrom
my-feature
Closed

WGSL → WESL conversion + shared shader library (in progress)#36
rulkens wants to merge 28 commits intomainfrom
my-feature

Conversation

@rulkens
Copy link
Copy Markdown
Owner

@rulkens rulkens commented May 7, 2026

Status

Draft PR. Tasks 1–4 of the 15-task migration plan are complete; tasks 5–15 are pending. Open early so the foundational tooling + uniform-layout work can be reviewed before the rest builds on it.

Summary

  • Bootstraps wesl-plugin@^0.6.74 (build-time WESL→WGSL linker for Vite) wired in via vite.config.ts and vitest.config.ts. All seven shaders renamed .wgsl.wesl; renderer imports switched from ?raw to ?static.
  • Extracts a lib/ of shared shader modules under src/services/gpu/shaders/lib/:
    • lib/math.wesl — saturate, rot2, sabs, toPolar, toRect, PI/TAU/LOG10
    • lib/camera.weslCameraUniforms (80-byte universal prefix: viewProj + viewportPx + 8 B pad), worldToClip, worldToNdc, worldEyeDepth
  • All six camera-using renderers (filaments, quads, disks, proceduralDisks, milkyWayImpostor, points + pick) now embed cam: CameraUniforms as the prefix of their uniform struct, with renderer-specific fields after the shared block.

What's pending (tasks 5–15)

lib/billboard.wesl, lib/orientation.wesl, lib/colorIndex.wesl, lib/cloudFade.wesl, lib/masks.wesl, lib/astro.wesl, lib/tonemap.wesl, lib/util.wesl, then the uniform vertex/fragment/io file split for every renderer (points gets a special two-fragment-file split for color+pick).

Spec & plan

  • Spec: docs/superpowers/specs/2026-05-07-wesl-conversion-design.md
  • Plan: docs/superpowers/plans/2026-05-07-wesl-conversion.md

Findings baked into the codebase + plan

Three concrete WESL-tooling gotchas surfaced and are documented inline:

  1. WESL parser rejects backticks ` in any comment (// or /* */). Project-wide substitution `' applied to every shader; doesn't affect .ts / .md. Reversible if upstream fixes the parser.
  2. tsconfig types array entry alone doesn't reliably resolve subpath types. src/@types/wesl.d.ts carries a /// <reference types="wesl-plugin/suffixes" /> triple-slash to make *?static resolve to string from any compiler entry.
  3. Vitest doesn't auto-inherit Vite plugins. vitest.config.ts registers wesl-plugin directly; without it the SSR transform pipeline tries to parse .wesl files as JavaScript and rolldown rejects them.

Test plan

  • npm run typecheck green (both src and tools tsconfigs)
  • npm run build green
  • npm test green — 895/895 across 117 test files
  • Visual: every renderer output identical to pre-PR — pan / zoom / rotate, click a galaxy (pickRenderer), tier-swap (cloudFade), tone-map dropdown cycles all five curves
  • Visual: still need a manual sanity check on localhost:5173 (main worktree) vs localhost:5174 (this branch worktree) before un-drafting

Notes

🤖 Generated with Claude Code

rulkens and others added 13 commits May 7, 2026 18:03
Captures the 15-task migration: wesl-plugin tooling bootstrap, lib/
extraction (math/, camera, billboard, orientation, colorIndex, cloudFade,
masks, astro, tonemap, util), and uniform vertex/fragment/io file split
across all 7 renderer shaders.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Bite-sized TDD-style plan for executing the WGSL→WESL refactor: tooling
bootstrap, lib/ extractions (math/, camera, billboard, orientation,
colorIndex, cloudFade, masks, astro, tonemap, util), and uniform
vertex/fragment/io split across all 7 shaders. Also corrects the spec
to use the actual wesl-plugin `?static` suffix and `::` import syntax
(verified against wesl-lang.dev — `?link` would have been wrong).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Adds wesl@^0.7.26 + wesl-plugin@^0.6.74 (build-time WESL→WGSL linker)
wired into Vite via the ?static import suffix. Renames toneMap.wgsl
→ toneMap.wesl as the smoke-test shader; toneMapPass.ts switches
?raw → ?static and gains a dev-mode getCompilationInfo log so we can
map browser shader-compile errors back to the linked WGSL output
(wesl-plugin doesn't yet emit sourcemaps that survive into Chrome's
WGSL diagnostics).

Three real findings from this smoke test, baked into the plan for
later tasks:

1. WESL parser rejects backticks in comments (// or /* */). Stripped
   from toneMap.wesl as a content change; task 2 globally replaces ` →
   ' across all remaining shaders. Mechanically reversible if upstream
   fixes the parser.

2. tsconfig types-array entry "wesl-plugin/suffixes" doesn't reliably
   resolve under moduleResolution=bundler. Required a triple-slash
   reference in src/@types/wesl.d.ts for TS to pick up the *?static
   ambient declarations.

3. Vitest doesn't auto-inherit Vite plugins from vite.config.ts. Added
   wesl-plugin to vitest.config.ts so the SSR transform pipeline can
   handle .wesl imports during tests.

Verification: npm run typecheck (green), npm run build (green), npm
test (880/880 green). Visual sanity check on tone-mapped scene is
pending the user — toneMap.wesl has zero imports so the linker is a
passthrough and output should be byte-identical.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Bulk rename. Each renderer's ?raw import becomes ?static so the WESL
linker runs on every shader. Output WGSL is byte-identical until
imports are introduced in later tasks, save for one mechanical content
change: backticks in shader comments are replaced with single quotes
project-wide because the WESL parser tokenises ` regardless of comment
context. The single-quote replacement preserves the visual intent of
the inline-code callouts and is mechanically reversible if the parser
later loosens up.

Also refreshes pointRenderer's stale module-import docblock — it still
described ?raw + WGSL semantics from before Task 1's wesl-plugin wire-up.

Verification: typecheck green, build green, npm test 880/880 green.
Visual sanity check pending the user — every shader still has zero
imports so the linker output is byte-identical.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…r, constants

Pulls scalar constants (PI/TAU/LOG10) and five small primitives
(saturate, rot2, sabs, toPolar, toRect) out of points.wesl and
milkyWayImpostor.wesl into a shared module. Removes the inline
duplicates and renames GLSL-style 'rot' → 'rot2' to free the bare
'rot' name for a future axis-angle 3D rotation helper.

Two important findings about WESL idioms surfaced while building this
out — both folded back into the spec, plan, and an auto-memory note:

1. The literal package prefix is 'package::', not 'skymap::'. The npm
   package.json name doesn't resolve through wesl-plugin in this
   setup; only the literal token 'package' does. Verified empirically
   by debug-instrumenting findSource in node_modules/wesl/dist/.
2. WESL imports a function FROM a module, treating the last segment
   of the path as the function name. One-function-one-file (the
   pre-execution plan) forces a verbose duplicated leaf in the
   import path ('lib::math::saturate::saturate'). The idiomatic
   WESL shape is one cohesive multi-function module per file, so
   the six original lib/math/<fn>.wesl files were collapsed into a
   single lib/math.wesl with section-divider comments. Each fn
   keeps its own docblock; reading top-to-bottom mirrors the
   previous per-file sequence.

Verification: typecheck green, build green, 880/880 tests green.
Visual sanity check pending the user — the module body is
byte-equivalent to the previously-inline definitions plus the rot →
rot2 rename, so output should be identical.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…+ helpers

Adds a shared module exposing the universal camera-uniform prefix
(viewProj + viewportPx + 8 B alignment pad = 80 B) plus three
helpers: worldToClip, worldToNdc, worldEyeDepth.

The struct is intentionally minimal. The plan's draft included
view, proj, kPerZ, dpr, timeSec, and cameraPos, but inventorying
the existing renderer Uniforms structs showed:

  - No renderer separates view/proj — all use combined viewProj.
  - kPerZ, dpr, timeSec are not in any uniform today.
  - cameraPos placement varies wildly across renderers (points,
    quads, disks, proceduralDisks, milkyWayImpostor each put
    different fields between viewport and the cameraPos slot).
  - filaments has no cameraPos at all.

So CameraUniforms holds only the truly-universal prefix; the
worldEyeDepth helper takes cameraPos as an explicit parameter
rather than reading it off the struct, letting renderers keep
their existing layout for that field.

No consumers yet — this commit is just the module.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Embed the shared 'CameraUniforms' prefix from 'lib/camera.wesl' as the
first 80 bytes of the filaments Uniforms struct, then keep the two
renderer-specific scalars ('halfWidthPx', 'intensityScale') at offsets
80/84 with an 8-byte tail pad. Total uniform size grows from 80 to 96
bytes; the CPU-side uploader writes the scalars at f32-indices 20/21
(was 18/19) and leaves the new reserved pad slots zero.

Replaces the inline 'u.viewProj * vec4<f32>(p, 1.0)' projection with
the shared 'worldToClip' helper. NDC math still uses the local
endpoint clips because the perspective divide's 'w' is reused below
to restore clip space — calling 'worldToNdc' would project twice.

First adopter of the shared camera library; remaining renderers
(quads, disks, proceduralDisks, milkyWayImpostor, points) follow in
subsequent commits per the WESL conversion plan.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The quads uniform layout already matched 'CameraUniforms' byte-for-byte
in its first 80 bytes ('viewProj + viewport + _pad0 + _pad1'), so this
adoption is a pure renaming: 'u.viewProj' becomes 'worldToClip(u.cam, ...)'
and 'u.viewport' becomes 'u.cam.viewportPx'. The renderer-specific
'camPosWorld + pxPerRad' pair stays at offset 80, and the CPU-side
uniform writes at f32-indices 20..23 are unchanged.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The disks uniform layout already matched 'CameraUniforms' byte-for-byte
in its first 80 bytes ('viewProj + viewport + _pad0 + _pad1'), so this
adoption is a pure renaming: 'u.viewProj' becomes 'worldToClip(u.cam, ...)'.
The disks vertex stage doesn't consult 'viewport' or 'camPos' — disk
orientation is an intrinsic, camera-independent galaxy property — so
only 'worldToClip' is imported, matching the restraint shown in the
filaments + quads adoptions. The renderer-specific 'camPos + _pad2'
pair stays at offset 80, and the CPU-side uniform writes at f32-indices
20..23 are unchanged.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Replace the inline viewProj/viewport/_pad0/_pad1 prefix with the shared
CameraUniforms struct from lib/camera.wesl, and route clip-space
projection through the worldToClip helper.  The procedural-disk vertex
stage is intentionally camera-independent (disk orientation is an
intrinsic galaxy property derived from Earth -> galaxy line of sight),
so we import only worldToClip; camPosWorld and pxPerRad remain in the
trailing renderer-specific tail at the same byte offsets, preserving
ABI with proceduralDiskRenderer.ts (no TS-side changes required).

Same prefix-rename pattern as the earlier disks.wesl adoption.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Embed the shared 'CameraUniforms' prefix from 'lib/camera.wesl' as the
first 80 bytes of the milkyWayImpostor Uniforms struct. The previous
layout placed 'fadeAlpha' + 'iTime' at offsets 72/76, which collide
with the '_pad0/_pad1' slots that 'CameraUniforms' reserves — so we
can't drop the shared prefix in without relocating those scalars.

Resolution: put 'cam: CameraUniforms' first (occupies 0..79), then
pack the renderer-specific fields after the cam block. 'cameraPosWorld'
(vec3, 16-byte alignment) lands naturally at offset 80, and the two
f32 scalars 'fadeAlpha' + 'iTime' fall in at 92 / 96. The struct grows
from 96 to 112 bytes (round-up to the next 16-byte multiple).

CPU-side offset changes:
  - fadeAlpha:      f32 index 18 → 23 (offset 72 → 92)
  - iTime:          f32 index 19 → 24 (offset 76 → 96)
  - cameraPosWorld: f32 indices 20..22 unchanged (offset 80, vec3
                    repacks against the f32 immediately after rather
                    than against a dedicated _pad slot)

Replaces the inline 'u.viewProj * vec4<f32>(p, 1.0)' projection in the
vertex stage with the shared 'worldToClip(u.cam, p)' helper. The
fragment-stage references to 'u.cameraPosWorld' / 'u.fadeAlpha' stay as
top-level fields — those are renderer-specific and live outside the
cam block.

Also bumps the UNIFORM_BUFFER_SIZE constant test from 96 to 112 to
match the new layout.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Embed the shared 'CameraUniforms' prefix from 'lib/camera.wesl' as the
first 80 bytes of the points 'Uniforms' struct.  The pre-refactor
layout placed 'pointSizePx' + 'brightness' at offsets 72/76, which
collide with the '_pad0/_pad1' slots that 'CameraUniforms' reserves —
so we can't drop the shared prefix in without relocating those scalars.

Resolution: put 'cam: CameraUniforms' first (occupies 0..79), then
swap 'pointSizePx' + 'brightness' into the 8 bytes of slack that the
old layout already had at offsets 88..95 (the '_pad0/_pad1' u32s
required for vec3-alignment before 'camPosWorld').  Same total size
(176 bytes), same alignment, every field from offset 96 onward
unchanged.  Replaces the inline 'u.viewProj * vec4(p, 1.0)'
projection with the shared 'worldToClip(u.cam, p)' helper and
'u.viewport' references with 'u.cam.viewportPx'.

CPU-side offset changes:
  - pointSizePx: f32 index 18 -> 22 (offset 72 -> 88)
  - brightness:  f32 index 19 -> 23 (offset 76 -> 92)
  - selectedIndex: UNCHANGED at offset 80
  - camPosWorld + pxPerRad + everything after: UNCHANGED

pickRenderer.ts DID need updating: its mid-frame
'POINT_SIZE_OFFSET' write moves from 72 to 88.  The
'SELECTED_INDEX_OFFSET = 80' write stays put, because
'CameraUniforms' is exactly 80 bytes and 'selectedIndex' is the
first renderer-specific field — the same byte address it occupied
before the refactor.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Vitest sets `import.meta.env.DEV = true` by default, so the dev-mode
WGSL logger added in the wesl-plugin bootstrap commit fires inside
postProcess.test.ts. The previous shader-module mock returned a bare
`{}`, so calling `module.getCompilationInfo()` blew up with a
TypeError. Add a no-op mock that resolves with an empty messages
array — exactly the shape the production logger pattern-matches.

This is the only mock site that needed updating; other GPU module
tests (pickRenderer, pointRenderer, ...) don't take this code path
because their shader-module call sites don't yet wire the logger.
That changes when the rest of the renderers gain `?static` imports
(later WESL conversion tasks); we'll bring their mocks up to the
same shape at that point.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@cloudflare-workers-and-pages
Copy link
Copy Markdown

cloudflare-workers-and-pages Bot commented May 7, 2026

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 ad086b2 Commit Preview URL

Branch Preview URL
May 07 2026, 10:02 PM

rulkens and others added 15 commits May 7, 2026 18:34
Regression: at any zoom where fadeAlpha rose above zero, the milkyWay
pipeline was invalidated and the whole frame went black. Root cause:
wesl-plugin@0.6.74's linker only resolves `import` statements that
appear at the top of the shader. The four `lib/math` imports were
sitting next to the call sites near line 392 (idiomatic in
TypeScript / Rust, but wrong here). The linker emitted the source
verbatim, the `import` keyword reached Chrome's WGSL parser, and
the shader module compile failed silently — surfacing as
"Invalid RenderPipeline (unlabeled)" only on the first frame the
renderer actually used the impostor.

Fix is mechanical: move the four `import package::lib::math::*`
lines up alongside the existing `lib::camera` import at the top of
the file. Production bundle now contains inlined `fn rot2`, `fn sabs`,
`fn toPolar`, `fn toRect` (verified by grep).

This is the same constraint that broke brace-list imports earlier
in this PR — wesl-plugin's parser is stricter than the WESL spec
suggests. Revisit when wesl-plugin gets bumped.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Adds `label` properties to every createShaderModule, createRenderPipeline,
createBindGroup, createBindGroupLayout, createPipelineLayout, createBuffer,
createTexture, and createSampler call across the renderer (41 new labels
across 11 files). Routes all shader-module creation through the new
src/services/gpu/shaderCompileLogger.ts helper so compile errors auto-dump
the linked WGSL alongside the labelled module name.

Pure metadata — zero behavioral change. Browser-console errors that
previously said "(unlabeled)" now name the offending resource, which
materially shortened the round-trip on the milkyWay zoom-regression
debug earlier in this branch (the upstream "Invalid ShaderModule" error
chain was much harder to trace without a label naming the failing module).

Test mocks (pointRenderer.test.ts, pickRenderer.test.ts, postProcess.test.ts)
gain a getCompilationInfo no-op shim so the helper's dev-mode logger path
runs cleanly under Vitest's default import.meta.env.DEV=true.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
… helpers

Standalone lib file with no consumers yet. Exports three helpers extracted from
the duplicated billboard-expansion patterns across points/quads/disks/
proceduralDisks:

- quadCorner(vid: u32) -> vec2<f32>: 6-vertex triangle-list corner offset
  in [-1,+1]², replacing the per-shader CORNERS constant array.
- quadUv(vid: u32) -> vec2<f32>: same as (quadCorner+1)*0.5, returning the
  unit-square UV in [0,1]².
- expandBillboardScreen(cam, centerClip, sizePx, corner) -> vec2<f32>: clip-XY
  offset for a screen-pixel-sized, screen-aligned billboard. Used by points.

Notable scope decisions captured in the file's docblock:

- The plan's draft 'expandBillboardWorld' helper is omitted. None of the four
  candidate renderers actually wants a generic view-aligned world basis —
  quads uses a celestial-north basis, disks/proceduralDisks use orientation-
  driven (PA + inclination) bases that belong in lib/orientation (Task 6),
  and points is screen-aligned. Adding an unused helper would also force a
  'view' matrix into CameraUniforms that no renderer currently needs.

- The 6-vertex corner ordering follows the (BL, BR, TR, BL, TR, TL) pattern
  used by 3 of 4 callers; points will be migrated to match in a follow-up
  commit. Both orderings produce identical fragment coverage and identical
  interpolated UVs at every pixel inside the square (CCW triangulations of
  the same convex region), so the migration is safe.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Replace the inline 6-vec CORNERS const + '(corner + 1) * 0.5' UV
remap with calls to 'lib/billboard::quadCorner' and 'quadUv'. The
view-aligned celestial-north basis math (NORTH_WORLD / upClip / upPx)
stays renderer-specific. Also split the previously brace-listed
camera import into one-per-line per the wesl-plugin gotcha.

No TS-side changes; corner ordering matches the lib (BL, BR, TR,
BL, TR, TL).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Replace the inline 6-vec CORNERS const + '(corner + 1) * 0.5' UV
remap with calls to 'lib/billboard::quadCorner' and 'quadUv'. The
orientation-aligned disk-plane basis (PA + inclination → 'major' /
'minor_3d' in 3D world space) stays renderer-specific and untouched
— that math is camera-independent and belongs to Task 6's
'lib/orientation.wesl' rather than the screen-space billboard lib.
Also split the previously brace-listed camera import into one-per-
line per the wesl-plugin gotcha.

No TS-side changes; corner ordering matches the lib (BL, BR, TR,
BL, TR, TL).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Mirror of the disks.wesl sub-commit: replace the inline 6-vec
CORNERS const with a call to 'lib/billboard::quadCorner'. The
orientation-aligned disk-plane basis (PA + inclination →
'majorAxis' / 'minorAxis' in 3D world space) stays renderer-
specific and untouched — that math is camera-independent and
belongs to Task 6's 'lib/orientation.wesl' rather than the
screen-space billboard lib. Also split the previously brace-
listed camera import into one-per-line per the wesl-plugin
gotcha.

Unlike disks.wesl, this pass does NOT import 'quadUv': the
fragment uses the raw [-1, +1]² corner directly as the radial
coordinate ('length(in.uv)' for the bulge + disk profile), so
the [0, 1]² remap that 'quadUv' performs would be wrong here.
The 'out.uv = corner' forwarding is unchanged.

No TS-side changes; corner ordering matches the lib (BL, BR,
TR, BL, TR, TL).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Replaces the inline QUAD const-array lookup and screen-space pixel-to-
clip expansion with the shared 'quadCorner' and 'expandBillboardScreen'
helpers from lib/billboard.wesl. Migrates from the points-specific
(BL, BR, TL, TL, BR, TR) corner ordering to the (BL, BR, TR, BL, TR, TL)
ordering used by quads/disks/proceduralDisks; both are CCW triangulations
of the same square and the points pipeline runs with the default
cullMode: 'none', so output is byte-identical. Per-instance sizeScale
(selection halo) is post-multiplied onto the helper's returned offset.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…is math

Standalone introduction of the disk-plane axis lib — no consumers
adopt it yet, so output WGSL is unchanged for existing renderers.
Both 'disks.wesl' and 'proceduralDisks.wesl' will adopt 'diskAxes' in
follow-up commits.

API: 'diskAxes(posWS, paRad, cosI, sinI) -> DiskAxes { major, minor }'.
Camera-independent by design: orientation is intrinsic to the galaxy
in 3D space (see disks.wesl's long header for why this is load-bearing).

Standardises the pole-degeneracy threshold on disks.wesl's wider ~8°
form ('abs(dot(northPole, losDir)) > 0.99') rather than
proceduralDisks.wesl's narrower ~exact-pole form ('length < 1e-4'). The
wider form wins because it eliminates a basis disagreement that could
otherwise show as an abrupt rotation at the crossfade boundary between
the procedural impostor and the textured thumbnail for any galaxy
within 8° of the celestial pole.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Replace the inline los/north/east/major/minor derivation in disks.wesl's
vertex stage with a call to 'diskAxes' from 'lib/orientation.wesl'.
Byte-equivalent for disks: the lib standardised on the wider
'|dot(north, los)| > 0.99' (~8°) pole-fallback threshold that disks
already used, so no near-pole behaviour changes. The axisRatio 0.05
clamp + (cosI, sinI) trig pair stay at the call site by design — the
lib intentionally takes pre-clamped trig inputs.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Switches from the wider disks form ('abs(dot(northPole, losDir)) >
0.99', ~8° from pole, swap seed BEFORE projection) to the tighter
proceduralDisks form ('length(northTangentRaw) < 1e-4', ~exactly at
pole, swap result AFTER projection). Most galaxies within 8° of the
celestial pole still produce a usable in-sky north tangent — float
math near the pole is fine until length(seed - dot(seed, los) * los)
genuinely underflows, which only happens when |dot| is essentially 1.

Net effect: PA fidelity preserved for ~8° of sky around each pole.
Both renderers still agree at the (much narrower) genuine-degeneracy
region where the fallback fires.

This is technically a behavior change for disks (a few catalog
galaxies very close to the pole no longer take the early world-Y
fallback), but the new path produces the SAME result the
proceduralDisks impostor was already producing for those galaxies —
so cross-pass consistency improves.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Replace the inline los/north/east/major/minor derivation in
proceduralDisks.wesl's vertex stage with a call to 'diskAxes' from
'lib/orientation.wesl'. Byte-equivalent for proceduralDisks: the lib
standardised on the tight 'northLen < 1e-4' post-projection pole
fallback that this renderer already used, so no near-pole behaviour
changes. The axisRatio 0.05 clamp + (cosI, sinI) trig pair stay at the
call site by design — the lib intentionally takes pre-clamped trig
inputs. Final sub-commit of task 6's orientation-lib adoption.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Collapses duplicate fn ramp(t) between points.wesl and proceduralDisks.wesl.
Both renderers now share a single color-index → RGB mapping.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Adds applyCloudFade(color, opacity) helper used by points.wesl and
filaments.wesl. The CloudUniforms struct itself stays per-renderer:
points carries a sourceCode field for pick-identity packing that
filaments has no equivalent for, so a shared struct would be a
fictional unification.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Both renderers now import the shared struct. Filaments doesn't read
sourceCode today, but the CPU-side CloudFade class already produces
that exact 16-byte layout for both, so a divergent shader struct was
a fictional separation.

Also collapses the helper to a scalar applyCloudFade(alpha, opacity)
to undo the vec4 ceremony at the call sites — both sites multiply
opacity into a scalar alpha alongside other modulators.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Collapses three recurring smoothstep mask shapes (circular cutoff,
luminance gate, axis edge-band) into named helpers. Naming the shape
makes intent visible at the call site and removes per-renderer copies
of the same three patterns.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@rulkens
Copy link
Copy Markdown
Owner Author

rulkens commented May 7, 2026

Renamed branch my-featurefeat/wesl-conversion to better reflect the work. Continuing in #39.

@rulkens rulkens closed this May 7, 2026
@rulkens rulkens deleted the my-feature branch May 7, 2026 22:09
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