@forgeax/engine-render 0.1.26 → 0.1.28

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 (83) hide show
  1. package/README.md +215 -0
  2. package/dist/assembly/dynamic-geometry-host.d.ts +31 -0
  3. package/dist/assembly/dynamic-geometry-host.d.ts.map +1 -0
  4. package/dist/assembly/host-contract.d.ts +11 -0
  5. package/dist/assembly/host-contract.d.ts.map +1 -1
  6. package/dist/assembly/renderer-facade.d.ts.map +1 -1
  7. package/dist/assembly/webgpu-renderer.d.ts.map +1 -1
  8. package/dist/authoring.mjs +2 -2
  9. package/dist/{chunk-FJ6P52EE.mjs → chunk-ILQ5MEZF.mjs} +3 -17
  10. package/dist/chunk-ILQ5MEZF.mjs.map +1 -0
  11. package/dist/{chunk-HKXTW355.mjs → chunk-IQMKJLNU.mjs} +3 -3
  12. package/dist/{chunk-HKXTW355.mjs.map → chunk-IQMKJLNU.mjs.map} +1 -1
  13. package/dist/{chunk-KM2NOX2I.mjs → chunk-J52NMQKR.mjs} +462 -6
  14. package/dist/chunk-J52NMQKR.mjs.map +1 -0
  15. package/dist/{chunk-ZZ4YQ474.mjs → chunk-J6KACNOH.mjs} +2 -2
  16. package/dist/{chunk-ZZ4YQ474.mjs.map → chunk-J6KACNOH.mjs.map} +1 -1
  17. package/dist/{chunk-ADHHVYLW.mjs → chunk-JAASCMXA.mjs} +3 -3
  18. package/dist/{chunk-ADHHVYLW.mjs.map → chunk-JAASCMXA.mjs.map} +1 -1
  19. package/dist/{chunk-TMIASV2N.mjs → chunk-MBU4ZVE7.mjs} +20 -12
  20. package/dist/chunk-MBU4ZVE7.mjs.map +1 -0
  21. package/dist/{chunk-E34VL5VI.mjs → chunk-MYJ5RPJO.mjs} +3 -3
  22. package/dist/{chunk-E34VL5VI.mjs.map → chunk-MYJ5RPJO.mjs.map} +1 -1
  23. package/dist/{chunk-JDWARUOI.mjs → chunk-RBDBZ5R7.mjs} +17 -3
  24. package/dist/chunk-RBDBZ5R7.mjs.map +1 -0
  25. package/dist/{chunk-YUQXSEHG.mjs → chunk-VSL23LZE.mjs} +26 -12
  26. package/dist/chunk-VSL23LZE.mjs.map +1 -0
  27. package/dist/construct-renderer.mjs +862 -106
  28. package/dist/construct-renderer.mjs.map +1 -1
  29. package/dist/device/gpu-residency.d.ts +10 -5
  30. package/dist/device/gpu-residency.d.ts.map +1 -1
  31. package/dist/device/mesh-residency-lifetime.d.ts +21 -0
  32. package/dist/device/mesh-residency-lifetime.d.ts.map +1 -0
  33. package/dist/dynamic-geometry.d.ts +125 -0
  34. package/dist/dynamic-geometry.d.ts.map +1 -0
  35. package/dist/index.d.ts +3 -0
  36. package/dist/index.d.ts.map +1 -1
  37. package/dist/index.mjs +7 -6
  38. package/dist/index.mjs.map +1 -1
  39. package/dist/internal.mjs +7 -7
  40. package/dist/record/frame.d.ts +1 -1
  41. package/dist/record/frame.d.ts.map +1 -1
  42. package/dist/record/main-pass-geometry.d.ts.map +1 -1
  43. package/dist/record/render-context.d.ts +2 -0
  44. package/dist/record/render-context.d.ts.map +1 -1
  45. package/dist/record/typed-frame-graph.d.ts.map +1 -1
  46. package/dist/render-contract.d.ts +12 -0
  47. package/dist/render-contract.d.ts.map +1 -1
  48. package/dist/render-system-extract-tail.d.ts.map +1 -1
  49. package/dist/render-system-extract.d.ts +6 -0
  50. package/dist/render-system-extract.d.ts.map +1 -1
  51. package/dist/render-system.d.ts +7 -0
  52. package/dist/render-system.d.ts.map +1 -1
  53. package/dist/temporal/index.mjs +4 -4
  54. package/package.json +20 -20
  55. package/src/__tests__/dynamic-geometry-host.unit.test.ts +1123 -0
  56. package/src/__tests__/dynamic-geometry.unit.test.ts +233 -0
  57. package/src/__tests__/factory-contract.integration.test.ts +6 -14
  58. package/src/__tests__/mesh-submission-lifetime.unit.test.ts +53 -0
  59. package/src/__tests__/renderer-factory-material-contract.unit.test.ts +1 -1
  60. package/src/assembly/dynamic-geometry-host.ts +839 -0
  61. package/src/assembly/host-contract.ts +26 -0
  62. package/src/assembly/renderer-facade.ts +17 -0
  63. package/src/assembly/webgpu-renderer.ts +49 -2
  64. package/src/components/sprite-animation.ts +1 -1
  65. package/src/device/gpu-residency.ts +62 -62
  66. package/src/device/mesh-residency-lifetime.ts +73 -0
  67. package/src/dynamic-geometry.ts +736 -0
  68. package/src/index.ts +16 -0
  69. package/src/record/__tests__/standard-pbr-ubo-layout.unit.test.ts +68 -1
  70. package/src/record/frame.ts +2 -0
  71. package/src/record/main-pass-geometry.ts +13 -4
  72. package/src/record/main-pass-sprite-draws.ts +2 -0
  73. package/src/record/render-context.ts +2 -0
  74. package/src/record/typed-frame-graph.ts +2 -9
  75. package/src/render-contract.ts +24 -0
  76. package/src/render-system-extract-tail.ts +3 -2
  77. package/src/render-system-extract.ts +30 -1
  78. package/src/render-system.ts +42 -0
  79. package/dist/chunk-FJ6P52EE.mjs.map +0 -1
  80. package/dist/chunk-JDWARUOI.mjs.map +0 -1
  81. package/dist/chunk-KM2NOX2I.mjs.map +0 -1
  82. package/dist/chunk-TMIASV2N.mjs.map +0 -1
  83. package/dist/chunk-YUQXSEHG.mjs.map +0 -1
@@ -1,18 +1,18 @@
1
- import { inspectVolumetricFog, inspectLodOcclusion, resolveSsrAdmissionGeneration, projectSsrDependencies, inspectReflectionFallback, admitReflectionProbe, DEFAULT_REFLECTION_PROBE_LIMITS, buildReflectionProbeTable, advanceProbeFilter, buildCubeCameraFaceViews, commitProbeFilterStep, resolveReflectionFallbackSource, deriveReflectionFallbackProjection, reflectionFallbackCoverage, reflectionFallbackSourceKey, reflectionFallbackProjectionSignature, createProbeFilterState, ReflectionProbeProjection, admitPointsLines, inspectPointShadow } from './chunk-KM2NOX2I.mjs';
2
- import { createExtendedLightingState, promoteExtendedLightingCandidate, OcclusionRenderRuntime, extractFrames, viewKey, primitiveKey, glyphTextLayoutSystem, projectExtendedLightingInspection, viewKeyId, primitiveKeyId, ProbeBlendSceneProjection, SKYLIGHT_RECOVERY_FALLBACK, InstanceBoundsCache, fingerprintNumericArray, createCookieProjectionMatrixData } from './chunk-YUQXSEHG.mjs';
3
- import './chunk-FJ6P52EE.mjs';
4
- import { MeshFilter, MeshRenderer, Instances, Layer, Visibility, Points, Lines, SortKey, Camera, MotionBlur, DirectionalLight, LightProbe, PointLight, PointLightShadow, SpotLight, Skylight, SkyboxBackground, PostProcessParams, ReflectionProbe, VolumetricFog, projectMeshMaterialBindingObservation, createVisibilityBudget, hasVolumetricFogCapability, CUBE_CAMERA_FACE_ORDER, CAMERA_PROJECTION_ORTHOGRAPHIC } from './chunk-ADHHVYLW.mjs';
5
- import { SpriteInstances, SpriteRegionOverride, TileLayer, decodeSortScope, Tilemap } from './chunk-JDWARUOI.mjs';
6
- import { assertStorageBufferCap, GpuBuffer, GpuTexture, deriveRenderDataTexture, deriveRenderDataMesh, deriveRenderDataCubemap, createIblPipelines, CUBEMAP_FACE_VERTICES, runIblPrecompute, InstanceProjectionStore, createClusterBinScratch, resetHdrpBuffers, retireTemporalGpuState, PostProcessError, entryHasDepthRead, DEPTH_MIN_PARAMS_BYTE_SIZE, postProcessShaderModuleLabel, resolveSsaoParameters, buildPerFrameBindGroups, postProcessShaderEntrySignature, PipelineSpecError, CAPTURE_VIEW_PROJS, PREFILTER_MIP_LEVELS, selectSwapChainFormat, setIblComposedShaders, POINTS_LINES_MATERIAL_SHADER_ID, createHdrpClusterMembershipBindGroupLayoutDescriptor, createSkylightFallback, buildSpecConstTable, buildLinearLdrMaterialSpecTable, getOrBuildPipeline, cacheKeyOf, REFLECTION_PROBE_VIEW_SLOT_BASE, VIEW_UNIFORM_SLOT_STRIDE, getOrCreateIblCache, postProcessShaderPipelineLabel, createPointsLinesLaneAdapter, computeViewMatrix, computeProjectionMatrix, prepareFrameLighting, warnMultiSkylight, warnMultiSkybox, selectLazyEquirectHandle, driveLazyEquirectProjection, warnZeroLightStandard, writeShadowParamsBuffer, writeSpotModifierTextures, writeHdrpClusterAndSsaoBuffers, resolveSkyboxActive, computeSplitLdrSprite, writeViewUbo, writePointsLinesViewUbo, writeShadowCasterUniforms, uploadMeshSsboBatch, passKindPolicyTable, colorFormatsForPassKind, configureSurface, VIEW_UNIFORM_BUFFER_SIZE, POINTS_LINES_VIEW_BUFFER_SIZE, SHADOW_CASTER_BUFFER_SIZE, FALLBACK_BYTES_PER_ROW, PREFILTER_SIZE, geometryRenderStateForTopology, assembleMaterialWithSkylightEntries, MESH_SSBO_BYTES, createPointsLinesLaneContract, foldDispatchBuckets, CUBE_CAPTURE_VIEW_SLOT_BASE, variantSetFromDefines, cleanPerEntityCache, ensureMeshSsboCapacity, buildFoldDispatchPlan, evaluateFoldBucketUniformCap, incrementFoldedDrawsMetric, writePbrMaterialUboPayload, applyParamSnapshotToUbo, applyMaterialTextureUvScales, residentTextureView, MATERIAL_PER_ENTITY_STRIDE, detectNineSliceScaleTooSmall, resolveGeometryInstanceBuffer, buildFullscreenPostProcessPass, getTemporalGpuState, getTemporalParamsBuffer, getTemporalBindGroupResources, resolveSurfaceFormatPair, resolveSurfaceProfile, encodeMainPass, STANDARD_PBR_UBO_SIZE, abortTemporalGpuSubmit, commitTemporalGpuSubmit, retireTemporalGpuStateAfterFence, hasPendingTemporalGpuSubmit, importRenderPipelineSurface, DEFERRED_COLOR_FORMATS, aggregateTemporalDemand, standardTemporalLaneAdmission, addTypedScenePass, addTypedSsaoPasses, typedFrameClearColor, addTypedSkyboxPass, createStandardSceneDataTarget, addStandardSceneDataPass, addTypedFrameObservationPass, temporalReadIndex, temporalWriteIndex, addReflectionFallbackObservationPass, BYTES_PER_DIRECT_LIGHT_SLOT, createRenderPipelineTarget, resolveOutputDither, addTypedTemporalResolvePass, addTypedBloomPasses, addTypedOutputTransformPass, addTypedFullscreenPass, addTypedCompositePostEffects, standardTopologyVariantSet, geometryRenderStateForPass, standardStorageVariantSet, getOrCreateHdrpBuffers, encodeDirectionalShadowPass, encodePointShadowPass, encodeSpotShadowPass, VIEW_UNIFORM_BYTES, variantSetFromVertexLayoutProjection, isTemporalFullscreenBinding, createFullscreenBindGroup, TYPED_BLOOM_PASS_NAMES } from './chunk-TMIASV2N.mjs';
1
+ import { createDynamicGeometryLifecycle, inspectVolumetricFog, inspectLodOcclusion, resolveSsrAdmissionGeneration, projectSsrDependencies, inspectReflectionFallback, admitReflectionProbe, DEFAULT_REFLECTION_PROBE_LIMITS, buildReflectionProbeTable, advanceProbeFilter, buildCubeCameraFaceViews, commitProbeFilterStep, resolveReflectionFallbackSource, deriveReflectionFallbackProjection, reflectionFallbackCoverage, reflectionFallbackSourceKey, reflectionFallbackProjectionSignature, createProbeFilterState, ReflectionProbeProjection, admitPointsLines, inspectPointShadow, DynamicGeometryError, sameCandidateCredential } from './chunk-J52NMQKR.mjs';
2
+ import { createExtendedLightingState, promoteExtendedLightingCandidate, OcclusionRenderRuntime, extractFrames, viewKey, primitiveKey, glyphTextLayoutSystem, projectExtendedLightingInspection, viewKeyId, primitiveKeyId, ProbeBlendSceneProjection, SKYLIGHT_RECOVERY_FALLBACK, InstanceBoundsCache, fingerprintNumericArray, createCookieProjectionMatrixData } from './chunk-VSL23LZE.mjs';
3
+ import './chunk-ILQ5MEZF.mjs';
4
+ import { MeshFilter, MeshRenderer, Instances, Layer, Visibility, Points, Lines, SortKey, Camera, MotionBlur, DirectionalLight, LightProbe, PointLight, PointLightShadow, SpotLight, Skylight, SkyboxBackground, PostProcessParams, ReflectionProbe, VolumetricFog, projectMeshMaterialBindingObservation, createVisibilityBudget, hasVolumetricFogCapability, CUBE_CAMERA_FACE_ORDER, CAMERA_PROJECTION_ORTHOGRAPHIC } from './chunk-JAASCMXA.mjs';
5
+ import { SpriteInstances, SpriteRegionOverride, TileLayer, decodeSortScope, Tilemap } from './chunk-RBDBZ5R7.mjs';
6
+ import { assertStorageBufferCap, GpuBuffer, GpuTexture, deriveRenderDataTexture, deriveRenderDataMesh, deriveRenderDataCubemap, createIblPipelines, CUBEMAP_FACE_VERTICES, runIblPrecompute, InstanceProjectionStore, createClusterBinScratch, resetHdrpBuffers, retireTemporalGpuState, PostProcessError, entryHasDepthRead, DEPTH_MIN_PARAMS_BYTE_SIZE, postProcessShaderModuleLabel, resolveSsaoParameters, buildPerFrameBindGroups, postProcessShaderEntrySignature, PipelineSpecError, CAPTURE_VIEW_PROJS, PREFILTER_MIP_LEVELS, selectSwapChainFormat, setIblComposedShaders, POINTS_LINES_MATERIAL_SHADER_ID, createHdrpClusterMembershipBindGroupLayoutDescriptor, createSkylightFallback, buildSpecConstTable, buildLinearLdrMaterialSpecTable, getOrBuildPipeline, cacheKeyOf, REFLECTION_PROBE_VIEW_SLOT_BASE, VIEW_UNIFORM_SLOT_STRIDE, getOrCreateIblCache, postProcessShaderPipelineLabel, createPointsLinesLaneAdapter, computeViewMatrix, computeProjectionMatrix, prepareFrameLighting, warnMultiSkylight, warnMultiSkybox, selectLazyEquirectHandle, driveLazyEquirectProjection, warnZeroLightStandard, writeShadowParamsBuffer, writeSpotModifierTextures, writeHdrpClusterAndSsaoBuffers, resolveSkyboxActive, computeSplitLdrSprite, writeViewUbo, writePointsLinesViewUbo, writeShadowCasterUniforms, uploadMeshSsboBatch, passKindPolicyTable, colorFormatsForPassKind, configureSurface, VIEW_UNIFORM_BUFFER_SIZE, POINTS_LINES_VIEW_BUFFER_SIZE, SHADOW_CASTER_BUFFER_SIZE, FALLBACK_BYTES_PER_ROW, PREFILTER_SIZE, geometryRenderStateForTopology, assembleMaterialWithSkylightEntries, MESH_SSBO_BYTES, createPointsLinesLaneContract, foldDispatchBuckets, CUBE_CAPTURE_VIEW_SLOT_BASE, variantSetFromDefines, cleanPerEntityCache, ensureMeshSsboCapacity, buildFoldDispatchPlan, evaluateFoldBucketUniformCap, incrementFoldedDrawsMetric, writePbrMaterialUboPayload, applyParamSnapshotToUbo, applyMaterialTextureUvScales, residentTextureView, MATERIAL_PER_ENTITY_STRIDE, detectNineSliceScaleTooSmall, resolveGeometryInstanceBuffer, buildFullscreenPostProcessPass, getTemporalGpuState, getTemporalParamsBuffer, getTemporalBindGroupResources, resolveSurfaceFormatPair, resolveSurfaceProfile, encodeMainPass, STANDARD_PBR_UBO_SIZE, abortTemporalGpuSubmit, commitTemporalGpuSubmit, retireTemporalGpuStateAfterFence, hasPendingTemporalGpuSubmit, importRenderPipelineSurface, DEFERRED_COLOR_FORMATS, aggregateTemporalDemand, standardTemporalLaneAdmission, addTypedScenePass, addTypedSsaoPasses, typedFrameClearColor, addTypedSkyboxPass, createStandardSceneDataTarget, addStandardSceneDataPass, addTypedFrameObservationPass, temporalReadIndex, temporalWriteIndex, addReflectionFallbackObservationPass, BYTES_PER_DIRECT_LIGHT_SLOT, createRenderPipelineTarget, resolveOutputDither, addTypedTemporalResolvePass, addTypedBloomPasses, addTypedOutputTransformPass, addTypedFullscreenPass, addTypedCompositePostEffects, standardTopologyVariantSet, geometryRenderStateForPass, standardStorageVariantSet, getOrCreateHdrpBuffers, encodeDirectionalShadowPass, encodePointShadowPass, encodeSpotShadowPass, VIEW_UNIFORM_BYTES, variantSetFromVertexLayoutProjection, isTemporalFullscreenBinding, createFullscreenBindGroup, TYPED_BLOOM_PASS_NAMES } from './chunk-MBU4ZVE7.mjs';
7
7
  import { getTransparentSortConfig, TRANSPARENT_SORT_MODE_DISTANCE, TRANSPARENT_SORT_MODE_LAYER_Z, TRANSPARENT_SORT_MODE_LAYER_Y, TRANSPARENT_SORT_MODE_LAYER_YZ } from './chunk-RXZ3RGOU.mjs';
8
- import { DEFAULT_STANDARD_PROFILE, DEFAULT_CLUSTER_GRID, createSceneDataCatalog, STANDARD_PIPELINE_ID, STANDARD_LIGHT_COUNTS, STANDARD_POST_STAGE_NAMES, resolveVolumetricFogProfile, isSceneDataTarget } from './chunk-E34VL5VI.mjs';
8
+ import { DEFAULT_STANDARD_PROFILE, DEFAULT_CLUSTER_GRID, createSceneDataCatalog, STANDARD_PIPELINE_ID, STANDARD_LIGHT_COUNTS, STANDARD_POST_STAGE_NAMES, resolveVolumetricFogProfile, isSceneDataTarget } from './chunk-MYJ5RPJO.mjs';
9
9
  import { SPRITE_PREMULTIPLIED_ALPHA_BLEND } from './chunk-GF523LHF.mjs';
10
- import { DeviceScope, SKIN_MATERIAL_SHADER_ID, EXTENDED_LIGHTING_REQUIRED_SAMPLED_TEXTURES, GPU_BUFFER_USAGE_COPY_DST, GPU_BUFFER_USAGE_MAP_READ, resolveRenderTargetMaterialSource, createRenderTargetMaterialSource, deriveExtendedLightingCapability, GPU_BUFFER_USAGE_VERTEX, GPU_BUFFER_USAGE_INDEX, EXTENDED_LIGHTING_TOPOLOGY, GPU_BUFFER_USAGE_UNIFORM, getTextureIdentity, worldEntityKey, createHdrpBindGroupLayoutDescriptor, createHdrpSkinBindGroupLayoutDescriptor, IES_SLICE_WIDTH, IES_SLICE_HEIGHT, COOKIE_SLICE_SIZE, COOKIE_MATRIX_BYTES, makeZeroCameraFallbackSnapshot, GPU_BUFFER_USAGE_QUERY_RESOLVE, GPU_BUFFER_USAGE_COPY_SRC, buildPbrPipelineLayouts, buildPbrSkinLayouts, GPU_BUFFER_USAGE_STORAGE, resolvePipelineGroup2Contract, GPU_SHADER_STAGE_FRAGMENT, instanceCollectionCacheKey, validateGraphTargetCaptureReadback, isCanonicalStandardPbrMaterialShader, isStandardPbrMaterialShader, buildBindGroupLayoutDescriptor, GPU_BUFFER_USAGE_INDIRECT, buildPbrMaterialUserRegionEntries, extendedLightingSampledTextureCapacityAvailable, GPU_SHADER_STAGE_VERTEX, SPRITE_PASS_PER_INSTANCE_REGION_VARIANT_SET, shadowCasterVariantSet, SHADOW_CASTER_SHADER_ID, LifecycleTransaction } from './chunk-HKXTW355.mjs';
11
- import { RendererContractFailureError, RendererOperationError, STANDARD_OUTPUT_TRANSFORM_FEATURE_ID, RenderTargetStateInvalidError, RenderTargetOperationFailedError, RenderTargetCapabilityMissingError, RenderTargetDescriptorInvalidError, GPU_TEXTURE_USAGE_TEXTURE_BINDING, GPU_TEXTURE_USAGE_COPY_DST, RENDER_PHASE_CATALOG, FrameReceiptStaleError, RenderFeatureStageFailedError, RenderFeatureCapabilityMissingError, RenderFeatureRegistrationConflictError, GPU_TEXTURE_USAGE_RENDER_ATTACHMENT_AND_TEXTURE_BINDING, GPU_TEXTURE_USAGE_COPY_SRC, EnvironmentGenerationFailedError, PointsLinesMaterialUnsupportedError, StandardProfileInvalidError, TransmissionCapabilityMissingError, SHADOW_ATLAS_DEFAULT_LAYERS, GPU_TEXTURE_USAGE_RENDER_ATTACHMENT, TemporalFrameSubmitError, ObservationUnavailableError, RenderFeaturePreparationFailedError, PointsLinesPrepareFailedError, SHADOW_ATLAS_DEFAULT_FACE_SIZE, SceneDataUnavailableError, RenderFeatureDrawRecordingFailedError, PointShadowAtlasUninitializedError, SkinPaletteOverflowError, RenderFeaturePreparedStateMismatchError, FXAA_POST_PROCESS_ID } from './chunk-ZZ4YQ474.mjs';
10
+ import { DeviceScope, SKIN_MATERIAL_SHADER_ID, EXTENDED_LIGHTING_REQUIRED_SAMPLED_TEXTURES, GPU_BUFFER_USAGE_COPY_DST, GPU_BUFFER_USAGE_MAP_READ, resolveRenderTargetMaterialSource, createRenderTargetMaterialSource, deriveExtendedLightingCapability, GPU_BUFFER_USAGE_VERTEX, GPU_BUFFER_USAGE_INDEX, EXTENDED_LIGHTING_TOPOLOGY, GPU_BUFFER_USAGE_UNIFORM, getTextureIdentity, worldEntityKey, createHdrpBindGroupLayoutDescriptor, createHdrpSkinBindGroupLayoutDescriptor, IES_SLICE_WIDTH, IES_SLICE_HEIGHT, COOKIE_SLICE_SIZE, COOKIE_MATRIX_BYTES, makeZeroCameraFallbackSnapshot, GPU_BUFFER_USAGE_QUERY_RESOLVE, GPU_BUFFER_USAGE_COPY_SRC, buildPbrPipelineLayouts, buildPbrSkinLayouts, GPU_BUFFER_USAGE_STORAGE, resolvePipelineGroup2Contract, GPU_SHADER_STAGE_FRAGMENT, instanceCollectionCacheKey, validateGraphTargetCaptureReadback, isCanonicalStandardPbrMaterialShader, isStandardPbrMaterialShader, buildBindGroupLayoutDescriptor, GPU_BUFFER_USAGE_INDIRECT, buildPbrMaterialUserRegionEntries, extendedLightingSampledTextureCapacityAvailable, GPU_SHADER_STAGE_VERTEX, SPRITE_PASS_PER_INSTANCE_REGION_VARIANT_SET, shadowCasterVariantSet, SHADOW_CASTER_SHADER_ID, LifecycleTransaction } from './chunk-IQMKJLNU.mjs';
11
+ import { RendererContractFailureError, RendererOperationError, STANDARD_OUTPUT_TRANSFORM_FEATURE_ID, RenderTargetStateInvalidError, RenderTargetOperationFailedError, RenderTargetCapabilityMissingError, RenderTargetDescriptorInvalidError, GPU_TEXTURE_USAGE_TEXTURE_BINDING, GPU_TEXTURE_USAGE_COPY_DST, RENDER_PHASE_CATALOG, FrameReceiptStaleError, RenderFeatureStageFailedError, RenderFeatureCapabilityMissingError, RenderFeatureRegistrationConflictError, GPU_TEXTURE_USAGE_RENDER_ATTACHMENT_AND_TEXTURE_BINDING, GPU_TEXTURE_USAGE_COPY_SRC, EnvironmentGenerationFailedError, PointsLinesMaterialUnsupportedError, StandardProfileInvalidError, TransmissionCapabilityMissingError, SHADOW_ATLAS_DEFAULT_LAYERS, GPU_TEXTURE_USAGE_RENDER_ATTACHMENT, TemporalFrameSubmitError, ObservationUnavailableError, RenderFeaturePreparationFailedError, PointsLinesPrepareFailedError, SHADOW_ATLAS_DEFAULT_FACE_SIZE, SceneDataUnavailableError, RenderFeatureDrawRecordingFailedError, PointShadowAtlasUninitializedError, SkinPaletteOverflowError, RenderFeaturePreparedStateMismatchError, FXAA_POST_PROCESS_ID } from './chunk-J6KACNOH.mjs';
12
12
  import { ok, err, RhiError, validateDrawArgs } from '@forgeax/engine-rhi';
13
13
  import { AssetRegistry, DynamicTextureStore, adaptDynamicTextureDevice, getOrCreateMipmapPipeline, prepareMipmaps, blitMipmapsSync, HANDLE_CUBE, HANDLE_TRIANGLE, HANDLE_QUAD, HANDLE_SPHERE, HANDLE_NINESLICE_QUAD, BuiltinAssetRegistry, resolveAssetHandle, numMipLevels, encodeMipmapLevel, resolveTilesetRuntime, MeshSsboCeilingReachedError, MeshSsboCapacityExceededError } from '@forgeax/engine-assets-runtime';
14
14
  import { audioLoader } from '@forgeax/engine-audio-webaudio';
15
- import { Update } from '@forgeax/engine-ecs';
15
+ import { Update, FixedTime } from '@forgeax/engine-ecs';
16
16
  import { createRenderReadLease } from '@forgeax/engine-ecs/projection';
17
17
  import { PROCEDURAL_FLOATS_PER_VERTEX, deriveVertexLayoutProjection, deriveVertexBufferLayoutFromProjection, deriveVertexBufferLayout } from '@forgeax/engine-geometry';
18
18
  import { GlobalTransform, ChildOf, MorphWeights, collectSubtree, Name, registerPropagateTransforms, Children, Transform } from '@forgeax/engine-scene';
@@ -284,6 +284,11 @@ function exposeRenderer(renderer) {
284
284
  })
285
285
  );
286
286
  },
287
+ prepareDynamicGeometry: (input) => renderer.prepareDynamicGeometry(input),
288
+ acceptDynamicGeometry: (candidate, ordering) => renderer.acceptDynamicGeometry(candidate, ordering),
289
+ dynamicGeometryReceipt: (candidate) => renderer.dynamicGeometryReceipt(candidate),
290
+ cancelDynamicGeometry: (candidate) => renderer.cancelDynamicGeometry(candidate),
291
+ retireDynamicGeometry: (candidate) => renderer.retireDynamicGeometry(candidate),
287
292
  createRenderTarget: (descriptor) => renderer.createRenderTarget(descriptor),
288
293
  resizeRenderTarget: (target4, descriptor) => renderer.resizeRenderTarget(target4, descriptor),
289
294
  createRenderTargetTextureSource: (target4, options) => renderer.createRenderTargetTextureSource(target4, options),
@@ -498,6 +503,68 @@ function freezeRenderProfile(profile) {
498
503
  postStages: Object.freeze([...profile.postStages])
499
504
  });
500
505
  }
506
+
507
+ // src/device/mesh-residency-lifetime.ts
508
+ var MeshResidencyLifetime = class {
509
+ constructor(destroy) {
510
+ this.destroy = destroy;
511
+ }
512
+ destroy;
513
+ leases = 0;
514
+ retired = false;
515
+ eviction;
516
+ submissions = /* @__PURE__ */ new Set();
517
+ waiters = /* @__PURE__ */ new Set();
518
+ get owners() {
519
+ return this.leases;
520
+ }
521
+ retain(evict) {
522
+ this.leases += 1;
523
+ this.eviction = void 0;
524
+ let released = false;
525
+ let completion;
526
+ return {
527
+ track: (submitted) => this.track(submitted),
528
+ release: (shouldEvict) => {
529
+ if (released) return completion;
530
+ released = true;
531
+ this.leases -= 1;
532
+ if (this.leases === 0 && shouldEvict) this.eviction = evict;
533
+ completion = this.submissions.size === 0 ? void 0 : new Promise((resolve) => this.waiters.add(resolve));
534
+ this.flush();
535
+ return completion;
536
+ }
537
+ };
538
+ }
539
+ track(completed) {
540
+ if (this.submissions.has(completed)) return;
541
+ this.submissions.add(completed);
542
+ const finish = () => {
543
+ this.submissions.delete(completed);
544
+ this.flush();
545
+ };
546
+ void completed.then(finish, finish);
547
+ }
548
+ retire() {
549
+ this.retired = true;
550
+ this.flush();
551
+ }
552
+ flush() {
553
+ if (this.submissions.size !== 0) return;
554
+ if (this.leases === 0 && this.eviction !== void 0) {
555
+ const evict = this.eviction;
556
+ this.eviction = void 0;
557
+ evict();
558
+ }
559
+ if (this.retired && this.leases === 0 && this.destroy !== void 0) {
560
+ const destroy = this.destroy;
561
+ this.destroy = void 0;
562
+ destroy();
563
+ }
564
+ for (const resolve of this.waiters) resolve();
565
+ this.waiters.clear();
566
+ }
567
+ };
501
568
  var DYNAMIC_OFFSET_STRIDE = 256;
502
569
  var FACE_COUNT = 6;
503
570
  var PREFILTER_SUBPASS_COUNT = PREFILTER_MIP_LEVELS * FACE_COUNT;
@@ -741,6 +808,33 @@ var GpuResidencyCache = class {
741
808
  // equirect source always resolves to the same cubemap (idempotent, A2).
742
809
  cubemapIdempotentMap = /* @__PURE__ */ new Map();
743
810
  meshGpuHandles = /* @__PURE__ */ new Map();
811
+ meshLifetimes = /* @__PURE__ */ new WeakMap();
812
+ retiredMeshes = /* @__PURE__ */ new Set();
813
+ meshLifetime(entry) {
814
+ let lifetime = this.meshLifetimes.get(entry);
815
+ if (lifetime === void 0) {
816
+ lifetime = new MeshResidencyLifetime(() => {
817
+ if (!entry.vertexBuffer.isDestroyed) entry.vertexBuffer.destroy();
818
+ if (entry.indexBuffer !== null && !entry.indexBuffer.isDestroyed)
819
+ entry.indexBuffer.destroy();
820
+ this.retiredMeshes.delete(entry);
821
+ });
822
+ this.meshLifetimes.set(entry, lifetime);
823
+ }
824
+ return lifetime;
825
+ }
826
+ /** All resident entries conservatively cover main, shadow and cached GPU-driven draws. */
827
+ trackMeshSubmission(completed) {
828
+ for (const entry of this.meshGpuHandles.values()) this.meshLifetime(entry).track(completed);
829
+ }
830
+ retireMeshEntry(key, entry) {
831
+ if (this.meshGpuHandles.get(key) === entry) {
832
+ this.meshGpuHandles.delete(key);
833
+ this.meshResidencyEpoch += 1;
834
+ }
835
+ this.retiredMeshes.add(entry);
836
+ this.meshLifetime(entry).retire();
837
+ }
744
838
  /**
745
839
  * Compose a cache key from the raw handle slot and its owning World. The
746
840
  * draw-time world index is only a position in one frame's `worlds[]`; it is
@@ -870,11 +964,12 @@ var GpuResidencyCache = class {
870
964
  }
871
965
  this.cubemapGpuHandles.clear();
872
966
  this.cubemapIdempotentMap.clear();
873
- for (const entry of this.meshGpuHandles.values()) {
967
+ for (const entry of [...this.meshGpuHandles.values(), ...this.retiredMeshes]) {
874
968
  destroyBuf(entry.vertexBuffer);
875
969
  if (entry.indexBuffer !== null) destroyBuf(entry.indexBuffer);
876
970
  }
877
971
  this.meshGpuHandles.clear();
972
+ this.retiredMeshes.clear();
878
973
  }
879
974
  /** Number of candidate-owned GPU resources currently held by this cache. */
880
975
  recoveryResourceCount() {
@@ -886,7 +981,7 @@ var GpuResidencyCache = class {
886
981
  cubemapResources += 1;
887
982
  }
888
983
  }
889
- return this.meshGpuHandles.size + this.textureGpuHandles.size + this.samplerGpuHandles.size + cubemapResources;
984
+ return this.meshGpuHandles.size + this.retiredMeshes.size + this.textureGpuHandles.size + this.samplerGpuHandles.size + cubemapResources;
890
985
  }
891
986
  /** Candidate root for the cache owner, not a scalar readiness marker. */
892
987
  createRecoveryRoot(scope) {
@@ -941,24 +1036,12 @@ var GpuResidencyCache = class {
941
1036
  const id = this.worldKey(handleSlot(handle), worldId);
942
1037
  const entry = this.meshGpuHandles.get(id);
943
1038
  if (entry === void 0) return { freed: 0, errors: [] };
944
- let freed = 0;
945
- const errors = [];
946
- const destroyBuf = (gpuBuf) => {
947
- if (!gpuBuf.isDestroyed) {
948
- const r = gpuBuf.destroy();
949
- if (r.ok) {
950
- freed += 1;
951
- } else {
952
- errors.push(r.error);
953
- if (this.errorRegistry) this.errorRegistry.fire(r.error);
954
- }
955
- }
1039
+ if (this.meshLifetime(entry).owners > 0) return { freed: 0, errors: [] };
1040
+ this.retireMeshEntry(id, entry);
1041
+ return {
1042
+ freed: Number(entry.vertexBuffer.isDestroyed) + Number(entry.indexBuffer?.isDestroyed ?? false),
1043
+ errors: []
956
1044
  };
957
- destroyBuf(entry.vertexBuffer);
958
- if (entry.indexBuffer !== null) destroyBuf(entry.indexBuffer);
959
- this.meshGpuHandles.delete(id);
960
- this.meshResidencyEpoch += 1;
961
- return { freed: freed > 0 ? 1 : 0, errors };
962
1045
  }
963
1046
  evictCubemap(id) {
964
1047
  const entry = this.cubemapGpuHandles.get(id);
@@ -989,7 +1072,6 @@ var GpuResidencyCache = class {
989
1072
  releaseUnreferenced(liveSet) {
990
1073
  let freed = 0;
991
1074
  const errors = [];
992
- let meshResidencyChanged = false;
993
1075
  let materialResidencyChanged = false;
994
1076
  const liveSlots = new Set([...liveSet].map((slot) => String(slot)));
995
1077
  const isLiveKey = (key) => {
@@ -1037,34 +1119,12 @@ var GpuResidencyCache = class {
1037
1119
  }
1038
1120
  }
1039
1121
  }
1040
- for (const key of this.meshGpuHandles.keys()) {
1041
- if (!isLiveKey(key)) {
1042
- const entry = this.meshGpuHandles.get(key);
1043
- if (entry !== void 0) {
1044
- if (!entry.vertexBuffer.isDestroyed) {
1045
- const r = entry.vertexBuffer.destroy();
1046
- if (r.ok) {
1047
- freed += 1;
1048
- } else {
1049
- errors.push(r.error);
1050
- if (this.errorRegistry) this.errorRegistry.fire(r.error);
1051
- }
1052
- }
1053
- if (entry.indexBuffer !== null && !entry.indexBuffer.isDestroyed) {
1054
- const r = entry.indexBuffer.destroy();
1055
- if (r.ok) {
1056
- freed += 1;
1057
- } else {
1058
- errors.push(r.error);
1059
- if (this.errorRegistry) this.errorRegistry.fire(r.error);
1060
- }
1061
- }
1062
- this.meshGpuHandles.delete(key);
1063
- meshResidencyChanged = true;
1064
- }
1122
+ for (const [key, entry] of this.meshGpuHandles) {
1123
+ if (!isLiveKey(key) && this.meshLifetime(entry).owners === 0) {
1124
+ this.retireMeshEntry(key, entry);
1125
+ freed += Number(entry.vertexBuffer.isDestroyed) + Number(entry.indexBuffer?.isDestroyed ?? false);
1065
1126
  }
1066
1127
  }
1067
- if (meshResidencyChanged) this.meshResidencyEpoch += 1;
1068
1128
  if (materialResidencyChanged) this.materialResourceEpoch += 1;
1069
1129
  return { freed, errors };
1070
1130
  }
@@ -1184,19 +1244,20 @@ var GpuResidencyCache = class {
1184
1244
  getMeshGpuHandles(handle, worldId = 0) {
1185
1245
  return this.meshGpuHandles.get(this.worldKey(handleSlot(handle), worldId));
1186
1246
  }
1187
- /**
1188
- * Drop one mutable shared mesh's GPU copy after ECS publishes `markChanged`.
1189
- * The next record pass resolves the same POD and rebuilds residency through
1190
- * the normal pull path, so ECS remains the payload authority.
1191
- */
1247
+ /** Candidate leases retain the concrete allocation even after cache invalidation. */
1248
+ retainMeshResidency(handle, worldId = 0) {
1249
+ const key = this.worldKey(handleSlot(handle), worldId);
1250
+ const entry = this.meshGpuHandles.get(key);
1251
+ return entry === void 0 ? void 0 : this.meshLifetime(entry).retain(() => {
1252
+ if (this.meshGpuHandles.get(key) === entry) this.invalidateMesh(handle, worldId);
1253
+ else this.meshLifetime(entry).retire();
1254
+ });
1255
+ }
1256
+ /** In-place author changes immediately miss the cache; old submissions keep their allocation. */
1192
1257
  invalidateMesh(handle, worldId = 0) {
1193
1258
  const key = this.worldKey(handleSlot(toShared(handle)), worldId);
1194
1259
  const entry = this.meshGpuHandles.get(key);
1195
- if (entry === void 0) return;
1196
- entry.vertexBuffer.destroy();
1197
- if (entry.indexBuffer !== null) entry.indexBuffer.destroy();
1198
- this.meshGpuHandles.delete(key);
1199
- this.meshResidencyEpoch += 1;
1260
+ if (entry !== void 0) this.retireMeshEntry(key, entry);
1200
1261
  }
1201
1262
  ensureResident(handle, pod, worldId = 0, lodMeshes = []) {
1202
1263
  const id = handleSlot(handle);
@@ -13460,12 +13521,6 @@ function ensureCompiledFrameGraph(internals, frameState, pipelineState, camera,
13460
13521
  }
13461
13522
  function executeCompiledFrameGraph(internals, frameState, frame, encoder, runPass, frameHooks, readbackFaces, onSubmitted, timingCapture) {
13462
13523
  const graph = frameState.compiledFrameGraph;
13463
- const invalidatePostProcessModule = (id) => {
13464
- const entry = internals.lookupPostProcess?.(id);
13465
- if (entry !== void 0) {
13466
- internals.invalidateShaderModule?.(postProcessShaderModuleLabel(id, entry.source));
13467
- }
13468
- };
13469
13524
  const rejectTemporalFrame = () => {
13470
13525
  if (frameState.temporalFrameInput === void 0) return;
13471
13526
  frameState.temporalFrameTransaction.commit({ accepted: false });
@@ -13753,7 +13808,6 @@ function executeCompiledFrameGraph(internals, frameState, frame, encoder, runPas
13753
13808
  frameState.activeTemporalGpuState = stagedGpuState;
13754
13809
  if (previousGpuState !== void 0 && previousGpuState !== stagedGpuState) {
13755
13810
  internals.clearPostProcessPipelineCache?.("forgeax.taa-resolve");
13756
- invalidatePostProcessModule("forgeax.taa-resolve");
13757
13811
  retireTemporalGpuStateAfterFence(
13758
13812
  previousGpuState,
13759
13813
  internals.device.queue,
@@ -13783,7 +13837,6 @@ function executeCompiledFrameGraph(internals, frameState, frame, encoder, runPas
13783
13837
  }
13784
13838
  if (stagedTemporalCommit.kind === "off") {
13785
13839
  internals.clearPostProcessPipelineCache?.("forgeax.taa-resolve");
13786
- invalidatePostProcessModule("forgeax.taa-resolve");
13787
13840
  const activeGpuState = frameState.activeTemporalGpuState;
13788
13841
  if (activeGpuState !== void 0) {
13789
13842
  retireTemporalGpuStateAfterFence(
@@ -14678,7 +14731,7 @@ function updateVolumetricFogInspection(frameState, fog, lights, prepared, submit
14678
14731
  memoryBytes: 0
14679
14732
  });
14680
14733
  }
14681
- function recordFrame(internals, world, cameras, lights, renderables, transparentDispatch, frameState, dispatchCounts, bindGroupCounts, skylight, skylightCount, skybox, skyboxCount, postProcessParams, worlds = [world], profilePhase, gpuDriven, renderReadLeases, featureGraphCandidate, pointsLinesOwner, cubeCapture, environmentSignature = "", fogSignature = "", environmentReady = true, transmissionDemand, volumetricFog, timingCapture) {
14734
+ function recordFrame(internals, world, cameras, lights, renderables, transparentDispatch, frameState, dispatchCounts, bindGroupCounts, skylight, skylightCount, skybox, skyboxCount, postProcessParams, worlds = [world], profilePhase, gpuDriven, renderReadLeases, featureGraphCandidate, pointsLinesOwner, cubeCapture, environmentSignature = "", fogSignature = "", environmentReady = true, transmissionDemand, volumetricFog, timingCapture, onRenderableDraw) {
14682
14735
  frameState.reflectionFallbackObservationSource = void 0;
14683
14736
  frameState.reflectionFallbackCompletion = void 0;
14684
14737
  let activeCameras = cameras;
@@ -15223,6 +15276,7 @@ function recordFrame(internals, world, cameras, lights, renderables, transparent
15223
15276
  ...cubeCapture?.reflectionProbes === void 0 ? {} : { reflectionProbes: cubeCapture.reflectionProbes },
15224
15277
  pointsLines: pointsLinesOwner,
15225
15278
  materialBgAssemblyCache: frameState.materialBgAssemblyCache,
15279
+ ...onRenderableDraw === void 0 ? {} : { onRenderableDraw },
15226
15280
  directionalShadowCacheReuse: directionalShadowCache.reuse,
15227
15281
  ...profilePhase !== void 0 ? { profilePhase } : {}
15228
15282
  };
@@ -21967,6 +22021,7 @@ function createRenderSystem(internals) {
21967
22021
  );
21968
22022
  let releaseProfilerCatalog = phaseCatalogRegistration?.ok === true ? phaseCatalogRegistration.value : void 0;
21969
22023
  let preparedWorlds = [];
22024
+ let lastSubmittedDynamicGeometryBindings = /* @__PURE__ */ new WeakMap();
21970
22025
  let latestCamera;
21971
22026
  const pointsLinesOwner = new StandardPointsLinesOwner(internals);
21972
22027
  const instanceCollections = new InstanceProjectionStore();
@@ -22952,6 +23007,14 @@ function createRenderSystem(internals) {
22952
23007
  releaseProfilerCatalog?.();
22953
23008
  releaseProfilerCatalog = void 0;
22954
23009
  },
23010
+ invalidateGeometryHistory() {
23011
+ frameState.temporalFrameTransaction.reset("signature-change");
23012
+ frameState.temporalFrame = void 0;
23013
+ frameState.temporalFrameInput = void 0;
23014
+ frameState.lastSuccessfulTemporalView = void 0;
23015
+ frameState.successfulTemporalFrameIndex = 0;
23016
+ frameState.pendingTemporalCommit = { kind: "none" };
23017
+ },
22955
23018
  get renderScene() {
22956
23019
  return {
22957
23020
  ...persistentRenderScene.inspect(),
@@ -23131,6 +23194,8 @@ function createRenderSystem(internals) {
23131
23194
  const profileSession = internals.profiler?.activeSession();
23132
23195
  let ownsProfileFrame = false;
23133
23196
  let submitted = false;
23197
+ lastSubmittedDynamicGeometryBindings = /* @__PURE__ */ new WeakMap();
23198
+ const frameConsumedDynamicGeometryBindings = /* @__PURE__ */ new WeakMap();
23134
23199
  if (profileSession !== void 0 && opts.profileFrame === void 0) {
23135
23200
  try {
23136
23201
  ownsProfileFrame = profileSession.beginFrame(++directFrameId).ok;
@@ -23655,9 +23720,23 @@ function createRenderSystem(internals) {
23655
23720
  environmentReady && environment !== void 0,
23656
23721
  persistentRenderScene.transmissionTopologyDemand(),
23657
23722
  volumetricFog,
23658
- timingCapture
23723
+ timingCapture,
23724
+ (entry) => {
23725
+ const renderable = entry.source;
23726
+ const world = compositionWorlds[renderable.worldId];
23727
+ if (world === void 0) return;
23728
+ let bindings = frameConsumedDynamicGeometryBindings.get(world);
23729
+ if (bindings === void 0) {
23730
+ bindings = /* @__PURE__ */ new Map();
23731
+ frameConsumedDynamicGeometryBindings.set(world, bindings);
23732
+ }
23733
+ bindings.set(renderable.entityKey, renderable.assetHandle);
23734
+ }
23659
23735
  )
23660
23736
  );
23737
+ if (submitted) {
23738
+ lastSubmittedDynamicGeometryBindings = frameConsumedDynamicGeometryBindings;
23739
+ }
23661
23740
  if (submitted) {
23662
23741
  try {
23663
23742
  internals.recoveryColdWorkGuard?.finish();
@@ -23750,16 +23829,16 @@ function createRenderSystem(internals) {
23750
23829
  if (!retired.ok) internals.errorRegistry.fire(retired.error);
23751
23830
  }
23752
23831
  }
23753
- } catch (err41) {
23832
+ } catch (err42) {
23754
23833
  if (!submitted) {
23755
23834
  internals.getPipelineState()?.perPassResources.discardBloomResources?.();
23756
23835
  }
23757
- const innerError = err41 instanceof PipelineSpecError ? {
23758
- name: err41.name,
23759
- code: err41.code,
23760
- message: err41.message,
23761
- detail: err41.detail
23762
- } : err41 instanceof RhiError ? err41 : { code: "unknown", message: String(err41), name: err41?.name };
23836
+ const innerError = err42 instanceof PipelineSpecError ? {
23837
+ name: err42.name,
23838
+ code: err42.code,
23839
+ message: err42.message,
23840
+ detail: err42.detail
23841
+ } : err42 instanceof RhiError ? err42 : { code: "unknown", message: String(err42), name: err42?.name };
23763
23842
  internals.errorRegistry.fire(
23764
23843
  new RhiError({
23765
23844
  code: "webgpu-runtime-error",
@@ -23778,6 +23857,10 @@ function createRenderSystem(internals) {
23778
23857
  }
23779
23858
  return submitted;
23780
23859
  },
23860
+ isDynamicGeometryConsumed(world, entity, meshHandle) {
23861
+ if (!Number.isInteger(entity) || meshHandle === void 0) return false;
23862
+ return lastSubmittedDynamicGeometryBindings.get(world)?.get(entity) === meshHandle;
23863
+ },
23781
23864
  pipelineDispatchCounts: dispatchCounts,
23782
23865
  observeCurrentFrame(options) {
23783
23866
  const currentFrameId = frameState.frameNumber - 1;
@@ -25085,6 +25168,641 @@ function deviceOptionsForAdapter(adapter, options) {
25085
25168
  const admission = deriveDeviceFeatureAdmission(adapter, options);
25086
25169
  return admission.requiredFeatures.length === 0 && admission.requiredLimits === void 0 ? void 0 : admission;
25087
25170
  }
25171
+ function fixedStepOf(world) {
25172
+ try {
25173
+ return world.getResource(FixedTime).tick;
25174
+ } catch {
25175
+ return void 0;
25176
+ }
25177
+ }
25178
+ function hasRenderableBinding(world, entity) {
25179
+ if (entity === void 0 || !Number.isInteger(entity)) return false;
25180
+ const transform = world.get(entity, Transform);
25181
+ const mesh = world.get(entity, MeshFilter);
25182
+ const material = world.get(entity, MeshRenderer);
25183
+ if (!transform.ok || !mesh.ok || !material.ok) return false;
25184
+ return true;
25185
+ }
25186
+ function meshHandleOf(world, entity) {
25187
+ if (entity === void 0 || !Number.isInteger(entity)) return void 0;
25188
+ const mesh = world.get(entity, MeshFilter);
25189
+ if (!mesh.ok) return void 0;
25190
+ return mesh.value.assetHandle;
25191
+ }
25192
+ function hasMaterialIdentity(world, entity, mesh, identity) {
25193
+ if (identity === void 0) return true;
25194
+ const normalized = identity.trim();
25195
+ if (normalized.length === 0 || entity === void 0) return false;
25196
+ const material = world.get(entity, MeshRenderer);
25197
+ if (!material.ok) return false;
25198
+ const values = material.value.materials ?? [];
25199
+ if (mesh.materialSlots.some((slot) => slot.sourceKey === normalized || slot.slotName === normalized))
25200
+ return true;
25201
+ if (mesh.materialSlots.some(
25202
+ (slot) => slot.defaultMaterial !== void 0 && AssetGuid.format(slot.defaultMaterial).toLowerCase() === normalized.toLowerCase()
25203
+ ))
25204
+ return true;
25205
+ return values.some((value) => {
25206
+ if (String(value) === normalized) return true;
25207
+ try {
25208
+ const resolved2 = world.sharedRefs.resolve(value);
25209
+ if (!resolved2.ok || typeof resolved2.value !== "object" || resolved2.value === null)
25210
+ return false;
25211
+ const payload = resolved2.value;
25212
+ return payload.sourceKey === normalized || payload.guid === normalized;
25213
+ } catch {
25214
+ return false;
25215
+ }
25216
+ });
25217
+ }
25218
+ function hasMeshBinding(world, handle) {
25219
+ try {
25220
+ const query = world.query({ read: [MeshFilter] }).unwrap();
25221
+ for (const row of query) {
25222
+ if (Number(row.get(MeshFilter).assetHandle) === handle) return true;
25223
+ }
25224
+ } catch {
25225
+ }
25226
+ return false;
25227
+ }
25228
+ function ownsMeshPayload(world, meshHandle, mesh) {
25229
+ if (meshHandle === void 0) return false;
25230
+ try {
25231
+ const resolved2 = world.sharedRefs.resolve(meshHandle);
25232
+ return resolved2.ok && resolved2.value === mesh;
25233
+ } catch {
25234
+ return false;
25235
+ }
25236
+ }
25237
+ function physicsPublicationOf(world, entity, admitting = false) {
25238
+ try {
25239
+ const physics = world.getResource("PhysicsWorld");
25240
+ return admitting ? physics.getDerivedAdmission?.(entity) : physics.getDerivedPublication?.(entity);
25241
+ } catch {
25242
+ return void 0;
25243
+ }
25244
+ }
25245
+ function createDynamicGeometryHost(options) {
25246
+ const { lifecycle, attachedWorlds, getGpuStore, currentGeneration, isConsumedByRenderFrame } = options;
25247
+ const hostCandidates = /* @__PURE__ */ new Map();
25248
+ let observedGeneration = -1;
25249
+ const releaseMeshLease = (record) => {
25250
+ if (!record.meshLeaseHeld) return void 0;
25251
+ const released = record.world.sharedRefs.release(record.meshHandle);
25252
+ if (!released.ok) {
25253
+ return new DynamicGeometryError(
25254
+ "dynamic-geometry-invalid",
25255
+ "the Renderer candidate lease remains a live World shared reference",
25256
+ "retain the MeshAsset handle until candidate cancellation or ECS acceptance completes",
25257
+ { candidateId: record.candidate.candidateId, actual: released.error }
25258
+ );
25259
+ }
25260
+ record.meshLeaseHeld = false;
25261
+ return void 0;
25262
+ };
25263
+ const releasePreviousMeshLease = (record) => {
25264
+ if (!record.previousMeshLeaseHeld) return void 0;
25265
+ const released = record.world.sharedRefs.release(record.previousMeshHandle);
25266
+ if (!released.ok) {
25267
+ return new DynamicGeometryError(
25268
+ "dynamic-geometry-invalid",
25269
+ "the previous ECS MeshFilter binding remains a live World shared reference",
25270
+ "retain the previous MeshAsset until candidate cancellation, retirement, or invalidation completes",
25271
+ { candidateId: record.candidate.candidateId, actual: released.error }
25272
+ );
25273
+ }
25274
+ record.previousMeshLeaseHeld = false;
25275
+ return void 0;
25276
+ };
25277
+ const releaseMeshLeases = (record) => {
25278
+ const candidateError = releaseMeshLease(record);
25279
+ const previousError = releasePreviousMeshLease(record);
25280
+ return candidateError ?? previousError;
25281
+ };
25282
+ const synchronizeGeneration = () => {
25283
+ const generation = currentGeneration();
25284
+ lifecycle.invalidateGeneration(generation);
25285
+ if (observedGeneration !== generation) {
25286
+ for (const [candidateId, record] of hostCandidates) {
25287
+ if (record.generation !== generation) {
25288
+ hostCandidates.delete(candidateId);
25289
+ scheduleCandidateCleanup(record, true);
25290
+ }
25291
+ }
25292
+ observedGeneration = generation;
25293
+ }
25294
+ return generation;
25295
+ };
25296
+ const candidateRecord2 = (candidate) => {
25297
+ const record = hostCandidates.get(candidate.candidateId);
25298
+ if (record === void 0 || !sameCandidateCredential(candidate, record.candidate))
25299
+ return void 0;
25300
+ return record;
25301
+ };
25302
+ const cleanupCandidateResidency = (record, force = false) => record.residency?.release(force || !hasMeshBinding(record.world, record.meshHandle));
25303
+ const scheduleCandidateCleanup = (record, force = false) => {
25304
+ hostCandidates.delete(record.candidate.candidateId);
25305
+ const completion = cleanupCandidateResidency(record, force);
25306
+ const finish = () => {
25307
+ releaseMeshLeases(record);
25308
+ lifecycle.finalizeRetirement(record.candidate);
25309
+ if (hostCandidates.get(record.candidate.candidateId) === record)
25310
+ hostCandidates.delete(record.candidate.candidateId);
25311
+ };
25312
+ if (completion === void 0) finish();
25313
+ else void completion.then(finish, finish);
25314
+ return completion;
25315
+ };
25316
+ return {
25317
+ prepareDynamicGeometry(input) {
25318
+ const generation = synchronizeGeneration();
25319
+ const world = input.world;
25320
+ if (!attachedWorlds.has(world))
25321
+ return err$1(
25322
+ new DynamicGeometryError(
25323
+ "dynamic-geometry-world-not-attached",
25324
+ "the candidate World is attached to this Renderer",
25325
+ "call renderer.attach(world) before preparing dynamic geometry"
25326
+ )
25327
+ );
25328
+ if (input.entity === void 0 || !hasRenderableBinding(world, input.entity)) {
25329
+ return err$1(
25330
+ new DynamicGeometryError(
25331
+ "dynamic-geometry-invalid",
25332
+ "the candidate entity and MeshAsset payload belong to the attached World",
25333
+ "attach MeshFilter/MeshRenderer and pass the payload resolved by its World shared handle",
25334
+ { actual: { entity: input.entity, meshHandle: input.meshHandle } }
25335
+ )
25336
+ );
25337
+ }
25338
+ if (input.meshHandle === void 0)
25339
+ return err$1(
25340
+ new DynamicGeometryError(
25341
+ "dynamic-geometry-gpu-not-ready",
25342
+ "the standard MeshAsset shared handle is present before Renderer admission",
25343
+ "allocate the MeshAsset through world.allocSharedRef and retry after asset projection",
25344
+ { actual: { entity: input.entity, meshHandle: input.meshHandle } }
25345
+ )
25346
+ );
25347
+ const meshHandle = input.meshHandle;
25348
+ if (!ownsMeshPayload(world, meshHandle, input.mesh) || !hasMaterialIdentity(world, input.entity, input.mesh, input.materialIdentity)) {
25349
+ return err$1(
25350
+ new DynamicGeometryError(
25351
+ "dynamic-geometry-invalid",
25352
+ "the candidate MeshAsset and material identity belong to the attached World",
25353
+ "pass the payload resolved by its World shared handle and its live material slot identity",
25354
+ { actual: { entity: input.entity, meshHandle } }
25355
+ )
25356
+ );
25357
+ }
25358
+ const fixedStep = fixedStepOf(world);
25359
+ if (fixedStep === void 0) {
25360
+ return err$1(
25361
+ new DynamicGeometryError(
25362
+ "dynamic-geometry-ordering-required",
25363
+ "the attached World exposes its ECS FixedTime resource",
25364
+ "prepare geometry from an initialized World fixed-step schedule"
25365
+ )
25366
+ );
25367
+ }
25368
+ const previousMeshHandle = meshHandleOf(world, input.entity);
25369
+ if (previousMeshHandle === void 0) {
25370
+ return err$1(
25371
+ new DynamicGeometryError(
25372
+ "dynamic-geometry-invalid",
25373
+ "the prepared entity retains a live MeshFilter binding",
25374
+ "keep the ECS render entity alive while staging geometry",
25375
+ { actual: { entity: input.entity } }
25376
+ )
25377
+ );
25378
+ }
25379
+ const previousRetained = world.sharedRefs.retain(previousMeshHandle);
25380
+ if (!previousRetained.ok)
25381
+ return err$1(
25382
+ new DynamicGeometryError(
25383
+ "dynamic-geometry-invalid",
25384
+ "the previous ECS MeshFilter binding remains live for Renderer preparation",
25385
+ "retain the current MeshAsset handle before staging replacement geometry",
25386
+ { actual: previousRetained.error }
25387
+ )
25388
+ );
25389
+ const retained = world.sharedRefs.retain(meshHandle);
25390
+ if (!retained.ok) {
25391
+ const releasedPrevious = world.sharedRefs.release(previousMeshHandle);
25392
+ return releasedPrevious.ok ? err$1(
25393
+ new DynamicGeometryError(
25394
+ "dynamic-geometry-invalid",
25395
+ "the candidate MeshAsset shared reference remains live for Renderer preparation",
25396
+ "retain the World-owned MeshAsset handle before staging a candidate",
25397
+ { actual: retained.error }
25398
+ )
25399
+ ) : err$1(
25400
+ new DynamicGeometryError(
25401
+ "dynamic-geometry-invalid",
25402
+ "candidate preparation leaves both MeshAsset leases recoverable",
25403
+ "repair the World shared references before retrying geometry preparation",
25404
+ { actual: { candidate: retained.error, previous: releasedPrevious.error } }
25405
+ )
25406
+ );
25407
+ }
25408
+ const releaseRetained = () => {
25409
+ const releasedCandidate = world.sharedRefs.release(meshHandle);
25410
+ const releasedPrevious = world.sharedRefs.release(previousMeshHandle);
25411
+ if (!releasedCandidate.ok || !releasedPrevious.ok)
25412
+ return new DynamicGeometryError(
25413
+ "dynamic-geometry-invalid",
25414
+ "failed candidate preparation releases both World shared-reference leases",
25415
+ "repair the World shared references before retrying geometry preparation",
25416
+ {
25417
+ actual: {
25418
+ candidate: releasedCandidate.ok ? void 0 : releasedCandidate.error,
25419
+ previous: releasedPrevious.ok ? void 0 : releasedPrevious.error
25420
+ }
25421
+ }
25422
+ );
25423
+ return void 0;
25424
+ };
25425
+ const prepared = lifecycle.prepare({ ...input, fixedStep }, generation);
25426
+ if (!prepared.ok) {
25427
+ const released = releaseRetained();
25428
+ return released === void 0 ? prepared : err$1(released);
25429
+ }
25430
+ const residencyStore = getGpuStore();
25431
+ const resident = residencyStore.ensureResident(
25432
+ meshHandle,
25433
+ prepared.value.mesh,
25434
+ input.world
25435
+ );
25436
+ if (!resident.ok) {
25437
+ lifecycle.cancel(prepared.value);
25438
+ cleanupCandidateResidency({
25439
+ candidate: prepared.value,
25440
+ world,
25441
+ entity: input.entity,
25442
+ meshHandle,
25443
+ residency: void 0});
25444
+ const released = releaseMeshLeases({
25445
+ candidate: prepared.value,
25446
+ world,
25447
+ entity: input.entity,
25448
+ previousMeshHandle,
25449
+ meshHandle,
25450
+ meshLeaseHeld: true,
25451
+ previousMeshLeaseHeld: true
25452
+ });
25453
+ if (released !== void 0) return err$1(released);
25454
+ return err$1(
25455
+ new DynamicGeometryError(
25456
+ "dynamic-geometry-gpu-failed",
25457
+ "the standard MeshAsset has GPU residency for the active device generation",
25458
+ "wait for Renderer.initialization or repair the RHI resource failure before retrying",
25459
+ { candidateId: prepared.value.candidateId, actual: resident.error }
25460
+ )
25461
+ );
25462
+ }
25463
+ const residency = residencyStore.retainMeshResidency(meshHandle, world);
25464
+ if (residency === void 0) {
25465
+ lifecycle.cancel(prepared.value);
25466
+ residencyStore.invalidateMesh(meshHandle, world);
25467
+ const released = releaseRetained();
25468
+ return released === void 0 ? err$1(
25469
+ new DynamicGeometryError(
25470
+ "dynamic-geometry-gpu-failed",
25471
+ "the standard MeshAsset retains a shared GPU residency owner",
25472
+ "repair the active GPU residency before retrying geometry preparation",
25473
+ { candidateId: prepared.value.candidateId }
25474
+ )
25475
+ ) : err$1(released);
25476
+ }
25477
+ hostCandidates.set(prepared.value.candidateId, {
25478
+ candidate: prepared.value,
25479
+ world,
25480
+ entity: input.entity,
25481
+ previousMeshHandle,
25482
+ meshHandle,
25483
+ residency,
25484
+ generation,
25485
+ meshLeaseHeld: true,
25486
+ previousMeshLeaseHeld: true,
25487
+ accepted: false,
25488
+ published: false
25489
+ });
25490
+ return prepared;
25491
+ },
25492
+ acceptDynamicGeometry(candidate, ordering) {
25493
+ synchronizeGeneration();
25494
+ const world = candidate.world;
25495
+ const hostRecordById = hostCandidates.get(candidate.candidateId);
25496
+ const hostRecord = candidateRecord2(candidate);
25497
+ if (hostRecord === void 0 && hostRecordById !== void 0)
25498
+ return err$1(
25499
+ new DynamicGeometryError(
25500
+ "dynamic-geometry-receipt-mismatch",
25501
+ "admission uses the exact host candidate owner and lifecycle credential",
25502
+ "discard the altered credential and use the value returned by preparation",
25503
+ { candidateId: candidate.candidateId }
25504
+ )
25505
+ );
25506
+ if (hostRecord !== void 0 && candidate.state !== "prepared")
25507
+ return err$1(
25508
+ new DynamicGeometryError(
25509
+ "dynamic-geometry-candidate-state",
25510
+ "admission consumes a prepared candidate exactly once",
25511
+ "retain the accepted or published candidate receipt instead of admitting it again",
25512
+ { candidateId: candidate.candidateId }
25513
+ )
25514
+ );
25515
+ if (ordering === void 0 || !attachedWorlds.has(world) || ordering.world !== candidate.world)
25516
+ return err$1(
25517
+ new DynamicGeometryError(
25518
+ "dynamic-geometry-world-not-attached",
25519
+ "candidate and fixed-step ordering belong to the same attached World",
25520
+ "attach the World and submit its PhysicsWorld ordering before accepting geometry",
25521
+ { candidateId: candidate.candidateId }
25522
+ )
25523
+ );
25524
+ if (!candidate.gpuReady)
25525
+ return err$1(
25526
+ new DynamicGeometryError(
25527
+ "dynamic-geometry-gpu-not-ready",
25528
+ "candidate has standard GPU residency before acceptance",
25529
+ "prepare with an existing MeshAsset shared handle and repair residency failures",
25530
+ { candidateId: candidate.candidateId }
25531
+ )
25532
+ );
25533
+ if (hostRecord === void 0 || !hasRenderableBinding(world, candidate.entity) || !hasMaterialIdentity(world, candidate.entity, candidate.mesh, candidate.materialIdentity))
25534
+ return err$1(
25535
+ new DynamicGeometryError(
25536
+ "dynamic-geometry-invalid",
25537
+ "the candidate is bound to a live ECS MeshFilter and MeshRenderer",
25538
+ "keep the candidate MeshFilter binding intact until the consuming draw",
25539
+ { candidateId: candidate.candidateId }
25540
+ )
25541
+ );
25542
+ if (meshHandleOf(world, candidate.entity) !== hostRecord.previousMeshHandle)
25543
+ return err$1(
25544
+ new DynamicGeometryError(
25545
+ "dynamic-geometry-invalid",
25546
+ "the old MeshFilter binding remains visible until candidate acceptance",
25547
+ "do not replace the live ECS binding while geometry is prepared",
25548
+ { candidateId: candidate.candidateId }
25549
+ )
25550
+ );
25551
+ const currentFixedStep = fixedStepOf(world);
25552
+ if (currentFixedStep === void 0 || candidate.fixedStep === void 0 || candidate.fixedStep > currentFixedStep || ordering.fixedStep !== currentFixedStep)
25553
+ return err$1(
25554
+ new DynamicGeometryError(
25555
+ "dynamic-geometry-ordering-required",
25556
+ "ordering matches the current World tick, no earlier than preparation",
25557
+ "accept a prepared candidate during the current fixed-step admission",
25558
+ {
25559
+ candidateId: candidate.candidateId,
25560
+ actual: {
25561
+ candidate: candidate.fixedStep,
25562
+ currentFixedStep,
25563
+ ordering: ordering.fixedStep
25564
+ }
25565
+ }
25566
+ )
25567
+ );
25568
+ if (candidate.physicsEntity !== void 0) {
25569
+ const publication = physicsPublicationOf(world, candidate.physicsEntity, true);
25570
+ if (publication === void 0 || publication.fixedStep !== currentFixedStep || publication.revision !== candidate.revision)
25571
+ return err$1(
25572
+ new DynamicGeometryError(
25573
+ "dynamic-geometry-ordering-required",
25574
+ "candidate physicsEntity has the active paired PhysicsWorld admission",
25575
+ "accept geometry inside admitDerivedShapeCandidate commitGeometry, before physics step",
25576
+ { candidateId: candidate.candidateId, actual: publication }
25577
+ )
25578
+ );
25579
+ }
25580
+ const superseded = [...hostCandidates.values()].filter(
25581
+ (record) => record.candidate.candidateId !== candidate.candidateId && record.world === world && record.entity === candidate.entity && !record.published
25582
+ );
25583
+ const accepted = lifecycle.accept(candidate, ordering);
25584
+ if (!accepted.ok) return accepted;
25585
+ const swapped = world.set(candidate.entity, MeshFilter, {
25586
+ assetHandle: hostRecord.meshHandle
25587
+ });
25588
+ if (!swapped.ok) {
25589
+ lifecycle.cancel(accepted.value);
25590
+ hostCandidates.delete(candidate.candidateId);
25591
+ cleanupCandidateResidency(hostRecord);
25592
+ const released = releaseMeshLeases(hostRecord);
25593
+ if (released !== void 0) return err$1(released);
25594
+ return err$1(
25595
+ new DynamicGeometryError(
25596
+ "dynamic-geometry-invalid",
25597
+ "the candidate MeshFilter swap commits through the ECS write barrier",
25598
+ "restore the live entity and retry from a fresh candidate",
25599
+ { candidateId: candidate.candidateId, actual: swapped.error }
25600
+ )
25601
+ );
25602
+ }
25603
+ hostRecord.candidate = accepted.value;
25604
+ hostRecord.accepted = true;
25605
+ options.onTopologyChanged?.();
25606
+ for (const record of superseded) {
25607
+ const completion = cleanupCandidateResidency(record);
25608
+ const cancelled = lifecycle.cancel(record.candidate, completion);
25609
+ if (!cancelled.ok && cancelled.error.code !== "dynamic-geometry-candidate-not-found") {
25610
+ const restored = world.set(candidate.entity, MeshFilter, {
25611
+ assetHandle: hostRecord.previousMeshHandle
25612
+ });
25613
+ lifecycle.cancel(accepted.value);
25614
+ hostCandidates.delete(candidate.candidateId);
25615
+ cleanupCandidateResidency(hostRecord);
25616
+ releasePreviousMeshLease(hostRecord);
25617
+ return err$1(
25618
+ new DynamicGeometryError(
25619
+ "dynamic-geometry-invalid",
25620
+ "candidate supersession cancels the prior credential atomically",
25621
+ "keep the prior ECS binding and candidate lifecycle intact when supersession fails",
25622
+ {
25623
+ candidateId: candidate.candidateId,
25624
+ actual: {
25625
+ cancelled: cancelled.error,
25626
+ restored: restored.ok ? void 0 : restored.error
25627
+ }
25628
+ }
25629
+ )
25630
+ );
25631
+ }
25632
+ hostCandidates.delete(record.candidate.candidateId);
25633
+ scheduleCandidateCleanup(record);
25634
+ }
25635
+ return accepted;
25636
+ },
25637
+ dynamicGeometryReceipt(candidate) {
25638
+ synchronizeGeneration();
25639
+ return lifecycle.receipt(candidate);
25640
+ },
25641
+ cancelDynamicGeometry(candidate) {
25642
+ synchronizeGeneration();
25643
+ const record = candidateRecord2(candidate);
25644
+ if (record === void 0) {
25645
+ if (hostCandidates.has(candidate.candidateId))
25646
+ return err$1(
25647
+ new DynamicGeometryError(
25648
+ "dynamic-geometry-receipt-mismatch",
25649
+ "cancellation uses the exact host candidate owner and lifecycle credential",
25650
+ "discard the altered credential and use the value returned by preparation",
25651
+ { candidateId: candidate.candidateId }
25652
+ )
25653
+ );
25654
+ return lifecycle.cancel(candidate);
25655
+ }
25656
+ if (!record.accepted && candidate.state !== "prepared" || record.accepted && !record.published && candidate.state !== "accepted" || record.published && candidate.state !== "accepted" && candidate.state !== "published")
25657
+ return err$1(
25658
+ new DynamicGeometryError(
25659
+ "dynamic-geometry-receipt-mismatch",
25660
+ "cancellation uses the candidate state issued by the host lifecycle",
25661
+ "discard the altered credential and use the value returned by preparation or acceptance",
25662
+ { candidateId: candidate.candidateId }
25663
+ )
25664
+ );
25665
+ if (record.published)
25666
+ return err$1(
25667
+ new DynamicGeometryError(
25668
+ "dynamic-geometry-candidate-state",
25669
+ "published geometry remains until its receipt retires",
25670
+ "retire the receipt-bound candidate instead of cancelling it",
25671
+ { candidateId: candidate.candidateId }
25672
+ )
25673
+ );
25674
+ if (record.accepted) {
25675
+ const currentHandle = meshHandleOf(record.world, record.entity);
25676
+ if (currentHandle !== record.meshHandle && currentHandle !== record.previousMeshHandle)
25677
+ return err$1(
25678
+ new DynamicGeometryError(
25679
+ "dynamic-geometry-invalid",
25680
+ "cancel does not overwrite a newer ECS MeshFilter binding",
25681
+ "reconcile the live entity before cancelling this candidate",
25682
+ { candidateId: candidate.candidateId }
25683
+ )
25684
+ );
25685
+ if (currentHandle === record.meshHandle) {
25686
+ const restored = record.world.set(record.entity, MeshFilter, {
25687
+ assetHandle: record.previousMeshHandle
25688
+ });
25689
+ if (!restored.ok)
25690
+ return err$1(
25691
+ new DynamicGeometryError(
25692
+ "dynamic-geometry-invalid",
25693
+ "cancel restores the previous MeshFilter through the ECS write barrier",
25694
+ "restore the entity before retrying cancellation",
25695
+ { candidateId: candidate.candidateId, actual: restored.error }
25696
+ )
25697
+ );
25698
+ }
25699
+ }
25700
+ const completion = cleanupCandidateResidency(record);
25701
+ const cancelled = lifecycle.cancel(candidate, completion);
25702
+ if (!cancelled.ok) return cancelled;
25703
+ hostCandidates.delete(candidate.candidateId);
25704
+ scheduleCandidateCleanup(record);
25705
+ return cancelled;
25706
+ },
25707
+ invalidateDynamicGeometryWorld(world) {
25708
+ const doomed = [...hostCandidates.values()].filter((record) => record.world === world);
25709
+ const completions = doomed.map((record) => scheduleCandidateCleanup(record, true)).filter((completion) => completion !== void 0);
25710
+ lifecycle.invalidateWorld(
25711
+ world,
25712
+ completions.length === 0 ? void 0 : Promise.allSettled(completions)
25713
+ );
25714
+ },
25715
+ retireDynamicGeometry(candidate) {
25716
+ synchronizeGeneration();
25717
+ const record = candidateRecord2(candidate);
25718
+ if (record === void 0) {
25719
+ if (hostCandidates.has(candidate.candidateId))
25720
+ return err$1(
25721
+ new DynamicGeometryError(
25722
+ "dynamic-geometry-receipt-mismatch",
25723
+ "retirement uses the exact host candidate owner and lifecycle credential",
25724
+ "discard the altered credential and use the value returned by acceptance",
25725
+ { candidateId: candidate.candidateId }
25726
+ )
25727
+ );
25728
+ return lifecycle.retire(candidate);
25729
+ }
25730
+ if (candidate.state !== "accepted" && candidate.state !== "published")
25731
+ return err$1(
25732
+ new DynamicGeometryError(
25733
+ "dynamic-geometry-receipt-mismatch",
25734
+ "retirement uses a candidate state issued by the host lifecycle",
25735
+ "discard the altered credential and use the value returned by acceptance",
25736
+ { candidateId: candidate.candidateId }
25737
+ )
25738
+ );
25739
+ if (meshHandleOf(record.world, record.entity) === record.meshHandle)
25740
+ return err$1(
25741
+ new DynamicGeometryError(
25742
+ "dynamic-geometry-invalid",
25743
+ "retirement does not evict the MeshFilter that still consumes the candidate",
25744
+ "swap the ECS MeshFilter through its write barrier before retiring this geometry",
25745
+ { candidateId: candidate.candidateId }
25746
+ )
25747
+ );
25748
+ const retired = lifecycle.retire(candidate);
25749
+ if (!retired.ok) return retired;
25750
+ record.candidate = Object.freeze({ ...record.candidate, state: "retired" });
25751
+ const previousReleased = releasePreviousMeshLease(record);
25752
+ if (previousReleased !== void 0) return err$1(previousReleased);
25753
+ scheduleCandidateCleanup(record);
25754
+ return retired;
25755
+ },
25756
+ publishDynamicGeometry(frame, worlds, fixedStep) {
25757
+ synchronizeGeneration();
25758
+ const targetWorlds = /* @__PURE__ */ new Set();
25759
+ for (const candidateWorld of worlds ?? attachedWorlds) {
25760
+ const world = candidateWorld;
25761
+ if (attachedWorlds.has(world)) targetWorlds.add(world);
25762
+ }
25763
+ const receipts = [];
25764
+ for (const world of targetWorlds) {
25765
+ const worldFixedStep = fixedStepOf(world);
25766
+ if (worldFixedStep === void 0) continue;
25767
+ const publicationFixedStep = fixedStep ?? worldFixedStep;
25768
+ receipts.push(
25769
+ ...lifecycle.publishFrame(frame, [world], publicationFixedStep, (candidate) => {
25770
+ const candidateWorld = candidate.world;
25771
+ if (candidateWorld !== world || !attachedWorlds.has(candidateWorld)) return false;
25772
+ const hostRecord = candidateRecord2(candidate);
25773
+ if (hostRecord === void 0 || !hostRecord.accepted || !hasRenderableBinding(candidateWorld, candidate.entity) || meshHandleOf(candidateWorld, candidate.entity) !== candidate.meshHandle || !hasMaterialIdentity(
25774
+ candidateWorld,
25775
+ candidate.entity,
25776
+ candidate.mesh,
25777
+ candidate.materialIdentity
25778
+ ))
25779
+ return false;
25780
+ if (!isConsumedByRenderFrame(candidate)) return false;
25781
+ const actualFixedStep = fixedStep ?? worldFixedStep;
25782
+ if (candidate.fixedStep === void 0 || actualFixedStep === void 0) return false;
25783
+ if (worldFixedStep !== actualFixedStep || actualFixedStep < candidate.fixedStep)
25784
+ return false;
25785
+ if (candidate.physicsEntity !== void 0) {
25786
+ const publication = physicsPublicationOf(candidateWorld, candidate.physicsEntity);
25787
+ if (publication === void 0 || publication.fixedStep < candidate.fixedStep || publication.revision !== candidate.revision)
25788
+ return false;
25789
+ }
25790
+ return true;
25791
+ })
25792
+ );
25793
+ }
25794
+ for (const receipt of receipts) {
25795
+ const record = hostCandidates.get(receipt.candidateId);
25796
+ if (record !== void 0) {
25797
+ record.published = true;
25798
+ if (receipt.frame.completed !== void 0)
25799
+ record.residency?.track(receipt.frame.completed);
25800
+ }
25801
+ }
25802
+ return receipts;
25803
+ }
25804
+ };
25805
+ }
25088
25806
  var STANDARD_PBR_REQUIRED_SAMPLED_TEXTURES2 = 17;
25089
25807
  function selectHdrpPbrPrewarmVariants(manifestEntry, storageBufferCapable, extendedLightingShaderAvailableOrTransmissionCapable = true, transmissionCapable = true, directionalPcssAvailable = true, projectorAvailable = true) {
25090
25808
  const variants = manifestEntry?.variants ?? [];
@@ -29950,11 +30668,11 @@ async function buildReadyWebGPU(rhiDevice, rendererScope, getShader, gpuStore, a
29950
30668
  }
29951
30669
  try {
29952
30670
  getOrBuildPipeline(spec, pipelineDeviceProvider, pipelineCache, modules);
29953
- } catch (err41) {
29954
- if (err41 instanceof PipelineSpecError) throw err41;
30671
+ } catch (err42) {
30672
+ if (err42 instanceof PipelineSpecError) throw err42;
29955
30673
  throw new PipelineSpecError({
29956
30674
  code: "pipeline-build-failed",
29957
- detail: { cause: err41 },
30675
+ detail: { cause: err42 },
29958
30676
  hint: `Boot-time SPEC_CONST pre-warm failed for shader '${spec.shader.id}'; inspect gpuMessage on the cause`
29959
30677
  });
29960
30678
  }
@@ -30123,11 +30841,11 @@ async function buildReadyWebGPU(rhiDevice, rendererScope, getShader, gpuStore, a
30123
30841
  pipelineCache,
30124
30842
  modules
30125
30843
  );
30126
- } catch (err41) {
30127
- if (err41 instanceof PipelineSpecError) throw err41;
30844
+ } catch (err42) {
30845
+ if (err42 instanceof PipelineSpecError) throw err42;
30128
30846
  throw new PipelineSpecError({
30129
30847
  code: "pipeline-build-failed",
30130
- detail: { cause: err41 },
30848
+ detail: { cause: err42 },
30131
30849
  hint: "createRenderPipeline (fxaa fullscreen) failed; inspect gpuMessage"
30132
30850
  });
30133
30851
  }
@@ -30381,11 +31099,11 @@ async function buildReadyWebGPU(rhiDevice, rendererScope, getShader, gpuStore, a
30381
31099
  bloomBrightPipelineHandle,
30382
31100
  () => void 0
30383
31101
  );
30384
- } catch (err41) {
30385
- if (err41 instanceof PipelineSpecError) throw err41;
31102
+ } catch (err42) {
31103
+ if (err42 instanceof PipelineSpecError) throw err42;
30386
31104
  throw new PipelineSpecError({
30387
31105
  code: "pipeline-build-failed",
30388
- detail: { cause: err41 },
31106
+ detail: { cause: err42 },
30389
31107
  hint: "createRenderPipeline (bloom-bright fullscreen) failed; inspect gpuMessage"
30390
31108
  });
30391
31109
  }
@@ -30482,11 +31200,11 @@ async function buildReadyWebGPU(rhiDevice, rendererScope, getShader, gpuStore, a
30482
31200
  bloomBlurHPipelineHandle,
30483
31201
  () => void 0
30484
31202
  );
30485
- } catch (err41) {
30486
- if (err41 instanceof PipelineSpecError) throw err41;
31203
+ } catch (err42) {
31204
+ if (err42 instanceof PipelineSpecError) throw err42;
30487
31205
  throw new PipelineSpecError({
30488
31206
  code: "pipeline-build-failed",
30489
- detail: { cause: err41 },
31207
+ detail: { cause: err42 },
30490
31208
  hint: "createRenderPipeline (bloom-blur-h fullscreen) failed; inspect gpuMessage"
30491
31209
  });
30492
31210
  }
@@ -30603,11 +31321,11 @@ async function buildReadyWebGPU(rhiDevice, rendererScope, getShader, gpuStore, a
30603
31321
  bloomCompositePipelineHandle,
30604
31322
  () => void 0
30605
31323
  );
30606
- } catch (err41) {
30607
- if (err41 instanceof PipelineSpecError) throw err41;
31324
+ } catch (err42) {
31325
+ if (err42 instanceof PipelineSpecError) throw err42;
30608
31326
  throw new PipelineSpecError({
30609
31327
  code: "pipeline-build-failed",
30610
- detail: { cause: err41 },
31328
+ detail: { cause: err42 },
30611
31329
  hint: "createRenderPipeline (bloom-composite fullscreen) failed; inspect gpuMessage"
30612
31330
  });
30613
31331
  }
@@ -30897,11 +31615,11 @@ async function buildReadyWebGPU(rhiDevice, rendererScope, getShader, gpuStore, a
30897
31615
  pipelineCache,
30898
31616
  { ...baseModules, fragmentEntryPoint: "fs_ssao_calc" }
30899
31617
  );
30900
- } catch (err41) {
30901
- if (err41 instanceof PipelineSpecError) throw err41;
31618
+ } catch (err42) {
31619
+ if (err42 instanceof PipelineSpecError) throw err42;
30902
31620
  throw new PipelineSpecError({
30903
31621
  code: "pipeline-build-failed",
30904
- detail: { cause: err41 },
31622
+ detail: { cause: err42 },
30905
31623
  hint: "createRenderPipeline (ssao-calc fullscreen) failed; inspect gpuMessage"
30906
31624
  });
30907
31625
  }
@@ -30932,11 +31650,11 @@ async function buildReadyWebGPU(rhiDevice, rendererScope, getShader, gpuStore, a
30932
31650
  pipelineCache,
30933
31651
  { ...baseModules, fragmentEntryPoint: "fs_ssao_blur" }
30934
31652
  );
30935
- } catch (err41) {
30936
- if (err41 instanceof PipelineSpecError) throw err41;
31653
+ } catch (err42) {
31654
+ if (err42 instanceof PipelineSpecError) throw err42;
30937
31655
  throw new PipelineSpecError({
30938
31656
  code: "pipeline-build-failed",
30939
- detail: { cause: err41 },
31657
+ detail: { cause: err42 },
30940
31658
  hint: "createRenderPipeline (ssao-blur fullscreen) failed; inspect gpuMessage"
30941
31659
  });
30942
31660
  }
@@ -31683,6 +32401,7 @@ async function makeWebGPURenderer(internals) {
31683
32401
  const publishRendererGeneration = (candidate) => {
31684
32402
  publishGeneration(generationPublication, candidate, (value) => value.scope.isAlive());
31685
32403
  activeDeviceScope = candidate.scope;
32404
+ dynamicGeometry.invalidateGeneration(activeDeviceScope.generation);
31686
32405
  internals.generationState.current = activeDeviceScope.generation;
31687
32406
  pipelineState = candidate.pipeline;
31688
32407
  const bindings = candidate.producerBindings;
@@ -32222,7 +32941,7 @@ async function makeWebGPURenderer(internals) {
32222
32941
  const currentState = currentPipelineState();
32223
32942
  if (currentState === null) return null;
32224
32943
  const ldrColorFormat = colorFormatOverride ?? currentState.colorAttachmentFormat;
32225
- const fragmentEntry = authoredFragmentEntry ?? (isHdr && (materialShaderId === "forgeax::sprite" || materialShaderId === "forgeax::sprite-lit") ? "fs_main_hdr" : void 0);
32944
+ const fragmentEntry = authoredFragmentEntry ?? (isHdr && passKind === "forward" && (materialShaderId === "forgeax::sprite" || materialShaderId === "forgeax::sprite-lit") ? "fs_main_hdr" : void 0);
32226
32945
  const built = buildPipelineForMaterialShader(
32227
32946
  cacheKey,
32228
32947
  // The catalog entry is projected into the material-shader entry shape.
@@ -32729,6 +33448,19 @@ async function makeWebGPURenderer(internals) {
32729
33448
  const attachedWorlds = /* @__PURE__ */ new Set();
32730
33449
  const attachedLeases = /* @__PURE__ */ new Map();
32731
33450
  const leasesByWorld = /* @__PURE__ */ new Map();
33451
+ const dynamicGeometry = createDynamicGeometryLifecycle();
33452
+ const dynamicGeometryHost = createDynamicGeometryHost({
33453
+ lifecycle: dynamicGeometry,
33454
+ attachedWorlds,
33455
+ getGpuStore: () => gpuStore,
33456
+ currentGeneration: () => activeDeviceScope.generation,
33457
+ onTopologyChanged: () => renderSystem.invalidateGeometryHistory(),
33458
+ isConsumedByRenderFrame: (candidate) => renderSystem.isDynamicGeometryConsumed(
33459
+ candidate.world,
33460
+ candidate.entity ?? -1,
33461
+ candidate.meshHandle
33462
+ )
33463
+ });
32732
33464
  const derivedSystemNames = /* @__PURE__ */ new Map();
32733
33465
  const transformReleases = /* @__PURE__ */ new Map();
32734
33466
  const attachmentOwner = {};
@@ -32749,6 +33481,7 @@ async function makeWebGPURenderer(internals) {
32749
33481
  leasesByWorld.set(world, lease);
32750
33482
  return ok(lease);
32751
33483
  },
33484
+ ...dynamicGeometryHost,
32752
33485
  createRenderTarget: (descriptor) => renderTargetHost.createRenderTarget(descriptor),
32753
33486
  resizeRenderTarget: (target4, descriptor) => renderTargetHost.resizeRenderTarget(target4, descriptor),
32754
33487
  createRenderTargetTextureSource: (target4, options) => renderTargetHost.createRenderTargetTextureSource(target4, options),
@@ -32852,6 +33585,7 @@ async function makeWebGPURenderer(internals) {
32852
33585
  })
32853
33586
  )
32854
33587
  ),
33588
+ dynamicGeometry: dynamicGeometry.inspect(),
32855
33589
  renderScene: renderSystem.renderScene,
32856
33590
  reflectionProbes: renderSystem.reflectionProbes,
32857
33591
  ssrDependencies: renderSystem.ssrDependencies,
@@ -32973,6 +33707,7 @@ async function makeWebGPURenderer(internals) {
32973
33707
  },
32974
33708
  detachScene(world) {
32975
33709
  if (!attachedWorlds.delete(world)) return;
33710
+ dynamicGeometryHost.invalidateDynamicGeometryWorld(world);
32976
33711
  for (const [lease, leaseWorld] of attachedLeases) {
32977
33712
  if (leaseWorld === world) {
32978
33713
  lease.dispose();
@@ -33147,6 +33882,21 @@ async function makeWebGPURenderer(internals) {
33147
33882
  const frameRequest = isFrameRequest ? worldsOrRequest : void 0;
33148
33883
  const worlds = frameRequest !== void 0 ? frameRequest.leases.map((lease) => attachedLeases.get(lease)).filter((world) => world !== void 0) : worldsOrRequest;
33149
33884
  const readLeases = frameRequest !== void 0 ? frameRequest.leases : worlds.map((world) => leasesByWorld.get(world)).every((lease) => lease !== void 0) ? worlds.map((world) => leasesByWorld.get(world)) : void 0;
33885
+ for (const world of worlds) {
33886
+ if (!world.hasResource("PhysicsWorld")) continue;
33887
+ const physics = world.getResource("PhysicsWorld");
33888
+ if (physics?.getDerivedAdmission?.() !== void 0 || physics?.getDerivedRecoveryState?.() === "rebuild-required") {
33889
+ return err(
33890
+ new RendererOperationError("frame-input-invalid", {
33891
+ operation: "draw",
33892
+ cause: new RendererContractFailureError(
33893
+ "draw",
33894
+ "paired geometry admission must finish physics step and writeback in a healthy World before draw"
33895
+ )
33896
+ })
33897
+ );
33898
+ }
33899
+ }
33150
33900
  if (frameRequest !== void 0 && worlds.length !== frameRequest.leases.length) {
33151
33901
  return err(
33152
33902
  new RendererOperationError("world-lease-invalid", {
@@ -33268,6 +34018,8 @@ async function makeWebGPURenderer(internals) {
33268
34018
  const timingCompletion = timingHost.gpuPassTimingSubmittedWork;
33269
34019
  timingHost.gpuPassTimingSubmittedWork = void 0;
33270
34020
  const reflectionFallbackCompletion = renderSystem.reflectionFallbackCompletion;
34021
+ const queueCompletion = timingCompletion ?? internals.device.queue.onSubmittedWorkDone();
34022
+ gpuStore.trackMeshSubmission(queueCompletion);
33271
34023
  internals.pack.instrumentation?.onFrameBoundary?.();
33272
34024
  if (!isFrameRequest) {
33273
34025
  renderTargetHost.onFrameSubmitted();
@@ -33275,7 +34027,6 @@ async function makeWebGPURenderer(internals) {
33275
34027
  }
33276
34028
  const receiptFrameId = ++frameId;
33277
34029
  const receiptGeneration = activeDeviceScope.generation;
33278
- const queueCompletion = timingCompletion ?? internals.device.queue.onSubmittedWorkDone();
33279
34030
  const completion = Promise.all([
33280
34031
  queueCompletion,
33281
34032
  reflectionFallbackCompletion ?? Promise.resolve()
@@ -33383,6 +34134,7 @@ async function makeWebGPURenderer(internals) {
33383
34134
  );
33384
34135
  }
33385
34136
  issuedReceipts.add(receipt);
34137
+ dynamicGeometryHost.publishDynamicGeometry(receipt, worlds, frameRequest?.fixedStep);
33386
34138
  return ok(receipt);
33387
34139
  } catch (cause) {
33388
34140
  const error = cause instanceof Error ? { code: "unknown", message: cause.message, name: cause.name } : { code: "unknown", message: String(cause) };
@@ -33510,6 +34262,10 @@ async function makeWebGPURenderer(internals) {
33510
34262
  dispose() {
33511
34263
  if (disposed) return ok(void 0);
33512
34264
  disposed = true;
34265
+ for (const world of attachedWorlds) {
34266
+ dynamicGeometryHost.invalidateDynamicGeometryWorld(world);
34267
+ }
34268
+ dynamicGeometry.dispose();
33513
34269
  for (const continuation of frameContinuations) {
33514
34270
  continuation.terminate({ code: "disposed" });
33515
34271
  }