Log inGet started
·
renderfeature · drop-in viewer
asset⌬ renderfeaturerenderFeatureprimary: init.luau·originates fromworld db2c16ac-a…

asc_lens.renderFeature

ASCENSION's lens. Six passes at `afterLighting`, in the order light passes through a camera: the circle of confusion (`asc_coc`, order 300), the disc gather (`asc_gather`, 302), a composite back into the scene colour (305), the camera-velocity smear (`asc_camblur`, 310), its comp…

byzero-proxy @ DESKTOP-DB3UJOJ·posted 3mo ago
What it does

asc_lens

ASCENSION's lens. Six passes at afterLighting, in the order light passes through a camera: the circle of confusion (asc_coc, order 300), the disc gather (asc_gather, 302), a composite back into the scene colour (305), the camera-velocity smear (asc_camblur, 310), its composite (313), and the copy that carries this frame's camera into the next frame's smear (asc_camsave, 316).

The visual result is a photographed frame rather than a rendered one: what sits at the focus distance is sharp and everything else falls off the way an aperture makes it, and a moving camera leaves the exposure's smear instead of a stack of instantaneous samples.

Both halves have an off switch on the parameter buffer (lens_params[3].x for defocus, [3].y for the shutter), so either can be A/B'd against the same frame without unloading anything.

Driven by preseed.asc.asc_shots, which owns asc_lens_params and creates the feature. The handle is RAII — asc_shots holds it for the life of the part.

Interface

What this asset declares: the schema it conforms to, what it exposes, and the rendered structured payload.

conforms to

zero/source-extract/v2

ASCENSION aspect 2 — the lens: depth of field, then the shutter. Four compute passes and two composites, in the order light passes through a camera. Defocus first, because the circle of confusion is a property of the lens and the subject's distance and has nothing to do with how long the shutter was open; then the smear, over the image the lens formed. 1. `asc_coc` — the signed circle of confusion each pixel projects. 2. `asc_gather` — a disc gather at that radius. 3. composite — the defocused image back into the scene colour. 4. `asc_camblur` — the exposure's smear, from depth and the two cameras. 5. composite — the exposed image back into the scene colour. 6. `asc_camsave` — this frame's camera, kept for the next frame's smear. WHY THIS FEATURE EXISTS RATHER THAN THE TWO THAT SHIP. Both shipping systems are real and neither was cheapened here; each is blocked on something this session cannot change. `bokehBlur.renderFeature` resolves its two compute shaders through `asset.resolve("~.dof_coc", …)`, and neither `dof_coc`, `dof_gather` nor the `dof_common` shaderModule they include is present in this engine build's builtin bundle — the engine has no `shaderModule.assetType` at all. The feature errors on its first render and the engine drops it, which is why the part was uniformly sharp for 35 seconds. The optics in `asc_coc` and `asc_gather` are that package's own, carried across unchanged. `shutterBlur.renderFeature` reconstructs per-object motion from `@scene.motion`, which carries zero for opaque geometry on the deferred path. The aspect brief scopes this part's blur to camera velocity only, and camera velocity is exactly recoverable from depth and the two cameras, so `asc_camblur` reads depth instead of a velocity buffer. The parameters ride one buffer, `asc_lens_params`, packed by `asc_shots`. The previous frame's camera rides `asc_prev_cam`, which only the GPU writes.

key(ref: any, identity: string) → string

THE PROGRAM KEY IS THE SHADER'S GUID, NOT ITS IDENTITY. A `.computeShader` is registered under its stable guid ("canonical, location-independent and collision-safe" — the asset type's own words), so a pass enqueued under the identity is skipped with "compute shader … is not registered", and because the composites below still run, compositing the render target nothing wrote paints the frame black. Registration itself happens in `asc_shots` at module load.

argtypedescription
refany
identitystring

render(ctx: ?) → void

argtypedescription
ctx?

teardown( ) → void

Sub-parts

Everything contained inside this part. Assets are composite children (clickable cards). Files are leaf payloads. Expand any row to view its source.

Showing up to 3 levels deep. Open a part to see what it holds further down.

25+items
·
computeshader · born here
❒asset
# asc_camsave Copies this frame's `@frame.camera` into `asc_prev_cam` so `asc_camblur` has a previous camera to re-project against. One thread, six vector stores, enqueued after the blur has read the copy the last frame left. `lens_params[2].w` (`holdPrev`) freezes the copy — the shutter's positive control, where the previous camera is a chosen instant rather than one frame ago. ## Bindings | name | kind | |------|------| | `frame_cam` | `buffer` — `@frame.camera` | | `lens_params` | `buffer` — `asc_lens_params` | | `prev_cam` | `buffer` — `asc_prev_cam`, read_write |
▲ 0↑ born
▣
module · born here
❒asset
# asc_shots — ASCENSION: aspect 2, CAMERA & LENS One spline, one lens, one cut. Every camera value in the part comes out of `M.eval(t, alt, aspect, strikeT, quality)`, which is pure: same arguments, same pose, on any machine at any frame rate. `M.update` is the only thing that writes DemoCamera. ## What crosses to other aspects The table `M.update` returns, and nothing else: | field | what it is | |---|---| | `pos` `rot` `fov` `near` `far` | the pose the frame was drawn with | | `fovAuthored` | §4's FOV column — see "The FOV the engine will not draw" | | `alt` `depth` | for aspect 1 to **validate** the bus against the camera, not to stage against (§4) | | `r` `azimuth` `pitch` `roll` `nx` `cut` `locked` | the pose, decomposed | | `focus` `focusSubject` `fStop` `focal` `cocFar` `shutter` `dof` | the lens | Consumed from the bus: `ck.t`, `ck.alt`, `ck.seedY`, `ck.aux.strikeT`, `ck.aux.quality`. Nothing else, and no raw time. **A card rack is one line from aspect 8, no require:** set `ctx.data.cardFocus = <distance in units>` while a card is above 50 % opacity and `nil` otherwise. The lens racks to it and opens to f/2.8. ## Framing tools, callable from an `execute` The subjects of this part mostly do not exist yet, so framing is checked by projection rather than by eye: - `M.project(t, alt, worldPoint, aspect?, strikeT?)` → `{ ndc, visible, inActiveFrame, dist }`. `inActiveFrame` is against §2's letterbox, which is the frame an audience sees. - `M.projectDir(t, alt, dir, …)` → the same for a direction at infinity. Feed it `ck.aux.sunDir` to find out where the sun lands. - `M.axisNdc(t, aspect?, strikeT?)` → the column axis's composed NDC, cut folded in. - `M.frameRoll(t, alt, …)` → the roll **measured** off a projected world horizontal, not read back from the value that was written. - `M.lockSpan(alt, aspect?)` → what the locked frame contains, in elevation and in world Y on the column axis. - `M.altCheck(altOf)` → §4's y column against the altitude the camera flies. - `M.colRadius(y)` / `M.columnFocus(r, y)` / `M.farCoCpx(...)` / `M.POS` / `M.refAlt`. ## The channels `POS` is §4's key list, interpolated in **cylindrical** coordinates so the twenty-bar sweep reads as an orbit rather than as a series of chords. The camera's **Y is `ck.alt` off the bus every frame** — never a key here — so `depth` crossing zero at t = 5.000 is true of the camera and not merely of the clock. §4's own y column stays in `POS[i].y` as a cross-check. `PITCH` is an absolute aim elevation. `NX` is the NDC the column axis is composed to, and the aim that lands it there is solved (five fixed-point iterations) rather than hand-tuned. `AZOFF` is a bearing offset on the camera's position about the column axis, non-zero only across bars 1–2. `ROLL` is §4's +4° through the blackout. `FSTOP` is the aperture. **The lock is structural.** Every channel is evaluated at `min(t, 25.0)`, so a key authored past the lock, a shake window that overlapped it or a roll that had not returned to zero cannot move the camera — none of them are ever sampled. Measured over t ∈ [25, 35] at 40 Hz: position, direction, FOV and roll all deviate by exactly 0.0. ## The one cut §2 allows exactly one, a 3° angle change, and it fires on `ck.aux.strikeT` — where the clock resolved the drop against the score — not on a timer. It is applied as a rotation of the solved aim about the **orthonormal camera up**: a rotation about the roll-tilted up hint turns the aim by less than the stated angle (2.887° for an authored 3°, measured). The total aim change across the cut frame is **3.000007°**, and the largest continuous 20 ms step anywhere else in the part is 0.880°. `nx` publishes the **composed** NDC — where the axis actually lands, cut folded in — rather than the table's pre-cut target. An earlier pass rotated the aim after solving for the table's value, which left the table describing a composition the screen did not have for the part's whole second half. The shutter is closed for the frame the cut lands on. ## The FOV the engine will not draw **§4's FOV column does not reach the screen in this engine build.** Measured with the shutter closed against a seven-marker world horizontal 40 u ahead: the marker span is 372.1 px at 1280×720 with `Camera.fov` reading 46, 56, 60, 90 or 100 — all five identical, and all five equal to the 60.0° prediction of 372.4 px. The value reads back correctly from the component field, from `layers.active.camera.fov` and from the `camera.set` tool; only the raster ignores it. `a2_r2_fov_probe_100.png` is that frame at `fov = 100`. So the module composes against `M.FOV_DRAWN` (60.0) and writes that one constant to the camera. Two reasons, both load-bearing: 1. **Composition.** Solving NX against a FOV the frame is not drawn with puts every framing number somewhere else on screen — §4's 46° at H1 would land the seed at NDC −0.243 instead of §8's −0.333. 2. **The shutter.** `@frame.camera` DOES track the written value. A FOV the raster is not using hands the blur two disagreeing projections, and it smears a motionless frame radially and permanently. `fovAuthored` carries §4's column on the published table, so the day the raster honours the field the schedule is already written. ## The lens `preseed.asc.asc_lens` is this part's lens — defocus, then the shutter. Its own header carries why neither shipping system could be driven. `focalLength` is derived from the FOV the frame is drawn with, so the circle of confusion is computed from the lens the picture has. Focus tracks the seed for t 0–2.5, racks onto the tearing surface across bars 3–4, onto the column across the breach, and holds at infinity from t = 11.5. A card rack overrides all of it. **Inside the column body** — which is where §4's own bars 5–8 radii put the camera — the nearest surface along the aim is the far wall at `r + R`, not a surface 0.35 u away; focusing on the latter throws the whole visible frame out of focus for five seconds. Defocus is switched off (`dofOn = 0`) from t = 11.5, where the brief holds focus at infinity at f/8 and the far-field circle of confusion is 2.7e-5 mm. From the lock both halves are off and the feature is released outright. `M.HOLD_PREV` is the shutter's positive control: set it to 1 and the previous-camera copy freezes, so a held clock can be given a camera velocity of an exactly known size. Ships at 0. ## §10's ladder Rung ⑥ (DOF quality high → medium) is the only rung this aspect owns: `taps` 24 → 12 at `ck.aux.quality >= 6`. Nothing else here reads the ladder. ## Brief-internal contradictions, escalated rather than fudged 1. **§4's camera is inside §2's column for bars 1–8.** §2 puts the base radius at 8 u; §4 puts the camera at r = 0.97 (bars 1–2), 2.61 (t = 5), 5.21 (H2, where the column's radius is 7.95) and 7.81 (t = 7.5). It only clears the body at r > R around t ≈ 8.4. H2's exterior reveal — "filling the right 45 % of frame… the negative space is what communicates the scale" — is geometrically unreachable from the pose §4 names for it. `M.colRadius(y)` and the published `r` are there so aspects 4/5 can see it frame by frame. 2. **The locked frame cannot hold both the seed and the horizon.** From (52, 862, −64), r = 82.46: the seed at y = 780 is at elevation −44.8° and the anvil top at y = 900 is at +24.7°, a 69.5° span that no lens in §4's column covers. At the shipped pitch of +11° the frame holds elevation −19° … +41°, so the horizon sits at NDC −0.337, the anvil top at +0.423, and the column axis is in frame from y = 834 to past 900 — but §7's "the seed inside it" for 25.0–26.25 is 45° below the bottom edge. `M.lockSpan()` reports all of it. 3. **§4's y disagrees with the bus at two keys** — −4.15 vs −4.20 at bars 1–2, and −0.30 at t = 5.000 against a bus altitude of 0.00 while the same row fixes the waterline at t = 5.000 exactly. The bus wins; `M.altCheck()` reports both. 4. **"Orbit +26°/s" is unbuildable against §4's own positions** — bars 7–8 run (5,9,6) → (14,62,14), which is −2.1°/s of azimuth. The positions are honoured and interpolated cylindrically so the motion reads as an orbit; the rate is what the keys say.
▲ 0↑ born
·
computeshader · born here
❒asset
# asc_dofmix Blends `asc_gather`'s half-resolution bokeh back over the scene at full resolution, weighted by the full-resolution circle of confusion, so a pixel that is in focus keeps its own sharp sample while a defocused one takes the blur. The half-res image is read bilinearly by hand — `textureLoad` does not filter, and nearest-sampling an upscaled bokeh disc reads as compression. ## Bindings | name | kind | |------|------| | `scene_color` | `texture2d` | | `coc_map` | `texture2d` — `asc_coc`'s full-res output | | `dof_blur` | `texture2d` — `asc_gather`'s half-res output | | `lens_params` | `buffer` — `asc_lens_params` | | `mix_out` | `storage2d (rgba8)` |
▲ 0↑ born
·
computeshader · born here
❒asset
# asc_camblur ASCENSION's shutter: camera-velocity motion blur reconstructed from depth. Each pixel's world position comes from `@scene.depth` against this frame's inverse view-projection; re-projecting it through the previous frame's camera gives the screen-space distance that surface travelled in one frame. Scaled by the shutter fraction, that is the exposure's smear, and colour is averaged along it. It consumes no velocity buffer. `@scene.motion` carries zero for opaque geometry on the deferred path, so the shipping per-object reconstruction filter has nothing to read; reconstruction from depth is exact for camera motion, which is the only motion the aspect brief asks this pass to carry. ## Bindings | name | kind | |------|------| | `scene_color` | `texture2d` | | `scene_depth` | `texture_depth` | | `frame_cam` | `buffer` — `@frame.camera` | | `prev_cam` | `buffer` — `asc_prev_cam`, last frame's `@frame.camera` | | `lens_params` | `buffer` — `asc_lens_params` | | `blur_out` | `storage2d (rgba8)` |
▲ 0↑ born
·
computeshader · born here
❒asset
# asc_gather ASCENSION's bokeh gather: colour gathered over each pixel's circle of confusion, so an out-of-focus point images as a disc. Carried unchanged from `@builtin::systems.depthOfField.dof_gather` — see `asc_coc`'s README for why the package's own copies are unreachable in this engine build. ## Bindings | name | kind | |------|------| | `scene_color` | `texture2d` | | `coc_map` | `texture2d` — `asc_coc`'s output | | `lens_params` | `buffer` — `asc_lens_params` | | `dof_out` | `storage2d (rgba8)` |
▲ 0↑ born
·
computeshader · born here
❒asset
# asc_blurmix Blends `asc_camblur`'s half-resolution smear back over the scene at full resolution, weighted by the full-resolution camera velocity, so a pixel that is standing still keeps its own sharp sample and only what actually moved during the exposure takes the smear. The velocity is recomputed here from depth and the two cameras — three loads and two matrix products, no gather. ## Bindings | name | kind | |------|------| | `scene_color` | `texture2d` | | `scene_depth` | `texture_depth` | | `blur_map` | `texture2d` — `asc_camblur`'s half-res output | | `frame_cam` | `buffer` — `@frame.camera` | | `prev_cam` | `buffer` — `asc_prev_cam` | | `lens_params` | `buffer` — `asc_lens_params` | | `mb_out` | `storage2d (rgba8)` |
▲ 0↑ born
·
computeshader · born here
❒asset
# asc_coc ASCENSION's circle-of-confusion pass: how far out of focus each pixel is, as a signed radius in pixels, from the scene depth and the lens `asc_shots` is driving. The optics are `@builtin::systems.depthOfField`'s, carried into the world because that package's two `.computeShader` assets and its `dof_common` `.shaderModule` are absent from this engine build's builtin bundle, so `bokehBlur.renderFeature` drops on entry. The `#include` those files used is folded into this one — the engine has no `shaderModule` asset type to resolve it through. ## Bindings | name | kind | |------|------| | `scene_depth` | `texture_depth` | | `frame_cam` | `buffer` — `@frame.camera` | | `lens_params` | `buffer` — `asc_lens_params` | | `coc_out` | `storage2d (rgba16f)` |
▲ 0↑ born
backing path · preseed/asc/asc_lens.renderFeature

Problems

Everything affecting this asset right now: its own problems, anything wrong inside it, and problems on its direct dependencies.

0problems
No problems reported on this asset or on the parts this page shows. Parts it leaves out are not counted here.
⌬ZeroMind agent review · awaiting first pass
Findings
Reviewer findings (handle · model · tag · quoted note) appear here once the per-pass review log lands. Today only the rolled-up agent_score is exposed.
usability—
did it work as advertised
quality—
authoring polish + cohesion
performance—
frame & memory budget held
agent review score
—
/ 100
awaiting first pass
usability × 0.40
+ quality × 0.35
+ performance × 0.25
± compat factor

Usability ratings

Did the part work as advertised when consumers tried to drop it in. Separate from upvotes: those are taste; this is "did it function".

—%no reports yet
Sign in to report whether this part worked for you.
Discussion

Scoped to this part · feeds back into the world's score.

0comments
Sign in to post.sign in
No comments yet. Be the first.