@forgeax/engine-render 0.1.6 → 0.1.7

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 (151) hide show
  1. package/README.md +139 -0
  2. package/dist/.tsbuildinfo +1 -1
  3. package/dist/__tests__/gpu-driven-indirect-raster-evidence.d.ts.map +1 -1
  4. package/dist/assembly/factory.d.ts +7 -0
  5. package/dist/assembly/factory.d.ts.map +1 -1
  6. package/dist/assembly/material/assembly.d.ts.map +1 -1
  7. package/dist/authoring.d.ts +1 -0
  8. package/dist/authoring.d.ts.map +1 -1
  9. package/dist/authoring.mjs +1 -1
  10. package/dist/{chunk-QC4VW3XJ.mjs → chunk-HCOGPHKS.mjs} +17 -3
  11. package/dist/chunk-HCOGPHKS.mjs.map +1 -0
  12. package/dist/chunk-YEJIUW7D.mjs +1152 -0
  13. package/dist/chunk-YEJIUW7D.mjs.map +1 -0
  14. package/dist/components/__tests__/points-lines-admission.unit.test.d.ts +2 -0
  15. package/dist/components/__tests__/points-lines-admission.unit.test.d.ts.map +1 -0
  16. package/dist/components/__tests__/points-lines-schema.unit.test.d.ts +2 -0
  17. package/dist/components/__tests__/points-lines-schema.unit.test.d.ts.map +1 -0
  18. package/dist/components/index.d.ts +2 -0
  19. package/dist/components/index.d.ts.map +1 -1
  20. package/dist/components/lines.d.ts +5 -0
  21. package/dist/components/lines.d.ts.map +1 -0
  22. package/dist/components/points.d.ts +15 -0
  23. package/dist/components/points.d.ts.map +1 -0
  24. package/dist/construct-renderer.mjs +952 -497
  25. package/dist/construct-renderer.mjs.map +1 -1
  26. package/dist/errors/render.d.ts +82 -1
  27. package/dist/errors/render.d.ts.map +1 -1
  28. package/dist/features/render-graph-raster.d.ts +5 -1
  29. package/dist/features/render-graph-raster.d.ts.map +1 -1
  30. package/dist/gpu-driven/production-raster.d.ts.map +1 -1
  31. package/dist/index.d.ts +4 -0
  32. package/dist/index.d.ts.map +1 -1
  33. package/dist/index.mjs +4 -2
  34. package/dist/index.mjs.map +1 -1
  35. package/dist/inspection-types.d.ts +3 -0
  36. package/dist/inspection-types.d.ts.map +1 -1
  37. package/dist/pbr-pipeline.d.ts +2 -0
  38. package/dist/pbr-pipeline.d.ts.map +1 -1
  39. package/dist/plugin.d.ts.map +1 -1
  40. package/dist/points-lines/__tests__/bounds.unit.test.d.ts +2 -0
  41. package/dist/points-lines/__tests__/bounds.unit.test.d.ts.map +1 -0
  42. package/dist/points-lines/__tests__/cache-recovery.unit.test.d.ts +2 -0
  43. package/dist/points-lines/__tests__/cache-recovery.unit.test.d.ts.map +1 -0
  44. package/dist/points-lines/__tests__/inspection-lanes.integration.test.d.ts +2 -0
  45. package/dist/points-lines/__tests__/inspection-lanes.integration.test.d.ts.map +1 -0
  46. package/dist/points-lines/__tests__/inspection.unit.test.d.ts +2 -0
  47. package/dist/points-lines/__tests__/inspection.unit.test.d.ts.map +1 -0
  48. package/dist/points-lines/__tests__/lane-structural.integration.test.d.ts +2 -0
  49. package/dist/points-lines/__tests__/lane-structural.integration.test.d.ts.map +1 -0
  50. package/dist/points-lines/__tests__/points-lines.bench.d.ts +2 -0
  51. package/dist/points-lines/__tests__/points-lines.bench.d.ts.map +1 -0
  52. package/dist/points-lines/__tests__/raster-lane.integration.test.d.ts +2 -0
  53. package/dist/points-lines/__tests__/raster-lane.integration.test.d.ts.map +1 -0
  54. package/dist/points-lines/__tests__/snapshot.unit.test.d.ts +2 -0
  55. package/dist/points-lines/__tests__/snapshot.unit.test.d.ts.map +1 -0
  56. package/dist/points-lines/__tests__/view-ubo.unit.test.d.ts +2 -0
  57. package/dist/points-lines/__tests__/view-ubo.unit.test.d.ts.map +1 -0
  58. package/dist/points-lines/admission.d.ts +36 -0
  59. package/dist/points-lines/admission.d.ts.map +1 -0
  60. package/dist/points-lines/bounds.d.ts +8 -0
  61. package/dist/points-lines/bounds.d.ts.map +1 -0
  62. package/dist/points-lines/expansion-cache.d.ts +28 -0
  63. package/dist/points-lines/expansion-cache.d.ts.map +1 -0
  64. package/dist/points-lines/inspection.d.ts +53 -0
  65. package/dist/points-lines/inspection.d.ts.map +1 -0
  66. package/dist/points-lines/prepare.d.ts +67 -0
  67. package/dist/points-lines/prepare.d.ts.map +1 -0
  68. package/dist/points-lines/record.d.ts +60 -0
  69. package/dist/points-lines/record.d.ts.map +1 -0
  70. package/dist/points-lines/snapshot.d.ts +49 -0
  71. package/dist/points-lines/snapshot.d.ts.map +1 -0
  72. package/dist/record/frame-lighting.d.ts.map +1 -1
  73. package/dist/record/frame.d.ts +1 -1
  74. package/dist/record/frame.d.ts.map +1 -1
  75. package/dist/record/main-pass-geometry.d.ts +7 -0
  76. package/dist/record/main-pass-geometry.d.ts.map +1 -1
  77. package/dist/record/main-pass.d.ts.map +1 -1
  78. package/dist/record/render-context.d.ts +17 -0
  79. package/dist/record/render-context.d.ts.map +1 -1
  80. package/dist/record/shadow-pass.d.ts.map +1 -1
  81. package/dist/record/skybox-post-pass.d.ts.map +1 -1
  82. package/dist/record/typed-frame-graph.d.ts.map +1 -1
  83. package/dist/record/view-ubo.d.ts +6 -0
  84. package/dist/record/view-ubo.d.ts.map +1 -1
  85. package/dist/render-contract.d.ts +10 -2
  86. package/dist/render-contract.d.ts.map +1 -1
  87. package/dist/render-system-extract.d.ts +3 -0
  88. package/dist/render-system-extract.d.ts.map +1 -1
  89. package/dist/render-system.d.ts +4 -1
  90. package/dist/render-system.d.ts.map +1 -1
  91. package/dist/scene/render-scene-types.d.ts +3 -0
  92. package/dist/scene/render-scene-types.d.ts.map +1 -1
  93. package/dist/scene/render-scene.d.ts +7 -0
  94. package/dist/scene/render-scene.d.ts.map +1 -1
  95. package/package.json +19 -19
  96. package/src/__tests__/authoring-path.unit.test.ts +9 -0
  97. package/src/__tests__/gpu-driven-indirect-raster-evidence.ts +12 -2
  98. package/src/__tests__/material-cooked-projection.unit.test.ts +29 -7
  99. package/src/__tests__/material-static-dynamic-route.unit.test.ts +28 -28
  100. package/src/__tests__/render-error-code-owner.test-d.ts +6 -0
  101. package/src/__tests__/render-error-exhaustive.test-d.ts +19 -0
  102. package/src/__tests__/renderer-factory-material-contract.unit.test.ts +38 -0
  103. package/src/assembly/factory.ts +72 -3
  104. package/src/assembly/material/assembly.ts +2 -5
  105. package/src/authoring.ts +1 -0
  106. package/src/components/__tests__/points-lines-admission.unit.test.ts +219 -0
  107. package/src/components/__tests__/points-lines-schema.unit.test.ts +56 -0
  108. package/src/components/index.ts +7 -0
  109. package/src/components/lines.ts +18 -0
  110. package/src/components/points.ts +40 -0
  111. package/src/errors/render.ts +144 -1
  112. package/src/features/render-graph-raster.ts +22 -3
  113. package/src/gpu-driven/production-raster.ts +6 -1
  114. package/src/index.ts +17 -0
  115. package/src/inspection-types.ts +4 -0
  116. package/src/pbr-pipeline.ts +8 -1
  117. package/src/plugin.ts +4 -0
  118. package/src/points-lines/__tests__/bounds.unit.test.ts +21 -0
  119. package/src/points-lines/__tests__/cache-recovery.unit.test.ts +157 -0
  120. package/src/points-lines/__tests__/inspection-lanes.integration.test.ts +146 -0
  121. package/src/points-lines/__tests__/inspection.unit.test.ts +173 -0
  122. package/src/points-lines/__tests__/lane-structural.integration.test.ts +122 -0
  123. package/src/points-lines/__tests__/points-lines.bench.ts +400 -0
  124. package/src/points-lines/__tests__/raster-lane.integration.test.ts +44 -0
  125. package/src/points-lines/__tests__/snapshot.unit.test.ts +101 -0
  126. package/src/points-lines/__tests__/view-ubo.unit.test.ts +73 -0
  127. package/src/points-lines/admission.ts +238 -0
  128. package/src/points-lines/bounds.ts +23 -0
  129. package/src/points-lines/expansion-cache.ts +215 -0
  130. package/src/points-lines/inspection.ts +97 -0
  131. package/src/points-lines/prepare.ts +177 -0
  132. package/src/points-lines/record.ts +125 -0
  133. package/src/points-lines/snapshot.ts +130 -0
  134. package/src/record/frame-lighting.ts +14 -2
  135. package/src/record/frame.ts +12 -1
  136. package/src/record/main-pass-geometry.ts +92 -0
  137. package/src/record/main-pass-sprite-draws.ts +1 -1
  138. package/src/record/main-pass.ts +6 -3
  139. package/src/record/render-context.ts +20 -0
  140. package/src/record/shadow-pass.ts +16 -5
  141. package/src/record/skybox-post-pass.ts +2 -1
  142. package/src/record/typed-frame-graph.ts +17 -16
  143. package/src/record/view-ubo.ts +47 -0
  144. package/src/render-contract.ts +13 -2
  145. package/src/render-system-extract.ts +71 -8
  146. package/src/render-system.ts +304 -2
  147. package/src/scene/render-scene-types.ts +3 -0
  148. package/src/scene/render-scene.ts +57 -2
  149. package/dist/chunk-QC4VW3XJ.mjs.map +0 -1
  150. package/dist/chunk-SCRSAK5B.mjs +0 -480
  151. package/dist/chunk-SCRSAK5B.mjs.map +0 -1
@@ -1,5 +1,5 @@
1
- import { getTransparentSortConfig, TRANSPARENT_SORT_MODE_DISTANCE, GlyphText, TRANSPARENT_SORT_MODE_LAYER_Z, TRANSPARENT_SORT_MODE_LAYER_Y, TRANSPARENT_SORT_MODE_LAYER_YZ } from './chunk-QC4VW3XJ.mjs';
2
- import { MeshFilter, MeshRenderer, Instances, Layer, Visibility, Camera, DirectionalLight, PointLight, PointLightShadow, SpotLight, Skylight, SkyboxBackground, PostProcessParams, DEFAULT_STANDARD_PROFILE, RENDER_PHASE_CATALOG, DEFAULT_CLUSTER_GRID, STANDARD_TONEMAP_FEATURE_ID, STANDARD_PIPELINE_ID, STANDARD_LIGHT_COUNTS, resolveVisibility, CLUSTER_GRID_STRIDE_U32, LIGHT_INDEX_LIST_CAPACITY, resolveStandardLane, CAMERA_PROJECTION_ORTHOGRAPHIC, bloomEnabledFromF32, antialiasFromF32, tonemapFromF32, cameraProjectionFromF32, tonemapToU32, MAX_LIGHTS } from './chunk-SCRSAK5B.mjs';
1
+ import { getTransparentSortConfig, TRANSPARENT_SORT_MODE_DISTANCE, GlyphText, TRANSPARENT_SORT_MODE_LAYER_Z, TRANSPARENT_SORT_MODE_LAYER_Y, TRANSPARENT_SORT_MODE_LAYER_YZ, selectActiveCameraIndex, getActiveCamera } from './chunk-HCOGPHKS.mjs';
2
+ import { MeshFilter, MeshRenderer, Instances, Layer, Visibility, Camera, DirectionalLight, PointLight, PointLightShadow, SpotLight, Skylight, SkyboxBackground, PostProcessParams, RendererContractFailureError, RendererOperationError, DEFAULT_STANDARD_PROFILE, RENDER_PHASE_CATALOG, DEFAULT_CLUSTER_GRID, FrameReceiptStaleError, RenderFeatureStageFailedError, RenderFeatureCapabilityMissingError, RenderFeatureRegistrationConflictError, LifecycleConstructionError, STANDARD_TONEMAP_FEATURE_ID, PointsLinesMaterialUnsupportedError, admitPointsLines, STANDARD_PIPELINE_ID, STANDARD_LIGHT_COUNTS, ObservationUnavailableError, resolveVisibility, Points, Lines, SortKey, SkinMaterialMismatchError, MaterialSkinAttrMissingError, pointShapeFromU32, CLUSTER_GRID_STRIDE_U32, LIGHT_INDEX_LIST_CAPACITY, RenderFeaturePreparationFailedError, PointShadowAtlasUninitializedError, PointShadowAtlasBoundsViolationError, EquirectProjectionFailedError, HdrpLightBudgetExceededError, resolveStandardLane, CAMERA_PROJECTION_ORTHOGRAPHIC, PointsLinesPrepareFailedError, bloomEnabledFromF32, antialiasFromF32, tonemapFromF32, cameraProjectionFromF32, ShadowInvalidConfigError, tonemapToU32, MAX_LIGHTS, HdrpIndexListOverflowError, RenderFeaturePreparedStateMismatchError, RenderFeatureDrawRecordingFailedError, HdrpDeferredCapsInsufficientError, SkinPaletteOverflowError, VideoUploadUnsupportedError, VertexColorVariantConflictError } from './chunk-YEJIUW7D.mjs';
3
3
  import { SpriteInstances, SpriteRegionOverride, TileLayer, decodeSortScope, Tilemap, SPRITE_PREMULTIPLIED_ALPHA_BLEND } from './chunk-CQXZC5XI.mjs';
4
4
  import { AssetRegistry, DynamicTextureStore, adaptDynamicTextureDevice, getOrCreateMipmapPipeline, blitMipmapsSync, numMipLevels, HANDLE_CUBE, HANDLE_TRIANGLE, HANDLE_QUAD, HANDLE_SPHERE, HANDLE_NINESLICE_QUAD, BuiltinAssetRegistry, resolveAssetHandle, walkMaterialPassesOverSharedRefs, resolveTilesetRuntime, MeshSsboCeilingReachedError, MeshSsboCapacityExceededError } from '@forgeax/engine-assets-runtime';
5
5
  import { audioLoader } from '@forgeax/engine-audio-webaudio';
@@ -2261,422 +2261,6 @@ var RenderRecoveryError = class extends Error {
2261
2261
  this.detail = detail;
2262
2262
  }
2263
2263
  };
2264
-
2265
- // src/errors/render.ts
2266
- var LifecycleConstructionError = class extends Error {
2267
- code = "lifecycle-construction-failed";
2268
- expected = "all DeviceScope resources establish and terminate in order";
2269
- hint = "inspect detail and cleanupFailures, then recover or rebuild the DeviceScope";
2270
- detail;
2271
- constructor(detail) {
2272
- super(`lifecycle-construction-failed: ${detail.resourceKind} lifecycle transaction failed`);
2273
- this.name = "LifecycleConstructionError";
2274
- this.detail = detail;
2275
- }
2276
- };
2277
- var ShadowInvalidConfigError = class extends Error {
2278
- code = "shadow-invalid-config";
2279
- expected;
2280
- hint;
2281
- detail;
2282
- constructor(field, value, min, maxOrComparator) {
2283
- const isComparator = maxOrComparator === ">" || maxOrComparator === ">=";
2284
- const max = !isComparator && typeof maxOrComparator === "number" ? maxOrComparator : void 0;
2285
- const comparator = isComparator ? maxOrComparator : ">=";
2286
- const isInteger = field === "cascadeCount" || field === "pcfKernelSize";
2287
- const isOdd = field === "pcfKernelSize";
2288
- const hint = max !== void 0 ? isInteger ? `set ${field} to an integer in [${min}, ${max}]; got ${value}` : `set ${field} to a value in [${min}, ${max}]; got ${value}` : isOdd ? `set ${field} to an odd integer ${comparator} ${min}; got ${value}` : `set ${field} to a value ${comparator} ${min}; got ${value}`;
2289
- const expected = max !== void 0 ? `${field} in [${min}, ${max}]` : `${field} ${comparator} ${min}`;
2290
- super(
2291
- max !== void 0 ? `shadow component .${field} must be in [${min}, ${max}], got ${value}` : `shadow component .${field} must be ${comparator} ${min}, got ${value}`
2292
- );
2293
- this.name = "ShadowInvalidConfigError";
2294
- this.hint = hint;
2295
- this.expected = expected;
2296
- this.detail = { field, value, min, ...max !== void 0 ? { max } : {} };
2297
- }
2298
- };
2299
- var EquirectProjectionFailedError = class extends Error {
2300
- code = "equirect-projection-failed";
2301
- expected;
2302
- hint;
2303
- detail;
2304
- constructor(handle) {
2305
- const expected = `equirect handle ${handle} projects to a GPU cubemap + IBL precompute`;
2306
- const hint = `equirect handle ${handle} referenced by Skylight/SkyboxBackground failed projection; declare Skylight{equirect} with a valid HDR equirect source and check device.caps.rgba16floatRenderable. The projection is internal (no user upload call); the record arm does not retry`;
2307
- super(`equirect handle ${handle} cubemap projection failed`);
2308
- this.name = "EquirectProjectionFailedError";
2309
- this.expected = expected;
2310
- this.hint = hint;
2311
- this.detail = { handle };
2312
- }
2313
- };
2314
- var HdrpLightBudgetExceededError = class extends Error {
2315
- code = "hdrp-light-budget-exceeded";
2316
- expected;
2317
- hint;
2318
- detail;
2319
- constructor(actual, budget) {
2320
- const expected = `light count <= ${budget}`;
2321
- const hint = `${actual} lights exceed HDRP budget ${budget}; truncating to 256. Reduce light count or wait for OOS-hdrp-larger-budget`;
2322
- super(`HDRP light budget exceeded: ${actual} > ${budget}`);
2323
- this.name = "HdrpLightBudgetExceededError";
2324
- this.expected = expected;
2325
- this.hint = hint;
2326
- this.detail = { actual, budget };
2327
- }
2328
- };
2329
- var HdrpIndexListOverflowError = class extends Error {
2330
- code = "hdrp-index-list-overflow";
2331
- expected;
2332
- hint;
2333
- detail;
2334
- constructor(actual, capacity) {
2335
- const expected = `light index list entries <= ${capacity}`;
2336
- const hint = `cluster binner overflow: writeCount ${actual} exceeds capacity ${capacity}; reduce lights, shrink cluster grid, or increase LIGHT_INDEX_LIST_CAPACITY`;
2337
- super(`HDRP index list overflow: ${actual} > ${capacity}`);
2338
- this.name = "HdrpIndexListOverflowError";
2339
- this.expected = expected;
2340
- this.hint = hint;
2341
- this.detail = { actual, capacity };
2342
- }
2343
- };
2344
- var HdrpDeferredCapsInsufficientError = class extends Error {
2345
- code = "hdrp-deferred-caps-insufficient";
2346
- expected;
2347
- hint;
2348
- detail;
2349
- constructor(actual) {
2350
- const _expected = 4;
2351
- const _expectedStr = `maxColorAttachments >= ${_expected}`;
2352
- const hint = `WebGPU maxColorAttachments = ${actual} (need >= ${_expected}). Upgrade browser to latest Chrome/Edge/Safari, or use URP (forgeax::urp) instead of HDRP.`;
2353
- super(
2354
- `HDRP deferred caps insufficient: maxColorAttachments = ${actual} (need >= ${_expected})`
2355
- );
2356
- this.name = "HdrpDeferredCapsInsufficientError";
2357
- this.expected = _expectedStr;
2358
- this.hint = hint;
2359
- this.detail = { actual, expected: _expected };
2360
- }
2361
- };
2362
- var PointShadowAtlasUninitializedError = class extends Error {
2363
- code = "point-shadow-atlas-uninitialized";
2364
- expected;
2365
- hint;
2366
- constructor() {
2367
- const expected = "ShadowAtlas.ensure() invoked before faceView()";
2368
- const hint = "Gate faceView on isAllocated() or call ensure() once in the per-frame extract step before iterating face views";
2369
- super("ShadowAtlas.faceView called before ensure(); the cube_array texture is not allocated");
2370
- this.name = "PointShadowAtlasUninitializedError";
2371
- this.expected = expected;
2372
- this.hint = hint;
2373
- }
2374
- };
2375
- var PointShadowAtlasBoundsViolationError = class extends Error {
2376
- code = "point-shadow-atlas-bounds-violation";
2377
- expected;
2378
- hint;
2379
- detail;
2380
- constructor(axis, value, max) {
2381
- const expected = `0 <= ${axis} < ${max}`;
2382
- const hint = axis === "layer" ? `clamp PointLight.shadowAtlasLayer to [0, ${max}); the cap on layers equals PointLightShadow cardinality (4)` : `clamp face index to [0, ${max}); cube faces are indexed 0..5 in +X/-X/+Y/-Y/+Z/-Z order`;
2383
- super(`ShadowAtlas.faceView ${axis} out of range: ${value} (must be in [0, ${max}))`);
2384
- this.name = "PointShadowAtlasBoundsViolationError";
2385
- this.expected = expected;
2386
- this.hint = hint;
2387
- this.detail = { axis, value, max };
2388
- }
2389
- };
2390
- var VideoUploadUnsupportedError = class extends Error {
2391
- code = "video-upload-unsupported";
2392
- expected;
2393
- hint;
2394
- constructor() {
2395
- const expected = "at least one video upload path available: a host HTMLVideoElement (general copyExternalImageToTexture path) or GPUExternalTexture capability (high-perf path)";
2396
- const hint = "this backend exposes no usable video upload path; render a static texture instead, or switch to a WebGPU backend that supports video texture upload";
2397
- super("video upload unsupported \u2014 no general or high-perf path available");
2398
- this.name = "VideoUploadUnsupportedError";
2399
- this.expected = expected;
2400
- this.hint = hint;
2401
- }
2402
- };
2403
- var VertexColorVariantConflictError = class extends Error {
2404
- code = "vertex-color-variant-conflict";
2405
- expected;
2406
- hint;
2407
- detail;
2408
- constructor(authoredValue, projected) {
2409
- const expected = `VERTEX_COLOR_AVAILABLE=${projected ? "true" : "false"}`;
2410
- const hint = "derive the shader variant from the mesh VertexLayoutProjection; COLOR_0 presence is a geometry fact";
2411
- super(`authored vertex-color variant ${authoredValue} disagrees with projected ${expected}`);
2412
- this.name = "VertexColorVariantConflictError";
2413
- this.expected = expected;
2414
- this.hint = hint;
2415
- this.detail = { authored: true, authoredValue, projected };
2416
- }
2417
- };
2418
- var SkinPaletteOverflowError = class extends Error {
2419
- code = "skin-palette-overflow";
2420
- expected;
2421
- hint;
2422
- detail;
2423
- constructor(requestedBytes, limit) {
2424
- const expected = `skinned joint palette (${requestedBytes} B) fits within device.limits.maxStorageBufferBindingSize (${limit} B)`;
2425
- const hint = "palette buffer exceeds device maxStorageBufferBindingSize; reduce skinned entity count or split into multiple palette buffers (OOS-skin-palette-batch)";
2426
- super(`skin palette buffer needs ${requestedBytes} B, exceeds device limit ${limit} B`);
2427
- this.name = "SkinPaletteOverflowError";
2428
- this.expected = expected;
2429
- this.hint = hint;
2430
- this.detail = { requestedBytes, limit };
2431
- }
2432
- };
2433
- var SkinMaterialMismatchError = class extends Error {
2434
- code = "skin-material-mismatch";
2435
- expected;
2436
- hint;
2437
- detail;
2438
- constructor(entity, actualShader) {
2439
- const expected = "Skin entity's first material program module === 'forgeax::pbr-skin'";
2440
- const hint = `entity ${entity} has Skin but the first material program module is ${actualShader ?? "<empty>"}; load the mesh via the gltf importer (cooker auto-routes 'forgeax::pbr-skin' for skinned primitives), or remove the Skin component to render the entity unskinned with Materials.standard / Materials.unlit`;
2441
- super(
2442
- `Skin / material mismatch on entity ${entity}: expected forgeax::pbr-skin, got ${actualShader ?? "<empty>"}`
2443
- );
2444
- this.name = "SkinMaterialMismatchError";
2445
- this.expected = expected;
2446
- this.hint = hint;
2447
- this.detail = { entity, actualShader };
2448
- }
2449
- };
2450
- var MaterialSkinAttrMissingError = class extends Error {
2451
- code = "material-skin-attr-missing";
2452
- expected;
2453
- hint;
2454
- detail;
2455
- constructor(entity, missing2) {
2456
- const expected = "pbr-skin material requires mesh.attributes.skinIndex + skinWeight";
2457
- const hint = `entity ${entity} uses forgeax::pbr-skin but its MeshAsset is missing ${missing2}; switch the entity to a skinned glTF mesh authored with JOINTS_0 + WEIGHTS_0, or change the material to a non-skin shader via Materials.standard / Materials.unlit`;
2458
- super(
2459
- `material/skin attribute mismatch on entity ${entity}: pbr-skin material with mesh missing ${missing2}`
2460
- );
2461
- this.name = "MaterialSkinAttrMissingError";
2462
- this.expected = expected;
2463
- this.hint = hint;
2464
- this.detail = { entity, missing: missing2 };
2465
- }
2466
- };
2467
- var renderFeatureRecoveryHintByRecovery = {
2468
- "next-frame": (featureIdentity, stage) => `correct '${featureIdentity}' ${stage} data and retry on the next frame`,
2469
- "renderer-recover": (featureIdentity, _stage) => `wait for renderer recovery before retrying '${featureIdentity}'`,
2470
- registration: (featureIdentity, _stage) => `correct '${featureIdentity}' registration before retrying`
2471
- };
2472
- var RenderFeatureRegistrationConflictError = class extends Error {
2473
- code = "render-feature-registration-conflict";
2474
- expected;
2475
- hint;
2476
- detail;
2477
- constructor(featureIdentity, order, conflictingOrder) {
2478
- const expected = `feature identity '${featureIdentity}' is unique`;
2479
- const hint = `rename '${featureIdentity}' or remove the duplicate feature before registration`;
2480
- super(`render feature registration conflict for '${featureIdentity}'`);
2481
- this.name = "RenderFeatureRegistrationConflictError";
2482
- this.expected = expected;
2483
- this.hint = hint;
2484
- this.detail = { featureIdentity, order, conflictingOrder };
2485
- }
2486
- };
2487
- var RenderFeatureStageFailedError = class extends Error {
2488
- code = "render-feature-stage-failed";
2489
- expected;
2490
- hint;
2491
- detail;
2492
- constructor(featureIdentity, order, stage, recovery) {
2493
- const expected = `feature '${featureIdentity}' completes its ${stage} stage without an error`;
2494
- const hint = renderFeatureRecoveryHintByRecovery[recovery](featureIdentity, stage);
2495
- super(`render feature '${featureIdentity}' failed during ${stage}`);
2496
- this.name = "RenderFeatureStageFailedError";
2497
- this.expected = expected;
2498
- this.hint = hint;
2499
- this.detail = { featureIdentity, order, stage, recovery };
2500
- }
2501
- };
2502
- var RenderFeatureCapabilityMissingError = class extends Error {
2503
- code = "render-feature-capability-missing";
2504
- expected;
2505
- hint;
2506
- detail;
2507
- constructor(featureIdentity, order, capability) {
2508
- const expected = `device capability '${capability}' is available for feature '${featureIdentity}'`;
2509
- const hint = `disable '${featureIdentity}' or use a device with '${capability}' capability`;
2510
- super(`render feature '${featureIdentity}' requires missing capability '${capability}'`);
2511
- this.name = "RenderFeatureCapabilityMissingError";
2512
- this.expected = expected;
2513
- this.hint = hint;
2514
- this.detail = { featureIdentity, order, capability };
2515
- }
2516
- };
2517
- var RenderFeaturePreparationFailedError = class extends Error {
2518
- code = "render-feature-preparation-failed";
2519
- expected;
2520
- hint;
2521
- detail;
2522
- constructor(featureIdentity, order, operation, resourceKind, resourceName, reason, recovery) {
2523
- const expected = `${resourceKind} '${resourceName}' is prepared during ${operation}`;
2524
- const hint = `repair '${resourceName}' and retry feature '${featureIdentity}' on ${recovery}`;
2525
- super(`render feature '${featureIdentity}' preparation failed for '${resourceName}'`);
2526
- this.name = "RenderFeaturePreparationFailedError";
2527
- this.expected = expected;
2528
- this.hint = hint;
2529
- this.detail = {
2530
- featureIdentity,
2531
- order,
2532
- stage: "prepare",
2533
- operation,
2534
- resourceKind,
2535
- resourceName,
2536
- reason,
2537
- recovery
2538
- };
2539
- }
2540
- };
2541
- var RenderFeaturePreparedStateMismatchError = class extends Error {
2542
- code = "render-feature-prepared-state-mismatch";
2543
- expected;
2544
- hint;
2545
- detail;
2546
- constructor(detail) {
2547
- const expected = `${detail.resourceKind} state is compatible for '${detail.operation}'`;
2548
- const hint = `repair '${detail.reason}' for feature '${detail.featureIdentity}' and retry`;
2549
- super(`render feature '${detail.featureIdentity}' prepared state mismatch`);
2550
- this.name = "RenderFeaturePreparedStateMismatchError";
2551
- this.expected = expected;
2552
- this.hint = hint;
2553
- this.detail = detail;
2554
- }
2555
- };
2556
- var RenderFeatureDrawRecordingFailedError = class extends Error {
2557
- code = "render-feature-draw-recording-failed";
2558
- expected;
2559
- hint;
2560
- detail;
2561
- constructor(featureIdentity, order, operation, resourceKind, reason, backendReason, recovery) {
2562
- const expected = `${resourceKind} recording succeeds for '${operation}'`;
2563
- const hint = `repair '${reason}' and retry feature '${featureIdentity}' after ${recovery}`;
2564
- super(`render feature '${featureIdentity}' draw recording failed`);
2565
- this.name = "RenderFeatureDrawRecordingFailedError";
2566
- this.expected = expected;
2567
- this.hint = hint;
2568
- this.detail = {
2569
- featureIdentity,
2570
- order,
2571
- stage: "record",
2572
- operation,
2573
- resourceKind,
2574
- reason,
2575
- backendReason,
2576
- recovery
2577
- };
2578
- }
2579
- };
2580
- var ObservationUnavailableError = class extends Error {
2581
- code = "observation-unavailable";
2582
- expected;
2583
- hint;
2584
- detail;
2585
- constructor(reason, hint) {
2586
- let recovery;
2587
- if (reason === "copy-src") recovery = "enable-copy-src";
2588
- else if (reason === "resource" || reason === "readback-failed") recovery = "retry-readback";
2589
- else recovery = "draw-current-frame";
2590
- super(`current-frame observation unavailable: ${reason}`);
2591
- this.name = "ObservationUnavailableError";
2592
- this.expected = "a fresh producer-owned rgba16float current-frame observation";
2593
- this.hint = hint;
2594
- this.detail = { reason, recovery };
2595
- }
2596
- };
2597
- var FrameReceiptStaleError = class extends Error {
2598
- code = "frame-receipt-stale";
2599
- expected = "the observation receipt belongs to the active device generation";
2600
- hint = "draw a new frame and call observe with its returned FrameReceipt";
2601
- detail;
2602
- constructor(detail) {
2603
- super("frame receipt belongs to a retired device generation");
2604
- this.name = "FrameReceiptStaleError";
2605
- this.detail = detail;
2606
- }
2607
- };
2608
- var RendererContractFailureError = class extends Error {
2609
- code = "renderer-contract-failed";
2610
- expected = "renderer owner contracts remain valid at the public boundary";
2611
- hint = "repair the owner contract and retry the operation";
2612
- detail;
2613
- constructor(operation, cause) {
2614
- super(`renderer ${operation} contract failed: ${cause}`);
2615
- this.name = "RendererContractFailureError";
2616
- this.detail = { operation, cause };
2617
- }
2618
- };
2619
- var RENDERER_OPERATION_ERROR_POLICY = {
2620
- "world-lease-invalid": {
2621
- expected: "every render World is represented by a live lease owned by this Renderer",
2622
- hint: "attach the World to this Renderer and use the returned lease"
2623
- },
2624
- "frame-input-invalid": {
2625
- expected: "the frame input references attached leases and a valid immutable RenderProfile",
2626
- hint: "repair the frame input and retry draw or setProfile"
2627
- },
2628
- "scene-projection-failed": {
2629
- expected: "the attached World projects into the renderer-owned RenderScene",
2630
- hint: "inspect the structured cause, repair the World publication, and retry draw"
2631
- },
2632
- "asset-binding-failed": {
2633
- expected: "all frame assets resolve to valid renderer-owned bindings",
2634
- hint: "inspect the structured cause, rebuild or cold-cook the asset, and retry draw"
2635
- },
2636
- "feature-plan-failed": {
2637
- expected: "every installed RenderFeature produces a valid declarative plan",
2638
- hint: "inspect the feature cause, repair its plan, and retry draw"
2639
- },
2640
- "graph-build-failed": {
2641
- expected: "the Standard graph builds from the active profile and feature plans",
2642
- hint: "inspect the graph cause, repair the profile or plan, and retry"
2643
- },
2644
- "device-operation-failed": {
2645
- expected: "the active device generation completes the renderer-owned operation",
2646
- hint: "inspect renderer state and the structured cause, then retry or recover"
2647
- },
2648
- "surface-unavailable": {
2649
- expected: "the presentation surface accepts the requested lifecycle operation",
2650
- hint: "inspect renderer state, then restore the surface or create a new Renderer"
2651
- },
2652
- "renderer-state-invalid": {
2653
- expected: "the Renderer is in a state that accepts the requested operation",
2654
- hint: "inspect renderer state and choose retry, restoreSurface, recover, or stop"
2655
- },
2656
- "recovery-failed": {
2657
- expected: "one recovery attempt publishes a complete replacement device generation",
2658
- hint: "inspect the structured cause and retry recover after the host-selected delay"
2659
- },
2660
- "cleanup-failed": {
2661
- expected: "Renderer disposal completes every registered cleanup in reverse ownership order",
2662
- hint: "inspect the ordered cleanup causes; the Renderer remains disposed and must not be reused"
2663
- }
2664
- };
2665
- var RendererOperationError = class extends Error {
2666
- code;
2667
- expected;
2668
- hint;
2669
- detail;
2670
- constructor(code, detail) {
2671
- const policy = RENDERER_OPERATION_ERROR_POLICY[code];
2672
- super(`${code}: ${policy.expected}`);
2673
- this.name = "RendererOperationError";
2674
- this.code = code;
2675
- this.expected = policy.expected;
2676
- this.hint = policy.hint;
2677
- this.detail = detail;
2678
- }
2679
- };
2680
2264
  function stageFailure(identity) {
2681
2265
  return {
2682
2266
  code: "render-feature-stage-failed",
@@ -4627,6 +4211,11 @@ function buildPbrViewBglEntries(caps) {
4627
4211
  binding: 8,
4628
4212
  visibility: GPU_SHADER_STAGE_FRAGMENT,
4629
4213
  texture: { sampleType: "depth", viewDimension: "2d" }
4214
+ },
4215
+ {
4216
+ binding: 10,
4217
+ visibility: GPU_SHADER_STAGE_VERTEX,
4218
+ buffer: { type: "uniform", hasDynamicOffset: true }
4630
4219
  }
4631
4220
  ];
4632
4221
  }
@@ -4831,7 +4420,7 @@ function buildBindGroupLayoutDescriptor(spec, options) {
4831
4420
  const desc = createHdrpBindGroupLayoutDescriptor(caps.storageBuffer);
4832
4421
  return {
4833
4422
  label: desc.label ?? "hdrp-unified-bgl-group2",
4834
- entries: desc.entries ?? []
4423
+ entries: [...desc.entries ?? []]
4835
4424
  };
4836
4425
  }
4837
4426
  case "fullscreen-post": {
@@ -7598,12 +7187,47 @@ function clampPcfKernelSize(value) {
7598
7187
 
7599
7188
  // src/record/view-ubo.ts
7600
7189
  var VIEW_UNIFORM_BYTES = 960;
7190
+ var POINTS_LINES_VIEW_BYTES = 160;
7191
+ var POINTS_LINES_VIEW_SLOT_STRIDE = 256;
7192
+ var POINTS_LINES_VIEW_SLOT_COUNT = 1024;
7193
+ var POINTS_LINES_VIEW_BUFFER_SIZE = POINTS_LINES_VIEW_SLOT_STRIDE * POINTS_LINES_VIEW_SLOT_COUNT;
7601
7194
  var VIEW_UNIFORM_SLOT_STRIDE = 1024;
7602
7195
  var POINT_SHADOW_VIEW_SLOT_COUNT = 24;
7603
7196
  var VIEW_UNIFORM_BUFFER_SIZE = VIEW_UNIFORM_SLOT_STRIDE * (1 + POINT_SHADOW_VIEW_SLOT_COUNT);
7604
7197
  function pointShadowViewOffset(layer, face) {
7605
7198
  return VIEW_UNIFORM_SLOT_STRIDE * (1 + layer * 6 + face);
7606
7199
  }
7200
+ function writePointsLinesViewUbo(queue, buffer, camera, width, height, model, style, byteOffset = 0) {
7201
+ const projection = computeProjectionMatrix(camera);
7202
+ const view = computeViewMatrix(camera);
7203
+ const worldViewProj = mat4.create();
7204
+ mat4.multiply(worldViewProj, projection, view);
7205
+ const payload = new Float32Array(40);
7206
+ payload.set(worldViewProj);
7207
+ if (model === void 0) {
7208
+ payload[16] = 1;
7209
+ payload[21] = 1;
7210
+ payload[26] = 1;
7211
+ payload[31] = 1;
7212
+ } else {
7213
+ for (let index = 0; index < 16; index += 1) {
7214
+ payload[16 + index] = model[index] ?? 0;
7215
+ }
7216
+ }
7217
+ payload[32] = width;
7218
+ payload[33] = height;
7219
+ if (style?.kind === "points") {
7220
+ payload[36] = style.sizePx;
7221
+ payload[38] = style.shape === "circle" ? 1 : 0;
7222
+ } else if (style?.kind === "lines") {
7223
+ payload[36] = style.widthPx;
7224
+ payload[37] = 1;
7225
+ } else {
7226
+ payload[36] = 1;
7227
+ }
7228
+ const uploaded = queue.writeBuffer(buffer, byteOffset, payload);
7229
+ if (!uploaded.ok) throw uploaded.error;
7230
+ }
7607
7231
  function writeViewUbo(queue, viewUniformBuffer, camera, light, lights, spotShadowSnapshots) {
7608
7232
  const projMatrix = computeProjectionMatrix(camera);
7609
7233
  const viewMatrix = computeViewMatrix(camera);
@@ -7719,7 +7343,8 @@ function buildPerFrameBindGroups(internals, frameState, pipelineState, hasValida
7719
7343
  shadowSampler,
7720
7344
  b5View,
7721
7345
  pipelineState.shadowParamsBuffer,
7722
- b8View
7346
+ b8View,
7347
+ pipelineState.pointsLinesViewBuffer ?? pipelineState.viewUniformBuffer
7723
7348
  ],
7724
7349
  "view-main",
7725
7350
  () => {
@@ -7806,11 +7431,22 @@ function buildPerFrameBindGroups(internals, frameState, pipelineState, hasValida
7806
7431
  kind: "textureView",
7807
7432
  value: b8View
7808
7433
  }
7434
+ },
7435
+ {
7436
+ binding: 10,
7437
+ resource: {
7438
+ kind: "buffer",
7439
+ value: {
7440
+ buffer: pipelineState.pointsLinesViewBuffer ?? pipelineState.viewUniformBuffer,
7441
+ size: POINTS_LINES_VIEW_BYTES
7442
+ }
7443
+ }
7809
7444
  }
7810
7445
  // feat-20260625-spot-light-shadow-mapping w25: the per-spot
7811
7446
  // fragment-read lightViewProj matrices fold into the View UBO
7812
7447
  // (binding 0, `view.spotLightViewProj`) — no standalone binding 9
7813
- // (WebGL2 fragment uniform-buffer budget). binding 8 is the last.
7448
+ // (WebGL2 fragment uniform-buffer budget). binding 10 is the
7449
+ // dedicated vertex-only Points/Lines viewport UBO.
7814
7450
  ]
7815
7451
  });
7816
7452
  if (!viewBindGroupResult.ok) throw viewBindGroupResult.error;
@@ -8306,6 +7942,53 @@ function resolveSkyboxActive(internals, frameState, camera, skybox) {
8306
7942
  return { tonemapActive, skyboxActive };
8307
7943
  }
8308
7944
 
7945
+ // src/points-lines/record.ts
7946
+ var POINTS_LINES_MATERIAL_SHADER_ID = "forgeax::points-lines";
7947
+ function createPointsLinesLaneContract(lane, backend) {
7948
+ return {
7949
+ lane,
7950
+ backend,
7951
+ authoring: "shared",
7952
+ evidence: backend === "null" ? "structural-only" : "runtime",
7953
+ material: {
7954
+ source: "MaterialAsset",
7955
+ shaderId: POINTS_LINES_MATERIAL_SHADER_ID,
7956
+ shadingModel: "unlit",
7957
+ clusteredLighting: false,
7958
+ lit: false
7959
+ },
7960
+ graph: { additionalAttachments: 0, additionalPasses: 0 },
7961
+ shadowDrawCount: 0,
7962
+ capabilities: { compute: false, storage: false, indirect: false }
7963
+ };
7964
+ }
7965
+ function createPointsLinesRecordPlan(snapshot, geometry, contract) {
7966
+ const primitiveCount = geometry.pointCount + geometry.segmentCount;
7967
+ const visible = snapshot.visible && primitiveCount > 0;
7968
+ const conservativeMarginPx = snapshot.style?.kind === "points" ? snapshot.style.sizePx * 0.5 : (snapshot.style?.widthPx ?? 0) * 0.5;
7969
+ return {
7970
+ lane: contract.lane,
7971
+ backend: contract.backend,
7972
+ materialShaderId: POINTS_LINES_MATERIAL_SHADER_ID,
7973
+ component: geometry.component,
7974
+ meshGeneration: snapshot.meshGeneration,
7975
+ materialGeneration: snapshot.materialGeneration,
7976
+ vertexCount: primitiveCount * 4,
7977
+ indexCount: primitiveCount * 6,
7978
+ drawCount: visible ? 1 : 0,
7979
+ shadowDrawCount: 0,
7980
+ conservativeMarginPx,
7981
+ graph: contract.graph
7982
+ };
7983
+ }
7984
+ function createPointsLinesLaneAdapter(lane, backend) {
7985
+ const contract = createPointsLinesLaneContract(lane, backend);
7986
+ return {
7987
+ contract,
7988
+ createRecordPlan: (snapshot, geometry) => createPointsLinesRecordPlan(snapshot, geometry, contract)
7989
+ };
7990
+ }
7991
+
8309
7992
  // src/record/render-context.ts
8310
7993
  var STANDARD_PBR_UBO_SIZE = 304;
8311
7994
  var MATERIAL_PER_ENTITY_STRIDE = 512;
@@ -8894,6 +8577,14 @@ function submitSubmeshDraws(pass, state, pipeline, instanceDraws, indexed, index
8894
8577
  }
8895
8578
  }
8896
8579
  }
8580
+ function recordPointsLinesDraw(pass, plan) {
8581
+ if (plan.drawCount === 0) return;
8582
+ if (plan.indexCount > 0) {
8583
+ pass.drawIndexed(plan.indexCount, 1, 0, 0, 0);
8584
+ return;
8585
+ }
8586
+ pass.draw(plan.vertexCount, 1, 0, 0);
8587
+ }
8897
8588
  function recordGeometryDraws(c, pass, matchedMaterials, materialSlotIndices, sampleCount, meshGroup2, meshBindGroup, resolveMaterialBindGroup, passKind = "forward") {
8898
8589
  const {
8899
8590
  runtime,
@@ -8904,7 +8595,8 @@ function recordGeometryDraws(c, pass, matchedMaterials, materialSlotIndices, sam
8904
8595
  tonemapActive,
8905
8596
  msaaActive,
8906
8597
  validatedOrdered,
8907
- splitLdrSprite
8598
+ splitLdrSprite,
8599
+ viewBindGroup
8908
8600
  } = c;
8909
8601
  const colorFormatOverride = c.transparentColorFormat;
8910
8602
  let lastVertexBuffer = null;
@@ -8989,6 +8681,9 @@ function recordGeometryDraws(c, pass, matchedMaterials, materialSlotIndices, sam
8989
8681
  for (let i = 0; i < validatedOrdered.length; i++) {
8990
8682
  const entry = validatedOrdered[i];
8991
8683
  if (entry === void 0) continue;
8684
+ const pointsLinesSubmission = c.pointsLines?.prepare(entry, frameState.isHdrpActive);
8685
+ if (entry.source.pointsLines !== void 0 && pointsLinesSubmission?.plan.drawCount !== 1)
8686
+ continue;
8992
8687
  if (passKind === "forward" && c.gpuDrivenEntityKeys.has(worldEntityKey(entry.source.worldId, entry.source.entityKey))) {
8993
8688
  continue;
8994
8689
  }
@@ -8999,6 +8694,66 @@ function recordGeometryDraws(c, pass, matchedMaterials, materialSlotIndices, sam
8999
8694
  pass.setStencilReference(stencilReference);
9000
8695
  lastStencilReference = stencilReference;
9001
8696
  }
8697
+ if (pointsLinesSubmission !== void 0 && entry.source.pointsLines !== void 0) {
8698
+ const pointsLinesMaterial = {
8699
+ ...entry.source.material,
8700
+ materialShaderId: POINTS_LINES_MATERIAL_SHADER_ID,
8701
+ materialParamSchema: runtime.getParamSchema?.(POINTS_LINES_MATERIAL_SHADER_ID) ?? entry.source.material.materialParamSchema
8702
+ };
8703
+ const materialSlot = materialSlotIndices[i]?.[0] ?? 0;
8704
+ const pointsLinesBindGroup = resolveMaterialBindGroup(
8705
+ materialSlot,
8706
+ pointsLinesMaterial,
8707
+ entry.source.entityKey,
8708
+ entry.world ?? c.world
8709
+ );
8710
+ const pointsLinesPipeline = resolveMaterialPipeline(
8711
+ POINTS_LINES_MATERIAL_SHADER_ID,
8712
+ {
8713
+ ...pointsLinesMaterial.renderState,
8714
+ cullMode: "none"
8715
+ },
8716
+ "triangle-list",
8717
+ "uint32",
8718
+ void 0,
8719
+ colorFormatOverride,
8720
+ pointsLinesSubmission.layoutProjection
8721
+ );
8722
+ if (pointsLinesPipeline === null) continue;
8723
+ if (pipelineState.pointsLinesViewBuffer === void 0) continue;
8724
+ writePointsLinesViewUbo(
8725
+ runtime.device.queue,
8726
+ pipelineState.pointsLinesViewBuffer,
8727
+ c.camera,
8728
+ c.targetW,
8729
+ c.targetH,
8730
+ entry.source.transform.world,
8731
+ entry.source.pointsLines.style,
8732
+ i * POINTS_LINES_VIEW_SLOT_STRIDE
8733
+ );
8734
+ pass.setBindGroup(
8735
+ 0,
8736
+ viewBindGroup,
8737
+ new Uint32Array([i * POINTS_LINES_VIEW_SLOT_STRIDE]),
8738
+ 0,
8739
+ 1
8740
+ );
8741
+ pass.setPipeline(pointsLinesPipeline);
8742
+ pass.setVertexBuffer(0, pointsLinesSubmission.vertexBuffer);
8743
+ pass.setIndexBuffer(pointsLinesSubmission.indexBuffer, "uint32");
8744
+ materialGroup1DynamicOffsets[0] = materialSlot * MATERIAL_PER_ENTITY_STRIDE;
8745
+ pass.setBindGroup(1, pointsLinesBindGroup, materialGroup1DynamicOffsets, 0, 1);
8746
+ const pointsLinesMeshGroup = meshBindGroup ?? meshGroup2;
8747
+ if (pointsLinesMeshGroup === null) continue;
8748
+ pass.setBindGroup(2, pointsLinesMeshGroup, meshGroup2DynamicOffsets);
8749
+ pass.setBindGroup(
8750
+ 3,
8751
+ identityInstanceDraws[0]?.instanceBindGroup ?? resolveGeometryInstancesBindGroup(c, identityInstanceBuffer)
8752
+ );
8753
+ recordPointsLinesDraw(pass, pointsLinesSubmission.plan);
8754
+ pass.setBindGroup(0, viewBindGroup, [0]);
8755
+ continue;
8756
+ }
9002
8757
  if (entry.mesh.vertexBuffer !== lastVertexBuffer) {
9003
8758
  pass.setVertexBuffer(0, entry.mesh.vertexBuffer.handle);
9004
8759
  lastVertexBuffer = entry.mesh.vertexBuffer;
@@ -9468,7 +9223,7 @@ function recordSpritePass(c, pass, matchedIndices, materialSlotIndices, sampleCo
9468
9223
  { colorLoadOp: "load", depthLoadOp: "load" }
9469
9224
  )
9470
9225
  );
9471
- spritePass.setBindGroup(0, viewBindGroup);
9226
+ spritePass.setBindGroup(0, viewBindGroup, [0]);
9472
9227
  const spritePremulBlend = {
9473
9228
  depthWriteEnabled: false,
9474
9229
  depthCompare: "less-equal",
@@ -10160,7 +9915,8 @@ function ensureTypedShadowViewBg(c, viewOffset, cascadeOffset, variant) {
10160
9915
  pipelineState.shadowAtlasFallbackTextureView,
10161
9916
  pipelineState.shadowParamsBuffer,
10162
9917
  pipelineState.shadowCasterCascadeBuffer,
10163
- pipelineState.shadowFallbackTextureView
9918
+ pipelineState.shadowFallbackTextureView,
9919
+ pipelineState.pointsLinesViewBuffer ?? pipelineState.viewUniformBuffer
10164
9920
  ],
10165
9921
  variant,
10166
9922
  () => {
@@ -10213,13 +9969,23 @@ function ensureTypedShadowViewBg(c, viewOffset, cascadeOffset, variant) {
10213
9969
  value: {
10214
9970
  buffer: pipelineState.shadowCasterCascadeBuffer,
10215
9971
  offset: cascadeOffset,
10216
- size: 80
9972
+ size: POINTS_LINES_VIEW_BYTES
10217
9973
  }
10218
9974
  }
10219
9975
  },
10220
9976
  {
10221
9977
  binding: 8,
10222
9978
  resource: { kind: "textureView", value: pipelineState.shadowFallbackTextureView }
9979
+ },
9980
+ {
9981
+ binding: 10,
9982
+ resource: {
9983
+ kind: "buffer",
9984
+ value: {
9985
+ buffer: pipelineState.pointsLinesViewBuffer ?? pipelineState.viewUniformBuffer,
9986
+ size: 80
9987
+ }
9988
+ }
10223
9989
  }
10224
9990
  ]
10225
9991
  });
@@ -10270,7 +10036,7 @@ function encodeDirectionalShadowPass(c, pass, cascadeIndex, viewport) {
10270
10036
  c.frameState.directionalShadowCacheRecorded = true;
10271
10037
  pass.setViewport(viewport.x, viewport.y, viewport.w, viewport.h, 0, 1);
10272
10038
  pass.setPipeline(pipeline);
10273
- pass.setBindGroup(0, viewBg);
10039
+ pass.setBindGroup(0, viewBg, [0]);
10274
10040
  pass.setBindGroup(1, materialBg, [0]);
10275
10041
  recordShadowCasterDraws(
10276
10042
  c,
@@ -10294,7 +10060,7 @@ function encodePointShadowPass(c, pass, snapshotIndex, face) {
10294
10060
  const materialBg = ensureSpotShadowMaterialBg(c);
10295
10061
  if (pipeline === null || viewBg === null || materialBg === null) return;
10296
10062
  pass.setPipeline(pipeline);
10297
- pass.setBindGroup(0, viewBg);
10063
+ pass.setBindGroup(0, viewBg, [0]);
10298
10064
  pass.setBindGroup(1, materialBg, [0]);
10299
10065
  recordShadowCasterDraws(
10300
10066
  c,
@@ -10332,7 +10098,7 @@ function encodeSpotShadowPass(c, pass, snapshotIndex) {
10332
10098
  1
10333
10099
  );
10334
10100
  pass.setPipeline(pipeline);
10335
- pass.setBindGroup(0, viewBg);
10101
+ pass.setBindGroup(0, viewBg, [0]);
10336
10102
  pass.setBindGroup(1, materialBg, [0]);
10337
10103
  recordShadowCasterDraws(
10338
10104
  c,
@@ -10819,8 +10585,8 @@ function recordMainPass(c, selector, options, graphPass) {
10819
10585
  owner.world ?? world
10820
10586
  );
10821
10587
  }
10822
- const resolveMaterialBindGroup = (materialSlot, material, entityKey, materialWorld = world) => preparedMaterialBindGroups[materialSlot] ?? buildPerSubmeshMaterialBg2(material, entityKey, materialWorld);
10823
- pass.setBindGroup(0, viewBindGroup);
10588
+ const resolveMaterialBindGroup = (materialSlot, material, entityKey, materialWorld = world) => material.materialShaderId !== POINTS_LINES_MATERIAL_SHADER_ID ? preparedMaterialBindGroups[materialSlot] ?? buildPerSubmeshMaterialBg2(material, entityKey, materialWorld) : buildPerSubmeshMaterialBg2(material, entityKey, materialWorld);
10589
+ pass.setBindGroup(0, viewBindGroup, [0]);
10824
10590
  const recordGeometry = () => {
10825
10591
  recordGeometryDraws(
10826
10592
  c,
@@ -10910,7 +10676,7 @@ function recordSkyboxPass(c, graphPass) {
10910
10676
  binding: 2,
10911
10677
  resource: {
10912
10678
  kind: "buffer",
10913
- value: { buffer: pipelineState.viewUniformBuffer }
10679
+ value: { buffer: pipelineState.viewUniformBuffer, size: VIEW_UNIFORM_BYTES }
10914
10680
  }
10915
10681
  },
10916
10682
  {
@@ -14829,7 +14595,7 @@ var GpuDrivenProduction = class {
14829
14595
  return ok({
14830
14596
  accesses,
14831
14597
  encode: (viewBindGroup, pass, resources) => {
14832
- pass.setBindGroup(0, viewBindGroup);
14598
+ pass.setBindGroup(0, viewBindGroup, [0]);
14833
14599
  let currentPipeline;
14834
14600
  let currentVertex;
14835
14601
  let currentIndex;
@@ -14905,6 +14671,305 @@ var GpuDrivenProduction = class {
14905
14671
  }
14906
14672
  };
14907
14673
 
14674
+ // src/points-lines/expansion-cache.ts
14675
+ var PointsLinesExpansionCache = class {
14676
+ entries = /* @__PURE__ */ new Map();
14677
+ getOrCreate(snapshot, mesh) {
14678
+ if (snapshot.component === void 0 || snapshot.style === void 0) {
14679
+ return emptyGeometry(snapshot);
14680
+ }
14681
+ const key = expansionKey(snapshot);
14682
+ const cached = this.entries.get(key);
14683
+ if (cached !== void 0) return cached;
14684
+ const geometry = deriveExpandedGeometry(snapshot, mesh, key);
14685
+ this.entries.set(key, geometry);
14686
+ return geometry;
14687
+ }
14688
+ clear() {
14689
+ this.entries.clear();
14690
+ }
14691
+ get size() {
14692
+ return this.entries.size;
14693
+ }
14694
+ };
14695
+ function expansionKey(snapshot) {
14696
+ return `mesh:${snapshot.meshHandle}:${snapshot.meshGeneration}:component=${snapshot.component ?? "none"}`;
14697
+ }
14698
+ function deriveExpandedGeometry(snapshot, mesh, key) {
14699
+ const component = snapshot.component;
14700
+ const style = snapshot.style;
14701
+ if (component === void 0 || style === void 0) return emptyGeometry(snapshot);
14702
+ const sourcePositions = readPositions(mesh);
14703
+ const positions = [];
14704
+ const vertices = [];
14705
+ const indices = [];
14706
+ let sourceCursor = 0;
14707
+ let pointCount = 0;
14708
+ let segmentCount = 0;
14709
+ const expectedTopology = component === "Points" ? "point-list" : "line-list";
14710
+ for (const submesh of mesh.submeshes) {
14711
+ if (submesh.topology !== expectedTopology) {
14712
+ sourceCursor += submesh.vertexCount;
14713
+ continue;
14714
+ }
14715
+ const elements = mesh.indices !== void 0 && submesh.indexCount > 0 ? Array.from(
14716
+ mesh.indices.slice(submesh.indexOffset, submesh.indexOffset + submesh.indexCount)
14717
+ ) : Array.from({ length: submesh.vertexCount }, (_, index) => sourceCursor + index);
14718
+ for (const index of elements) {
14719
+ const offset2 = index * 3;
14720
+ positions.push(
14721
+ sourcePositions[offset2] ?? 0,
14722
+ sourcePositions[offset2 + 1] ?? 0,
14723
+ sourcePositions[offset2 + 2] ?? 0
14724
+ );
14725
+ }
14726
+ if (component === "Points") {
14727
+ pointCount += elements.length;
14728
+ for (const index of elements) {
14729
+ const offset2 = index * 3;
14730
+ const position = [
14731
+ sourcePositions[offset2] ?? 0,
14732
+ sourcePositions[offset2 + 1] ?? 0,
14733
+ sourcePositions[offset2 + 2] ?? 0
14734
+ ];
14735
+ if (style.kind !== "points") continue;
14736
+ emitQuad(vertices, indices, position, position);
14737
+ }
14738
+ } else {
14739
+ segmentCount += Math.floor(elements.length / 2);
14740
+ for (let element = 0; element + 1 < elements.length; element += 2) {
14741
+ const startOffset = (elements[element] ?? 0) * 3;
14742
+ const endOffset = (elements[element + 1] ?? 0) * 3;
14743
+ emitQuad(
14744
+ vertices,
14745
+ indices,
14746
+ [
14747
+ sourcePositions[startOffset] ?? 0,
14748
+ sourcePositions[startOffset + 1] ?? 0,
14749
+ sourcePositions[startOffset + 2] ?? 0
14750
+ ],
14751
+ [
14752
+ sourcePositions[endOffset] ?? 0,
14753
+ sourcePositions[endOffset + 1] ?? 0,
14754
+ sourcePositions[endOffset + 2] ?? 0
14755
+ ]
14756
+ );
14757
+ }
14758
+ }
14759
+ sourceCursor += submesh.vertexCount;
14760
+ }
14761
+ const expanded = new Float32Array(positions);
14762
+ const expandedVertices = new Float32Array(vertices);
14763
+ const expandedIndices = new Uint32Array(indices);
14764
+ return {
14765
+ key,
14766
+ component,
14767
+ positions: expanded,
14768
+ vertices: expandedVertices,
14769
+ indices: expandedIndices,
14770
+ sourceVertexCount: sourcePositions.length / 3,
14771
+ pointCount,
14772
+ segmentCount,
14773
+ expandedVertexCount: expandedVertices.length / 8,
14774
+ expandedIndexCount: expandedIndices.length,
14775
+ sourceBytes: mesh.vertices.byteLength + (mesh.indices?.byteLength ?? 0),
14776
+ derivedBytes: expandedVertices.byteLength + expandedIndices.byteLength,
14777
+ bounds: boundsOf(expanded)
14778
+ };
14779
+ }
14780
+ function emitQuad(vertices, indices, start, end) {
14781
+ const base = vertices.length / 8;
14782
+ const corners = [
14783
+ [-1, -1],
14784
+ [-1, 1],
14785
+ [1, -1],
14786
+ [1, 1]
14787
+ ];
14788
+ for (const [x, y] of corners) {
14789
+ vertices.push(
14790
+ start[0] ?? 0,
14791
+ start[1] ?? 0,
14792
+ start[2] ?? 0,
14793
+ end[0] ?? 0,
14794
+ end[1] ?? 0,
14795
+ end[2] ?? 0,
14796
+ x,
14797
+ y
14798
+ );
14799
+ }
14800
+ indices.push(base, base + 1, base + 2, base + 2, base + 1, base + 3);
14801
+ }
14802
+ function readPositions(mesh) {
14803
+ const attribute = mesh.attributes.position;
14804
+ if (attribute instanceof Float32Array && attribute.length >= 3) {
14805
+ return attribute;
14806
+ }
14807
+ const vertexCount = mesh.vertices.length / 3;
14808
+ const stride = vertexCount > 0 ? mesh.vertices.length / vertexCount : 3;
14809
+ const positions = new Float32Array(vertexCount * 3);
14810
+ for (let vertex = 0; vertex < vertexCount; vertex += 1) {
14811
+ const source = vertex * stride;
14812
+ positions.set(mesh.vertices.subarray(source, source + 3), vertex * 3);
14813
+ }
14814
+ return positions;
14815
+ }
14816
+ function boundsOf(positions) {
14817
+ if (positions.length === 0) return new Float32Array(0);
14818
+ const bounds = new Float32Array([Infinity, Infinity, Infinity, -Infinity, -Infinity, -Infinity]);
14819
+ for (let index = 0; index < positions.length; index += 3) {
14820
+ bounds[0] = Math.min(bounds[0] ?? Infinity, positions[index] ?? 0);
14821
+ bounds[1] = Math.min(bounds[1] ?? Infinity, positions[index + 1] ?? 0);
14822
+ bounds[2] = Math.min(bounds[2] ?? Infinity, positions[index + 2] ?? 0);
14823
+ bounds[3] = Math.max(bounds[3] ?? -Infinity, positions[index] ?? 0);
14824
+ bounds[4] = Math.max(bounds[4] ?? -Infinity, positions[index + 1] ?? 0);
14825
+ bounds[5] = Math.max(bounds[5] ?? -Infinity, positions[index + 2] ?? 0);
14826
+ }
14827
+ return bounds;
14828
+ }
14829
+ function emptyGeometry(snapshot) {
14830
+ return {
14831
+ key: `empty:${snapshot.entityKey}`,
14832
+ component: snapshot.component ?? "Points",
14833
+ positions: new Float32Array(0),
14834
+ vertices: new Float32Array(0),
14835
+ indices: new Uint32Array(0),
14836
+ sourceVertexCount: 0,
14837
+ pointCount: 0,
14838
+ segmentCount: 0,
14839
+ expandedVertexCount: 0,
14840
+ expandedIndexCount: 0,
14841
+ sourceBytes: 0,
14842
+ derivedBytes: 0,
14843
+ bounds: new Float32Array(0)
14844
+ };
14845
+ }
14846
+
14847
+ // src/points-lines/inspection.ts
14848
+ function inspectPointsLines(input) {
14849
+ const { snapshot } = input;
14850
+ if (snapshot.component === void 0) {
14851
+ throw new Error("Points/Lines inspection requires a retained style component");
14852
+ }
14853
+ return {
14854
+ entityKey: snapshot.entityKey,
14855
+ worldId: snapshot.worldId,
14856
+ component: snapshot.component,
14857
+ meshHandle: snapshot.meshHandle,
14858
+ meshGeneration: snapshot.meshGeneration,
14859
+ materialHandle: snapshot.materialHandle,
14860
+ materialGeneration: snapshot.materialGeneration,
14861
+ style: snapshot.style ?? { kind: "lines", widthPx: 0 },
14862
+ topology: input.topology,
14863
+ lane: input.lane,
14864
+ pointCount: input.pointCount,
14865
+ segmentCount: input.segmentCount,
14866
+ sourceBytes: input.sourceBytes,
14867
+ derivedBytes: input.derivedBytes,
14868
+ cache: { ...input.cache },
14869
+ drawCount: input.drawCount,
14870
+ uploadBytes: input.uploadBytes,
14871
+ lastKnownGood: input.lastKnownGood,
14872
+ ...input.refusal === void 0 ? {} : { refusal: { ...input.refusal } }
14873
+ };
14874
+ }
14875
+ var PointsLinesPreparation = class {
14876
+ constructor(options) {
14877
+ this.options = options;
14878
+ }
14879
+ options;
14880
+ live;
14881
+ pendingCandidateBytes = 0;
14882
+ status = "empty";
14883
+ uploads = 0;
14884
+ rebuilds = 0;
14885
+ failures = 0;
14886
+ prepare(snapshot, mesh) {
14887
+ const geometry = this.options.cache.getOrCreate(snapshot, mesh);
14888
+ if (this.live?.geometry.key === geometry.key) {
14889
+ this.live = {
14890
+ ...this.live,
14891
+ snapshot,
14892
+ uploadedBytes: 0
14893
+ };
14894
+ this.status = "resident";
14895
+ return ok$1(this.live);
14896
+ }
14897
+ this.pendingCandidateBytes = geometry.derivedBytes;
14898
+ const created = this.options.adapter.create(geometry);
14899
+ if (!created.ok) return this.fail(snapshot, created.error);
14900
+ const candidate = created.value;
14901
+ const uploaded = this.options.adapter.upload(candidate, geometry);
14902
+ if (!uploaded.ok) {
14903
+ this.options.adapter.destroy(candidate);
14904
+ return this.fail(snapshot, uploaded.error);
14905
+ }
14906
+ this.uploads += 1;
14907
+ const validated = this.options.adapter.validate(candidate, geometry);
14908
+ if (!validated.ok) {
14909
+ this.options.adapter.destroy(candidate);
14910
+ return this.fail(snapshot, validated.error);
14911
+ }
14912
+ const previous = this.live;
14913
+ this.live = {
14914
+ resource: candidate,
14915
+ geometry,
14916
+ snapshot,
14917
+ uploadedBytes: uploaded.value,
14918
+ lastKnownGood: true
14919
+ };
14920
+ this.pendingCandidateBytes = 0;
14921
+ this.status = "resident";
14922
+ this.rebuilds += 1;
14923
+ if (previous !== void 0) this.options.adapter.destroy(previous.resource);
14924
+ return ok$1(this.live);
14925
+ }
14926
+ resetForDeviceLoss() {
14927
+ if (this.live !== void 0) this.options.adapter.destroy(this.live.resource);
14928
+ this.live = void 0;
14929
+ this.pendingCandidateBytes = 0;
14930
+ this.status = "rebuild-pending";
14931
+ }
14932
+ lastKnownGood() {
14933
+ return this.live;
14934
+ }
14935
+ inspect() {
14936
+ return {
14937
+ status: this.status,
14938
+ lastKnownGood: this.live?.lastKnownGood ?? false,
14939
+ liveResource: this.live?.resource,
14940
+ liveBytes: this.live?.geometry.derivedBytes ?? 0,
14941
+ candidateBytes: this.pendingCandidateBytes,
14942
+ cacheEntries: this.options.cache.size,
14943
+ uploads: this.uploads,
14944
+ rebuilds: this.rebuilds,
14945
+ failures: this.failures
14946
+ };
14947
+ }
14948
+ fail(snapshot, cause) {
14949
+ this.pendingCandidateBytes = 0;
14950
+ this.failures += 1;
14951
+ return err$1(
14952
+ new PointsLinesPrepareFailedError({
14953
+ owner: "points-lines",
14954
+ generation: snapshot.meshGeneration,
14955
+ stage: "prepare",
14956
+ cause: cause.message,
14957
+ lastKnownGood: this.live !== void 0
14958
+ })
14959
+ );
14960
+ }
14961
+ };
14962
+ function createPointsLinesLanePreparationAdapter(lane, backend, options) {
14963
+ const preparation = new PointsLinesPreparation(options);
14964
+ return {
14965
+ contract: createPointsLinesLaneContract(lane, backend),
14966
+ prepare: (snapshot, mesh) => preparation.prepare(snapshot, mesh),
14967
+ resetForDeviceLoss: () => preparation.resetForDeviceLoss(),
14968
+ lastKnownGood: () => preparation.lastKnownGood(),
14969
+ inspect: () => preparation.inspect()
14970
+ };
14971
+ }
14972
+
14908
14973
  // src/instance-buffer-cache.ts
14909
14974
  function destroyInstanceBufferEntries(entries, errorRegistry) {
14910
14975
  for (const entry of entries) {
@@ -15515,10 +15580,12 @@ var RenderFeatureRasterGraphProjection = class {
15515
15580
  for (const [group, reference] of draw.bindings.entries()) {
15516
15581
  const binding = resolved(snapshot, reference);
15517
15582
  if (binding?.kind !== "bindings") throw missing(featureIdentity, order);
15518
- const handle = binding.handle ?? this.resolveBindings?.({ frame, binding, resources, resolveTarget: this.resolveTarget });
15583
+ const resolvedBindings = binding.handle ?? this.resolveBindings?.({ frame, binding, resources, resolveTarget: this.resolveTarget });
15584
+ const handle = resolvedBindings !== void 0 && typeof resolvedBindings === "object" && "handle" in resolvedBindings ? resolvedBindings.handle : resolvedBindings;
15519
15585
  if (handle === void 0) throw missing(featureIdentity, order);
15520
15586
  const targetGroup = binding.descriptor?.values.group ?? group;
15521
- pass.setBindGroup(targetGroup, handle, binding.dynamicOffsets);
15587
+ const dynamicOffsets = resolvedBindings !== void 0 && typeof resolvedBindings === "object" && "handle" in resolvedBindings ? resolvedBindings.dynamicOffsets : binding.dynamicOffsets;
15588
+ pass.setBindGroup(targetGroup, handle, dynamicOffsets);
15522
15589
  }
15523
15590
  for (const vertex of draw.vertexData) {
15524
15591
  const handle = buffers.vertex.get(vertex.resource);
@@ -15786,7 +15853,7 @@ function resolveTargetBindings(input) {
15786
15853
  return created2.value;
15787
15854
  }
15788
15855
  if (sceneDepth === void 0) {
15789
- return buildPerFrameBindGroups(
15856
+ const viewBindGroup = buildPerFrameBindGroups(
15790
15857
  frame.runtime,
15791
15858
  frame.frameState,
15792
15859
  frame.pipelineState,
@@ -15796,7 +15863,8 @@ function resolveTargetBindings(input) {
15796
15863
  directionalShadow: frame.frameState.currentDirectionalShadowView ?? void 0,
15797
15864
  spotShadow: frame.frameState.currentSpotShadowView ?? void 0
15798
15865
  }
15799
- ).viewBindGroup ?? void 0;
15866
+ ).viewBindGroup;
15867
+ return viewBindGroup === null ? void 0 : { handle: viewBindGroup, dynamicOffsets: [0] };
15800
15868
  }
15801
15869
  const target3 = input.resolveTarget(sceneDepth);
15802
15870
  if (target3 === void 0) return void 0;
@@ -16023,7 +16091,7 @@ var EMPTY_ENTITY_KEYS = /* @__PURE__ */ new Set();
16023
16091
  function runRecordProfilePhase(runner, phase, action) {
16024
16092
  return runner === void 0 ? action() : runner(phase, action);
16025
16093
  }
16026
- function recordFrame(internals, world, cameras, lights, renderables, transparentDispatch, frameState, dispatchCounts, bindGroupCounts, skylight, skylightCount, skybox, skyboxCount, postProcessParams, worlds = [world], profilePhase, gpuDriven, renderReadLeases, featureGraphCandidate) {
16094
+ function recordFrame(internals, world, cameras, lights, renderables, transparentDispatch, frameState, dispatchCounts, bindGroupCounts, skylight, skylightCount, skybox, skyboxCount, postProcessParams, worlds = [world], profilePhase, gpuDriven, renderReadLeases, featureGraphCandidate, pointsLinesOwner) {
16027
16095
  let activeCameras = cameras;
16028
16096
  if (activeCameras.length === 0) {
16029
16097
  internals.errorRegistry.fire(
@@ -16220,6 +16288,15 @@ function recordFrame(internals, world, cameras, lights, renderables, transparent
16220
16288
  lights,
16221
16289
  frameState.spotShadowSnapshots
16222
16290
  );
16291
+ if (pipelineState.pointsLinesViewBuffer !== void 0) {
16292
+ writePointsLinesViewUbo(
16293
+ internals.device.queue,
16294
+ pipelineState.pointsLinesViewBuffer,
16295
+ camera,
16296
+ targetW,
16297
+ targetH
16298
+ );
16299
+ }
16223
16300
  writeShadowCasterUniforms(
16224
16301
  internals.device.queue,
16225
16302
  pipelineState.shadowCasterCascadeBuffer,
@@ -16310,6 +16387,7 @@ function recordFrame(internals, world, cameras, lights, renderables, transparent
16310
16387
  materialSlots,
16311
16388
  materialSlotOwners,
16312
16389
  materialSlotCount,
16390
+ pointsLines: pointsLinesOwner,
16313
16391
  materialBgAssemblyCache: frameState.materialBgAssemblyCache,
16314
16392
  directionalShadowCacheReuse: directionalShadowCache.reuse,
16315
16393
  ...profilePhase !== void 0 ? { profilePhase } : {}
@@ -16788,15 +16866,19 @@ function resolveMeshMaterialBindings(mesh, rendererOverrides, deps) {
16788
16866
  return { ok: true, bindings, diagnostics };
16789
16867
  }
16790
16868
 
16791
- // src/systems/active-camera.ts
16792
- var ACTIVE_CAMERA_KEY = "ActiveCamera";
16793
- function getActiveCamera(world) {
16794
- if (!world.hasResource(ACTIVE_CAMERA_KEY)) return void 0;
16795
- return world.getResource(ACTIVE_CAMERA_KEY);
16796
- }
16797
- function selectActiveCameraIndex(cameraEntities, activeEntity) {
16798
- if (activeEntity === void 0) return -1;
16799
- return cameraEntities.indexOf(activeEntity);
16869
+ // src/points-lines/bounds.ts
16870
+ function expandPointsLinesBounds(bounds, style) {
16871
+ if (bounds.length < 6 || style === void 0) return new Float32Array(bounds);
16872
+ const marginPx = style.kind === "points" ? style.sizePx * 0.5 : style.widthPx * 0.5;
16873
+ if (!Number.isFinite(marginPx) || marginPx <= 0) return new Float32Array(bounds);
16874
+ return new Float32Array([
16875
+ (bounds[0] ?? 0) - marginPx,
16876
+ (bounds[1] ?? 0) - marginPx,
16877
+ (bounds[2] ?? 0) - marginPx,
16878
+ (bounds[3] ?? 0) + marginPx,
16879
+ (bounds[4] ?? 0) + marginPx,
16880
+ (bounds[5] ?? 0) + marginPx
16881
+ ]);
16800
16882
  }
16801
16883
 
16802
16884
  // src/render-system-extract.ts
@@ -16832,7 +16914,9 @@ function appendMaterialDispatchEntries(pendingDispatch, passes, entity, material
16832
16914
  layer,
16833
16915
  tags: passState.tags ?? {},
16834
16916
  renderState: pipelineRenderState(passState),
16835
- defines: pass.program.moduleSlots,
16917
+ // Material module identity is already closed in the cooked program.
16918
+ // It must never become a draw-time define map.
16919
+ defines: void 0,
16836
16920
  vertexEntry: pass.program.vertexEntry,
16837
16921
  fragmentEntry: pass.program.fragmentEntry,
16838
16922
  materialShaderId: runtimeMaterialShaderId(pass.program.module, pass.name),
@@ -17023,7 +17107,7 @@ function isEngineInjectedTextureField(shaderId, fieldName) {
17023
17107
  function materialParametersToParamSchema(parameters, shaderId) {
17024
17108
  const engineInjectedTextureFields = isStandardPbrMaterialShader(shaderId) ? ENGINE_INJECTED_TEXTURE_FIELDS : void 0;
17025
17109
  return parameters.flatMap((parameter) => {
17026
- if (parameter.static || parameter.type === "bool") return [];
17110
+ if (parameter.type === "bool") return [];
17027
17111
  if (parameter.type === "texture") {
17028
17112
  if (engineInjectedTextureFields?.has(parameter.name) === true) return [];
17029
17113
  return [{ name: parameter.name, type: "texture2d" }];
@@ -17536,9 +17620,9 @@ function extractFrames(worlds, owner, assets, pipelineState, materialSnapshotCac
17536
17620
  });
17537
17621
  const frame = extractFrame(world, prepared);
17538
17622
  framesByWorld.set(wi, frame);
17539
- } catch (err28) {
17623
+ } catch (err29) {
17540
17624
  try {
17541
- createWorldInternalView(world)._routeError(err28, {
17625
+ createWorldInternalView(world)._routeError(err29, {
17542
17626
  severity: Severity.Error,
17543
17627
  systemName: `RenderSystem.extractFrames(world[${wi}])`
17544
17628
  });
@@ -17571,7 +17655,11 @@ function extractFrames(worlds, owner, assets, pipelineState, materialSnapshotCac
17571
17655
  }
17572
17656
  hiddenEntityReports.push(...f.hiddenEntityReports);
17573
17657
  for (const r of f.renderables) {
17574
- renderables.push({ ...r, worldId: wId });
17658
+ renderables.push({
17659
+ ...r,
17660
+ worldId: wId,
17661
+ ...r.pointsLines === void 0 ? {} : { pointsLines: { ...r.pointsLines, worldId: wId } }
17662
+ });
17575
17663
  }
17576
17664
  for (const d of f.dispatch) {
17577
17665
  dispatchEntries.push({ ...d, renderableIndex: (d.renderableIndex ?? 0) + base });
@@ -18133,7 +18221,10 @@ function extractFrame(world, context) {
18133
18221
  Layer,
18134
18222
  MorphWeights,
18135
18223
  SpriteRegionOverride,
18136
- SpriteInstances
18224
+ SpriteInstances,
18225
+ Points,
18226
+ Lines,
18227
+ SortKey
18137
18228
  ]
18138
18229
  }).unwrap();
18139
18230
  const resolveArchVersion = (entity) => {
@@ -18164,6 +18255,10 @@ function extractFrame(world, context) {
18164
18255
  const hasMeshFilter = meshFilter !== void 0;
18165
18256
  const hasSkin = skin !== void 0;
18166
18257
  const hasSpriteInstances = row.has(SpriteInstances);
18258
+ const points = row.get(Points);
18259
+ const lines = row.get(Lines);
18260
+ const sortKey = row.get(SortKey)?.value;
18261
+ const pointsLinesComponent = points !== void 0 ? "Points" : lines !== void 0 ? "Lines" : void 0;
18167
18262
  const isRenderable = hasTransform && hasMeshFilter;
18168
18263
  const fAssetHandle = meshFilter?.assetHandle;
18169
18264
  const fLayerValue = row.get(Layer)?.value;
@@ -18361,28 +18456,28 @@ function extractFrame(world, context) {
18361
18456
  } else {
18362
18457
  const resolvedResult = walkMaterialPassesOverSharedRefs(world, tagged, assets);
18363
18458
  if (!resolvedResult.ok) {
18364
- const err28 = resolvedResult.error;
18365
- switch (err28.code) {
18459
+ const err29 = resolvedResult.error;
18460
+ switch (err29.code) {
18366
18461
  case "material-parent-not-found":
18367
18462
  case "material-no-effective-pass":
18368
18463
  case "material-value-unknown":
18369
18464
  case "material-value-type-mismatch":
18370
18465
  case "material-contract-program-mismatch":
18371
- worldInternal._routeError(err28, {
18466
+ worldInternal._routeError(err29, {
18372
18467
  severity: Severity.Error,
18373
- systemName: `RenderSystem.extract (${err28.code})`
18468
+ systemName: `RenderSystem.extract (${err29.code})`
18374
18469
  });
18375
18470
  break;
18376
18471
  case "material-circular-inheritance":
18377
- worldInternal._routeError(err28, {
18472
+ worldInternal._routeError(err29, {
18378
18473
  severity: Severity.Error,
18379
18474
  systemName: "RenderSystem.extract (material-circular-inheritance)"
18380
18475
  });
18381
18476
  break;
18382
18477
  default:
18383
- worldInternal._routeError(err28, {
18478
+ worldInternal._routeError(err29, {
18384
18479
  severity: Severity.Error,
18385
- systemName: `RenderSystem.extract (_materialWalk: ${err28.code})`
18480
+ systemName: `RenderSystem.extract (_materialWalk: ${err29.code})`
18386
18481
  });
18387
18482
  }
18388
18483
  continue;
@@ -18841,6 +18936,27 @@ function extractFrame(world, context) {
18841
18936
  }
18842
18937
  }
18843
18938
  }
18939
+ const pointsLinesStyle = points !== void 0 ? (() => {
18940
+ const shape = pointShapeFromU32(points.shape);
18941
+ return shape === void 0 ? void 0 : { kind: "points", sizePx: points.sizePx, shape };
18942
+ })() : lines === void 0 ? void 0 : { kind: "lines", widthPx: lines.widthPx };
18943
+ const cullingLocalAabb = expandPointsLinesBounds(localAabb ?? [], pointsLinesStyle);
18944
+ const pointsLines = pointsLinesComponent === void 0 ? void 0 : {
18945
+ worldId: 0,
18946
+ entityKey: entity,
18947
+ component: pointsLinesComponent,
18948
+ meshHandle: assetHandleRaw,
18949
+ meshGeneration: assets?.catalogEpoch ?? 0,
18950
+ materialHandle: handleRaw,
18951
+ materialGeneration: assets?.catalogEpoch ?? 0,
18952
+ style: pointsLinesStyle,
18953
+ layer: layerVal,
18954
+ sortKey,
18955
+ visible: true,
18956
+ sourceBounds: cullingLocalAabb,
18957
+ viewport: { width: 0, height: 0, dpr: 1 },
18958
+ projection: identityProjection()
18959
+ };
18844
18960
  let nonIndexedFirst = 0;
18845
18961
  const gpuDrivenDraws = gpuDrivenSubmeshes.flatMap((submesh) => {
18846
18962
  const drawMaterial = materialsArr[submesh.materialSlot] ?? materialSnap;
@@ -18863,7 +18979,7 @@ function extractFrame(world, context) {
18863
18979
  const baseRenderable = {
18864
18980
  assetHandle: Math.round(fAssetHandle ?? 0),
18865
18981
  transform: transformSnap,
18866
- ...localAabb !== void 0 ? { localAabb } : {},
18982
+ ...localAabb !== void 0 ? { localAabb: cullingLocalAabb } : {},
18867
18983
  material: materialSnap,
18868
18984
  materials: materialsArr,
18869
18985
  materialBindingSources,
@@ -18873,11 +18989,12 @@ function extractFrame(world, context) {
18873
18989
  entityKey: entity,
18874
18990
  ...skinSlice !== void 0 ? { skin: skinSlice } : {},
18875
18991
  ...morph !== void 0 ? { morph } : {},
18876
- ...spriteInstancesSnap !== void 0 ? { spriteInstances: spriteInstancesSnap } : {}
18992
+ ...spriteInstancesSnap !== void 0 ? { spriteInstances: spriteInstancesSnap } : {},
18993
+ ...pointsLines !== void 0 ? { pointsLines } : {}
18877
18994
  };
18878
18995
  if (localAabb !== void 0) {
18879
18996
  const worldAabb = box3.create();
18880
- box3.transformBox3(worldAabb, localAabb, transformSnap.world);
18997
+ box3.transformBox3(worldAabb, cullingLocalAabb, transformSnap.world);
18881
18998
  frustumTotal += 1;
18882
18999
  let visible = frustumPlanes.length === 0;
18883
19000
  for (let ci = 0; ci < frustumPlanes.length; ci++) {
@@ -18968,6 +19085,14 @@ function defaultMaterialSnapshot(materialHandle = 0) {
18968
19085
  materialHandle
18969
19086
  };
18970
19087
  }
19088
+ function identityProjection() {
19089
+ const projection = new Float32Array(16);
19090
+ projection[0] = 1;
19091
+ projection[5] = 1;
19092
+ projection[10] = 1;
19093
+ projection[15] = 1;
19094
+ return projection;
19095
+ }
18971
19096
 
18972
19097
  // src/gpu-driven/batch-topology.ts
18973
19098
  function eligibleKeys(slot) {
@@ -19762,7 +19887,17 @@ function ownSnapshot(snapshot) {
19762
19887
  transforms: new Float32Array(snapshot.spriteInstances.transforms),
19763
19888
  regions: new Float32Array(snapshot.spriteInstances.regions)
19764
19889
  }
19765
- }
19890
+ },
19891
+ ...snapshot.pointsLines === void 0 ? {} : { pointsLines: ownPointsLinesSnapshot(snapshot.pointsLines) }
19892
+ };
19893
+ }
19894
+ function ownPointsLinesSnapshot(snapshot) {
19895
+ return {
19896
+ ...snapshot,
19897
+ style: snapshot.style === void 0 ? void 0 : { ...snapshot.style },
19898
+ sourceBounds: new Float32Array(snapshot.sourceBounds),
19899
+ viewport: { ...snapshot.viewport },
19900
+ projection: new Float32Array(snapshot.projection)
19766
19901
  };
19767
19902
  }
19768
19903
  function withWorld(snapshot, world) {
@@ -19803,6 +19938,10 @@ var RenderScene = class {
19803
19938
  freeSlots = [];
19804
19939
  slotsByWorld = /* @__PURE__ */ new Map();
19805
19940
  slotsByMaterial = /* @__PURE__ */ new Map();
19941
+ // World bounds are a pure projection of each immutable snapshot. Cache the
19942
+ // derived value by snapshot identity so repeated view queries do not redo
19943
+ // the eight-corner transform for every view in the same frame.
19944
+ worldBoundsCache = /* @__PURE__ */ new WeakMap();
19806
19945
  orderedSlots = [];
19807
19946
  materialized;
19808
19947
  slotSnapshot;
@@ -19994,6 +20133,17 @@ var RenderScene = class {
19994
20133
  snapshot(worldId, entityKey) {
19995
20134
  return this.lookup(worldId, entityKey)?.snapshot;
19996
20135
  }
20136
+ pointsLinesSnapshots() {
20137
+ const snapshots = [];
20138
+ for (const slot of this.orderedSlots) {
20139
+ const record = this.slots[slot];
20140
+ const snapshot = record?.snapshot.pointsLines;
20141
+ if (record !== void 0 && snapshot !== void 0) {
20142
+ snapshots.push(ownPointsLinesSnapshot({ ...snapshot, worldId: record.worldId }));
20143
+ }
20144
+ }
20145
+ return Object.freeze(snapshots);
20146
+ }
19997
20147
  slotsForMaterial(materialHandle) {
19998
20148
  const slots = this.slotsByMaterial.get(materialHandle);
19999
20149
  if (slots === void 0) return [];
@@ -20009,8 +20159,14 @@ var RenderScene = class {
20009
20159
  for (const slot of this.orderedSlots) {
20010
20160
  const record = this.slots[slot];
20011
20161
  if (record === void 0) continue;
20012
- const candidate = worldBounds(record.snapshot);
20013
- if (candidate !== void 0 && intersects(candidate, bounds)) records.push(record);
20162
+ const snapshot = record.snapshot;
20163
+ let candidate = this.worldBoundsCache.get(snapshot);
20164
+ if (candidate === void 0 && !this.worldBoundsCache.has(snapshot)) {
20165
+ candidate = worldBounds(snapshot) ?? null;
20166
+ this.worldBoundsCache.set(snapshot, candidate);
20167
+ }
20168
+ if (candidate === null || candidate === void 0) continue;
20169
+ if (intersects(candidate, bounds)) records.push(record);
20014
20170
  }
20015
20171
  return records;
20016
20172
  }
@@ -20230,6 +20386,7 @@ var PersistentRenderScene = class {
20230
20386
  gpuOwner;
20231
20387
  gpuDevice;
20232
20388
  gpuStatus = "inactive";
20389
+ pointsLinesInspections = [];
20233
20390
  /** RenderScene owns the per-World material fact cache used during extraction. */
20234
20391
  materialSnapshotCacheStore() {
20235
20392
  return this.materialSnapshotCaches;
@@ -20375,6 +20532,13 @@ var PersistentRenderScene = class {
20375
20532
  this.gpuDevice = void 0;
20376
20533
  this.gpuStatus = this.options.getDevice === void 0 ? "inactive" : "rebuild-pending";
20377
20534
  }
20535
+ setPointsLinesInspections(inspections) {
20536
+ this.pointsLinesInspections = inspections.map((inspection) => ({
20537
+ ...inspection,
20538
+ cache: { ...inspection.cache },
20539
+ ...inspection.refusal === void 0 ? {} : { refusal: { ...inspection.refusal } }
20540
+ }));
20541
+ }
20378
20542
  dispose() {
20379
20543
  this.gpuScene?.dispose();
20380
20544
  this.gpuScene = void 0;
@@ -20382,9 +20546,11 @@ var PersistentRenderScene = class {
20382
20546
  this.gpuDevice = void 0;
20383
20547
  this.gpuStatus = "inactive";
20384
20548
  this.composition = void 0;
20549
+ this.pointsLinesInspections = [];
20385
20550
  }
20386
20551
  inspect() {
20387
20552
  const entry = this.composition;
20553
+ const pointsLines = this.pointsLinesInspections;
20388
20554
  return {
20389
20555
  worldEntitiesScanned: this.worldEntitiesScanned,
20390
20556
  fullRebuilds: this.fullRebuilds,
@@ -20401,7 +20567,8 @@ var PersistentRenderScene = class {
20401
20567
  patches: 0,
20402
20568
  ineligible: 0
20403
20569
  },
20404
- gpu: this.inspectGpu()
20570
+ gpu: this.inspectGpu(),
20571
+ pointsLines
20405
20572
  };
20406
20573
  }
20407
20574
  compositionGpuDrivenState() {
@@ -20415,6 +20582,9 @@ var PersistentRenderScene = class {
20415
20582
  slots: entry.projection.slotsSnapshot()
20416
20583
  };
20417
20584
  }
20585
+ pointsLinesSnapshots() {
20586
+ return this.composition?.projection.pointsLinesSnapshots() ?? [];
20587
+ }
20418
20588
  inspectGpu() {
20419
20589
  if (this.gpuStatus === "resident" && this.gpuScene !== void 0) {
20420
20590
  return { status: "resident", ...this.gpuScene.inspect() };
@@ -20628,6 +20798,242 @@ function makePreparedPipelinePendingError() {
20628
20798
  hint: "retry the prepared graphics pass on the next frame"
20629
20799
  });
20630
20800
  }
20801
+ var StandardPointsLinesOwner = class {
20802
+ constructor(internals) {
20803
+ this.internals = internals;
20804
+ }
20805
+ internals;
20806
+ cache = new PointsLinesExpansionCache();
20807
+ preparations = /* @__PURE__ */ new Map();
20808
+ active = /* @__PURE__ */ new Map();
20809
+ layoutProjection = deriveVertexLayoutProjection({
20810
+ position: new Float32Array(0),
20811
+ normal: new Float32Array(0),
20812
+ uv: new Float32Array(0),
20813
+ tangent: new Float32Array(0)
20814
+ });
20815
+ beginFrame() {
20816
+ this.active.clear();
20817
+ }
20818
+ prepare(entry, clustered) {
20819
+ const snapshot = entry.source.pointsLines;
20820
+ if (snapshot === void 0 || snapshot.component === void 0 || entry.world === void 0) {
20821
+ return void 0;
20822
+ }
20823
+ const key = `${snapshot.worldId}:${snapshot.entityKey}`;
20824
+ const topology = snapshot.component === "Points" ? "point-list" : "line-list";
20825
+ const backend = this.backend();
20826
+ const lane = backend === "wgpu-webgl2" ? "cpu-webgl2" : clustered ? "clustered" : "direct";
20827
+ const meshResult = resolveAssetHandle(
20828
+ entry.world,
20829
+ toShared(entry.source.assetHandle)
20830
+ );
20831
+ const materialResult = resolveAssetHandle(
20832
+ entry.world,
20833
+ toShared(snapshot.materialHandle)
20834
+ );
20835
+ if (!meshResult.ok || !materialResult.ok) {
20836
+ this.publishRefusal(
20837
+ snapshot,
20838
+ new PointsLinesMaterialUnsupportedError({
20839
+ entity: snapshot.entityKey,
20840
+ material: "unresolved",
20841
+ pass: "forward",
20842
+ module: "asset-resolution",
20843
+ reason: "source MeshAsset or MaterialAsset could not be resolved"
20844
+ })
20845
+ );
20846
+ return void 0;
20847
+ }
20848
+ const admission = admitPointsLines({
20849
+ entity: snapshot.entityKey,
20850
+ mesh: meshResult.value,
20851
+ material: materialResult.value,
20852
+ ...snapshot.style?.kind === "points" ? {
20853
+ points: {
20854
+ sizePx: snapshot.style.sizePx,
20855
+ shape: snapshot.style.shape === "circle" ? 1 : 0
20856
+ }
20857
+ } : { lines: { widthPx: snapshot.style?.widthPx ?? 1 } }
20858
+ });
20859
+ if (!admission.ok) {
20860
+ this.publishRefusal(snapshot, admission.error);
20861
+ return void 0;
20862
+ }
20863
+ const geometryKey = this.cache.getOrCreate(snapshot, meshResult.value).key;
20864
+ const prepKey = `${geometryKey}:${lane}:${backend}`;
20865
+ let preparation = this.preparations.get(prepKey);
20866
+ if (preparation === void 0) {
20867
+ preparation = createPointsLinesLanePreparationAdapter(lane, backend, {
20868
+ cache: this.cache,
20869
+ adapter: {
20870
+ create: (geometry) => {
20871
+ const vertex = this.internals.device.createBuffer({
20872
+ label: `points-lines-vertices:${geometryKey}`,
20873
+ size: Math.max(4, geometry.vertices.byteLength),
20874
+ usage: GPU_BUFFER_USAGE_VERTEX | GPU_BUFFER_USAGE_COPY_DST,
20875
+ mappedAtCreation: false
20876
+ });
20877
+ if (!vertex.ok) return err(vertex.error);
20878
+ const index = this.internals.device.createBuffer({
20879
+ label: `points-lines-indices:${geometryKey}`,
20880
+ size: Math.max(4, geometry.indices.byteLength),
20881
+ usage: GPU_BUFFER_USAGE_INDEX | GPU_BUFFER_USAGE_COPY_DST,
20882
+ mappedAtCreation: false
20883
+ });
20884
+ if (!index.ok) {
20885
+ this.internals.device.destroyBuffer(vertex.value);
20886
+ return err(index.error);
20887
+ }
20888
+ return ok({ vertexBuffer: vertex.value, indexBuffer: index.value });
20889
+ },
20890
+ upload: (resource, geometry) => {
20891
+ if (geometry.vertices.byteLength > 0) {
20892
+ const vertexWrite = this.internals.device.queue.writeBuffer(
20893
+ resource.vertexBuffer,
20894
+ 0,
20895
+ geometry.vertices
20896
+ );
20897
+ if (!vertexWrite.ok) return err(vertexWrite.error);
20898
+ }
20899
+ if (geometry.indices.byteLength > 0) {
20900
+ const indexWrite = this.internals.device.queue.writeBuffer(
20901
+ resource.indexBuffer,
20902
+ 0,
20903
+ geometry.indices
20904
+ );
20905
+ if (!indexWrite.ok) return err(indexWrite.error);
20906
+ }
20907
+ return ok(geometry.derivedBytes);
20908
+ },
20909
+ validate: (_resource, geometry) => geometry.expandedVertexCount > 0 && geometry.expandedIndexCount > 0 ? ok(void 0) : err(new Error("Points/Lines expansion contains no drawable triangles")),
20910
+ destroy: (resource) => {
20911
+ this.internals.device.destroyBuffer(resource.vertexBuffer);
20912
+ this.internals.device.destroyBuffer(resource.indexBuffer);
20913
+ }
20914
+ }
20915
+ });
20916
+ this.preparations.set(prepKey, preparation);
20917
+ }
20918
+ const prepared = preparation.prepare(snapshot, meshResult.value);
20919
+ if (!prepared.ok) {
20920
+ const lkg = preparation.lastKnownGood();
20921
+ if (lkg !== void 0) {
20922
+ const lkgPlan = createPointsLinesLaneAdapter(lane, backend).createRecordPlan(
20923
+ lkg.snapshot,
20924
+ lkg.geometry
20925
+ );
20926
+ const state2 = preparation.inspect();
20927
+ this.active.set(
20928
+ key,
20929
+ inspectPointsLines({
20930
+ snapshot,
20931
+ topology,
20932
+ lane,
20933
+ pointCount: lkg.geometry.pointCount,
20934
+ segmentCount: lkg.geometry.segmentCount,
20935
+ sourceBytes: lkg.geometry.sourceBytes,
20936
+ derivedBytes: lkg.geometry.derivedBytes,
20937
+ cache: { hit: true, rebuilds: state2.rebuilds, evictions: 0 },
20938
+ drawCount: lkgPlan.drawCount,
20939
+ uploadBytes: 0,
20940
+ lastKnownGood: true,
20941
+ refusal: {
20942
+ code: prepared.error.code,
20943
+ expected: prepared.error.expected,
20944
+ hint: prepared.error.hint,
20945
+ detail: prepared.error.detail,
20946
+ generation: prepared.error.detail.generation,
20947
+ lastKnownGood: true
20948
+ }
20949
+ })
20950
+ );
20951
+ return {
20952
+ plan: lkgPlan,
20953
+ vertexBuffer: lkg.resource.vertexBuffer,
20954
+ indexBuffer: lkg.resource.indexBuffer,
20955
+ layoutProjection: this.layoutProjection
20956
+ };
20957
+ }
20958
+ this.publishRefusal(snapshot, prepared.error);
20959
+ return void 0;
20960
+ }
20961
+ const plan = createPointsLinesLaneAdapter(lane, backend).createRecordPlan(
20962
+ snapshot,
20963
+ prepared.value.geometry
20964
+ );
20965
+ const state = preparation.inspect();
20966
+ this.active.set(
20967
+ key,
20968
+ inspectPointsLines({
20969
+ snapshot,
20970
+ topology,
20971
+ lane,
20972
+ pointCount: prepared.value.geometry.pointCount,
20973
+ segmentCount: prepared.value.geometry.segmentCount,
20974
+ sourceBytes: prepared.value.geometry.sourceBytes,
20975
+ derivedBytes: prepared.value.geometry.derivedBytes,
20976
+ cache: {
20977
+ hit: prepared.value.uploadedBytes === 0,
20978
+ rebuilds: state.rebuilds,
20979
+ evictions: 0
20980
+ },
20981
+ drawCount: plan.drawCount,
20982
+ uploadBytes: prepared.value.uploadedBytes,
20983
+ lastKnownGood: prepared.value.lastKnownGood
20984
+ })
20985
+ );
20986
+ return {
20987
+ plan,
20988
+ vertexBuffer: prepared.value.resource.vertexBuffer,
20989
+ indexBuffer: prepared.value.resource.indexBuffer,
20990
+ layoutProjection: this.layoutProjection
20991
+ };
20992
+ }
20993
+ resetForDeviceLoss() {
20994
+ for (const preparation of this.preparations.values()) preparation.resetForDeviceLoss();
20995
+ this.active.clear();
20996
+ }
20997
+ inspections() {
20998
+ return [...this.active.values()];
20999
+ }
21000
+ backend() {
21001
+ switch (this.internals.device.caps.backendKind) {
21002
+ case "wgpu-webgl2":
21003
+ return "wgpu-webgl2";
21004
+ case "null":
21005
+ return "null";
21006
+ default:
21007
+ return "webgpu";
21008
+ }
21009
+ }
21010
+ publishRefusal(snapshot, error) {
21011
+ this.active.set(
21012
+ `${snapshot.worldId}:${snapshot.entityKey}`,
21013
+ inspectPointsLines({
21014
+ snapshot,
21015
+ topology: snapshot.component === "Points" ? "point-list" : "line-list",
21016
+ lane: "refused",
21017
+ pointCount: 0,
21018
+ segmentCount: 0,
21019
+ sourceBytes: 0,
21020
+ derivedBytes: 0,
21021
+ cache: { hit: false, rebuilds: 0, evictions: 0 },
21022
+ drawCount: 0,
21023
+ uploadBytes: 0,
21024
+ lastKnownGood: false,
21025
+ refusal: {
21026
+ code: error.code,
21027
+ expected: error.expected,
21028
+ hint: error.hint,
21029
+ detail: error.detail,
21030
+ generation: snapshot.meshGeneration,
21031
+ lastKnownGood: false
21032
+ }
21033
+ })
21034
+ );
21035
+ }
21036
+ };
20631
21037
  function createRenderSystem(internals) {
20632
21038
  const phaseCatalogRegistration = internals.profiler?.registerPhaseCatalog(
20633
21039
  "render",
@@ -20635,6 +21041,7 @@ function createRenderSystem(internals) {
20635
21041
  );
20636
21042
  let releaseProfilerCatalog = phaseCatalogRegistration?.ok === true ? phaseCatalogRegistration.value : void 0;
20637
21043
  let preparedWorlds = [];
21044
+ const pointsLinesOwner = new StandardPointsLinesOwner(internals);
20638
21045
  const persistentRenderScene = new PersistentRenderScene({
20639
21046
  getDevice: () => internals.device,
20640
21047
  onGpuError: (error) => internals.errorRegistry.fire(error),
@@ -21152,6 +21559,9 @@ function createRenderSystem(internals) {
21152
21559
  gpuDriven: gpuDrivenProduction.inspect()
21153
21560
  };
21154
21561
  },
21562
+ get pointsLinesSnapshots() {
21563
+ return persistentRenderScene.pointsLinesSnapshots();
21564
+ },
21155
21565
  detachScene(world) {
21156
21566
  persistentRenderScene.detach(world);
21157
21567
  },
@@ -21168,6 +21578,7 @@ function createRenderSystem(internals) {
21168
21578
  }
21169
21579
  }
21170
21580
  try {
21581
+ pointsLinesOwner.beginFrame();
21171
21582
  const { cameraOwner, resourceOwner } = opts;
21172
21583
  if (frameState.installedPipelineHandle !== lastBuiltPipelineHandle) {
21173
21584
  resetRenderFeatureGraphState(internals);
@@ -21330,9 +21741,11 @@ function createRenderSystem(internals) {
21330
21741
  }
21331
21742
  },
21332
21743
  renderReadLeases,
21333
- featureGraphCandidate
21744
+ featureGraphCandidate,
21745
+ pointsLinesOwner
21334
21746
  )
21335
21747
  );
21748
+ persistentRenderScene.setPointsLinesInspections(pointsLinesOwner.inspections());
21336
21749
  if (internals.featureHost !== void 0 && preparedResourceBatches.length > 0) {
21337
21750
  const batches = preparedResourceBatches;
21338
21751
  if (submitted) {
@@ -21348,8 +21761,8 @@ function createRenderSystem(internals) {
21348
21761
  if (!retired.ok) internals.errorRegistry.fire(retired.error);
21349
21762
  }
21350
21763
  }
21351
- } catch (err28) {
21352
- const innerError = err28 instanceof RhiError ? err28 : { code: "unknown", message: String(err28), name: err28?.name };
21764
+ } catch (err29) {
21765
+ const innerError = err29 instanceof RhiError ? err29 : { code: "unknown", message: String(err29), name: err29?.name };
21353
21766
  internals.errorRegistry.fire(
21354
21767
  new RhiError({
21355
21768
  code: "webgpu-runtime-error",
@@ -22468,7 +22881,7 @@ function selectHdrpPbrPrewarmVariants(manifestEntry, storageBufferCapable) {
22468
22881
  ) ?? [];
22469
22882
  }
22470
22883
  function selectNoColorPbrVariant(manifestEntry, storageBufferCapable, variantSet) {
22471
- const hdrpRequested = variantSet === "" || variantSet?.includes("CLUSTER_FORWARD_AVAILABLE=true") === true;
22884
+ const hdrpRequested = variantSet === "" || (variantSet?.includes("CLUSTER_FORWARD_AVAILABLE=true") ?? false);
22472
22885
  return manifestEntry?.variants.find(
22473
22886
  (variant) => variant.defines.STORAGE_BUFFER_AVAILABLE === storageBufferCapable && variant.defines.VERTEX_COLOR_AVAILABLE === false && (!("CLUSTER_FORWARD_AVAILABLE" in variant.defines) || variant.defines.CLUSTER_FORWARD_AVAILABLE === hdrpRequested)
22474
22887
  );
@@ -22544,6 +22957,9 @@ function resolveMaterialShaderVariantSet(requestedVariantSet, variants, backendK
22544
22957
  const sorted = Object.entries(filtered).sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0);
22545
22958
  return sorted.every(([, value]) => value) ? requestedVariantSet === "" ? "" : void 0 : sorted.map(([name, value]) => `${name}=${value}`).join("+");
22546
22959
  }
22960
+ function normalizeMaterialShaderVariantSet(requestedVariantSet, manifestEntry) {
22961
+ return manifestEntry !== void 0 && manifestEntry.variants.length === 0 ? void 0 : requestedVariantSet;
22962
+ }
22547
22963
  async function createRenderer(canvas, options, bundler, backend) {
22548
22964
  const pack = backend ?? (options?.rhi === void 0 ? void 0 : loadRhiPack({ rhi: options.rhi, ...options.rhi }, options.rhiInstrumentation));
22549
22965
  if (pack === void 0) {
@@ -22594,20 +23010,20 @@ function attachDeviceLostFanout(device, pack, registries) {
22594
23010
  );
22595
23011
  }
22596
23012
  }
22597
- }).catch((err28) => {
23013
+ }).catch((err29) => {
22598
23014
  pack.instrumentation?.onDeviceLost?.();
22599
- onDeviceLost?.(`device.lost rejected: ${String(err28)}`);
22600
- lostRegistry.fire({ reason: "unknown", message: String(err28) });
23015
+ onDeviceLost?.(`device.lost rejected: ${String(err29)}`);
23016
+ lostRegistry.fire({ reason: "unknown", message: String(err29) });
22601
23017
  healthRegistry.fire({
22602
23018
  reason: "device-lost",
22603
- detail: { lostReason: "unknown", message: String(err28) },
23019
+ detail: { lostReason: "unknown", message: String(err29) },
22604
23020
  recoverable: true
22605
23021
  });
22606
23022
  errorRegistry.fire(
22607
23023
  new RhiError({
22608
23024
  code: "device-lost",
22609
23025
  expected: "device.lost Promise resolves with GPUDeviceLostInfo",
22610
- hint: `device.lost Promise rejected unexpectedly: ${String(err28)}`
23026
+ hint: `device.lost Promise rejected unexpectedly: ${String(err29)}`
22611
23027
  })
22612
23028
  );
22613
23029
  });
@@ -23181,7 +23597,7 @@ async function makeWebGPURenderer(internals) {
23181
23597
  const resolvedUvSetCount = shaderUvSetCount ?? materialShaderUvSetCounts.get(materialShaderId);
23182
23598
  const manifestEntry = findMaterialShaderManifestEntry(materialShaderId);
23183
23599
  const resolvedVariantSet = resolveCachedMaterialShaderVariantSet(variantSet, manifestEntry);
23184
- let effectiveVariantSet = resolvedVariantSet;
23600
+ let effectiveVariantSet = normalizeMaterialShaderVariantSet(resolvedVariantSet, manifestEntry);
23185
23601
  if (vertexLayoutProjection === void 0 && manifestEntry?.variants.some((variant) => "VERTEX_COLOR_AVAILABLE" in variant.defines)) {
23186
23602
  if (effectiveVariantSet === "") {
23187
23603
  const noColorVariant = manifestEntry.variants.find(
@@ -23573,6 +23989,7 @@ async function makeWebGPURenderer(internals) {
23573
23989
  features: Object.freeze(
23574
23990
  (internals.featureHost?.features ?? []).map((feature) => feature.identity)
23575
23991
  ),
23992
+ featureDiagnostics: internals.featureHost?.diagnostics() ?? Object.freeze([]),
23576
23993
  frustumStats: Object.freeze({ ...renderSystem.frustumStats }),
23577
23994
  visibilityStats: Object.freeze({ ...renderSystem.visibilityStats }),
23578
23995
  renderScene: renderSystem.renderScene,
@@ -24356,6 +24773,13 @@ function exposeRenderer(renderer) {
24356
24773
  draw: (request) => {
24357
24774
  const result = renderer.drawFrame(request);
24358
24775
  if (!result.ok) return err(drawError(result.error));
24776
+ emit(
24777
+ Object.freeze({
24778
+ kind: "frame-submitted",
24779
+ frameId: result.value.frameId,
24780
+ deviceGeneration: result.value.deviceGeneration
24781
+ })
24782
+ );
24359
24783
  return ok(result.value);
24360
24784
  },
24361
24785
  inspect: () => renderer.inspect(),
@@ -24989,6 +25413,24 @@ async function buildReadyWebGPU(rhiDevice, getShader, gpuStore, asyncCreateShade
24989
25413
  brdfLut: iblBrdfLutEntry.wgsl
24990
25414
  });
24991
25415
  }
25416
+ const pointsLinesLookup = registry.findMaterialArtifact(POINTS_LINES_MATERIAL_SHADER_ID);
25417
+ if (pointsLinesLookup.ok) {
25418
+ const pointsLinesLabel = `module-${POINTS_LINES_MATERIAL_SHADER_ID}`;
25419
+ const pointsLinesShaderResult = await runShimStep(
25420
+ () => asyncCreateShaderModule ? asyncCreateShaderModule(rhiDevice, {
25421
+ code: pointsLinesLookup.value.source,
25422
+ label: pointsLinesLabel
25423
+ }) : invokeDeviceCreateShaderModule(rhiDevice, {
25424
+ code: pointsLinesLookup.value.source,
25425
+ label: pointsLinesLabel
25426
+ }),
25427
+ "shader-compile-failed",
25428
+ `engine material shader '${POINTS_LINES_MATERIAL_SHADER_ID}' compiled`,
25429
+ `inspect the composed WGSL for '${POINTS_LINES_MATERIAL_SHADER_ID}' and check device.features`
25430
+ );
25431
+ if (!pointsLinesShaderResult.ok) throw pointsLinesShaderResult.error;
25432
+ seedShaderModule(pointsLinesLabel, pointsLinesShaderResult.value);
25433
+ }
24992
25434
  for (const materialShaderId of requiredMaterialShaders) {
24993
25435
  const lookup = registry.findMaterialArtifact(materialShaderId);
24994
25436
  if (!lookup.ok) {
@@ -25452,6 +25894,18 @@ async function buildReadyWebGPU(rhiDevice, getShader, gpuStore, asyncCreateShade
25452
25894
  "check device.limits.maxUniformBufferBindingSize"
25453
25895
  );
25454
25896
  if (!viewUboResult.ok) throw viewUboResult.error;
25897
+ const pointsLinesViewBufferResult = runShimSyncStep(
25898
+ () => rhiDevice.createBuffer({
25899
+ label: "points-lines-view-ubo",
25900
+ size: POINTS_LINES_VIEW_BUFFER_SIZE,
25901
+ usage: GPU_BUFFER_USAGE_UNIFORM | GPU_BUFFER_USAGE_COPY_DST,
25902
+ mappedAtCreation: false
25903
+ }),
25904
+ "webgpu-runtime-error",
25905
+ "createBuffer (points-lines view ubo) succeeded",
25906
+ "check device.limits.maxUniformBufferBindingSize"
25907
+ );
25908
+ if (!pointsLinesViewBufferResult.ok) throw pointsLinesViewBufferResult.error;
25455
25909
  const shadowCasterCascadeUboResult = runShimSyncStep(
25456
25910
  () => rhiDevice.createBuffer({
25457
25911
  label: "shadow-caster-cascade-ubo",
@@ -25927,11 +26381,11 @@ async function buildReadyWebGPU(rhiDevice, getShader, gpuStore, asyncCreateShade
25927
26381
  }
25928
26382
  try {
25929
26383
  getOrBuildPipeline(spec, pipelineDeviceProvider, pipelineCache, modules);
25930
- } catch (err28) {
25931
- if (err28 instanceof PipelineSpecError) throw err28;
26384
+ } catch (err29) {
26385
+ if (err29 instanceof PipelineSpecError) throw err29;
25932
26386
  throw new PipelineSpecError({
25933
26387
  code: "pipeline-build-failed",
25934
- detail: { cause: err28 },
26388
+ detail: { cause: err29 },
25935
26389
  hint: `Boot-time SPEC_CONST pre-warm failed for shader '${spec.shader.id}'; inspect gpuMessage on the cause`
25936
26390
  });
25937
26391
  }
@@ -26051,11 +26505,11 @@ async function buildReadyWebGPU(rhiDevice, getShader, gpuStore, asyncCreateShade
26051
26505
  pipelineCache,
26052
26506
  modules
26053
26507
  );
26054
- } catch (err28) {
26055
- if (err28 instanceof PipelineSpecError) throw err28;
26508
+ } catch (err29) {
26509
+ if (err29 instanceof PipelineSpecError) throw err29;
26056
26510
  throw new PipelineSpecError({
26057
26511
  code: "pipeline-build-failed",
26058
- detail: { cause: err28 },
26512
+ detail: { cause: err29 },
26059
26513
  hint: "createRenderPipeline (fxaa fullscreen) failed; inspect gpuMessage"
26060
26514
  });
26061
26515
  }
@@ -26295,11 +26749,11 @@ async function buildReadyWebGPU(rhiDevice, getShader, gpuStore, asyncCreateShade
26295
26749
  pipelineCache,
26296
26750
  modules
26297
26751
  );
26298
- } catch (err28) {
26299
- if (err28 instanceof PipelineSpecError) throw err28;
26752
+ } catch (err29) {
26753
+ if (err29 instanceof PipelineSpecError) throw err29;
26300
26754
  throw new PipelineSpecError({
26301
26755
  code: "pipeline-build-failed",
26302
- detail: { cause: err28 },
26756
+ detail: { cause: err29 },
26303
26757
  hint: "createRenderPipeline (bloom-bright fullscreen) failed; inspect gpuMessage"
26304
26758
  });
26305
26759
  }
@@ -26388,11 +26842,11 @@ async function buildReadyWebGPU(rhiDevice, getShader, gpuStore, asyncCreateShade
26388
26842
  pipelineCache,
26389
26843
  modules
26390
26844
  );
26391
- } catch (err28) {
26392
- if (err28 instanceof PipelineSpecError) throw err28;
26845
+ } catch (err29) {
26846
+ if (err29 instanceof PipelineSpecError) throw err29;
26393
26847
  throw new PipelineSpecError({
26394
26848
  code: "pipeline-build-failed",
26395
- detail: { cause: err28 },
26849
+ detail: { cause: err29 },
26396
26850
  hint: "createRenderPipeline (bloom-blur-h fullscreen) failed; inspect gpuMessage"
26397
26851
  });
26398
26852
  }
@@ -26499,11 +26953,11 @@ async function buildReadyWebGPU(rhiDevice, getShader, gpuStore, asyncCreateShade
26499
26953
  pipelineCache,
26500
26954
  modules
26501
26955
  );
26502
- } catch (err28) {
26503
- if (err28 instanceof PipelineSpecError) throw err28;
26956
+ } catch (err29) {
26957
+ if (err29 instanceof PipelineSpecError) throw err29;
26504
26958
  throw new PipelineSpecError({
26505
26959
  code: "pipeline-build-failed",
26506
- detail: { cause: err28 },
26960
+ detail: { cause: err29 },
26507
26961
  hint: "createRenderPipeline (bloom-composite fullscreen) failed; inspect gpuMessage"
26508
26962
  });
26509
26963
  }
@@ -26635,11 +27089,11 @@ async function buildReadyWebGPU(rhiDevice, getShader, gpuStore, asyncCreateShade
26635
27089
  pipelineCache,
26636
27090
  { ...baseModules, fragmentEntryPoint: "fs_ssao_calc" }
26637
27091
  );
26638
- } catch (err28) {
26639
- if (err28 instanceof PipelineSpecError) throw err28;
27092
+ } catch (err29) {
27093
+ if (err29 instanceof PipelineSpecError) throw err29;
26640
27094
  throw new PipelineSpecError({
26641
27095
  code: "pipeline-build-failed",
26642
- detail: { cause: err28 },
27096
+ detail: { cause: err29 },
26643
27097
  hint: "createRenderPipeline (ssao-calc fullscreen) failed; inspect gpuMessage"
26644
27098
  });
26645
27099
  }
@@ -26670,11 +27124,11 @@ async function buildReadyWebGPU(rhiDevice, getShader, gpuStore, asyncCreateShade
26670
27124
  pipelineCache,
26671
27125
  { ...baseModules, fragmentEntryPoint: "fs_ssao_blur" }
26672
27126
  );
26673
- } catch (err28) {
26674
- if (err28 instanceof PipelineSpecError) throw err28;
27127
+ } catch (err29) {
27128
+ if (err29 instanceof PipelineSpecError) throw err29;
26675
27129
  throw new PipelineSpecError({
26676
27130
  code: "pipeline-build-failed",
26677
- detail: { cause: err28 },
27131
+ detail: { cause: err29 },
26678
27132
  hint: "createRenderPipeline (ssao-blur fullscreen) failed; inspect gpuMessage"
26679
27133
  });
26680
27134
  }
@@ -26768,6 +27222,7 @@ async function buildReadyWebGPU(rhiDevice, getShader, gpuStore, asyncCreateShade
26768
27222
  materialBindGroupLayout: materialBglResult.value,
26769
27223
  meshBindGroupLayout: meshArrayBglResult.value,
26770
27224
  viewUniformBuffer: viewUboResult.value,
27225
+ pointsLinesViewBuffer: pointsLinesViewBufferResult.value,
26771
27226
  shadowCasterCascadeBuffer: shadowCasterCascadeUboResult.value,
26772
27227
  // feat-20260608-mesh-ssbo-dynamic-grow-l1-lift-1024-entity-cap M2 /
26773
27228
  // T-M2-05: PipelineState now references the wrapper objects owned by