@woosh/meep-engine 3.14.6 → 3.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (154) hide show
  1. package/package.json +1 -1
  2. package/src/engine/network/orchestrator/ServerAuthoritativeClient.d.ts +48 -0
  3. package/src/engine/network/orchestrator/ServerAuthoritativeClient.d.ts.map +1 -1
  4. package/src/engine/network/orchestrator/ServerAuthoritativeClient.js +246 -3
  5. package/src/shade/RENDERER_CONTRACT.md +584 -580
  6. package/src/shade/playground/avboit/README.md +117 -0
  7. package/src/shade/playground/avboit/index.html +215 -0
  8. package/src/shade/playground/avboit/main.d.ts +2 -0
  9. package/src/shade/playground/avboit/main.d.ts.map +1 -0
  10. package/src/shade/playground/avboit/main.js +875 -0
  11. package/src/shade/renderer/Renderer.d.ts +16 -0
  12. package/src/shade/renderer/Renderer.d.ts.map +1 -1
  13. package/src/shade/renderer/Renderer.js +29 -28
  14. package/src/shade/renderer/ShadeTransparencyMode.d.ts +13 -0
  15. package/src/shade/renderer/ShadeTransparencyMode.d.ts.map +1 -0
  16. package/src/shade/renderer/ShadeTransparencyMode.js +30 -0
  17. package/src/shade/renderer/path_tracer/accumulating/AccumulatingPathTracer.d.ts +9 -0
  18. package/src/shade/renderer/path_tracer/accumulating/AccumulatingPathTracer.d.ts.map +1 -1
  19. package/src/shade/renderer/path_tracer/accumulating/AccumulatingPathTracer.js +26 -0
  20. package/src/shade/renderer/rasterize/bucket/prepare_meshlet_draw_commands_by_material.d.ts +7 -2
  21. package/src/shade/renderer/rasterize/bucket/prepare_meshlet_draw_commands_by_material.d.ts.map +1 -1
  22. package/src/shade/renderer/rasterize/bucket/prepare_meshlet_draw_commands_by_material.js +12 -1
  23. package/src/shade/renderer/rasterize/expand/shader_filter_triangle_by_material_side.js +1 -1
  24. package/src/shade/renderer/rasterize/forward/FORWARD_VERTEX_SHADER_STRUCT.d.ts +3 -0
  25. package/src/shade/renderer/rasterize/forward/FORWARD_VERTEX_SHADER_STRUCT.d.ts.map +1 -0
  26. package/src/shade/renderer/rasterize/{native/oit → forward}/FORWARD_VERTEX_SHADER_STRUCT.js +4 -4
  27. package/src/shade/renderer/rasterize/forward/SHADING_SCENE_RESOURCE_GROUP.d.ts +3 -0
  28. package/src/shade/renderer/rasterize/forward/SHADING_SCENE_RESOURCE_GROUP.d.ts.map +1 -0
  29. package/src/shade/renderer/rasterize/forward/SHADING_SCENE_RESOURCE_GROUP.js +17 -0
  30. package/src/shade/renderer/rasterize/{native/oit → forward}/chunk_forward_shade_standard_fragment_brick4.d.ts +1 -1
  31. package/src/shade/renderer/rasterize/forward/chunk_forward_shade_standard_fragment_brick4.d.ts.map +1 -0
  32. package/src/shade/renderer/rasterize/{native/oit → forward}/chunk_forward_shade_standard_fragment_brick4.js +16 -16
  33. package/src/shade/renderer/rasterize/{native/oit → forward}/chunk_forward_shade_standard_fragment_ibl.d.ts +1 -1
  34. package/src/shade/renderer/rasterize/forward/chunk_forward_shade_standard_fragment_ibl.d.ts.map +1 -0
  35. package/src/shade/renderer/rasterize/{native/oit → forward}/chunk_forward_shade_standard_fragment_ibl.js +17 -17
  36. package/src/shade/renderer/rasterize/{native/oit → forward}/chunk_material_side_should_draw.d.ts +1 -1
  37. package/src/shade/renderer/rasterize/forward/chunk_material_side_should_draw.d.ts.map +1 -0
  38. package/src/shade/renderer/rasterize/{native/oit → forward}/chunk_material_side_should_draw.js +2 -2
  39. package/src/shade/renderer/rasterize/forward/vertex_shader_chunk.d.ts +3 -0
  40. package/src/shade/renderer/rasterize/forward/vertex_shader_chunk.d.ts.map +1 -0
  41. package/src/shade/renderer/rasterize/{native/oit → forward}/vertex_shader_chunk.js +4 -4
  42. package/src/shade/renderer/rasterize/native/avboit/AVBOITSettings.d.ts +27 -0
  43. package/src/shade/renderer/rasterize/native/avboit/AVBOITSettings.d.ts.map +1 -0
  44. package/src/shade/renderer/rasterize/native/avboit/AVBOITSettings.js +27 -0
  45. package/src/shade/renderer/rasterize/native/avboit/AVBOIT_CONSTANTS.d.ts +107 -0
  46. package/src/shade/renderer/rasterize/native/avboit/AVBOIT_CONSTANTS.d.ts.map +1 -0
  47. package/src/shade/renderer/rasterize/native/avboit/AVBOIT_CONSTANTS.js +120 -0
  48. package/src/shade/renderer/rasterize/native/avboit/AVBOIT_LEDGER.md +463 -0
  49. package/src/shade/renderer/rasterize/native/avboit/AVBOIT_PLAN_2026_09_03.md +509 -0
  50. package/src/shade/renderer/rasterize/native/avboit/AVBOIT_WARP_STRUCT.d.ts +15 -0
  51. package/src/shade/renderer/rasterize/native/avboit/AVBOIT_WARP_STRUCT.d.ts.map +1 -0
  52. package/src/shade/renderer/rasterize/native/avboit/AVBOIT_WARP_STRUCT.js +33 -0
  53. package/src/shade/renderer/rasterize/native/avboit/avboit_build_depth_curve.d.ts +20 -0
  54. package/src/shade/renderer/rasterize/native/avboit/avboit_build_depth_curve.d.ts.map +1 -0
  55. package/src/shade/renderer/rasterize/native/avboit/avboit_build_depth_curve.js +38 -0
  56. package/src/shade/renderer/rasterize/native/avboit/avboit_draw_fragment_body.d.ts +38 -0
  57. package/src/shade/renderer/rasterize/native/avboit/avboit_draw_fragment_body.d.ts.map +1 -0
  58. package/src/shade/renderer/rasterize/native/avboit/avboit_draw_fragment_body.js +130 -0
  59. package/src/shade/renderer/rasterize/native/avboit/avboit_spec_support.d.ts +37 -0
  60. package/src/shade/renderer/rasterize/native/avboit/avboit_spec_support.d.ts.map +1 -0
  61. package/src/shade/renderer/rasterize/native/avboit/avboit_spec_support.js +73 -0
  62. package/src/shade/renderer/rasterize/native/avboit/chunk_avboit_counts_to_extinction.d.ts +9 -0
  63. package/src/shade/renderer/rasterize/native/avboit/chunk_avboit_counts_to_extinction.d.ts.map +1 -0
  64. package/src/shade/renderer/rasterize/native/avboit/chunk_avboit_counts_to_extinction.js +17 -0
  65. package/src/shade/renderer/rasterize/native/avboit/chunk_avboit_dilate_word.d.ts +11 -0
  66. package/src/shade/renderer/rasterize/native/avboit/chunk_avboit_dilate_word.d.ts.map +1 -0
  67. package/src/shade/renderer/rasterize/native/avboit/chunk_avboit_dilate_word.js +18 -0
  68. package/src/shade/renderer/rasterize/native/avboit/chunk_avboit_extinction_to_counts.d.ts +9 -0
  69. package/src/shade/renderer/rasterize/native/avboit/chunk_avboit_extinction_to_counts.d.ts.map +1 -0
  70. package/src/shade/renderer/rasterize/native/avboit/chunk_avboit_extinction_to_counts.js +17 -0
  71. package/src/shade/renderer/rasterize/native/avboit/chunk_avboit_extinction_to_transmittance.d.ts +9 -0
  72. package/src/shade/renderer/rasterize/native/avboit/chunk_avboit_extinction_to_transmittance.d.ts.map +1 -0
  73. package/src/shade/renderer/rasterize/native/avboit/chunk_avboit_extinction_to_transmittance.js +17 -0
  74. package/src/shade/renderer/rasterize/native/avboit/chunk_avboit_fold_pairs.d.ts +15 -0
  75. package/src/shade/renderer/rasterize/native/avboit/chunk_avboit_fold_pairs.d.ts.map +1 -0
  76. package/src/shade/renderer/rasterize/native/avboit/chunk_avboit_fold_pairs.js +25 -0
  77. package/src/shade/renderer/rasterize/native/avboit/chunk_avboit_integral_depth_coordinate.d.ts +17 -0
  78. package/src/shade/renderer/rasterize/native/avboit/chunk_avboit_integral_depth_coordinate.d.ts.map +1 -0
  79. package/src/shade/renderer/rasterize/native/avboit/chunk_avboit_integral_depth_coordinate.js +35 -0
  80. package/src/shade/renderer/rasterize/native/avboit/chunk_avboit_occupancy_range.d.ts +11 -0
  81. package/src/shade/renderer/rasterize/native/avboit/chunk_avboit_occupancy_range.d.ts.map +1 -0
  82. package/src/shade/renderer/rasterize/native/avboit/chunk_avboit_occupancy_range.js +21 -0
  83. package/src/shade/renderer/rasterize/native/avboit/chunk_avboit_occupancy_word_mask.d.ts +9 -0
  84. package/src/shade/renderer/rasterize/native/avboit/chunk_avboit_occupancy_word_mask.d.ts.map +1 -0
  85. package/src/shade/renderer/rasterize/native/avboit/chunk_avboit_occupancy_word_mask.js +26 -0
  86. package/src/shade/renderer/rasterize/native/avboit/chunk_avboit_overflows.d.ts +11 -0
  87. package/src/shade/renderer/rasterize/native/avboit/chunk_avboit_overflows.d.ts.map +1 -0
  88. package/src/shade/renderer/rasterize/native/avboit/chunk_avboit_overflows.js +22 -0
  89. package/src/shade/renderer/rasterize/native/avboit/chunk_avboit_pack_counts.d.ts +10 -0
  90. package/src/shade/renderer/rasterize/native/avboit/chunk_avboit_pack_counts.d.ts.map +1 -0
  91. package/src/shade/renderer/rasterize/native/avboit/chunk_avboit_pack_counts.js +21 -0
  92. package/src/shade/renderer/rasterize/native/avboit/chunk_avboit_sample_transmittance.d.ts +10 -0
  93. package/src/shade/renderer/rasterize/native/avboit/chunk_avboit_sample_transmittance.d.ts.map +1 -0
  94. package/src/shade/renderer/rasterize/native/avboit/chunk_avboit_sample_transmittance.js +32 -0
  95. package/src/shade/renderer/rasterize/native/avboit/chunk_avboit_transmission_tint.d.ts +20 -0
  96. package/src/shade/renderer/rasterize/native/avboit/chunk_avboit_transmission_tint.d.ts.map +1 -0
  97. package/src/shade/renderer/rasterize/native/avboit/chunk_avboit_transmission_tint.js +27 -0
  98. package/src/shade/renderer/rasterize/native/avboit/chunk_avboit_transmittance_to_extinction.d.ts +10 -0
  99. package/src/shade/renderer/rasterize/native/avboit/chunk_avboit_transmittance_to_extinction.d.ts.map +1 -0
  100. package/src/shade/renderer/rasterize/native/avboit/chunk_avboit_transmittance_to_extinction.js +18 -0
  101. package/src/shade/renderer/rasterize/native/avboit/chunk_avboit_unpack_counts.d.ts +9 -0
  102. package/src/shade/renderer/rasterize/native/avboit/chunk_avboit_unpack_counts.d.ts.map +1 -0
  103. package/src/shade/renderer/rasterize/native/avboit/chunk_avboit_unpack_counts.js +21 -0
  104. package/src/shade/renderer/rasterize/native/avboit/chunk_avboit_view_depth_to_physical.d.ts +9 -0
  105. package/src/shade/renderer/rasterize/native/avboit/chunk_avboit_view_depth_to_physical.d.ts.map +1 -0
  106. package/src/shade/renderer/rasterize/native/avboit/chunk_avboit_view_depth_to_physical.js +23 -0
  107. package/src/shade/renderer/rasterize/native/avboit/chunk_avboit_view_depth_to_virtual.d.ts +13 -0
  108. package/src/shade/renderer/rasterize/native/avboit/chunk_avboit_view_depth_to_virtual.d.ts.map +1 -0
  109. package/src/shade/renderer/rasterize/native/avboit/chunk_avboit_view_depth_to_virtual.js +27 -0
  110. package/src/shade/renderer/rasterize/native/avboit/chunk_avboit_warp.d.ts +22 -0
  111. package/src/shade/renderer/rasterize/native/avboit/chunk_avboit_warp.d.ts.map +1 -0
  112. package/src/shade/renderer/rasterize/native/avboit/chunk_avboit_warp.js +38 -0
  113. package/src/shade/renderer/rasterize/native/avboit/graph_rasterize_meshes_transparent_avboit.d.ts +117 -0
  114. package/src/shade/renderer/rasterize/native/avboit/graph_rasterize_meshes_transparent_avboit.d.ts.map +1 -0
  115. package/src/shade/renderer/rasterize/native/avboit/graph_rasterize_meshes_transparent_avboit.js +935 -0
  116. package/src/shade/renderer/rasterize/native/avboit/shader_avboit_build_warp.d.ts +8 -0
  117. package/src/shade/renderer/rasterize/native/avboit/shader_avboit_build_warp.d.ts.map +1 -0
  118. package/src/shade/renderer/rasterize/native/avboit/shader_avboit_build_warp.js +135 -0
  119. package/src/shade/renderer/rasterize/native/avboit/shader_avboit_draw_brick4.d.ts +3 -0
  120. package/src/shade/renderer/rasterize/native/avboit/shader_avboit_draw_brick4.d.ts.map +1 -0
  121. package/src/shade/renderer/rasterize/native/avboit/shader_avboit_draw_brick4.js +101 -0
  122. package/src/shade/renderer/rasterize/native/avboit/shader_avboit_draw_ibl.d.ts +3 -0
  123. package/src/shade/renderer/rasterize/native/avboit/shader_avboit_draw_ibl.d.ts.map +1 -0
  124. package/src/shade/renderer/rasterize/native/avboit/shader_avboit_draw_ibl.js +96 -0
  125. package/src/shade/renderer/rasterize/native/avboit/shader_avboit_integrate.d.ts +3 -0
  126. package/src/shade/renderer/rasterize/native/avboit/shader_avboit_integrate.d.ts.map +1 -0
  127. package/src/shade/renderer/rasterize/native/avboit/shader_avboit_integrate.js +83 -0
  128. package/src/shade/renderer/rasterize/native/avboit/shader_avboit_occupancy.d.ts +9 -0
  129. package/src/shade/renderer/rasterize/native/avboit/shader_avboit_occupancy.d.ts.map +1 -0
  130. package/src/shade/renderer/rasterize/native/avboit/shader_avboit_occupancy.js +119 -0
  131. package/src/shade/renderer/rasterize/native/avboit/shader_avboit_resolve.d.ts +3 -0
  132. package/src/shade/renderer/rasterize/native/avboit/shader_avboit_resolve.d.ts.map +1 -0
  133. package/src/shade/renderer/rasterize/native/avboit/shader_avboit_resolve.js +81 -0
  134. package/src/shade/renderer/rasterize/native/avboit/shader_avboit_splat.d.ts +15 -0
  135. package/src/shade/renderer/rasterize/native/avboit/shader_avboit_splat.d.ts.map +1 -0
  136. package/src/shade/renderer/rasterize/native/avboit/shader_avboit_splat.js +237 -0
  137. package/src/shade/renderer/rasterize/native/material_draw_indirect.d.ts +22 -0
  138. package/src/shade/renderer/rasterize/native/material_draw_indirect.d.ts.map +1 -0
  139. package/src/shade/renderer/rasterize/native/material_draw_indirect.js +69 -0
  140. package/src/shade/renderer/rasterize/native/oit/graph_rasterize_meshes_transparent_oit.d.ts.map +1 -1
  141. package/src/shade/renderer/rasterize/native/oit/graph_rasterize_meshes_transparent_oit.js +665 -722
  142. package/src/shade/renderer/rasterize/native/oit/shader_oit_generate_moments.js +3 -3
  143. package/src/shade/renderer/rasterize/native/oit/shader_oit_resolve_moments.js +4 -4
  144. package/src/shade/renderer/rasterize/native/oit/shader_oit_resolve_moments_ibl.js +4 -4
  145. package/src/shade/renderer/rasterize/native/oit/FORWARD_VERTEX_SHADER_STRUCT.d.ts +0 -3
  146. package/src/shade/renderer/rasterize/native/oit/FORWARD_VERTEX_SHADER_STRUCT.d.ts.map +0 -1
  147. package/src/shade/renderer/rasterize/native/oit/SHADING_SCENE_RESOURCE_GROUP.d.ts +0 -3
  148. package/src/shade/renderer/rasterize/native/oit/SHADING_SCENE_RESOURCE_GROUP.d.ts.map +0 -1
  149. package/src/shade/renderer/rasterize/native/oit/SHADING_SCENE_RESOURCE_GROUP.js +0 -17
  150. package/src/shade/renderer/rasterize/native/oit/chunk_forward_shade_standard_fragment_brick4.d.ts.map +0 -1
  151. package/src/shade/renderer/rasterize/native/oit/chunk_forward_shade_standard_fragment_ibl.d.ts.map +0 -1
  152. package/src/shade/renderer/rasterize/native/oit/chunk_material_side_should_draw.d.ts.map +0 -1
  153. package/src/shade/renderer/rasterize/native/oit/vertex_shader_chunk.d.ts +0 -3
  154. package/src/shade/renderer/rasterize/native/oit/vertex_shader_chunk.d.ts.map +0 -1
@@ -0,0 +1,463 @@
1
+ # AVBOIT — ledger
2
+
3
+ Companion to [`AVBOIT_PLAN_2026_09_03.md`](./AVBOIT_PLAN_2026_09_03.md). One entry per step of §8, in the
4
+ order they landed, each recording what was built, how it was verified, and what the step found that the plan
5
+ did not predict. Findings that change the plan are noted here and the plan's revision header is bumped, never
6
+ silently edited.
7
+
8
+ Branch: `claude/avboit-transparency-pipeline-192cff`, worktree
9
+ `.claude/worktrees/avboit-transparency-pipeline-192cff`, from `9ab84a154`.
10
+
11
+ ---
12
+
13
+ ## 0. Survey — 2026-09-03
14
+
15
+ **Done.** Read the deck (text extraction via `pdftotext`; the page renderer is not installed here) and the
16
+ MBOIT pipeline end to end: orchestrator, three shaders, frame-graph API, resource descriptors, the bucket →
17
+ draw-command prep, the emulator's capabilities, the software device's recording API, and the `Renderer` call
18
+ site and mode-switch precedents.
19
+
20
+ **Findings that shaped the plan:**
21
+
22
+ - The sorted meshlet buffer the draws consume has no count header
23
+ (`shader_sort_meshlets_by_material.js:20` writes a bare `array<MeshletDefinition>`); the collection *before*
24
+ sorting does, and the prep helper already builds an indirect dispatch command over it. The occupancy pass
25
+ needs both, so the helper returns two more handles (plan §3.1).
26
+ - No render pipeline in the tree writes a storage buffer from the fragment stage, and the pipeline-layout
27
+ helper applies one visibility to all groups. The splat pass builds its layout per group (plan §3.11).
28
+ - The emulator models `atomic<T>` as `{ value }` objects and implements `atomicAdd/Min/Max/Or`, `textureStore`
29
+ on 3D `CPUBitmapData`, `extractBits/insertBits`, `countOneBits`; barriers are no-ops and there is no
30
+ workgroup-cooperative dispatch. The warp build is therefore a single invocation so it stays
31
+ emulator-testable (plan §3.4).
32
+ - View-space depth sign: the volumetrics take `-(view_matrix × p).z`, so the camera looks down −Z; the
33
+ occupancy pass uses the same (plan §3.3).
34
+ - `float32-blendable` is on the device floor purely for MBOIT's `r32float`/`rgba32float` targets; AVBOIT
35
+ blends `rgba16float` only (plan §3.11).
36
+ - The in-app browser pane cannot run the full renderer (immediate data unsupported there, per memory), so the
37
+ playground look (step 9) needs a real Chrome tab; module compilation (step 8) works in either.
38
+
39
+ ---
40
+
41
+ ## 1. Skeleton and switch — 2026-09-03
42
+
43
+ **Done.** `ShadeTransparencyMode` (`MBOIT = 0`, `AVBOIT = 1`), `Renderer.transparency_mode` defaulting to
44
+ MBOIT, a ternary at the single transparency call site selecting the recorder, and
45
+ `graph_prepare_meshlet_draw_commands_by_material` returning `meshlet_collection` and
46
+ `meshlet_iterator_command` beside what it returned before. `Renderer.js` and the prep helper are CRLF files;
47
+ edited byte-exactly.
48
+
49
+ **Verified.** The rasterize spec tree (14 files, 81 tests) passes with the helper change; the source-level spec
50
+ in `graph_rasterize_meshes_transparent_avboit.spec.js` pins the import, the default and the ternary.
51
+
52
+ **Found.** Nothing the plan did not predict.
53
+
54
+ ---
55
+
56
+ ## 2. Depth curve, packing, warp — 2026-09-03
57
+
58
+ **Done.** `chunk_avboit_depth_curve` (t(z), z(t)), `chunk_avboit_pack` (10|10|10, counts ↔ extinction ↔
59
+ transmittance, the overflow predicate), `AVBOIT_WARP_STRUCT`, `chunk_avboit_warp` (φ, and the view-depth →
60
+ virtual → physical composition), each with an emulator spec.
61
+
62
+ **Found.**
63
+
64
+ - The emulator hands a `u32` with its top bit set back as a negative JS number (`0xFFFFFF00` reads as
65
+ `-256`). Specs compare through `>>> 0`. `countOneBits` and the shift operators are unsigned internally, so
66
+ the shaders themselves are unaffected.
67
+ - `vec3<f32>` values travel as `Float32Array` through the emulator, so round-trip expectations are f32-exact
68
+ (six digits), not f64.
69
+
70
+ ---
71
+
72
+ ## 3. Occupancy and warp build — 2026-09-03
73
+
74
+ **Done.** `chunk_avboit_occupancy` (range, word mask, dilate, fold), `shader_avboit_occupancy` (per meshlet,
75
+ indirect over the filtered collection), `shader_avboit_build_warp` (one invocation: dilate → count → halve
76
+ until it fits, then the exclusive-prefix LUT), with specs for the chunk and for the whole build shader
77
+ dispatched through the emulator.
78
+
79
+ **Found.** The implementation dilates at *every* level it evaluates rather than once at the finest and once at
80
+ the settled level, which is the same thing where it matters (the settled level) and simpler to reason about.
81
+ `AVBOIT_OCCUPANCY_DILATION` at the prepass is therefore only a belt-and-braces one slice. Plan §3.4 describes
82
+ the per-level version.
83
+
84
+ Specs pin: empty → `V_max` and an all-zero LUT; a run that dilates to exactly `P` does not halve; one over
85
+ halves once and is re-dilated at the coarser level; everywhere-occupied halves down to `V = P`; the prefix is
86
+ exclusive, monotone, and never exceeds `P`.
87
+
88
+ ---
89
+
90
+ ## 4. Splat — 2026-09-03
91
+
92
+ **Done.** `shader_avboit_splat`: low-resolution render pass, no depth attachment, a `r8unorm` dummy target
93
+ with `writeMask: 0`, per-group pipeline layout with the atomics group fragment-only, linear splat with
94
+ per-channel overflow marks stored inverted under `atomicMax`. `chunk_avboit_tint` factored out so the splat
95
+ and the draw compute the transmission tint from one function.
96
+
97
+ **Found.** No un-jitter in the splat: `unjitter_uv` is derivative-based and the jitter is in
98
+ internal-resolution pixels, so at 1/8 resolution the correction would be eight times too large. Recorded in
99
+ the shader's docblock; the plan did not mention it.
100
+
101
+ **Verified.** Validity tier (vertex + fragment) and the tier-3 layout spec: group 3 entries are `FRAGMENT`
102
+ with `buffer.type === "storage"`, groups 0–2 are `VERTEX | FRAGMENT`, the target's `writeMask` is 0, no
103
+ `depthStencil`.
104
+
105
+ ---
106
+
107
+ ## 5. Integrate — 2026-09-03
108
+
109
+ **Done.** `shader_avboit_integrate`, 8×8 workgroups, exclusive prefix into a `P + 1`-deep `rgba8unorm` 3D
110
+ storage texture, per-channel saturation and overflow pinning. Emulator spec over one column: exclusive
111
+ prefix, independent channels, saturation, a summed field past 255 saturating in one step, an overflow mark
112
+ pinning only its channel, a mark at slice 0.
113
+
114
+ **Found.** Nothing unpredicted.
115
+
116
+ ---
117
+
118
+ ## 6. Draw and sample — 2026-09-03
119
+
120
+ **Done.** `chunk_avboit_sample_transmittance` (bias in virtual slices, warp, `(p + 0.5) / (P + 1)` depth
121
+ coordinate, `exp`), `avboit_draw_fragment_body` (one WGSL text shared by the IBL and Brick4 pipelines, where
122
+ the two MBOIT resolve shaders share theirs by duplication), `shader_avboit_draw_ibl`,
123
+ `shader_avboit_draw_brick4`, `AVBOIT_INTEGRAL_SAMPLER_DESCRIPTOR`. Emulator spec of the sample chunk against a
124
+ hand-built integral: run boundaries land on texel centres, an event in front attenuates by `exp(-E × ln 256)`,
125
+ the bias clears a surface's own linear splat, a gap sample reads the previous run's total and none of the
126
+ next run's, beyond the far plane the sample reads the total.
127
+
128
+ **Found.** The emulator's `null` sampler is the linear clamp-to-edge fallback the spec wants, so no sampler
129
+ object is built there.
130
+
131
+ ---
132
+
133
+ ## 7. Resolve, orchestrator, tier 3 — 2026-09-03
134
+
135
+ **Done.** `shader_avboit_resolve` (reads the scene, writes a fresh `rgba16float`; RGB total transmittance
136
+ cannot blend in place), the full `graph_rasterize_meshes_transparent_avboit` with its sub-recorders exported
137
+ (`graph_avboit_occupancy`, `_build_warp`, `_splat`, `_integrate`, `_draw`),
138
+ `avboit_shaders_wgsl_validity.spec.js` (10 modules, zero diagnostics),
139
+ `graph_rasterize_meshes_transparent_avboit.spec.js` on the software device.
140
+
141
+ **Found.**
142
+
143
+ - `graph_compute_pass` cannot create a `read_write` storage buffer from a descriptor: the helper puts every
144
+ readable binding through the read path first, so the descriptor arrives at `pass.write` as an object. The
145
+ warp build is recorded by hand instead, and its `settings` uniform is serialized with
146
+ `encoder.allocateTransientValueBuffer` — the plain-object convenience only exists inside the graph
147
+ helpers, not in `ComputeShader.dispatch`. Plan §3.1's table is unchanged; the pass is the same, just not
148
+ via the helper.
149
+ - No spec builds a `GPUViewContext` over a scene on the mock, so the tier-3 spec pins the exported
150
+ sub-recorders (warp build: one workgroup into a buffer of the struct's size; integration: the dispatch
151
+ covers the voxel grid and the texture is `3d`, `P + 1` deep, storage + sampled; resolve: one 3-vertex draw
152
+ into a fresh target) and the `Renderer` switch at source level. The whole orchestrator's first encode is on
153
+ a real device (steps 8–9).
154
+
155
+ **Verified.** 62 tests across the avboit directory; 81 across `rasterize/`; the full `src/shade` suite
156
+ (489 files, 3360 tests) passes; `scripts/check-types.mjs` reports the declaration emit clean and zero
157
+ consumer-visible errors.
158
+
159
+ ---
160
+
161
+ ## 8–9. Real compile and the look — 2026-09-03, handed over
162
+
163
+ **Done.** `src/shade/playground/avboit/`: the deck's harness in miniature (a stack of cards at a controlled
164
+ spacing, a tinted glass pane, an opaque backdrop), the MBOIT/AVBOIT switch, and a **Compile check** that
165
+ pushes all ten AVBOIT modules through `createShaderModule` on the live device and prints
166
+ `getCompilationInfo`. The README carries the one scripted session (six steps) with what "pass" looks like
167
+ for each.
168
+
169
+ **Not done here, and why.** The in-app browser pane returned `null` from `requestAdapter()` this session
170
+ (its `wgslLanguageFeatures` are present, the adapter is not — the same state
171
+ `playground-needs-immediate-data` recorded on 2026-09-02), and no Claude-in-Chrome browser was connected.
172
+ So neither the real compile nor the picture happened on this side. What the pane did prove: the page
173
+ loads under the dev server, every import resolves (the ten shader modules assemble at import time, so a
174
+ chunk-dependency or struct error would have thrown there), and `start()` runs up to
175
+ `Renderer.initialize`, which reports `requestAdapter returned null` cleanly. The sources were dumped
176
+ (`scratchpad/wgsl/*.wgsl`, 217 KB) for a pane compile should an adapter come back. What is known: every
177
+ module passes the validator, which has no type checker; the type-level risks are listed below so the first
178
+ compile failure, if any, is quick to place.
179
+
180
+ **Type-level things the validator could not see** (in the order a compiler would meet them):
181
+
182
+ - `avboit_warp` takes `ptr<storage, AVBOITWarp, read>`; the splat and draw pass `&warp` where `warp` is
183
+ declared through `addStorageBuffer(..., false)`. `var<storage>` without an explicit mode is `read`, which
184
+ matches.
185
+ - `atomicAdd(&extinction_volume[column + u32(s)], …)` indexes an `array<atomic<u32>>` binding from the
186
+ fragment stage — core WGSL, but the first such pipeline in the tree.
187
+ - `textureStore(integral, vec3<i32>, vec4<f32>)` on `texture_storage_3d<rgba8unorm, write>`.
188
+ - `select(vec3<u32>, vec3<u32>, vec3<bool>)` and `select(vec3<f32>, vec3<f32>, vec3<bool>)` in the
189
+ integration; both are the component-wise overload.
190
+ - The splat fragment entry has no return type against a pipeline with one colour target at `writeMask: 0`.
191
+
192
+ ---
193
+
194
+ ## 10. Docs — 2026-09-03
195
+
196
+ **Done.** `RENDERER_CONTRACT.md` §2 says which mode needs `float32-blendable`; §9 lists
197
+ `transparency_mode`. `CHANGELOG.md` carries the entry under the unreleased 4.0.0 "Added". The plan is at
198
+ revision 1; nothing in steps 1–9 required a revision of its design sections.
199
+
200
+ **Open, for the session in the playground README:** the ten real compiles; the first frame under AVBOIT;
201
+ the stack at the default spacing (does the bar behind it show through?); the spacing at which the cards
202
+ fall into one slice; the red pane's tint on the backdrop. Results go here as entry 11.
203
+
204
+ ---
205
+
206
+ ## 11. Path-traced reference on the playground — 2026-09-03
207
+
208
+ **Done.** A third mode on the page, "path traced (reference)": `feature_path_tracing_enabled` on, TAA
209
+ forced off while it is, the tracer's running-mean floor lowered from 0.01 to 0.002 so the image keeps
210
+ settling past 100 samples, a **Reset accumulation** button, and a once-a-second readout of how converged
211
+ the image is. That readout needed one small engine addition: `AccumulatingPathTracer.min_sample_count`,
212
+ the sample count of the tile that has had the fewest (the per-tile counters were private, and frames
213
+ elapsed is not a sample count — a frame advances one tile).
214
+
215
+ **What the reference is good for, and not** (README "The reference"):
216
+
217
+ - The tracer rolls each transparent hit against alpha and continues the ray, so it converges to the
218
+ exact order-independent composite of the card stack — the thing both OIT modes approximate. Good.
219
+ - It has no per-channel transmission tint (thin-walled transmission carries only `(1 - F)`), so it
220
+ cannot confirm AVBOIT's glass tint; the pane is compared with the tint off.
221
+ - Its shadow rays treat geometry as opaque (the tracer's own `TODO`), so the cards cast full shadows
222
+ there and none under either OIT mode. The stack, not its shadow, is the comparison.
223
+
224
+ **Verified.** `path_tracer/` has no specs of its own; the getter is a read of a private array. The
225
+ page's module graph was re-loaded in the pane (see entry 8–9 for what the pane can and cannot prove).
226
+
227
+ ---
228
+
229
+ ## 12. First renders on a real device — 2026-09-03
230
+
231
+ Alex ran the playground and pasted three canvas captures at the default settings (four cards, alpha
232
+ 0.85, spacing 1.5, red glass pane): path traced, AVBOIT, MBOIT. Read against the code, not measured.
233
+
234
+ **The stack — the deck's depth-complexity claim reproduces.** Under MBOIT the green card, two layers
235
+ back, shows through both the red and the blue card as a lighter rectangle inside the red one. Under
236
+ the path tracer and under AVBOIT it does not; the overlap is red with the blue card directly behind
237
+ faintly visible. AVBOIT is the closer of the two to the reference on the stack. Both rasterized modes
238
+ render the cards darker than the tracer, and they agree with each other, so that is a forward-shading
239
+ versus tracer difference and not this pipeline's.
240
+
241
+ **Also as predicted:** the tracer casts full shadows from the cards (its occlusion query treats
242
+ geometry as opaque), neither OIT mode does.
243
+
244
+ **The glass pane — the tint is working as written, and is the wrong default.** Under AVBOIT the pane
245
+ is deep red; under the tracer and MBOIT it is clear glass. The material is transmission 1 with a red
246
+ base colour, and `chunk_avboit_tint` multiplies transmitted light by that base colour, so green and
247
+ blue drop to ~14 %. It is not opaque — the horizon shows through as the dark-to-bright step at floor
248
+ level — but it is a material semantic the other paths do not have: the forward shader states there is
249
+ no per-channel transmission tint in the material model, and the tracer's thin-walled transmission
250
+ carries only the Fresnel split. The contract's promise that the two modes take the same inputs and
251
+ are safe to flip per frame does not survive a flip that recolours glass.
252
+
253
+ Two corrections to what the code says about itself: the chunk docblock calls base-colour-as-tint
254
+ "the OpenPBR default", which it is not (OpenPBR's transmission colour defaults to white; glTF's
255
+ transmission extension is the one that uses the base colour); and plan §4's first row rests on the
256
+ same reading.
257
+
258
+ **Proposed:** keep the RGB plumbing, default the tint off, expose it as a runtime switch
259
+ (`renderer.avboit.transmission_tint_from_base_color`, carried in the warp header so the splat and
260
+ draw agree per frame), a checkbox on the page, plan to revision 2. The alternative — keep it on and
261
+ document it as an AVBOIT-only semantic — makes one asset mean two things depending on a renderer
262
+ switch. Alex chose the first; entry 14.
263
+
264
+ **Also closed by these renders:** step 8's real compile, for the IBL variant. An AVBOIT frame on a real
265
+ device means the occupancy, warp, splat, integrate, IBL draw and resolve modules all compiled and the
266
+ pipeline ran end to end. The Brick4 draw variant is not exercised by the page (`indirect_lighting_mode`
267
+ is IBL there) and remains compiled only by the validator.
268
+
269
+ ---
270
+
271
+ ## 13. The deck's opaque-card case on the page — 2026-09-03
272
+
273
+ **Done.** At Alex's request: a small pale quad, alpha 1 on a `Transparent` material, one unit behind
274
+ the last card and offset so part of it is seen through the stack and part stands against the backdrop
275
+ and the dark bar. Both pipelines clamp its coverage at `OIT_COVERAGE_MAX` (0.997), so it is the "0 %
276
+ transmittance card" of deck slides 35–38: the case where a moment reconstruction cannot hold the
277
+ step, leaks the background through the surface, and tears along the depth discontinuity at its edge.
278
+ A checkbox (`opaque_quad`, default on) and step 8 of the README's session.
279
+
280
+ **What to expect from AVBOIT here, from the design:** the quad's extinction saturates its slice
281
+ (`-ln(1 - 0.997) / ln 256 ≈ 1.05`, clamped to 1.0), so the integral behind it reads 1.0 and every
282
+ fragment further back is weighted by a transmittance of 1/256. Its edge is a step in the extinction
283
+ volume at 1/8 resolution, softened by the draw's trilinear sample over about one voxel — so a slight
284
+ halo of that width is the expected artefact, not a leak. Anything behind the quad showing through its
285
+ interior would be a finding.
286
+
287
+ ---
288
+
289
+ ## 14. The tint, kept and off by default — 2026-09-03
290
+
291
+ **Done.** `AVBOITSettings` on `Renderer.avboit` with one switch, `transmission_tint_from_base_color`
292
+ (default false). The orchestrator takes `avboit` beside its other inputs (the MBOIT one ignores it), the
293
+ warp build copies it into a new `tint_from_base_color` header word, and both the splat and the draw
294
+ weight `chunk_avboit_tint` by `transmission_factor × f32(warp.tint_from_base_color)`. The chunk's
295
+ docblock now says whose semantics the mapping is (glTF's, not OpenPBR's). Plan to revision 2 (§3.5,
296
+ §4, §7). Contract §9 lists `avboit`. The page has a checkbox for it, off by default, and step 6 of the
297
+ session turns it on before comparing the pane.
298
+
299
+ **Why a header word and not a shader constant:** the splat and the draw must agree within a frame, and
300
+ the header is already the one place both read per-frame facts from; a runtime flag lets the page do
301
+ the A/B without a rebuild.
302
+
303
+ **Verified.** Build-warp emulator spec asserts the header copies the flag; validity tier over the
304
+ edited modules; rasterize spec tree.
305
+
306
+ ---
307
+
308
+ ## 15. Decisions on the open items — 2026-09-03
309
+
310
+ Alex's answers to the open list, and what each became.
311
+
312
+ **Slice floor — mine to call; 5 cm is a spacing worth resolving.** Virtual slice thickness is
313
+ `(z0 + z - near) · ln(1 + (far - near) / z0) / V`. At the page's camera (`near 0.1`, `far 200`,
314
+ `z0 = 0.8`) and `V = 1024` that was 5.8 cm at 10 m: one slice, under the two-slice bias, so 5 cm
315
+ blended. `AVBOIT_VIRTUAL_SLICES_MAX` is now 4096 — 1.5 cm at 10 m, 2.8 cm at 20 m, 7 cm at 50 m — so
316
+ 5 cm resolves out to about 17 m before the bias eats it, and the adaptive halving still pulls it back
317
+ when more than 128 slices are occupied. The bias stays at 2.0 rather than dropping to the analytic
318
+ 1.5: the half-slice margin is what covers the depth difference between a splat fragment (a
319
+ 1/8-resolution pixel centre) and a draw fragment on a sloped surface. Cost: 128 occupancy words, a
320
+ 16 KB LUT, a longer single-lane loop in the warp build. Specs unchanged in intent (they read the
321
+ constant) and green.
322
+
323
+ **The opaque quad — fine as is.** Stays on the page.
324
+
325
+ **Camera.** `OrbitalCameraController` on the page (drag, wheel, right-drag), **Copy camera** to the
326
+ clipboard and a box, **Apply camera** from the box, `camera_state()` / `apply_camera()` /
327
+ `configure({ camera })` from the console. Plan §9 gains the slope risk in its own words: a splat
328
+ fragment carries its low-resolution pixel centre's depth, and on a grazing surface that can exceed
329
+ the bias.
330
+
331
+ **Brick4 — should land.** The variant exists and is validator-clean; the page now has an
332
+ indirect-lighting select (IBL / Brick4) so the Brick4 draw pipeline compiles and draws on a device.
333
+ The scene has no probes, so the indirect term is black either way; the step is in the session.
334
+
335
+ **Timings — hook up the profiler.** Done as on `volumetrics_froxel`: **Profile** starts a
336
+ `GPUProfileSession` at `WORKLOAD`, pressing it again drains and downloads
337
+ `avboit-<mode>-<timestamp>.sgpt` for the gpu-inspector-tool, with the mode and the page's settings in
338
+ the note and the adapter in the meta. Same settings, two recordings, one per mode, is the comparison.
339
+
340
+ **Software device — wrong tool for an end-to-end spec, and that is fine.** Plan §6 tier 3 now says
341
+ so; the full-orchestrator spec is off the list.
342
+
343
+ **Phase 2 — only clean, unambiguous, non-situational.** Decided item by item in plan §7 revision 3.
344
+ One landed: the draw discards a fragment the volume already calls extinct (`T ≤ 1/256` in every
345
+ channel) *before* forward shading. No new resource or pass; the error is bounded by `1/256` of the
346
+ background (`AVBOIT_EXTINCT_TRANSMITTANCE`), below the 8-bit volume's own resolution. Tile skipping
347
+ (no: sub-millisecond passes), the zero-transmittance depth cull (not now: situational, touches the
348
+ depth other passes read, wants its own plan and a scene that needs it), the monochrome buffer (no:
349
+ two representations for content we do not have), the sparse texture (no: speculative).
350
+
351
+ **Back-sided materials — ignored for now.** Plan §7 keeps the note.
352
+
353
+ **AVBOIT is the default.** `Renderer.transparency_mode = ShadeTransparencyMode.AVBOIT`; the enum,
354
+ the contract (§2 keeps `float32-blendable` on the floor while MBOIT is selectable; §9 row), the
355
+ changelog and the page's default select follow. The source-level spec pins the new default.
356
+
357
+ **Committed** on the branch after this entry.
358
+
359
+ ---
360
+
361
+ ## 16. The page's camera and profile buttons, reported not working — 2026-09-03
362
+
363
+ Alex: the camera could not be moved, and **Profile** appeared to do nothing with no way to stop it.
364
+
365
+ **What was verified here.** The page was restructured so the camera, its controller and every control
366
+ are wired *before* the device is requested. That made the input path testable in the in-app pane,
367
+ which has no adapter: a synthetic pointer drag dispatched on the canvas orbited the camera from
368
+ `(0, 2.5, 12)` to `(-7.3, 3.7, 9.4)` and a wheel event zoomed it, read back through `camera_state()`.
369
+ So `OrbitalCameraController` on `document.body`, the pointer device and the transform write are
370
+ correct as wired. What could not be reproduced is the failure in a real session.
371
+
372
+ **What the restructure found.** Building geometry before `Renderer.initialize` threw
373
+ `Cannot read properties of undefined (reading 'exports')`: the meshlet builder runs on a WASM
374
+ instance (`MeshoptClusterizer`) that instantiates asynchronously at import, and the renderer's own
375
+ initialization had been what gave it time. The page now awaits `MeshoptClusterizer.ready` itself. Not
376
+ the reported fault (the committed order built geometry after the device), but the same class of
377
+ thing: a step that silently depended on another step's timing.
378
+
379
+ **Made robust, since the fault could not be seen.** Every control handler runs through a guard that
380
+ puts a failure into the report panel (`<what> failed: <message>`) instead of only the console; the
381
+ final settings application is guarded so a settings error cannot stop the frame loop from starting;
382
+ the Profile button carries a `recording` state (label and red border) so a running capture is
383
+ unmistakable and the same button stops it; the controls are spelled out under the canvas. If a real
384
+ session still does not orbit, the report panel now says why.
385
+
386
+ **Suspicion, unverified.** With HMR off, a reload taken while the page's files were being rewritten
387
+ on disk can pair one version of `index.html` with another of `main.js` or `Renderer.js`; a
388
+ `TypeError` in the settings step then killed `start()` before the frame loop, which is the one
389
+ sequence that leaves the buttons registered, the camera frozen and no picture of the cause. A hard
390
+ reload after this commit is the first thing to try.
391
+
392
+ ---
393
+
394
+ ## 17. First timings — 2026-09-03
395
+
396
+ Alex ran a short profiling session on the page (the deck's harness in miniature: four cards, the
397
+ glass pane, the opaque quad, 1280x720): **AVBOIT 289.9 us, MBOIT 282.4 us**, averages of the
398
+ transparency work, which he reads as within the noise. Device and capture files not recorded here.
399
+
400
+ **What that says.** On this scene the two pipelines cost the same. That is the expected shape, not a
401
+ surprise: the deck's cost argument (AVBOIT approaching no-OIT, slides 106-110) comes from VFX content
402
+ with tens of millions of overdrawn transparent pixels, where the 1/8-resolution splat, the saturated
403
+ integral and the early-out remove most of the shading work. Six quads at 720p have almost nothing
404
+ for those to remove, and AVBOIT's fixed costs — the occupancy pass, the warp build, the integration
405
+ over a 160x90x129 volume, the resolve into a fresh target — are of the same order as MBOIT's two
406
+ full-resolution passes over the same few quads. So the number closes the question "does the new path
407
+ cost more on light content" (no), and leaves open the one the deck actually answers, which needs a
408
+ scene with real transparent overdraw. Plan §9's contention risk is unmeasured until then.
409
+
410
+ **Committed** with this entry.
411
+
412
+ ---
413
+
414
+ ## 18. Review against the engine — 2026-09-03
415
+
416
+ Alex asked three questions of the code: is anything reinvented, does it follow one function per
417
+ chunk, does it fight the engine. Surveyed and fixed; plan to revision 4.
418
+
419
+ **Reinvented, now reused.**
420
+
421
+ - *The depth curve.* `chunk_avboit_depth_curve` (t(z), z(t), a `z0` knob) was a second copy of what the
422
+ light clusters and the froxels already use: `cluster_depth_to_z_slice` with parameters from
423
+ `build_light_cluster_parameters`. That builder's `scale` argument is the linearization the deck
424
+ describes; `avboit_build_depth_curve` chooses it so the curve is pure log from the near offset,
425
+ because the warp makes near precision free. Gone: the chunk, its inverse, its spec, the
426
+ `AVBOIT_DEPTH_CURVE_LINEARIZATION` constant, and the camera binding of the warp build (the curve now
427
+ arrives as three floats in the settings and in the occupancy pass's own settings uniform).
428
+ - *The bit fold.* `avboit_fold_pairs` is the OR of the even bits compacted and the odd bits compacted,
429
+ which is `decode32_morton2`'s job. Found on the way: for a 32-bit code that function's `.y` is *not*
430
+ the odd bits — the `<< 31` that separates them in the 16-bit variant (`<< 15`) cannot work in 32
431
+ bits — and the first reuse trusted it; the warp-build spec caught it as an occupied count of 70
432
+ against 66. The fold now goes through the correct `.x` half twice, once on `x >> 1`, and has a spec
433
+ against a bit-by-bit reference. `decode32_morton2` is used nowhere else in the tree; its `.y` is a
434
+ latent engine defect, noted for whoever reaches for it next.
435
+ - *The sampler.* `AVBOIT_INTEGRAL_SAMPLER_DESCRIPTOR` duplicated `SAMPLER_LINEAR_UPSCALE` (linear,
436
+ clamped on every axis), which the draw already binds as `sLinear` for the split-sum LUT. The integral
437
+ samples through that; one binding fewer in the view group.
438
+ - *The draw loop.* `material_draw` existed privately in both orchestrators. It is
439
+ `rasterize/native/material_draw_indirect.js` now, with the material bind-group layout it needs,
440
+ imported by both.
441
+
442
+ **Convention.** 650 of the engine's ~700 chunk files hold exactly one function named after the file;
443
+ mine held two to six. Five chunks became seventeen (`chunk_avboit_pack_counts`, `_unpack_counts`,
444
+ `_transmittance_to_extinction`, `_extinction_to_transmittance`, `_extinction_to_counts`,
445
+ `_counts_to_extinction`, `_overflows`, `_occupancy_range`, `_occupancy_word_mask`, `_dilate_word`,
446
+ `_fold_pairs`, `_warp`, `_view_depth_to_virtual`, `_view_depth_to_physical`,
447
+ `_integral_depth_coordinate`, `_sample_transmittance`, `_transmission_tint`), each with its spec; the
448
+ hand-built warp and the curve's inverse the specs share live in `avboit_spec_support.js`, test-only.
449
+
450
+ **Kept, on purpose.** `avboit_draw_fragment_body.js` shares one WGSL text between the IBL and Brick4
451
+ draw pipelines where MBOIT duplicates its resolve body. No precedent either way in the tree; two
452
+ 45-line bodies drifting apart is the worse failure, and the two files stay concrete modules with no
453
+ conditionals in them.
454
+
455
+ **Also checked and found conforming:** the manual frame-graph passes match the MBOIT orchestrator's
456
+ shape; `AVBOITSettings` on `Renderer.avboit` matches the other sub-object settings; the settings
457
+ structs defined beside their shaders match `shader_read_out_bucket`; the bit packing uses WGSL's
458
+ `insertBits`/`extractBits` directly, which is what the engine's `bitfield_*` chunks are thin proxies
459
+ for; no `safe_*` math helper was reinvented — the one floor (`max(T, 1e-6)` before a log) has no
460
+ counterpart in `shader/chunk/math/`.
461
+
462
+ **Verified.** Every spec in `rasterize/` (the avboit directory's now-larger set, MBOIT's, the shared
463
+ bucket code) and the type check.