@woosh/meep-engine 3.31.0 → 3.32.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 (72) hide show
  1. package/build/bundle-worker-terrain.js +1 -1
  2. package/package.json +1 -1
  3. package/src/engine/graphics3/DebugDrawSystem.d.ts.map +1 -1
  4. package/src/engine/graphics3/DebugDrawSystem.js +6 -2
  5. package/src/engine/graphics3/PathDisplaySystem.d.ts.map +1 -1
  6. package/src/engine/graphics3/PathDisplaySystem.js +311 -307
  7. package/src/engine/physics/fluid/ecs/FluidObstacleSystem.d.ts +4 -4
  8. package/src/shade/RENDERER_CONTRACT.md +15 -2
  9. package/src/shade/device/IMMEDIATE_DATA_PLAN_2026_08_28.md +5 -0
  10. package/src/shade/device/ShadeGPUCommandContext.d.ts +15 -2
  11. package/src/shade/device/ShadeGPUCommandContext.d.ts.map +1 -1
  12. package/src/shade/device/ShadeGPUCommandContext.js +154 -25
  13. package/src/shade/renderer/GraphicsContext.d.ts +9 -1
  14. package/src/shade/renderer/GraphicsContext.d.ts.map +1 -1
  15. package/src/shade/renderer/GraphicsContext.js +54 -15
  16. package/src/shade/renderer/Renderer.d.ts +19 -4
  17. package/src/shade/renderer/Renderer.d.ts.map +1 -1
  18. package/src/shade/renderer/Renderer.js +113 -2
  19. package/src/shade/renderer/animation/GPUAnimationManager.d.ts +10 -7
  20. package/src/shade/renderer/animation/GPUAnimationManager.d.ts.map +1 -1
  21. package/src/shade/renderer/animation/GPUAnimationManager.js +147 -45
  22. package/src/shade/renderer/buffer/GPUBufferStagingAllocator.d.ts +11 -2
  23. package/src/shade/renderer/buffer/GPUBufferStagingAllocator.d.ts.map +1 -1
  24. package/src/shade/renderer/buffer/GPUBufferStagingAllocator.js +95 -13
  25. package/src/shade/renderer/buffer/GPUBufferUploadAllocator.d.ts +60 -0
  26. package/src/shade/renderer/buffer/GPUBufferUploadAllocator.d.ts.map +1 -0
  27. package/src/shade/renderer/buffer/GPUBufferUploadAllocator.js +202 -0
  28. package/src/shade/renderer/buffer/GPUUploadBlock.d.ts +68 -0
  29. package/src/shade/renderer/buffer/GPUUploadBlock.d.ts.map +1 -0
  30. package/src/shade/renderer/buffer/GPUUploadBlock.js +97 -0
  31. package/src/shade/renderer/dynamic/GPUDynamicMeshRenderer.d.ts +20 -7
  32. package/src/shade/renderer/dynamic/GPUDynamicMeshRenderer.d.ts.map +1 -1
  33. package/src/shade/renderer/dynamic/GPUDynamicMeshRenderer.js +20 -7
  34. package/src/shade/renderer/geometry/meshlet/build/meshoptimizer/geometry_to_meshlets_meshoptimizer.d.ts +5 -0
  35. package/src/shade/renderer/geometry/meshlet/build/meshoptimizer/geometry_to_meshlets_meshoptimizer.d.ts.map +1 -1
  36. package/src/shade/renderer/geometry/meshlet/build/meshoptimizer/geometry_to_meshlets_meshoptimizer.js +19 -0
  37. package/src/shade/renderer/loader/gltf/tiny-gltf.d.ts +1 -1
  38. package/src/shade/renderer/postprocess/nss/NSS.d.ts +1 -1
  39. package/src/shade/renderer/rasterize/bucket/rasterization_has_opaque_bucket.d.ts +3 -1
  40. package/src/shade/renderer/rasterize/bucket/rasterization_has_opaque_bucket.d.ts.map +1 -1
  41. package/src/shade/renderer/rasterize/bucket/rasterization_has_opaque_bucket.js +3 -1
  42. package/src/shade/renderer/rasterize/expand/bucket/mesh/shader_instances_rasterization_bucket_sort.d.ts.map +1 -1
  43. package/src/shade/renderer/rasterize/expand/bucket/mesh/shader_instances_rasterization_bucket_sort.js +134 -88
  44. package/src/shade/renderer/rasterize/expand/bucket/meshlet/shader_meshlets_rasterization_bucket_sort.d.ts.map +1 -1
  45. package/src/shade/renderer/rasterize/expand/bucket/meshlet/shader_meshlets_rasterization_bucket_sort.js +57 -26
  46. package/src/shade/renderer/rasterize/fast/graph_rasterize_scene_fast.d.ts.map +1 -1
  47. package/src/shade/renderer/rasterize/fast/graph_rasterize_scene_fast.js +28 -20
  48. package/src/shade/renderer/rasterize/graph_clear_raster_targets.d.ts +38 -0
  49. package/src/shade/renderer/rasterize/graph_clear_raster_targets.d.ts.map +1 -0
  50. package/src/shade/renderer/rasterize/graph_clear_raster_targets.js +105 -0
  51. package/src/shade/renderer/rasterize/standard/graph_rasterize_partial_opaque.d.ts +1 -1
  52. package/src/shade/renderer/rasterize/standard/graph_rasterize_partial_opaque.d.ts.map +1 -1
  53. package/src/shade/renderer/rasterize/standard/graph_rasterize_partial_opaque.js +21 -1
  54. package/src/shade/renderer/rasterize/standard/graph_rasterize_scene.d.ts.map +1 -1
  55. package/src/shade/renderer/rasterize/standard/graph_rasterize_scene.js +12 -20
  56. package/src/shade/renderer/shader/graph/graph_import_typed_buffer.d.ts +2 -1
  57. package/src/shade/renderer/shader/graph/graph_import_typed_buffer.d.ts.map +1 -1
  58. package/src/shade/renderer/shader/graph/graph_import_typed_buffer.js +15 -28
  59. package/src/core/geom/3d/atlas/atlas_bench_lib.d.ts +0 -73
  60. package/src/core/geom/3d/atlas/atlas_bench_lib.d.ts.map +0 -1
  61. package/src/engine/graphics3/terrain/GPUTerrainSplatRenderer.d.ts +0 -59
  62. package/src/engine/graphics3/terrain/GPUTerrainSplatRenderer.d.ts.map +0 -1
  63. package/src/engine/graphics3/terrain/pack_terrain_row_table.d.ts +0 -22
  64. package/src/engine/graphics3/terrain/pack_terrain_row_table.d.ts.map +0 -1
  65. package/src/engine/graphics3/terrain/shader_terrain_splat.d.ts +0 -26
  66. package/src/engine/graphics3/terrain/shader_terrain_splat.d.ts.map +0 -1
  67. package/src/shade/renderer/animation/ANIMATION_BOUND_TRACK_GROUP_STRUCT.d.ts +0 -25
  68. package/src/shade/renderer/animation/ANIMATION_BOUND_TRACK_GROUP_STRUCT.d.ts.map +0 -1
  69. package/src/shade/renderer/animation/ANIMATION_KEYFRAME_BLOCK_STRUCT.d.ts +0 -26
  70. package/src/shade/renderer/animation/ANIMATION_KEYFRAME_BLOCK_STRUCT.d.ts.map +0 -1
  71. package/src/shade/renderer/animation/ANIMATION_SKIN_JOINT_BLOCK_STRUCT.d.ts +0 -29
  72. package/src/shade/renderer/animation/ANIMATION_SKIN_JOINT_BLOCK_STRUCT.d.ts.map +0 -1
package/package.json CHANGED
@@ -6,7 +6,7 @@
6
6
  "description": "Advanced 3D game engine",
7
7
  "type": "module",
8
8
  "author": "Alexander Goldring",
9
- "version": "3.31.0",
9
+ "version": "3.32.0",
10
10
  "exports": {
11
11
  "./build/*": "./build/*",
12
12
  "./src/*": "./src/*",
@@ -1 +1 @@
1
- {"version":3,"file":"DebugDrawSystem.d.ts","sourceRoot":"","sources":["../../../../src/engine/graphics3/DebugDrawSystem.js"],"names":[],"mappings":"AAcA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH;IAkEI;;OAEG;IACH,sCAYC;IAhFD;;;;;;;;;OASG;IACH,YAFU,MAAM,CAED;IA8Cf;;;;OAIG;IACH,8BAEC;IAmBD,2CAMC;IAED,4CAYC;IAED;;OAEG;IACH,kCAwBC;;CACJ;AAED;;;;;;;;GAQG;AACH;IAYI;;OAEG;IACH,oBAFW,eAAe,EAMzB;IAED,yBAEC;;CACJ;uBApMsB,kBAAkB;iCADR,gDAAgD;gCAJjD,mDAAmD"}
1
+ {"version":3,"file":"DebugDrawSystem.d.ts","sourceRoot":"","sources":["../../../../src/engine/graphics3/DebugDrawSystem.js"],"names":[],"mappings":"AAcA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH;IAkEI;;OAEG;IACH,sCAYC;IAhFD;;;;;;;;;OASG;IACH,YAFU,MAAM,CAED;IA8Cf;;;;OAIG;IACH,8BAEC;IAmBD,2CAMC;IAED,4CAYC;IAED;;OAEG;IACH,kCA4BC;;CACJ;AAED;;;;;;;;GAQG;AACH;IAYI;;OAEG;IACH,oBAFW,eAAe,EAMzB;IAED,yBAEC;;CACJ;uBAxMsB,kBAAkB;iCADR,gDAAgD;gCAJjD,mDAAmD"}
@@ -159,14 +159,18 @@ export class DebugDrawSystem extends System {
159
159
  }
160
160
 
161
161
  const scene = frame.get(SceneColor);
162
+ const view = frame.get(ViewTextures);
162
163
 
163
- scene.color = this.#renderer.graph_draw({
164
+ const drawn = this.#renderer.graph_draw({
164
165
  graph: frame.graph,
165
166
  batch: this.#batch,
166
167
  color: scene.color,
167
- depth: frame.get(ViewTextures).depth,
168
+ depth: view.depth,
168
169
  camera: frame.view.camera.buffer
169
170
  });
171
+
172
+ scene.color = drawn.color;
173
+ view.depth = drawn.depth;
170
174
  }
171
175
  }
172
176
 
@@ -1 +1 @@
1
- {"version":3,"file":"PathDisplaySystem.d.ts","sourceRoot":"","sources":["../../../../src/engine/graphics3/PathDisplaySystem.js"],"names":[],"mappings":"AAkBA;;;;;;;;;;;;;;;;;;GAkBG;AACH;IA8CI;;OAEG;IACH,sCAMC;IAtDD,mDAAmC;IAoCnC;;;;OAIG;IACH,8BAEC;IAaD,2CAIC;IAED,4CAYC;IAED;;;;OAIG;IACH,cAJW,WAAW,QACX,IAAI,UACJ,MAAM,QAehB;IAED;;;;OAIG;IACH,gBAJW,WAAW,QACX,IAAI,UACJ,MAAM,QAkBhB;IAED,6BAgBC;IAsFD;;OAEG;IACH,kCAeC;;CACJ;AAED;;;GAGG;AACH;IAUI;;OAEG;IACH,oBAFW,iBAAiB,EAM3B;IAED,yBAEC;;CACJ;uBAzSsB,kBAAkB;iBAIxB,sCAAsC;4BAH3B,qCAAqC;iCAFhC,gDAAgD;gCAJjD,mDAAmD"}
1
+ {"version":3,"file":"PathDisplaySystem.d.ts","sourceRoot":"","sources":["../../../../src/engine/graphics3/PathDisplaySystem.js"],"names":[],"mappings":"AAkBA;;;;;;;;;;;;;;;;;;GAkBG;AACH;IA8CI;;OAEG;IACH,sCAMC;IAtDD,mDAAmC;IAoCnC;;;;OAIG;IACH,8BAEC;IAaD,2CAIC;IAED,4CAYC;IAED;;;;OAIG;IACH,cAJW,WAAW,QACX,IAAI,UACJ,MAAM,QAehB;IAED;;;;OAIG;IACH,gBAJW,WAAW,QACX,IAAI,UACJ,MAAM,QAkBhB;IAED,6BAgBC;IAsFD;;OAEG;IACH,kCAmBC;;CACJ;AAED;;;GAGG;AACH;IAUI;;OAEG;IACH,oBAFW,iBAAiB,EAM3B;IAED,yBAEC;;CACJ;uBA7SsB,kBAAkB;iBAIxB,sCAAsC;4BAH3B,qCAAqC;iCAFhC,gDAAgD;gCAJjD,mDAAmD"}
@@ -1,307 +1,311 @@
1
- import { assert } from "../../core/assert.js";
2
- import { GPUDynamicMeshRenderer } from "../../shade/renderer/dynamic/GPUDynamicMeshRenderer.js";
3
- import { Geometry } from "../../shade/renderer/geometry/Geometry.js";
4
- import { FramePhase } from "../../shade/renderer/extension/FramePhase.js";
5
- import { RenderExtension } from "../../shade/renderer/extension/RenderExtension.js";
6
- import { SceneColor } from "../../shade/renderer/extension/SceneColor.js";
7
- import { ViewTextures } from "../../shade/renderer/extension/ViewTextures.js";
8
- import { DynamicMesh } from "../../shade/renderer/scene/DynamicMesh.js";
9
- import { DynamicMeshBatch } from "../../shade/renderer/scene/DynamicMeshBatch.js";
10
- import { System } from "../ecs/System.js";
11
- import { PathDisplay } from "../graphics/ecs/path/PathDisplay.js";
12
- import { PathDisplayEvents } from "../graphics/ecs/path/PathDisplayEvents.js";
13
- import { PathDisplayType } from "../graphics/ecs/path/PathDisplayType.js";
14
- import Path from "../navigation/ecs/components/Path.js";
15
- import { PathEvents } from "../navigation/ecs/components/PathEvents.js";
16
- import { build_tube_path_streams } from "./path/build_tube_path_streams.js";
17
- import { tube_stream_write_geometry } from "./path/tube_stream_write_geometry.js";
18
-
19
- /**
20
- * Draws {@link PathDisplay}'s tubes under Shade, on the dynamic-geometry path.
21
- *
22
- * **A tube is dynamic content by definition** — the game's one path display is the combat targeting
23
- * arc, rebuilt every time the cursor moves — so it goes down S4's dedicated non-meshlet path rather
24
- * than becoming a `ShadedGeometry` behind the meshlet encode that D5/I-10 rule out (D21).
25
- *
26
- * **No owned entities.** The legacy system spawns a child entity per mask span because three needs
27
- * an object-carrying entity per drawable; here the system holds the meshes itself, so link builds
28
- * them, a path or style change rewrites them, and unlink releases them.
29
- *
30
- * **The geometry is kept and rewritten, never replaced.** GPU residency on this path is keyed on
31
- * the geometry and survives leaving the batch, so a rebuild that produced a new one each time would
32
- * strand a vertex buffer per cursor move.
33
- *
34
- * Tubes are all it draws. `PathDisplayType.Ribbon` retires with the three path (I-6c) and
35
- * `PathDisplayType.Entity` builds marker entities that carry their own components — neither is
36
- * this system's to render.
37
- */
38
- export class PathDisplaySystem extends System {
39
- dependencies = [PathDisplay, Path];
40
-
41
- /**
42
- * @type {GraphicsEngine}
43
- */
44
- #graphics;
45
-
46
- /**
47
- * @type {GPUDynamicMeshRenderer}
48
- */
49
- #renderer = new GPUDynamicMeshRenderer();
50
-
51
- /**
52
- * @type {DynamicMeshBatch}
53
- */
54
- #batch = new DynamicMeshBatch();
55
-
56
- /**
57
- * @type {Map<number, {display: PathDisplay, path: Path, meshes: DynamicMesh[], listener: Function}>}
58
- */
59
- #entries = new Map();
60
-
61
- /**
62
- * Entities whose tubes are out of date. A path change and a style change both arrive as events,
63
- * and the arc sends one of each per cursor move — coalescing to one rebuild a tick is what the
64
- * queue is for.
65
- *
66
- * @type {Set<number>}
67
- */
68
- #dirty = new Set();
69
-
70
- /**
71
- * @type {RenderExtension|null}
72
- */
73
- #extension = null;
74
-
75
- /**
76
- * What the pass draws. Read-only — the meshes follow the entities.
77
- *
78
- * @returns {DynamicMeshBatch}
79
- */
80
- get batch() {
81
- return this.#batch;
82
- }
83
-
84
- /**
85
- * @param {GraphicsEngine} graphics
86
- */
87
- constructor(graphics) {
88
- super();
89
-
90
- assert.defined(graphics, 'graphics');
91
-
92
- this.#graphics = graphics;
93
- }
94
-
95
- async startup(entityManager) {
96
- this.entityManager = entityManager;
97
-
98
- this.#extension = this.#graphics.add_extension(new PathDisplayExtension(this));
99
- }
100
-
101
- async shutdown(entityManager) {
102
- if (this.#extension !== null) {
103
- this.#graphics.remove_extension(this.#extension);
104
-
105
- this.#extension = null;
106
- }
107
-
108
- this.#renderer.destroy();
109
-
110
- this.#batch.clear();
111
- this.#entries.clear();
112
- this.#dirty.clear();
113
- }
114
-
115
- /**
116
- * @param {PathDisplay} display
117
- * @param {Path} path
118
- * @param {number} entity
119
- */
120
- link(display, path, entity) {
121
- const dataset = this.entityManager.dataset;
122
-
123
- const listener = () => this.#dirty.add(entity);
124
-
125
- dataset.addEntityEventListener(entity, PathEvents.Changed, listener);
126
- dataset.addEntityEventListener(entity, PathDisplayEvents.Changed, listener);
127
-
128
- const entry = { display, path, meshes: [], listener };
129
-
130
- this.#entries.set(entity, entry);
131
-
132
- this.#rebuild(entry);
133
- }
134
-
135
- /**
136
- * @param {PathDisplay} display
137
- * @param {Path} path
138
- * @param {number} entity
139
- */
140
- unlink(display, path, entity) {
141
- const entry = this.#entries.get(entity);
142
-
143
- if (entry === undefined) {
144
- return;
145
- }
146
-
147
- const dataset = this.entityManager.dataset;
148
-
149
- dataset.removeEntityEventListener(entity, PathEvents.Changed, entry.listener);
150
- dataset.removeEntityEventListener(entity, PathDisplayEvents.Changed, entry.listener);
151
-
152
- this.#release(entry, 0);
153
-
154
- this.#entries.delete(entity);
155
- this.#dirty.delete(entity);
156
- }
157
-
158
- update(timeDelta) {
159
- if (this.#dirty.size === 0) {
160
- return;
161
- }
162
-
163
- for (const entity of this.#dirty) {
164
- const entry = this.#entries.get(entity);
165
-
166
- if (entry === undefined) {
167
- continue;
168
- }
169
-
170
- this.#rebuild(entry);
171
- }
172
-
173
- this.#dirty.clear();
174
- }
175
-
176
- /**
177
- * @param {{display: PathDisplay, path: Path, meshes: DynamicMesh[]}} entry
178
- */
179
- #rebuild(entry) {
180
- const specs = entry.display.specs;
181
-
182
- let written = 0;
183
-
184
- for (let i = 0; i < specs.length; i++) {
185
- const spec = specs[i];
186
-
187
- if (spec.type !== PathDisplayType.Tube) {
188
- continue;
189
- }
190
-
191
- const style = spec.style;
192
-
193
- const streams = build_tube_path_streams(entry.path, style);
194
-
195
- const color = style.color;
196
-
197
- for (let j = 0; j < streams.length; j++) {
198
- const mesh = this.#obtain_mesh(entry, written);
199
-
200
- tube_stream_write_geometry(mesh.geometry, streams[j]);
201
-
202
- mesh.color[0] = color.r;
203
- mesh.color[1] = color.g;
204
- mesh.color[2] = color.b;
205
- mesh.color[3] = style.opacity;
206
-
207
- written++;
208
- }
209
- }
210
-
211
- this.#release(entry, written);
212
- }
213
-
214
- /**
215
- * The mesh drawing span `index` of this entity, created on first use.
216
- *
217
- * @param {{meshes: DynamicMesh[]}} entry
218
- * @param {number} index
219
- * @returns {DynamicMesh}
220
- */
221
- #obtain_mesh(entry, index) {
222
- const existing = entry.meshes[index];
223
-
224
- if (existing !== undefined) {
225
- return existing;
226
- }
227
-
228
- // the sweep produces world-space vertices, and what the draw applies is the mesh's own
229
- // transform, identity until moved
230
- const mesh = DynamicMesh.from(new Geometry());
231
-
232
- entry.meshes.push(mesh);
233
-
234
- this.#batch.add(mesh);
235
-
236
- return mesh;
237
- }
238
-
239
- /**
240
- * Give up every mesh from `count` on — a style whose mask lost a span, or an entity going away.
241
- *
242
- * @param {{meshes: DynamicMesh[]}} entry
243
- * @param {number} count
244
- */
245
- #release(entry, count) {
246
- const meshes = entry.meshes;
247
-
248
- for (let i = count; i < meshes.length; i++) {
249
- const mesh = meshes[i];
250
-
251
- this.#batch.remove(mesh);
252
-
253
- // residency deliberately survives leaving the batch, so nothing else would free it
254
- this.#renderer.release(mesh.geometry);
255
- }
256
-
257
- meshes.length = count;
258
- }
259
-
260
- /**
261
- * @param {FrameContext} frame
262
- */
263
- record(frame) {
264
- if (this.#batch.count === 0) {
265
- // nothing to draw, and the frame's colour is left exactly as it was
266
- return;
267
- }
268
-
269
- const scene = frame.get(SceneColor);
270
-
271
- scene.color = this.#renderer.graph_draw({
272
- graph: frame.graph,
273
- batch: this.#batch,
274
- color: scene.color,
275
- depth: frame.get(ViewTextures).depth,
276
- camera: frame.view.camera.buffer
277
- });
278
- }
279
- }
280
-
281
- /**
282
- * The targeting arc draws into the scene, so it belongs before the fog of war that conceals it and
283
- * before the upscale that resolves it.
284
- */
285
- export class PathDisplayExtension extends RenderExtension {
286
- name = "path display";
287
-
288
- phase = FramePhase.AfterTransparency;
289
-
290
- /**
291
- * @type {PathDisplaySystem}
292
- */
293
- #system;
294
-
295
- /**
296
- * @param {PathDisplaySystem} system
297
- */
298
- constructor(system) {
299
- super();
300
-
301
- this.#system = system;
302
- }
303
-
304
- record(frame) {
305
- this.#system.record(frame);
306
- }
307
- }
1
+ import { assert } from "../../core/assert.js";
2
+ import { GPUDynamicMeshRenderer } from "../../shade/renderer/dynamic/GPUDynamicMeshRenderer.js";
3
+ import { Geometry } from "../../shade/renderer/geometry/Geometry.js";
4
+ import { FramePhase } from "../../shade/renderer/extension/FramePhase.js";
5
+ import { RenderExtension } from "../../shade/renderer/extension/RenderExtension.js";
6
+ import { SceneColor } from "../../shade/renderer/extension/SceneColor.js";
7
+ import { ViewTextures } from "../../shade/renderer/extension/ViewTextures.js";
8
+ import { DynamicMesh } from "../../shade/renderer/scene/DynamicMesh.js";
9
+ import { DynamicMeshBatch } from "../../shade/renderer/scene/DynamicMeshBatch.js";
10
+ import { System } from "../ecs/System.js";
11
+ import { PathDisplay } from "../graphics/ecs/path/PathDisplay.js";
12
+ import { PathDisplayEvents } from "../graphics/ecs/path/PathDisplayEvents.js";
13
+ import { PathDisplayType } from "../graphics/ecs/path/PathDisplayType.js";
14
+ import Path from "../navigation/ecs/components/Path.js";
15
+ import { PathEvents } from "../navigation/ecs/components/PathEvents.js";
16
+ import { build_tube_path_streams } from "./path/build_tube_path_streams.js";
17
+ import { tube_stream_write_geometry } from "./path/tube_stream_write_geometry.js";
18
+
19
+ /**
20
+ * Draws {@link PathDisplay}'s tubes under Shade, on the dynamic-geometry path.
21
+ *
22
+ * **A tube is dynamic content by definition** — the game's one path display is the combat targeting
23
+ * arc, rebuilt every time the cursor moves — so it goes down S4's dedicated non-meshlet path rather
24
+ * than becoming a `ShadedGeometry` behind the meshlet encode that D5/I-10 rule out (D21).
25
+ *
26
+ * **No owned entities.** The legacy system spawns a child entity per mask span because three needs
27
+ * an object-carrying entity per drawable; here the system holds the meshes itself, so link builds
28
+ * them, a path or style change rewrites them, and unlink releases them.
29
+ *
30
+ * **The geometry is kept and rewritten, never replaced.** GPU residency on this path is keyed on
31
+ * the geometry and survives leaving the batch, so a rebuild that produced a new one each time would
32
+ * strand a vertex buffer per cursor move.
33
+ *
34
+ * Tubes are all it draws. `PathDisplayType.Ribbon` retires with the three path (I-6c) and
35
+ * `PathDisplayType.Entity` builds marker entities that carry their own components — neither is
36
+ * this system's to render.
37
+ */
38
+ export class PathDisplaySystem extends System {
39
+ dependencies = [PathDisplay, Path];
40
+
41
+ /**
42
+ * @type {GraphicsEngine}
43
+ */
44
+ #graphics;
45
+
46
+ /**
47
+ * @type {GPUDynamicMeshRenderer}
48
+ */
49
+ #renderer = new GPUDynamicMeshRenderer();
50
+
51
+ /**
52
+ * @type {DynamicMeshBatch}
53
+ */
54
+ #batch = new DynamicMeshBatch();
55
+
56
+ /**
57
+ * @type {Map<number, {display: PathDisplay, path: Path, meshes: DynamicMesh[], listener: Function}>}
58
+ */
59
+ #entries = new Map();
60
+
61
+ /**
62
+ * Entities whose tubes are out of date. A path change and a style change both arrive as events,
63
+ * and the arc sends one of each per cursor move — coalescing to one rebuild a tick is what the
64
+ * queue is for.
65
+ *
66
+ * @type {Set<number>}
67
+ */
68
+ #dirty = new Set();
69
+
70
+ /**
71
+ * @type {RenderExtension|null}
72
+ */
73
+ #extension = null;
74
+
75
+ /**
76
+ * What the pass draws. Read-only — the meshes follow the entities.
77
+ *
78
+ * @returns {DynamicMeshBatch}
79
+ */
80
+ get batch() {
81
+ return this.#batch;
82
+ }
83
+
84
+ /**
85
+ * @param {GraphicsEngine} graphics
86
+ */
87
+ constructor(graphics) {
88
+ super();
89
+
90
+ assert.defined(graphics, 'graphics');
91
+
92
+ this.#graphics = graphics;
93
+ }
94
+
95
+ async startup(entityManager) {
96
+ this.entityManager = entityManager;
97
+
98
+ this.#extension = this.#graphics.add_extension(new PathDisplayExtension(this));
99
+ }
100
+
101
+ async shutdown(entityManager) {
102
+ if (this.#extension !== null) {
103
+ this.#graphics.remove_extension(this.#extension);
104
+
105
+ this.#extension = null;
106
+ }
107
+
108
+ this.#renderer.destroy();
109
+
110
+ this.#batch.clear();
111
+ this.#entries.clear();
112
+ this.#dirty.clear();
113
+ }
114
+
115
+ /**
116
+ * @param {PathDisplay} display
117
+ * @param {Path} path
118
+ * @param {number} entity
119
+ */
120
+ link(display, path, entity) {
121
+ const dataset = this.entityManager.dataset;
122
+
123
+ const listener = () => this.#dirty.add(entity);
124
+
125
+ dataset.addEntityEventListener(entity, PathEvents.Changed, listener);
126
+ dataset.addEntityEventListener(entity, PathDisplayEvents.Changed, listener);
127
+
128
+ const entry = { display, path, meshes: [], listener };
129
+
130
+ this.#entries.set(entity, entry);
131
+
132
+ this.#rebuild(entry);
133
+ }
134
+
135
+ /**
136
+ * @param {PathDisplay} display
137
+ * @param {Path} path
138
+ * @param {number} entity
139
+ */
140
+ unlink(display, path, entity) {
141
+ const entry = this.#entries.get(entity);
142
+
143
+ if (entry === undefined) {
144
+ return;
145
+ }
146
+
147
+ const dataset = this.entityManager.dataset;
148
+
149
+ dataset.removeEntityEventListener(entity, PathEvents.Changed, entry.listener);
150
+ dataset.removeEntityEventListener(entity, PathDisplayEvents.Changed, entry.listener);
151
+
152
+ this.#release(entry, 0);
153
+
154
+ this.#entries.delete(entity);
155
+ this.#dirty.delete(entity);
156
+ }
157
+
158
+ update(timeDelta) {
159
+ if (this.#dirty.size === 0) {
160
+ return;
161
+ }
162
+
163
+ for (const entity of this.#dirty) {
164
+ const entry = this.#entries.get(entity);
165
+
166
+ if (entry === undefined) {
167
+ continue;
168
+ }
169
+
170
+ this.#rebuild(entry);
171
+ }
172
+
173
+ this.#dirty.clear();
174
+ }
175
+
176
+ /**
177
+ * @param {{display: PathDisplay, path: Path, meshes: DynamicMesh[]}} entry
178
+ */
179
+ #rebuild(entry) {
180
+ const specs = entry.display.specs;
181
+
182
+ let written = 0;
183
+
184
+ for (let i = 0; i < specs.length; i++) {
185
+ const spec = specs[i];
186
+
187
+ if (spec.type !== PathDisplayType.Tube) {
188
+ continue;
189
+ }
190
+
191
+ const style = spec.style;
192
+
193
+ const streams = build_tube_path_streams(entry.path, style);
194
+
195
+ const color = style.color;
196
+
197
+ for (let j = 0; j < streams.length; j++) {
198
+ const mesh = this.#obtain_mesh(entry, written);
199
+
200
+ tube_stream_write_geometry(mesh.geometry, streams[j]);
201
+
202
+ mesh.color[0] = color.r;
203
+ mesh.color[1] = color.g;
204
+ mesh.color[2] = color.b;
205
+ mesh.color[3] = style.opacity;
206
+
207
+ written++;
208
+ }
209
+ }
210
+
211
+ this.#release(entry, written);
212
+ }
213
+
214
+ /**
215
+ * The mesh drawing span `index` of this entity, created on first use.
216
+ *
217
+ * @param {{meshes: DynamicMesh[]}} entry
218
+ * @param {number} index
219
+ * @returns {DynamicMesh}
220
+ */
221
+ #obtain_mesh(entry, index) {
222
+ const existing = entry.meshes[index];
223
+
224
+ if (existing !== undefined) {
225
+ return existing;
226
+ }
227
+
228
+ // the sweep produces world-space vertices, and what the draw applies is the mesh's own
229
+ // transform, identity until moved
230
+ const mesh = DynamicMesh.from(new Geometry());
231
+
232
+ entry.meshes.push(mesh);
233
+
234
+ this.#batch.add(mesh);
235
+
236
+ return mesh;
237
+ }
238
+
239
+ /**
240
+ * Give up every mesh from `count` on — a style whose mask lost a span, or an entity going away.
241
+ *
242
+ * @param {{meshes: DynamicMesh[]}} entry
243
+ * @param {number} count
244
+ */
245
+ #release(entry, count) {
246
+ const meshes = entry.meshes;
247
+
248
+ for (let i = count; i < meshes.length; i++) {
249
+ const mesh = meshes[i];
250
+
251
+ this.#batch.remove(mesh);
252
+
253
+ // residency deliberately survives leaving the batch, so nothing else would free it
254
+ this.#renderer.release(mesh.geometry);
255
+ }
256
+
257
+ meshes.length = count;
258
+ }
259
+
260
+ /**
261
+ * @param {FrameContext} frame
262
+ */
263
+ record(frame) {
264
+ if (this.#batch.count === 0) {
265
+ // nothing to draw, and the frame's colour is left exactly as it was
266
+ return;
267
+ }
268
+
269
+ const scene = frame.get(SceneColor);
270
+ const view = frame.get(ViewTextures);
271
+
272
+ const drawn = this.#renderer.graph_draw({
273
+ graph: frame.graph,
274
+ batch: this.#batch,
275
+ color: scene.color,
276
+ depth: view.depth,
277
+ camera: frame.view.camera.buffer
278
+ });
279
+
280
+ scene.color = drawn.color;
281
+ view.depth = drawn.depth;
282
+ }
283
+ }
284
+
285
+ /**
286
+ * The targeting arc draws into the scene, so it belongs before the fog of war that conceals it and
287
+ * before the upscale that resolves it.
288
+ */
289
+ export class PathDisplayExtension extends RenderExtension {
290
+ name = "path display";
291
+
292
+ phase = FramePhase.AfterTransparency;
293
+
294
+ /**
295
+ * @type {PathDisplaySystem}
296
+ */
297
+ #system;
298
+
299
+ /**
300
+ * @param {PathDisplaySystem} system
301
+ */
302
+ constructor(system) {
303
+ super();
304
+
305
+ this.#system = system;
306
+ }
307
+
308
+ record(frame) {
309
+ this.#system.record(frame);
310
+ }
311
+ }