@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 +0,0 @@
1
- {"version":3,"sources":["../src/components/camera.ts","../src/components/directional-light.ts","../src/components/instances.ts","../src/components/layer.ts","../src/components/mesh-filter.ts","../src/components/mesh-renderer.ts","../src/components/point-light.ts","../src/components/point-light-shadow.ts","../src/components/post-process-params.ts","../src/components/scene-instance.ts","../src/components/skybox-background.ts","../src/components/skylight.ts","../src/components/sort-key.ts","../src/components/spot-light.ts","../src/components/visibility.ts","../src/pipeline/standard-profile.ts","../src/render-contract.ts","../src/extract/visibility.ts"],"names":["defineComponent"],"mappings":";;;;;AAyCO,IAAM,6BAAA,GAAgC;AAEtC,IAAM,8BAAA,GAAiC;AAQvC,SAAS,wBAAwB,KAAA,EAAiC;AACvE,EAAA,OAAO,KAAA,KAAU,iCAAiC,cAAA,GAAiB,aAAA;AACrE;AA8BO,IAAM,eAAe,mBAAA,CAAoB;AAKzC,IAAM,4BAA4B,mBAAA,CAAoB;AAEtD,IAAM,mBAAmB,mBAAA,CAAoB;AAE7C,IAAM,iBAAiB,mBAAA,CAAoB;AAE3C,IAAM,iBAAiB,mBAAA,CAAoB;AAE3C,IAAM,sBAAsB,mBAAA,CAAoB;AAEhD,IAAM,cAAc,mBAAA,CAAoB;AAExC,IAAM,kBAAkB,mBAAA,CAAoB;AAM5C,SAAS,eAAe,KAAA,EAAwB;AACrD,EAAA,QAAQ,KAAA;AAAO,IACb,KAAK,yBAAA;AACH,MAAA,OAAO,mBAAA;AAAA,IACT,KAAK,gBAAA;AACH,MAAA,OAAO,UAAA;AAAA,IACT,KAAK,cAAA;AACH,MAAA,OAAO,QAAA;AAAA,IACT,KAAK,cAAA;AACH,MAAA,OAAO,QAAA;AAAA,IACT,KAAK,mBAAA;AACH,MAAA,OAAO,aAAA;AAAA,IACT,KAAK,WAAA;AACH,MAAA,OAAO,KAAA;AAAA,IACT,KAAK,eAAA;AACH,MAAA,OAAO,SAAA;AAAA,IACT;AACE,MAAA,OAAO,MAAA;AAAA;AAEb;AAUO,SAAS,aAAa,IAAA,EAAuB;AAClD,EAAA,QAAQ,IAAA;AAAM,IACZ,KAAK,mBAAA;AACH,MAAA,OAAO,yBAAA;AAAA,IACT,KAAK,UAAA;AACH,MAAA,OAAO,gBAAA;AAAA,IACT,KAAK,QAAA;AACH,MAAA,OAAO,cAAA;AAAA,IACT,KAAK,QAAA;AACH,MAAA,OAAO,cAAA;AAAA,IACT,KAAK,aAAA;AACH,MAAA,OAAO,mBAAA;AAAA,IACT,KAAK,KAAA;AACH,MAAA,OAAO,WAAA;AAAA,IACT,KAAK,SAAA;AACH,MAAA,OAAO,eAAA;AAAA,IACT,KAAK,MAAA;AACH,MAAA,OAAO,YAAA;AAAA;AAEX,EAAA,MAAM,IAAI,UAAA,CAAW,CAAA,sBAAA,EAAyB,MAAA,CAAO,IAAI,CAAC,CAAA,CAAA,CAAG,CAAA;AAC/D;AAsBO,IAAM,cAAA,GAAiB;AAEvB,IAAM,cAAA,GAAiB;AAEvB,IAAM,cAAA,GAAiB;AAEvB,IAAM,aAAA,GAAgB,CAAA;AAMtB,SAAS,iBAAiB,KAAA,EAA0B;AACzD,EAAA,IAAI,KAAA,KAAU,gBAAgB,OAAO,MAAA;AACrC,EAAA,IAAI,KAAA,KAAU,gBAAgB,OAAO,MAAA;AACrC,EAAA,IAAI,KAAA,KAAU,gBAAgB,OAAO,MAAA;AACrC,EAAA,IAAI,KAAA,KAAU,eAAe,OAAO,KAAA;AACpC,EAAA,MAAM,IAAI,UAAA;AAAA,IACR,CAAA,yBAAA,EAA4B,KAAK,CAAA,WAAA,EAAc,cAAc,YAAY,cAAc,CAAA,SAAA,EAAY,cAAc,CAAA,YAAA,EAAe,aAAa,CAAA,OAAA;AAAA,GAC/I;AACF;AAgBO,IAAM,cAAA,GAAiB;AAEvB,IAAM,aAAA,GAAgB;AAMtB,SAAS,oBAAoB,KAAA,EAA6B;AAC/D,EAAA,IAAI,KAAA,KAAU,gBAAgB,OAAO,KAAA;AACrC,EAAA,IAAI,KAAA,KAAU,eAAe,OAAO,IAAA;AACpC,EAAA,MAAM,IAAI,UAAA;AAAA,IACR,CAAA,qBAAA,EAAwB,KAAK,CAAA,WAAA,EAAc,cAAc,aAAa,aAAa,CAAA,MAAA;AAAA,GACrF;AACF;AA4EO,IAAM,MAAA,GAAS,gBAAgB,QAAA,EAAU;AAAA,EAC9C,GAAA,EAAK,EAAE,IAAA,EAAM,KAAA,EAAM;AAAA,EACnB,MAAA,EAAQ,EAAE,IAAA,EAAM,KAAA,EAAM;AAAA,EACtB,IAAA,EAAM,EAAE,IAAA,EAAM,KAAA,EAAM;AAAA,EACpB,GAAA,EAAK,EAAE,IAAA,EAAM,KAAA,EAAM;AAAA,EACnB,UAAA,EAAY,EAAE,IAAA,EAAM,KAAA,EAAO,SAAS,CAAA,EAAE;AAAA,EACtC,IAAA,EAAM,EAAE,IAAA,EAAM,KAAA,EAAO,SAAS,EAAA,EAAG;AAAA,EACjC,KAAA,EAAO,EAAE,IAAA,EAAM,KAAA,EAAO,SAAS,CAAA,EAAE;AAAA,EACjC,MAAA,EAAQ,EAAE,IAAA,EAAM,KAAA,EAAO,SAAS,EAAA,EAAG;AAAA,EACnC,GAAA,EAAK,EAAE,IAAA,EAAM,KAAA,EAAO,SAAS,CAAA,EAAE;AAAA,EAC/B,OAAA,EAAS,EAAE,IAAA,EAAM,KAAA,EAAO,SAAS,CAAA,EAAE;AAAA,EACnC,QAAA,EAAU,EAAE,IAAA,EAAM,KAAA,EAAO,SAAS,CAAA,EAAI;AAAA,EACtC,UAAA,EAAY,EAAE,IAAA,EAAM,KAAA,EAAO,SAAS,CAAA,EAAI;AAAA,EACxC,SAAA,EAAW,EAAE,IAAA,EAAM,KAAA,EAAO,SAAS,CAAA,EAAE;AAAA,EACrC,cAAA,EAAgB,EAAE,IAAA,EAAM,KAAA,EAAO,SAAS,CAAA,EAAE;AAAA,EAC1C,KAAA,EAAO,EAAE,IAAA,EAAM,KAAA,EAAO,SAAS,CAAA,EAAE;AAAA,EACjC,cAAA,EAAgB,EAAE,IAAA,EAAM,KAAA,EAAO,SAAS,CAAA,EAAI;AAAA,EAC5C,cAAA,EAAgB,EAAE,IAAA,EAAM,KAAA,EAAO,SAAS,CAAA,EAAI;AAAA,EAC5C,eAAA,EAAiB,EAAE,IAAA,EAAM,KAAA,EAAO,SAAS,CAAA,EAAI;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAU7C,UAAA,EAAY,EAAE,IAAA,EAAM,eAAA,EAAiB,OAAA,EAAS,IAAI,YAAA,CAAa,CAAC,CAAA,EAAG,CAAA,EAAG,CAAA,EAAG,CAAC,CAAC,CAAA,EAAE;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAW7E,UAAA,EAAY,EAAE,IAAA,EAAM,MAAA,EAAQ,SAAS,IAAA;AACvC,CAAC;AAkED,SAAS,qBAAA,GAAmC;AAC1C,EAAA,MAAM,OAAgC,EAAC;AACvC,EAAA,KAAA,MAAW,CAAC,KAAK,KAAK,CAAA,IAAK,OAAO,OAAA,CAAQ,MAAA,CAAO,MAAM,CAAA,EAAG;AACxD,IAAA,IAAI,SAAA,IAAa,KAAA,IAAS,KAAA,CAAM,OAAA,KAAY,MAAA,EAAW;AACrD,MAAA,IAAA,CAAK,GAAG,IAAI,KAAA,CAAM,OAAA;AAAA,IACpB;AAAA,EACF;AACA,EAAA,OAAO,IAAA;AACT;AA0CO,SAAS,YAAY,IAAA,EAAwC;AAClE,EAAA,OAAO;AAAA;AAAA;AAAA;AAAA,IAIL,GAAG,qBAAA,EAAsB;AAAA,IACzB,KAAK,IAAA,CAAK,GAAA;AAAA,IACV,QAAQ,IAAA,CAAK,MAAA;AAAA,IACb,IAAA,EAAM,KAAK,IAAA,IAAQ,GAAA;AAAA,IACnB,GAAA,EAAK,KAAK,GAAA,IAAO,GAAA;AAAA,IACjB,UAAA,EAAY,6BAAA;AAAA,IACZ,GAAI,KAAK,UAAA,KAAe,MAAA,GAAY,EAAE,UAAA,EAAY,IAAA,CAAK,UAAA,EAAW,GAAI;AAAC,GACzE;AACF;AA0BO,SAAS,aAAa,IAAA,EAAyC;AACpE,EAAA,OAAO;AAAA,IACL,GAAG,qBAAA,EAAsB;AAAA,IACzB,GAAA,EAAK,CAAA;AAAA,IACL,MAAA,EAAQ,CAAA;AAAA,IACR,IAAA,EAAM,KAAK,IAAA,IAAQ,GAAA;AAAA,IACnB,GAAA,EAAK,KAAK,GAAA,IAAO,GAAA;AAAA,IACjB,UAAA,EAAY,8BAAA;AAAA,IACZ,MAAM,IAAA,CAAK,IAAA;AAAA,IACX,OAAO,IAAA,CAAK,KAAA;AAAA,IACZ,QAAQ,IAAA,CAAK,MAAA;AAAA,IACb,KAAK,IAAA,CAAK;AAAA,GACZ;AACF;AChaO,IAAM,gBAAA,GAAmBA,gBAAgB,kBAAA,EAAoB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAMlE,SAAA,EAAW,EAAE,IAAA,EAAM,eAAA,EAAgB;AAAA,EACnC,KAAA,EAAO,EAAE,IAAA,EAAM,eAAA,EAAiB,OAAA,EAAS,IAAI,YAAA,CAAa,CAAC,CAAA,EAAG,CAAA,EAAG,CAAC,CAAC,CAAA,EAAE;AAAA,EACrE,SAAA,EAAW,EAAE,IAAA,EAAM,KAAA,EAAO,SAAS,CAAA,EAAE;AAAA;AAAA,EAErC,UAAA,EAAY,EAAE,IAAA,EAAM,MAAA,EAAQ,SAAS,IAAA,EAAK;AAAA;AAAA,EAE1C,YAAA,EAAc,EAAE,IAAA,EAAM,KAAA,EAAO,SAAS,CAAA,EAAE;AAAA,EACxC,WAAA,EAAa,EAAE,IAAA,EAAM,KAAA,EAAO,SAAS,IAAA,EAAK;AAAA,EAC1C,YAAA,EAAc,EAAE,IAAA,EAAM,KAAA,EAAO,SAAS,GAAA,EAAI;AAAA,EAC1C,OAAA,EAAS,EAAE,IAAA,EAAM,KAAA,EAAO,SAAS,IAAA,EAAK;AAAA,EACtC,SAAA,EAAW,EAAE,IAAA,EAAM,KAAA,EAAO,SAAS,IAAA,EAAM;AAAA,EACzC,UAAA,EAAY,EAAE,IAAA,EAAM,KAAA,EAAO,SAAS,IAAA,EAAK;AAAA;AAAA;AAAA;AAAA;AAAA,EAKzC,cAAA,EAAgB,EAAE,IAAA,EAAM,KAAA,EAAO,SAAS,GAAA,EAAI;AAAA,EAC5C,aAAA,EAAe,EAAE,IAAA,EAAM,KAAA,EAAO,SAAS,CAAA;AACzC,CAAC;ACoEM,IAAM,SAAA,GAAYA,gBAAgB,WAAA,EAAa;AAAA,EACpD,UAAA,EAAY,EAAE,IAAA,EAAM,YAAA;AACtB,CAAC;AC3HM,IAAM,KAAA,GAAQA,gBAAgB,OAAA,EAAS;AAAA,EAC5C,KAAA,EAAO,EAAE,IAAA,EAAM,KAAA,EAAO,SAAS,CAAA;AACjC,CAAC;ACXM,IAAM,UAAA,GAAaA,gBAAgB,YAAA,EAAc;AAAA,EACtD,WAAA,EAAa,EAAE,IAAA,EAAM,mBAAA;AACvB,CAAC;ACDM,IAAM,YAAA,GAAeA,gBAAgB,cAAA,EAAgB;AAAA,EAC1D,WAAW,EAAE,IAAA,EAAM,8BAAA,EAAgC,OAAA,EAAS,EAAC;AAC/D,CAAC;ACFM,IAAM,UAAA,GAAaA,gBAAgB,YAAA,EAAc;AAAA;AAAA;AAAA,EAGtD,KAAA,EAAO,EAAE,IAAA,EAAM,eAAA,EAAiB,OAAA,EAAS,IAAI,YAAA,CAAa,CAAC,CAAA,EAAG,CAAA,EAAG,CAAC,CAAC,CAAA,EAAE;AAAA,EACrE,SAAA,EAAW,EAAE,IAAA,EAAM,KAAA,EAAO,SAAS,CAAA,EAAE;AAAA,EACrC,KAAA,EAAO,EAAE,IAAA,EAAM,KAAA,EAAO,SAAS,EAAA;AACjC,CAAC;AC7BM,IAAM,gBAAA,GAAmBA,gBAAgB,kBAAA,EAAoB;AAAA,EAClE,OAAA,EAAS,EAAE,IAAA,EAAM,KAAA,EAAO,SAAS,GAAA,EAAI;AAAA,EACrC,SAAA,EAAW,EAAE,IAAA,EAAM,KAAA,EAAO,SAAS,IAAA,EAAM;AAAA,EACzC,UAAA,EAAY,EAAE,IAAA,EAAM,KAAA,EAAO,SAAS,IAAA,EAAK;AAAA,EACzC,SAAA,EAAW,EAAE,IAAA,EAAM,KAAA,EAAO,SAAS,GAAA,EAAI;AAAA,EACvC,QAAA,EAAU,EAAE,IAAA,EAAM,KAAA,EAAO,SAAS,EAAA,EAAG;AAAA,EACrC,aAAA,EAAe,EAAE,IAAA,EAAM,KAAA,EAAO,SAAS,CAAA;AACzC,CAAC;ACbD,IAAM,MAAA,GAAS;AAAA,EACb,MAAA,EAAQ,QAAA;AAAA,EACR,IAAA,EAAM;AACR,CAAA;AAEO,IAAM,iBAAA,GAAoBA,eAAAA,CAAgB,mBAAA,EAAqB,MAAM;ACoJrE,IAAM,aAAA,GAAgBA,eAAAA;AAAA,EAC3B,eAAA;AAAA,EACA;AAAA,IACE,MAAA,EAAQ,EAAE,IAAA,EAAM,oBAAA,EAAqB;AAAA,IACrC,OAAA,EAAS,EAAE,IAAA,EAAM,eAAA,EAAgB;AAAA;AAAA;AAAA,IAGjC,KAAA,EAAO,EAAE,IAAA,EAAM,4BAAA,EAA8B,OAAO,QAAA;AAAS,GAC/D;AAAA,EACA,EAAE,WAAW,IAAA;AACf;AC1JO,IAAM,mBAAA,GAAsB;AAyD5B,IAAM,gBAAA,GAAmBA,gBAAgB,kBAAA,EAAoB;AAAA;AAAA;AAAA;AAAA,EAIlE,QAAA,EAAU,EAAE,IAAA,EAAM,uBAAA,EAAyB,qBAAqB,IAAA,EAAK;AAAA,EACrE,IAAA,EAAM,EAAE,IAAA,EAAM,KAAA,EAAO,SAAS,mBAAA,EAAoB;AAAA,EAClD,QAAA,EAAU,EAAE,IAAA,EAAM,eAAA,EAAiB,OAAA,EAAS,IAAI,YAAA,CAAa,CAAC,CAAA,EAAG,CAAA,EAAG,CAAA,EAAG,CAAC,CAAC,CAAA;AAC3E,CAAC;AChBM,IAAM,QAAA,GAAWA,gBAAgB,UAAA,EAAY;AAAA;AAAA;AAAA;AAAA,EAIlD,QAAA,EAAU,EAAE,IAAA,EAAM,uBAAA,EAAyB,qBAAqB,IAAA,EAAK;AAAA;AAAA;AAAA,EAGrE,KAAA,EAAO,EAAE,IAAA,EAAM,eAAA,EAAiB,OAAA,EAAS,IAAI,YAAA,CAAa,CAAC,CAAA,EAAG,CAAA,EAAG,CAAC,CAAC,CAAA,EAAE;AAAA,EACrE,SAAA,EAAW,EAAE,IAAA,EAAM,KAAA,EAAO,SAAS,CAAA,EAAI;AAAA,EACvC,QAAA,EAAU,EAAE,IAAA,EAAM,eAAA,EAAiB,OAAA,EAAS,IAAI,YAAA,CAAa,CAAC,CAAA,EAAG,CAAA,EAAG,CAAA,EAAG,CAAC,CAAC,CAAA;AAC3E,CAAC;ACxBM,IAAM,OAAA,GAAUA,gBAAgB,SAAA,EAAW;AAAA,EAChD,KAAA,EAAO,EAAE,IAAA,EAAM,KAAA,EAAO,SAAS,CAAA;AACjC,CAAC;AC8BM,IAAM,SAAA,GAAYA,gBAAgB,WAAA,EAAa;AAAA;AAAA;AAAA;AAAA,EAIpD,SAAA,EAAW,EAAE,IAAA,EAAM,eAAA,EAAgB;AAAA,EACnC,KAAA,EAAO,EAAE,IAAA,EAAM,eAAA,EAAiB,OAAA,EAAS,IAAI,YAAA,CAAa,CAAC,CAAA,EAAG,CAAA,EAAG,CAAC,CAAC,CAAA,EAAE;AAAA,EACrE,SAAA,EAAW,EAAE,IAAA,EAAM,KAAA,EAAO,SAAS,CAAA,EAAE;AAAA,EACrC,KAAA,EAAO,EAAE,IAAA,EAAM,KAAA,EAAO,SAAS,EAAA,EAAK;AAAA,EACpC,YAAA,EAAc,EAAE,IAAA,EAAM,KAAA,EAAO,SAAS,CAAA,EAAE;AAAA,EACxC,YAAA,EAAc,EAAE,IAAA,EAAM,KAAA,EAAO,SAAS,EAAA,EAAG;AAAA;AAAA,EAEzC,UAAA,EAAY,EAAE,IAAA,EAAM,MAAA,EAAQ,SAAS,IAAA,EAAK;AAAA;AAAA,EAE1C,OAAA,EAAS,EAAE,IAAA,EAAM,KAAA,EAAO,SAAS,IAAA,EAAK;AAAA,EACtC,SAAA,EAAW,EAAE,IAAA,EAAM,KAAA,EAAO,SAAS,IAAA,EAAM;AAAA,EACzC,UAAA,EAAY,EAAE,IAAA,EAAM,KAAA,EAAO,SAAS,IAAA,EAAK;AAAA,EACzC,SAAA,EAAW,EAAE,IAAA,EAAM,KAAA,EAAO,SAAS,GAAA,EAAI;AAAA,EACvC,QAAA,EAAU,EAAE,IAAA,EAAM,KAAA,EAAO,SAAS,EAAA,EAAG;AAAA,EACrC,aAAA,EAAe,EAAE,IAAA,EAAM,KAAA,EAAO,SAAS,CAAA;AACzC,CAAC;ACxHM,IAAM,oBAAA,GAAuB,OAAO,MAAA,CAAO;AAAA,EAChD,SAAA,EAAW,CAAA;AAAA,EACX,MAAA,EAAQ,CAAA;AAAA,EACR,OAAA,EAAS;AACX,CAAU;AAUH,SAAS,uBAAuB,KAAA,EAA4C;AACjF,EAAA,QAAQ,KAAA;AAAO,IACb,KAAK,oBAAA,CAAqB,SAAA;AACxB,MAAA,OAAO,WAAA;AAAA,IACT,KAAK,oBAAA,CAAqB,MAAA;AACxB,MAAA,OAAO,QAAA;AAAA,IACT,KAAK,oBAAA,CAAqB,OAAA;AACxB,MAAA,OAAO,SAAA;AAAA,IACT;AACE,MAAA,OAAO,MAAA;AAAA;AAEb;AAMO,IAAM,UAAA,GAAaA,eAAAA;AAAA,EACxB,YAAA;AAAA,EACA;AAAA,IACE,KAAA,EAAO;AAAA,MACL,IAAA,EAAM,MAAA;AAAA,MACN,SAAS,oBAAA,CAAqB,SAAA;AAAA,MAC9B,MAAA,EAAQ;AAAA;AACV,GACF;AAAA,EACA;AAAA,IACE,IAAA,EAAM;AAAA,MACJ,UAAA,EAAY,0DAAA;AAAA,MACZ,WAAA,EAAa,4EAAA;AAAA,MACb,QAAA,EAAU,uEAAA;AAAA,MACV,UAAA,EACE;AAAA;AACJ;AAEJ;;;ACpDO,IAAM,oBAAA,GAAuB;AAC7B,IAAM,qBAAA,GAAwB,CAAC,CAAA,EAAG,EAAA,EAAI,GAAG;AAEzC,IAAM,uBAAuB,EAAE,CAAA,EAAG,IAAI,CAAA,EAAG,CAAA,EAAG,GAAG,EAAA;AAC/C,IAAM,uBAAA,GAA0B;AAChC,IAAM,yBAAA,GAA4B;AAClC,IAAM,UAAA,GAAa;AAkCnB,IAAM,wBAAA,GAA4C,OAAO,MAAA,CAAO;AAAA,EACrE,UAAA,EAAY,oBAAA;AAAA,EACZ,UAAA,EAAY,EAAA;AAAA,EACZ,QAAA,EAAU,QAAA;AAAA,EACV,OAAA,EAAS,UAAA;AAAA,EACT,GAAA,EAAK,IAAA;AAAA,EACL,GAAA,EAAK,IAAA;AAAA,EACL,IAAA,EAAM,KAAA;AAAA,EACN,KAAA,EAAO,IAAA;AAAA,EACP,IAAA,EAAM,aAAA;AAAA,EACN,SAAA,EAAW,MAAA;AAAA,EACX,GAAA,EAAK,IAAA;AAAA,EACL,QAAA,EAAU,QAAA;AAAA,EACV,YAAY,CAAC,mBAAA,EAAqB,OAAA,EAAS,MAAA,EAAQ,QAAQ,QAAQ;AACrE,CAAC;AAIM,SAAS,mBAAA,CACd,SACA,YAAA,EACc;AACd,EAAA,IAAI,CAAC,YAAA,CAAa,OAAA,IAAW,CAAC,YAAA,CAAa,eAAe,OAAO,YAAA;AACjE,EAAA,OAAO,OAAA,CAAQ,QAAA;AACjB;;;ACYO,IAAM,2BAAA,GAA8B;AAsRpC,IAAM,oCAAA,GAAuC;AAAA,EAClD,mCAAA;AAAA,EACA,4CAAA;AAAA,EACA,+BAAA;AAAA,EACA,6CAAA;AAAA,EACA,oDAAA;AAAA,EACA,kDAAA;AAAA,EACA,2CAAA;AAAA,EACA,gCAAA;AAAA,EACA,gCAAA;AAAA,EACA,+BAAA;AAAA,EACA,8BAAA;AAAA,EACA,4CAAA;AAAA,EACA,mDAAA;AAAA,EACA,iDAAA;AAAA,EACA,0CAAA;AAAA,EACA,8BAAA;AAAA,EACA,oCAAA;AAAA,EACA,6BAAA;AAAA,EACA,kCAAA;AAAA,EACA,6BAAA;AAAA,EACA,2BAAA;AAAA,EACA,2BAAA;AAAA,EACA,mCAAA;AAAA,EACA,mCAAA;AAAA,EACA,mCAAA;AAAA,EACA,sCAAA;AAAA,EACA;AACF,CAAA;AAKO,IAAM,gCAAA,GAAmC;AAAA,EAC9C,mEAAA;AAAA,EACA,8EAAA;AAAA,EACA,qFAAA;AAAA,EACA,wDAAA;AAAA,EACA,0DAAA;AAAA,EACA,iDAAA;AAAA,EACA,0DAAA;AAAA,EACA,sEAAA;AAAA,EACA;AACF,CAAA;AAEO,IAAM,iCAAA,GAAoC;AAAA,EAC/C,wCAAA;AAAA,EACA,GAAG,gCAAA;AAAA,EACH,iDAAA;AAAA,EACA;AACF,CAAA;AAIO,IAAM,gCAAA,GAAmC;AAAA,EAC9C,iCAAA;AAAA,EACA,kCAAA;AAAA,EACA,uCAAA;AAAA,EACA,6CAAA;AAAA,EACA,iCAAA;AAAA,EACA,GAAG;AACL,CAAA;AASO,IAAM,2BAAA,GAA8B;AAAA,EACzC,oBAAA;AAAA,EACA,GAAG,gCAAA;AAAA,EACH,kBAAA;AAAA,EACA,qBAAA;AAAA,EACA,qBAAA;AAAA,EACA,mBAAA;AAAA,EACA,sBAAA;AAAA,EACA,gBAAA;AAAA,EACA,oBAAA;AAAA,EACA,sBAAA;AAAA,EACA,GAAG;AACL,CAAA;AAIO,IAAM,oBAAA,GAAuB;AAAA,EAClC,SAAA;AAAA,EACA,aAAA;AAAA,EACA,UAAA;AAAA,EACA,MAAA;AAAA,EACA,QAAA;AAAA,EACA,GAAG;AACL;ACpZO,SAAS,iBAAA,CACd,KAAA,EACA,SAAA,GAAoC,gBAAA,CAAiB,KAAK,CAAA,EACtC;AACpB,EAAA,MAAM,KAAA,GAAQ,KAAA,CAAM,KAAA,CAAM,EAAE,IAAA,EAAM,CAAC,MAAM,CAAA,EAAG,QAAA,EAAU,CAAC,UAAU,CAAA,EAAG,CAAA;AACpE,EAAA,IAAI,WAAA,GAAc,CAAA;AAClB,EAAA,IAAI,kBAAA,GAAqB,KAAA;AACzB,EAAA,IAAI,MAAM,EAAA,EAAI;AACZ,IAAA,KAAA,MAAW,GAAA,IAAO,MAAM,KAAA,EAAO;AAC7B,MAAA,MAAM,UAAA,GAAa,GAAA,CAAI,GAAA,CAAI,UAAU,CAAA;AACrC,MAAA,IAAI,eAAe,MAAA,EAAW;AAC9B,MAAA,WAAA,IAAe,CAAA;AACf,MAAA,IAAI,UAAA,CAAW,KAAA,KAAU,oBAAA,CAAqB,MAAA,EAAQ;AACpD,QAAA,kBAAA,GAAqB,IAAA;AAAA,MACvB;AAAA,IACF;AAAA,EACF;AAEA,EAAA,IAAI,aAAA;AACJ,EAAA,IAAI,SAAA;AACJ,EAAA,MAAM,iBAAiB,MAAY;AACjC,IAAA,IAAI,kBAAkB,MAAA,EAAW;AACjC,IAAA,MAAM,cAAA,uBAAqB,GAAA,EAAmC;AAC9D,IAAA,IAAI,MAAM,EAAA,EAAI;AACZ,MAAA,KAAA,MAAW,GAAA,IAAO,MAAM,KAAA,EAAO;AAC7B,QAAA,MAAM,GAAA,GAAM,GAAA,CAAI,GAAA,CAAI,UAAU,CAAA;AAC9B,QAAA,MAAM,SAAS,GAAA,KAAQ,MAAA,GAAY,MAAA,GAAY,sBAAA,CAAuB,IAAI,KAAK,CAAA;AAC/E,QAAA,IAAI,WAAW,MAAA,EAAW;AACxB,UAAA,cAAA,CAAe,GAAA,CAAI,GAAA,CAAI,MAAA,EAAQ,MAAM,CAAA;AAAA,QACvC;AAAA,MACF;AAAA,IACF;AACA,IAAA,SAAA,GAAY,CAAC,MAAA,KAAkC,cAAA,CAAe,GAAA,CAAI,MAAM,CAAA;AACxE,IAAA,MAAM,QAAA,uBAAe,GAAA,EAAmC;AACxD,IAAA,MAAM,SAAA,uBAAgB,GAAA,EAAkB;AACxC,IAAA,aAAA,GAAgB,CAAC,MAAA,KAAsD;AACrE,MAAA,MAAM,QAAA,GAAW,QAAA,CAAS,GAAA,CAAI,MAAM,CAAA;AACpC,MAAA,IAAI,QAAA,KAAa,QAAW,OAAO,QAAA;AACnC,MAAA,IAAI,SAAA,CAAU,GAAA,CAAI,MAAM,CAAA,EAAG,OAAO,MAAA;AAElC,MAAA,MAAM,MAAA,GAAS,cAAA,CAAe,GAAA,CAAI,MAAM,CAAA,IAAK,WAAA;AAC7C,MAAA,SAAA,CAAU,IAAI,MAAM,CAAA;AAEpB,MAAA,IAAI,MAAA;AACJ,MAAA,IAAI,WAAW,QAAA,EAAU;AACvB,QAAA,MAAA,GAAS,EAAE,MAAA,EAAQ,SAAA,EAAW,QAAA,EAAU,QAAQ,MAAA,EAAO;AAAA,MACzD,CAAA,MAAA,IAAW,WAAW,SAAA,EAAW;AAC/B,QAAA,MAAA,GAAS,EAAE,MAAA,EAAQ,SAAA,EAAW,SAAA,EAAW,QAAQ,MAAA,EAAO;AAAA,MAC1D,CAAA,MAAO;AACL,QAAA,MAAM,MAAA,GAAS,SAAA,CAAU,SAAA,CAAU,MAAM,CAAA;AACzC,QAAA,MAAM,YAAA,GAAe,MAAA,KAAW,MAAA,GAAY,MAAA,GAAY,gBAAgB,MAAM,CAAA;AAC9E,QAAA,MAAA,GACE,YAAA,KAAiB,MAAA,GACb,EAAE,MAAA,EAAQ,WAAW,SAAA,EAAW,MAAA,EAAQ,SAAA,EAAU,GAClD,EAAE,MAAA,EAAQ,SAAA,EAAW,YAAA,CAAa,SAAA,EAAW,QAAQ,QAAA,EAAS;AAAA,MACtE;AAEA,MAAA,SAAA,CAAU,OAAO,MAAM,CAAA;AACvB,MAAA,QAAA,CAAS,GAAA,CAAI,QAAQ,MAAM,CAAA;AAC3B,MAAA,OAAO,MAAA;AAAA,IACT,CAAA;AAAA,EACF,CAAA;AAEA,EAAA,OAAO;AAAA,IACL,aAAa,SAAA,CAAU,WAAA;AAAA,IACvB,cAAc,WAAA,GAAc,CAAA;AAAA,IAC5B,kBAAA;AAAA,IACA,IAAI,MAAA,EAAwD;AAC1D,MAAA,cAAA,EAAe;AACf,MAAA,OAAO,SAAA,GAAY,MAAM,CAAA,GAAI,aAAA,GAAgB,MAAM,CAAA,GAAI,MAAA;AAAA,IACzD,CAAA;AAAA,IACA,UAAU,MAAA,EAA4C;AACpD,MAAA,cAAA,EAAe;AACf,MAAA,OAAO,aAAA,GAAgB,MAAM,CAAA,EAAG,SAAA,IAAa,SAAA;AAAA,IAC/C;AAAA,GACF;AACF","file":"chunk-SCRSAK5B.mjs","sourcesContent":["// @forgeax/engine-render - Camera (projection variant + ortho extension).\n//\n// Schema: 9 f32 columns — perspective quartet (fov + aspect + near + far)\n// + projection discriminator (0 = perspective, 1 = orthographic) + ortho\n// quartet (left + right + bottom + top). The view matrix continues to be\n// derived from the entity's Transform (AC-06 case B fires\n// 'render-system-no-camera' when 0 such entities exist).\n//\n// Projection discriminator:\n// projection === 0 → perspective path; RenderSystem builds\n// mat4.perspective(out, fov, aspect, near, far).\n// WebGPU [0, 1] NDC z; perspective reverse-Z hook is\n// a future spin-off path (plan-strategy §R-7) kept\n// outside M3 scope — the short-name mat4.perspective\n// already writes [0, 1] NDC, matching the ortho branch.\n// projection === 1 → orthographic path; RenderSystem builds\n// mat4.orthographic(out, left, right, bottom, top,\n// near, far) (WebGPU [0, 1] NDC; same z convention).\n//\n// Naming convention: forgeax uses the bare `Camera` name (no `Component`\n// suffix) to follow the unity-style flavor for \"individuating\" component\n// names per plan-strategy 7.2 + D-Q5a user lock.\n//\n// Related: requirements §AC-16 (Camera Ortho + Box3 / Sphere);\n// plan-strategy §M3 range + §R-7 risk (single perspective camera\n// in hello-room does NOT trigger depth-func conflict; multi-camera\n// mixed perspective + ortho deferred to feat-future-camera-depth-func);\n// plan-tasks.json w9 acceptanceCheck.\n\nimport { defineComponent, type SchemaOf, type ShapeOf } from '@forgeax/engine-ecs';\nimport { TONEMAP_SHADER_MODE } from '@forgeax/engine-shader';\n\n/**\n * Projection discriminator literal union (AC-16 narrowing surface).\n * Exposed for consumer code that maps `camera.projection` numeric values back\n * to a switch-able string literal (e.g. RenderSystem dispatch / inspector\n * snapshot rendering).\n */\nexport type CameraProjection = 'perspective' | 'orthographic';\n\n/** Numeric encoding of perspective projection (schema value for `projection`). */\nexport const CAMERA_PROJECTION_PERSPECTIVE = 0;\n/** Numeric encoding of orthographic projection (schema value for `projection`). */\nexport const CAMERA_PROJECTION_ORTHOGRAPHIC = 1;\n\n/**\n * Map a `camera.projection` numeric value to the closed `CameraProjection`\n * string-literal union. Values other than 0 / 1 fall back to `'perspective'`\n * (defensive for defaulted / uninitialised entities; charter proposition 4\n * no silent exception).\n */\nexport function cameraProjectionFromF32(value: number): CameraProjection {\n return value === CAMERA_PROJECTION_ORTHOGRAPHIC ? 'orthographic' : 'perspective';\n}\n\n/**\n * Tone-mapping mode discriminator literal union (AC-01 narrowing surface;\n * feat-20260519-tonemap-reinhard-mvp / M1).\n *\n * Two members for the MVP:\n * `'none'` - default; render-target stays `bgra8unorm-srgb`\n * and the geometry pass writes directly to the\n * swap-chain (zero-overhead opt-out path).\n * `'reinhard-extended'` - Forgeax Reinhard 2002 extended (luminance-domain)\n * opt-in;\n * `'reinhard'` - Three r184 per-channel Reinhard opt-in;\n * geometry pass routes through an `rgba16float`\n * HDR target and a fullscreen tonemap pass\n * (`packages/shader/src/tonemap.wgsl`).\n *\n * The remaining members mirror the Three r184 public names and formulas.\n */\nexport type Tonemap =\n | 'none'\n | 'reinhard-extended'\n | 'reinhard'\n | 'linear'\n | 'cineon'\n | 'aces-filmic'\n | 'agx'\n | 'neutral';\n\n/** Numeric encoding of the no-op tonemap path (schema value for `tonemap`). */\nexport const TONEMAP_NONE = TONEMAP_SHADER_MODE.none;\n/**\n * Numeric encoding of the Reinhard-extended tonemap path\n * (schema value for `tonemap`).\n */\nexport const TONEMAP_REINHARD_EXTENDED = TONEMAP_SHADER_MODE.reinhardExtended;\n/** Numeric encoding of the Three r184 per-channel Reinhard path. */\nexport const TONEMAP_REINHARD = TONEMAP_SHADER_MODE.reinhard;\n/** Numeric encoding of the linear (identity after exposure) tonemap path. */\nexport const TONEMAP_LINEAR = TONEMAP_SHADER_MODE.linear;\n/** Numeric encoding of the Cineon (Kodak log) tonemap path. */\nexport const TONEMAP_CINEON = TONEMAP_SHADER_MODE.cineon;\n/** Numeric encoding of the ACES filmic (Narkowicz 2015) tonemap path. */\nexport const TONEMAP_ACES_FILMIC = TONEMAP_SHADER_MODE.acesFilmic;\n/** Numeric encoding of the AgX (Troy Sobotka / Blender 3.x) tonemap path. */\nexport const TONEMAP_AGX = TONEMAP_SHADER_MODE.agx;\n/** Numeric encoding of the Khronos PBR neutral tonemap path. */\nexport const TONEMAP_NEUTRAL = TONEMAP_SHADER_MODE.neutral;\n\n/**\n * Map a `camera.tonemap` numeric value to the closed `Tonemap` string-literal\n * union. Unknown values fall back to `'none'` for defensive schema decoding.\n */\nexport function tonemapFromF32(value: number): Tonemap {\n switch (value) {\n case TONEMAP_REINHARD_EXTENDED:\n return 'reinhard-extended';\n case TONEMAP_REINHARD:\n return 'reinhard';\n case TONEMAP_LINEAR:\n return 'linear';\n case TONEMAP_CINEON:\n return 'cineon';\n case TONEMAP_ACES_FILMIC:\n return 'aces-filmic';\n case TONEMAP_AGX:\n return 'agx';\n case TONEMAP_NEUTRAL:\n return 'neutral';\n default:\n return 'none';\n }\n}\n\n/**\n * Inverse of {@link tonemapFromF32}: map the closed `Tonemap` string-literal\n * union to the u32 mode the tonemap WGSL `params.mode` switch reads. SSOT for\n * the mode encoding shared by the extract-stage built-in tonemap provider\n * (feat-20260621 M-A3 / w13: `Camera.tonemap` -> `forgeax::tonemap` 16B data)\n * and any other consumer. `'none'` maps to 0 (the tonemap pass never dispatches\n * on the LDR path, so 0 is only ever a placeholder).\n */\nexport function tonemapToU32(mode: Tonemap): number {\n switch (mode) {\n case 'reinhard-extended':\n return TONEMAP_REINHARD_EXTENDED;\n case 'reinhard':\n return TONEMAP_REINHARD;\n case 'linear':\n return TONEMAP_LINEAR;\n case 'cineon':\n return TONEMAP_CINEON;\n case 'aces-filmic':\n return TONEMAP_ACES_FILMIC;\n case 'agx':\n return TONEMAP_AGX;\n case 'neutral':\n return TONEMAP_NEUTRAL;\n case 'none':\n return TONEMAP_NONE;\n }\n throw new RangeError(`Invalid tonemap mode: ${String(mode)}.`);\n}\n\n/**\n * Anti-alias mode discriminator literal union\n * (feat-20260528-fxaa-post-processing / w2;\n * feat-20260604-learn-render-4-10-anti-aliasing-msaa adds `'msaa'`).\n *\n * Four members:\n * `'none'` - default; no anti-aliasing (zero-overhead opt-out path)\n * `'fxaa'` - FXAA 3.11 fullscreen post-processing pass (screen-space, shading-aliasing)\n * `'msaa'` - 4x hardware multi-sample anti-aliasing (geometry-edge coverage).\n * Active on both HDR and LDR-swap-chain paths, so a default Camera\n * (`tonemap='none'`) with `antialias='msaa'` is NOT a silent no-op.\n * `'taa'` - temporal anti-aliasing; renderer temporal projection owns its\n * jitter and successful-submit history contract.\n *\n * MSAA and FXAA are orthogonal: MSAA resolves geometry-edge aliasing, FXAA\n * resolves shading/high-frequency aliasing. TAA is mutually exclusive with both.\n */\nexport type Antialias = 'none' | 'fxaa' | 'msaa' | 'taa';\n\n/** Numeric encoding of anti-alias disabled (schema value for `antialias`). */\nexport const ANTIALIAS_NONE = 0;\n/** Numeric encoding of FXAA anti-aliasing (schema value for `antialias`). */\nexport const ANTIALIAS_FXAA = 1;\n/** Numeric encoding of MSAA multi-sample anti-aliasing (schema value for `antialias`). */\nexport const ANTIALIAS_MSAA = 2;\n/** Numeric encoding of temporal anti-aliasing (schema value for `antialias`). */\nexport const ANTIALIAS_TAA = 3;\n\n/**\n * Map a `camera.antialias` numeric value to the closed `Antialias`\n * string-literal union. Invalid values fail-fast with structured error (charter P3).\n */\nexport function antialiasFromF32(value: number): Antialias {\n if (value === ANTIALIAS_NONE) return 'none';\n if (value === ANTIALIAS_FXAA) return 'fxaa';\n if (value === ANTIALIAS_MSAA) return 'msaa';\n if (value === ANTIALIAS_TAA) return 'taa';\n throw new RangeError(\n `Invalid antialias value: ${value}. Expected ${ANTIALIAS_NONE} (none), ${ANTIALIAS_FXAA} (fxaa), ${ANTIALIAS_MSAA} (msaa), or ${ANTIALIAS_TAA} (taa).`,\n );\n}\n\n/**\n * Bloom enabled discriminator literal union\n * (feat-20260531-bloom-first-declarative-render-graph-pass / w2).\n *\n * Two members:\n * `'off'` - default; no bloom post-processing (zero-overhead opt-out path)\n * `'on'` - bloom bright-pass + separable blur + composite pipeline\n *\n * Bloom is a discrete enum (0/1) — illegal values fail-fast with RangeError\n * (charter P3), matching antialiasFromF32 precedent.\n */\nexport type BloomEnabled = 'off' | 'on';\n\n/** Numeric encoding of bloom disabled (schema value for `bloom`). */\nexport const BLOOM_DISABLED = 0;\n/** Numeric encoding of bloom enabled (schema value for `bloom`). */\nexport const BLOOM_ENABLED = 1;\n\n/**\n * Map a `camera.bloom` numeric value to the closed `BloomEnabled` string-literal\n * union. Invalid values fail-fast with structured error (charter P3).\n */\nexport function bloomEnabledFromF32(value: number): BloomEnabled {\n if (value === BLOOM_DISABLED) return 'off';\n if (value === BLOOM_ENABLED) return 'on';\n throw new RangeError(\n `Invalid bloom value: ${value}. Expected ${BLOOM_DISABLED} (off) or ${BLOOM_ENABLED} (on).`,\n );\n}\n\n/**\n * Camera projection parameters (perspective + orthographic variants) +\n * tonemap trio (mode + exposure + whitePoint).\n *\n * Camera transform comes from the entity's `Transform` (AI users\n * spawn camera entities with both components: see example below).\n *\n * Defaults (do not auto-apply; spawn explicitly for the perspective quartet):\n * fov = pi/4 - 45-degree vertical field of view\n * aspect = 16/9 - widescreen aspect (use canvas.width / canvas.height)\n * near = 0.1\n * far = 100\n * projection = 0 - CAMERA_PROJECTION_PERSPECTIVE (layer-2)\n * left = -1, right = 1, bottom = -1, top = 1 (orthographic; layer-2)\n * tonemap = 0 - TONEMAP_NONE (layer-2; opt-in path stays\n * 0-overhead by default)\n * exposure = 1.0 - layer-2 default for the Reinhard-extended path\n * @applicableWhen tonemap === 'reinhard-extended'\n * whitePoint = 4.0 - layer-2 default for the Reinhard-extended path\n * @applicableWhen tonemap === 'reinhard-extended'\n * @minimum 0 (shader floor `max(Y, 1e-5)` keeps\n * the divisor finite even at 0; D-O3)\n * clearColor = [0, 0, 0, 0]\n * - feat-20260709 M3 / D-3: clear-color is one\n * inline `array<f32,4>` column (collapsed from the\n * feat-20260608 clearR/G/B/A quartet). Defaults to\n * transparent black; an explicit alpha remains\n * visible through this public field.\n *\n * MVP supports a single active camera (the first archetype iteration hit;\n * N>1 fires 'render-system-multi-camera' + uses first). Multi-viewport is\n * OOS (see feat-future-multi-viewport). The orthographic path reuses the\n * same near / far as the perspective path — both variants share the single\n * Camera archetype (17 scalar f32 columns + one historyVersion u32 column +\n * the `clearColor` array<f32,4> column + the `autoAspect` bool column).\n *\n * @example Perspective camera at (0, 0, 3) looking down -Z (zero-config tonemap):\n * world.spawn(\n * { component: Transform, data: {\n * pos: [0, 0, 3],\n * quat: [0, 0, 0, 1],\n * scale: [1, 1, 1],\n * } },\n * { component: Camera, data: {\n * fov: Math.PI / 4, aspect: 16 / 9, near: 0.1, far: 100,\n * } },\n * );\n * // tonemap defaults to 0 (TONEMAP_NONE) - 0-overhead path.\n *\n * @example Opt-in Reinhard-extended tonemap (high-intensity HDR scene):\n * world.spawn(\n * { component: Transform, data: { ... } },\n * { component: Camera, data: {\n * fov: Math.PI / 4, aspect: 16 / 9, near: 0.1, far: 100,\n * tonemap: TONEMAP_REINHARD_EXTENDED,\n * exposure: 1.0,\n * whitePoint: 4.0,\n * } },\n * );\n *\n * @example Orthographic camera spanning [-10, 10]^2 on the near plane:\n * world.spawn(\n * { component: Transform, data: {\n * pos: [0, 0, 5],\n * quat: [0, 0, 0, 1],\n * scale: [1, 1, 1],\n * } },\n * { component: Camera, data: {\n * fov: 0, aspect: 1, near: 0.1, far: 100,\n * projection: CAMERA_PROJECTION_ORTHOGRAPHIC,\n * left: -10, right: 10, bottom: -10, top: 10,\n * } },\n * );\n */\nexport const Camera = defineComponent('Camera', {\n fov: { type: 'f32' },\n aspect: { type: 'f32' },\n near: { type: 'f32' },\n far: { type: 'f32' },\n projection: { type: 'f32', default: 0 },\n left: { type: 'f32', default: -1 },\n right: { type: 'f32', default: 1 },\n bottom: { type: 'f32', default: -1 },\n top: { type: 'f32', default: 1 },\n tonemap: { type: 'f32', default: 0 },\n exposure: { type: 'f32', default: 1.0 },\n whitePoint: { type: 'f32', default: 4.0 },\n antialias: { type: 'f32', default: 0 },\n historyVersion: { type: 'u32', default: 0 },\n bloom: { type: 'f32', default: 0 },\n bloomThreshold: { type: 'f32', default: 1.0 },\n bloomIntensity: { type: 'f32', default: 1.0 },\n bloomBlurRadius: { type: 'f32', default: 4.0 },\n // feat-20260709 M3 / D-3: clear-color is one inline `array<f32,4>` column.\n // The earlier 4-scalar form (clearR/G/B/A) was chosen when this was believed\n // to be the only SoA-safe shape; the Transform (pos/quat/scale) and light\n // (direction/color) precedents disprove that -- an `array<f32,N>` IS an\n // inline stride-N SoA column, read on the hot path as `col[i*N+a]` with zero\n // allocation, so collapsing four scalars into one column removes three field\n // names a reader must track without changing the storage layout or read\n // pattern. The default `[0, 0, 0, 0]` is transparent black; explicit alpha\n // stays visible through the same public array field.\n clearColor: { type: 'array<f32, 4>', default: new Float32Array([0, 0, 0, 0]) },\n // feat-20260617-host-engine-contract-and-video-cutscene / M3 / D-4: the\n // aspect-sync sidecar on the createApp(canvas) path writes\n // canvas.width / canvas.height into `aspect` every frame when this flag is\n // true. Reuses the existing `bool` column tier (AnimationPlayer.paused /\n // AudioSource.playing precedent) -- zero ECS infrastructure change. Default\n // true so demos that never touch aspect track the canvas automatically\n // (charter P1 default-is-correct); set false for render-to-texture /\n // split-screen cameras that drive aspect themselves. Read it via\n // world.get (readRow narrows bool -> JS boolean); the query-bundle path\n // returns a raw 0/1 number (the `!== 0` always-true trap).\n autoAspect: { type: 'bool', default: true },\n});\n\n// ─── Camera POD type (derived from Camera token — single source, AC-07) ─────\n//\n// ShapeOf<SchemaOf<typeof Camera>> resolves the 20-field POD from the Camera\n// token's schema, which is itself derived from Camera.fields[k].type (D-A7).\n// This replaces the hand-maintained CameraDataPod interface — the field set\n// lives exclusively in the Camera component definition above.\ntype CameraPod = ShapeOf<SchemaOf<typeof Camera>>;\n\n// ─── Camera factory functions (w13 SSOT refactoring) ─────────────────────\n//\n// Standalone factory functions that return 20-field CameraPod objects\n// matching the Camera component column shape. Not static methods because\n// TypeScript const-namespace merge is not supported, and Object.assign\n// would break the Camera token's reference identity (archetype columns /\n// queries key off the global owner identity associated with the token).\n//\n// Import as:\n// import { Camera, perspective, orthographic } from '@forgeax/engine-render';\n// world.spawn({ component: Camera, data: perspective({ fov: 60, aspect: 4/3 }) });\n//\n// Charter P1 progressive disclosure: Barrel re-exports put perspective\n// / orthographic next to Camera in IDE autocomplete.\n//\n// D-A4 factory retention reason: perspective() / orthographic() carry\n// required/optional parameter semantics that cannot be derived from\n// the schema. perspective requires fov + aspect (no universal default),\n// orthographic requires left/right/bottom/top. The schema/defaults system\n// only records type + optional default value per field — it has no concept\n// of \"this field MUST be supplied by the caller.\" The factories encode\n// that contract at the TypeScript type level, which schema/defaults alone\n// cannot express. Per AC-07, the factories are kept; only the POD type\n// and default literals are derived from the Camera token (SSOT).\n\ninterface CameraPerspectiveOpts {\n fov: number;\n aspect: number;\n near?: number;\n far?: number;\n /**\n * feat-20260617-host-engine-contract-and-video-cutscene / M3: when true\n * (the schema default), the aspect-sync sidecar on the createApp(canvas)\n * path overwrites `aspect` with `canvas.width / canvas.height` every frame.\n * Omit it for the default-correct behaviour; set `false` to opt out (the\n * factory then leaves `aspect` under your control -- render-to-texture,\n * split-screen). Cameras built on the bare `createRenderer` path never\n * receive aspect-sync regardless of this flag.\n */\n autoAspect?: boolean;\n}\n\ninterface CameraOrthographicOpts {\n left: number;\n right: number;\n bottom: number;\n top: number;\n near?: number;\n far?: number;\n}\n\n/**\n * Build a CameraPod base from Camera.fields defaults (per-field SSOT).\n * Fields without a default (fov / aspect / near / far — OOS-5) are left\n * unset; caller fills them from opts or sentinel values.\n */\nfunction cameraPodFromDefaults(): CameraPod {\n const base: Record<string, unknown> = {};\n for (const [key, field] of Object.entries(Camera.fields)) {\n if ('default' in field && field.default !== undefined) {\n base[key] = field.default;\n }\n }\n return base as CameraPod;\n}\n\n/**\n * Convenience factory: perspective CameraData POD.\n *\n * `fov` and `aspect` are required (no sensible universal default).\n * `near` defaults to 0.1, `far` defaults to 100.\n * Orthographic quartet defaults to [-1, 1]x[-1, 1]; tonemap defaults to\n * TONEMAP_NONE (0-overhead path).\n *\n * When `autoAspect` is `true` (the default once shipped in M3), the\n * aspect-sync sidecar on the `createApp(canvas)` path automatically writes\n * `canvas.width / canvas.height` into `Camera.aspect` every frame. Set\n * `autoAspect = false` to opt out (render-to-texture, split-screen).\n * Cameras on the `createRenderer` path do not receive aspect-sync.\n *\n * @see {@link https://github.com/Ubpa/forgeax-engine/blob/main/docs/how-to/2026-06-18-host-engine-contract.md | Host-engine contract SSOT}\n *\n * @example\n * ```ts\n * import { Camera, perspective } from '@forgeax/engine-render';\n * world.spawn(\n * { component: Camera, data: perspective({ fov: Math.PI / 3, aspect: 4 / 3 }) }\n * ).unwrap();\n * ```\n *\n * @example With explicit near/far:\n * ```ts\n * const camData = perspective({ fov: Math.PI / 4, aspect: 16 / 9, near: 0.01, far: 1000 });\n * world.spawn({ component: Camera, data: camData }).unwrap();\n * ```\n *\n * @example With non-default clear color (default is `[0, 0, 0, 0]`).\n * `clearColor` is an inline `array<f32,4>` field on `Camera`; spread the\n * factory then override:\n * ```ts\n * world.spawn({\n * component: Camera,\n * data: { ...perspective({ fov: Math.PI / 3, aspect: 4 / 3 }), clearColor: [0, 1, 0, 1] },\n * }).unwrap();\n * ```\n */\nexport function perspective(opts: CameraPerspectiveOpts): CameraPod {\n return {\n // cameraPodFromDefaults() reads autoAspect's schema default (true), so an\n // omitted opts.autoAspect lands the default-correct value; an explicit\n // false overrides it below (D-4: factory one-step opt-out, charter P1).\n ...cameraPodFromDefaults(),\n fov: opts.fov,\n aspect: opts.aspect,\n near: opts.near ?? 0.1,\n far: opts.far ?? 100,\n projection: CAMERA_PROJECTION_PERSPECTIVE,\n ...(opts.autoAspect !== undefined ? { autoAspect: opts.autoAspect } : {}),\n };\n}\n\n/**\n * Convenience factory: orthographic CameraData POD.\n *\n * All four ortho bounds (`left` / `right` / `bottom` / `top`) are required.\n * `near` defaults to 0.1, `far` defaults to 100.\n * Perspective fields get sentinel defaults (fov=0, aspect=1) for column\n * alignment with the 12-field schema.\n *\n * @example\n * ```ts\n * import { Camera, orthographic } from '@forgeax/engine-render';\n * world.spawn(\n * { component: Camera, data: orthographic({\n * left: -10, right: 10, bottom: -10, top: 10,\n * }) }\n * ).unwrap();\n * ```\n *\n * @example Screen-aligned orthographic for pixel-unit rendering:\n * ```ts\n * const camData = orthographic({ left: 0, right: 800, bottom: 600, top: 0 });\n * world.spawn({ component: Camera, data: camData }).unwrap();\n * ```\n */\nexport function orthographic(opts: CameraOrthographicOpts): CameraPod {\n return {\n ...cameraPodFromDefaults(),\n fov: 0,\n aspect: 1,\n near: opts.near ?? 0.1,\n far: opts.far ?? 100,\n projection: CAMERA_PROJECTION_ORTHOGRAPHIC,\n left: opts.left,\n right: opts.right,\n bottom: opts.bottom,\n top: opts.top,\n };\n}\n","// @forgeax/engine-render - DirectionalLight (directional light parameters\n// with merged shadow config).\n//\n// Schema: direction array<f32,3> + color array<f32,3> + intensity f32 + 1 bool\n// castShadow + 8 f32 shadow columns (feat-20260709 M2: direction/color collapsed\n// from 6 per-axis scalar columns to two inline array<f32,3> columns).\n//\n// Direction (xyz, outgoing) + color (rgb, linear space) + intensity in lux.\n// No Transform dependency (directional lights have no position).\n// The public world-unit convention is 1 world unit = 1 meter. Exposure is a\n// camera output concern and is not folded into this light's intensity.\n//\n// 0 DirectionalLight + standard material -> physically correct black render\n// (charter v2 P3/P4); details: AGENTS.md section Breaking changes 2026-05-18.\n//\n// Naming convention: bare entity name aligned with Bevy ECS conventions\n// (feat-20260513-component-naming-bevy-align). Camera / Transform / etc.\n// follow the same single-semantic-entity rule (charter proposition 5).\n//\n// charter mapping: proposition 1 (single import) + proposition 3 (silent\n// failure -> explicit warning; first-frame warn) + proposition 5 (consistent\n// abstraction: lighting parameters mirror GPU-side pbr.wgsl @group(0) view\n// BG light fields).\n//\n// feat-20260621-merge-directionallightshadow-into-directionallight M1:\n// DirectionalLightShadow's 9 shadow fields are merged into DirectionalLight\n// to collapse the dual-component spawn into a single component (D-6 primary\n// decision). castShadow defaults to true so zero-config spawns get shadows\n// by default; set castShadow=false to opt out. D-6 directional stays\n// first-hit-wins -- no ECS cardinality cap. Renderer allocation policy stays\n// with the shadow atlas owner.\n\nimport { defineComponent } from '@forgeax/engine-ecs';\n\n/**\n * Directional light (sun-like infinite light source) with merged shadow\n * parameters.\n *\n * `direction` @semantics outgoing — points FROM light source TO surface\n * (opposite of Three.js convention; the shader internally negates this\n * vector to obtain the L vector for BRDF evaluation). `color` is\n * linear-space rgb in [0, 1] per channel and `intensity` is lux. Exposure is\n * applied after lighting by the camera output stage; no hidden light\n * multiplier is applied here.\n *\n * castShadow defaults to true — zero-config spawns cast cascaded shadow maps.\n * Set castShadow: false to opt out (shadow fields are still stored but their\n * validation is skipped).\n *\n * Shadow fields (migrated from DirectionalLightShadow, feat-20260621 M1):\n * cascadeCount ∈ {1,2,3,4} — number of cascades (default 4)\n * splitLambda ∈ [0,1] — PSSM split weight (default 0.75)\n * cascadeBlend ∈ [0,0.5] — blend width between cascades (default 0.2)\n * mapSize >= 1 — shadow map resolution (default 2048)\n * depthBias — shadow acne bias (default 0.005)\n * normalBias — shadow acne normal offset (default 0.05)\n * shadowDistance > 0 — how far shadows reach in front of the camera,\n * in world units (default 200). This is the sole\n * coverage knob: the CSM cascades are PSSM-split\n * across [camera near, shadowDistance] and each\n * cascade's orthographic bounds are fit to the\n * camera frustum slice automatically. The near\n * end is derived from the active camera's near\n * plane (no separate near knob — any value other\n * than the camera near either drops near shadows\n * or wastes cascade-0 resolution).\n * pcfKernelSize odd >= 1 — PCF kernel width (default 3)\n *\n * @example Spawn a single directional light with default shadows:\n * world.spawn({ component: DirectionalLight, data: {\n * direction: [-0.5, -1, -0.3], // [x, y, z]\n * color: [1, 1, 1], // [r, g, b]\n * intensity: 1,\n * } });\n *\n * @example Opt out of shadows:\n * world.spawn({ component: DirectionalLight, data: {\n * direction: [0, -1, 0], // [x, y, z]\n * castShadow: false,\n * } });\n *\n * @example Explicit shadow config (single-component spawn):\n * world.spawn({ component: DirectionalLight, data: {\n * direction: [0.2, -0.98, 0], // [x, y, z]\n * color: [1, 1, 1], intensity: 1, // color is [r, g, b]\n * cascadeCount: 4, splitLambda: 0.75, cascadeBlend: 0.2,\n * mapSize: 2048, shadowDistance: 200,\n * } });\n *\n * @example 0-light scene must use an unlit shader (standard 0 light = physically correct black):\n * const world = new World();\n * // ... spawn cube + camera, no light ...\n * await renderer.ready;\n * renderer.draw(world); // standard material renders black; switch to an unlit shader (Materials.unlit(...)) for an unlit display\n */\nexport const DirectionalLight = defineComponent('DirectionalLight', {\n // direction is the ONLY field with no default (D-5): omitting it lands the\n // array layer-3 all-zero [0,0,0], which the renderer owner rejects -- there is no\n // universal default direction, so \"default is illegal\" forces an explicit\n // non-zero value. color carries an explicit layer-2 default [1,1,1] (white);\n // the array layer-3 fallback is all-zero, so the default MUST be explicit.\n direction: { type: 'array<f32, 3>' },\n color: { type: 'array<f32, 3>', default: new Float32Array([1, 1, 1]) },\n intensity: { type: 'f32', default: 1 },\n // Shadow opt-out gate: defaults to true so zero-config spawns get shadows.\n castShadow: { type: 'bool', default: true },\n // 9 shadow fields migrated from DirectionalLightShadow (feat-20260621 M1).\n cascadeCount: { type: 'f32', default: 4 },\n splitLambda: { type: 'f32', default: 0.75 },\n cascadeBlend: { type: 'f32', default: 0.2 },\n mapSize: { type: 'f32', default: 2048 },\n depthBias: { type: 'f32', default: 0.005 },\n normalBias: { type: 'f32', default: 0.05 },\n // Sole shadow coverage knob (feat replaces nearPlane/farPlane). The PSSM\n // near end derives from the active camera near; shadowDistance is the far\n // reach. Default 200 world units (matches UE-style \"dynamic shadow\n // distance\"; the old farPlane default was 50).\n shadowDistance: { type: 'f32', default: 200 },\n pcfKernelSize: { type: 'f32', default: 3 },\n});\n","// @forgeax/engine-render - Instances component (per-entity instanced-draw transforms).\n//\n// Two sources, one component (feat-20260622-chunk-gpu-instancing-sprite-tilemap\n// / AC-09; charter P4 consistent abstraction + charter P1 progressive disclosure).\n// The `Instances` ECS component has exactly TWO sources of `transforms` data, and\n// AI users should hold the boundary explicitly to avoid misreading the ECS view:\n//\n// Source 1 (EXPLICIT, the only source that lands in ECS) — AI user opt-in.\n// The AI user hand-attaches `Instances { transforms }` onto an entity at\n// spawn / set time for high-performance instanced draw. `world.get(e,\n// Instances)` returns the AI user's payload verbatim (a `Float32Array`\n// snapshot of column-major mat4 instances; stride = 16). This is the sole\n// code path through which `Instances` ever appears on an entity.\n//\n// Source 2 (TRANSPARENT, never written back into ECS) — engine record-stage\n// fold. When the AI user spawns N independent sprite / tilemap-cell\n// entities sharing the same (Layer.value, posZ, materialHandle) sort\n// equivalence class, the engine record stage transparently folds the\n// equivalent run into ONE `drawIndexed(indexCount, count)` using an\n// INTERNAL transient buffer (see `render-system-record.ts` fold operator\n// + `render.instancing.foldedDraws` metric, and the `RhiError\n// 'instancing-exceeds-uniform-cap'` fallback). The fold operator does\n// NOT auto-inject `Instances` onto those entities — `world.get(e,\n// Instances)` on a folded entity returns `Result.err` exactly as before.\n// The ECS view is preserved bit-for-bit (per-cell / per-sprite entity\n// retained, query / despawn / picking unchanged); the optimisation is\n// invisible at the component layer (charter P4: consistent abstraction —\n// the high-performance path is opt-in via Source 1, the default spawn\n// shape stays untouched).\n//\n// AI user reading this header: do NOT expect engine-side fold to materialise\n// as an `Instances` row on your entity. Read the metric (foldedDraws) or the\n// RhiError code to observe fold behaviour; never gate logic on a runtime\n// `world.get(e, Instances)` lookup after spawn.\n//\n// Schema: 1 array<f32> field `transforms` carrying packed column-major mat4\n// instance transforms (16 f32 per instance, stride = 16). Stride is enforced\n// through a TWO-LAYER contract:\n//\n// 1. AI user set-site: spawn / `world.set` / `world.push` callers pass a\n// `Float32Array` (or numeric array) whose `length` is a non-zero\n// multiple of 16. The set site is the AI user's responsibility -- the\n// engine cannot prove the column-major mat4 packing without the\n// caller's intent.\n// 2. RenderSystem extract entry: `render-system-extract.ts` performs a\n// defensive `transforms.length % 16 === 0` check on every\n// `world.get(e, Instances).transforms` snapshot at frame extract time;\n// violations route a structured `InstanceTransformsStrideMismatchError`\n// (`code: 'instance-transforms-stride-mismatch'`,\n// `detail: { actualLength, expectedStride: 16 }`) through the World\n// Layer-3 ErrorHandler and the renderable is skipped (fail-fast: the\n// malformed length never reaches the GPU upload path).\n//\n// feat-20260515-buffer-array-vocab-collapse M3 / w16 (decision §2.3 stride\n// responsibility migration): the legacy component-level\n// stride-declaration option (`{ transforms: 16 }` keyed on the retired\n// per-component stride defineComponent option key) was retired because:\n// - The ECS layer no longer carries a per-component stride schema (M2 /\n// w9 dropped the option from `DefineComponentOptions` -- the SSOT\n// moved to the RenderSystem entry + AI user set site).\n// - Centralising the check at the consumer (RenderSystem extract) rather\n// than the producer (ECS write paths) lets the GPU upload path stay\n// trust-the-snapshot; AI users get one structured error per offending\n// frame rather than one per write.\n//\n// feat-20260514-ecs-children-instances-managed-buffer-array M3 / w14:\n// migrated from the legacy `{ buffer: 'ref', count: 'u32' }` pair (which\n// cross-coupled with the now-deleted `AssetRegistry.createInstancedBuffer`\n// pipeline + the `InstancedBufferAsset` POD) to the ECS-managed array path.\n// Resize is a `world.push(e, Instances, 'transforms', value)` away (one f32\n// at a time) plus a future `world.grow` if a bulk-resize affordance is\n// added.\n//\n// Storage buffer cap-gate (D-5 OOS-08 follow-up):\n// - The runtime RenderSystem consumer (`render-system-record.ts`) checks\n// `device.caps.storageBuffer` before binding the per-entity transforms;\n// `caps.storageBuffer === false` (backend lacking storage buffer support,\n// e.g. rhi-wgpu webgl backend) routes a `RhiError` ('feature-not-enabled').\n// A future loop owns the per-draw fallback path.\n//\n// Group transform chained semantics (charter proposition 5 mental migration):\n// - When the same entity carries both `Transform` (entity_world) and\n// `Instances` (per-instance local transforms), the vertex shader\n// composes per instance:\n//\n// world_position[i] = entity_world * instances_local[i] * vertex_position\n//\n// - `Instances.transforms[i*16..i*16+15]` is interpreted as a local-space\n// transform under the entity, exactly like a child entity's `Transform`\n// under `ChildOf { parent }` in the hierarchy system. AI users reuse\n// the `parent x local` intuition without a new concept. Set the\n// entity's `Transform` to identity to make `instances_local[i]`\n// directly world-space.\n//\n// 4-segment minimum contract (read this header before reaching for\n// the source body):\n//\n// ===== (a) single-component import + spawn example =====\n//\n// import {\n// createRenderer, HANDLE_CUBE,\n// MeshFilter, MeshRenderer, Transform,\n// Instances, type InstancesData,\n// } from '@forgeax/engine-render';\n//\n// // 1 entity rendering N instanced cubes (16N packed f32, column-major mat4):\n// const transforms = new Float32Array(N * 16);\n// // ... fill column-major mat4 columns per instance ...\n// world.spawn(\n// { component: MeshFilter, data: { assetHandle: HANDLE_CUBE } },\n// { component: MeshRenderer, data: {} },\n// { component: Instances, data: { transforms } },\n// );\n//\n// ===== (b) packed mat4 layout =====\n//\n// const transforms = new Float32Array(N * 16); // column-major mat4 per instance\n// // instance i occupies floats [i*16 .. i*16+15]:\n// // [m00 m10 m20 m30 <- column 0\n// // m01 m11 m21 m31 <- column 1\n// // m02 m12 m22 m32 <- column 2\n// // m03 m13 m23 m33] <- column 3 (translation in m03/m13/m23)\n//\n// `transforms.length` MUST be a non-zero multiple of 16. AI users gate at\n// the set / push site; the RenderSystem extract entry holds the second\n// defensive (AC-06: detail carries `{ expectedStride: 16, actualLength }`).\n// The instance count is the live snapshot length / 16.\n//\n// ===== (c) error code consumption (typed property access, no message regex) =====\n//\n// // Stride violation surfaces through the engine-ecs Layer-3 ErrorHandler\n// // when extract reads a malformed snapshot:\n// // on('error', (err) => {\n// // if (err.code === 'instance-transforms-stride-mismatch') {\n// // // err.detail.expectedStride === 16\n// // // err.detail.actualLength === <user-supplied length>\n// // }\n// // });\n// //\n// // RenderSystem cap-gate (D-5) -> RhiError 'feature-not-enabled' on the\n// // RhiErrorListenerRegistry channel:\n// // on('error', (err) => {\n// // if (err.code === 'feature-not-enabled') {\n// // console.warn(err.expected, err.hint);\n// // }\n// // });\n//\n// charter mapping: proposition 1 (single import surface\n// `import { Instances, type InstancesData } from '@forgeax/engine-render'`);\n// proposition 3 (machine-readable schema > prose:\n// `{ transforms: 'array<f32>' }` is the SSOT, stride 16 documented here +\n// enforced at the RenderSystem entry);\n// proposition 4 (explicit failure: stride violation routes a structured\n// EcsError, not a silent half-row); proposition 5 (consistent abstraction:\n// Instances mirrors Children -- both ride the array-vocab path).\n//\n// Anchors: requirements §AC-06 (stride 16 + RenderSystem upload path);\n// plan-strategy §2.3 (stride responsibility migration to RenderSystem entry\n// defensive + AI user set site) + §3.3 stride error path; plan-decisions\n// D-1 / D-5.\n// OOS-09 (no addChild Commands API) does not apply here; OOS-01 (no\n// dangling-entity sweep) does not apply (Instances stores f32 not entity).\n\nimport { defineComponent } from '@forgeax/engine-ecs';\n\n/**\n * Per-entity instanced-draw transforms (ECS component).\n *\n * Carries a variable-length `array<f32>` of column-major mat4 instance\n * transforms (16 f32 per instance, stride = 16; documented above and\n * enforced at the RenderSystem extract entry, NOT at the ECS write paths).\n *\n * The runtime RenderSystem consumer materialises a fresh `Float32Array`\n * snapshot on every `world.get(e, Instances).transforms` access (D-4\n * no-cache), reads the live count from the snapshot's `length`, and\n * uploads the bytes to a per-entity GPU storage buffer in the record stage\n * (`render-system-record.ts`).\n *\n * @example Spawn an entity rendering 10000 instanced cubes:\n * const transforms = new Float32Array(10000 * 16);\n * // ... fill column-major mat4 columns ...\n * world.spawn(\n * { component: MeshFilter, data: { assetHandle: HANDLE_CUBE } },\n * { component: MeshRenderer, data: { ... } },\n * { component: Instances, data: { transforms } },\n * );\n */\nexport const Instances = defineComponent('Instances', {\n transforms: { type: 'array<f32>' },\n});\n\n/**\n * Type-level hint for `data` at the `Instances` spawn site.\n *\n * The runtime ECS column shape for `array<f32>` accepts a `Float32Array`\n * payload at spawn / set time (the bytes are copied into the BufferPool\n * slot). The `transforms` field is documented as `Float32Array` here so\n * AI-user IDE autocomplete picks up the typed-array shape.\n *\n * @example\n * import type { InstancesData } from '@forgeax/engine-render';\n * const data: InstancesData = { transforms: new Float32Array(N * 16) };\n */\nexport type InstancesData = {\n readonly transforms: Float32Array;\n};\n","// @forgeax/engine-render - Layer (render-order layer index, signed 32-bit).\n//\n// Schema: 1 i32 column (value). Signed two's complement so negatives travel\n// through the spawn payload unchanged — background sprites live at negative\n// values (e.g. -100), the default game layer at 0, foreground at 100, UI at\n// 1000. Layer is consumed at sort time by `transparent-sort.ts` (M-3 w23)\n// as the primary key in the (layer asc, sortValue asc) composite ordering.\n//\n// Naming convention: bare entity name (no Component suffix; single-semantic\n// component idiom aligned with Bevy ECS conventions — Transform / Camera /\n// DirectionalLight follow the same shape, see AGENTS.md §Component naming).\n//\n// Layer is a generic ECS renderer component — NOT a 2D-only special. 3D\n// entities may also carry Layer to bias their bucket placement (charter\n// P4 consistent abstraction: same component drives both pipelines).\n//\n// AC-18 path (1): an entity spawned without an explicit Layer reads back\n// 0 via the existing 4-layer spawn fallback chain\n// (feat-20260517-spawn-default-fallback). This feat does NOT introduce a\n// fifth fallback layer; the silent default flows through layer-3\n// `typeDefault('i32') === 0`.\n//\n// @derives defineComponent factory (packages/ecs/src/component.ts) — i32\n// is a legacy scalar type; its intrinsic properties (byteSize / viewCtor\n// / storage) live in TYPE_METADATA['i32'] (feat-20260602 M1).\n// @reuses spawn 4-layer fallback chain (feat-20260517-spawn-default-fallback)\n// — layer-3 typeDefault('i32') returns 0, no fifth layer.\n//\n// charter mapping: F1 (single-import barrel discovery — Layer joins\n// Transform / MeshFilter / MeshRenderer / Camera / DirectionalLight)\n// + P3 (explicit default — 0 surfaces as a read-back value, not undefined)\n// + P4 (consistent abstraction — Layer is a generic render component, not\n// a 2D-only special).\n\nimport { defineComponent } from '@forgeax/engine-ecs';\n\n/**\n * Render-order layer index.\n *\n * Convention (non-binding; conveys intent, not enforced):\n * - negative (e.g. -100): background layers (sky / parallax tiles).\n * - 0: default game layer.\n * - positive (e.g. 100): foreground layers (overlays, FX).\n * - 1000+: UI / HUD.\n *\n * Signed i32 (two's complement, range \\u00b12\\u00b3\\u00b9). Negatives round-trip\n * through the spawn payload unchanged — no schema-layer mutate. i32 lives in\n * the CPU sort path (transparent-sort.ts M-3 w23); no GPU-side stride\n * compatibility risk.\n *\n * @example Minimal spawn (defaults to game layer 0):\n * world.spawn({ component: Layer, data: {} }); // yields value=0\n *\n * @example Spawn 4 sprites across the conventional layer band:\n * world.spawn({ component: Layer, data: { value: -100 } }); // background\n * world.spawn({ component: Layer, data: { value: 0 } }); // default\n * world.spawn({ component: Layer, data: { value: 100 } }); // foreground\n * world.spawn({ component: Layer, data: { value: 1000 } }); // UI\n *\n * @example Spawn payload omitting Layer — the 4-layer fallback fills 0:\n * const e = world.spawn(\n * { component: Transform, data: { pos: [0, 0, 0],\n * quat: [0, 0, 0, 1], scale: [1, 1, 1] } },\n * );\n * // Layer is not on the entity; query joins must check membership.\n */\nexport const Layer = defineComponent('Layer', {\n value: { type: 'i32', default: 0 },\n});\n","// @forgeax/engine-render - MeshFilter (mesh asset reference).\n//\n// Schema: `{ assetHandle: 'shared<MeshAsset>' }`. The schema-vocab\n// `'shared<T>'` keyword stores a u32 column and type-derives to\n// `Handle<'MeshAsset', 'shared'>` (engine-ecs Handle<T,M> twoParam\n// phantom; `'shared'` mode = ref-counted retain on set / release on\n// clear, lifecycle owned by `SharedRefStore` per feat-20260614).\n// The brand prevents cross-asset assignment at compile time (e.g.\n// `Handle<'TextureAsset','shared'>` is not assignable to `assetHandle`).\n//\n// AI users spawn with the assets-runtime constants `HANDLE_CUBE` /\n// `HANDLE_TRIANGLE` (now branded `Handle<'MeshAsset','shared'>` to\n// match the schema-derived shape); custom mesh registration is owned by\n// feat-future-asset-system (this MVP only exposes builtin handles).\n//\n// Naming flavor: unity-style \"MeshFilter / MeshRenderer\" pair, but the forgeax\n// pair does NOT mirror Unity's filter-toggle semantics - MeshFilter only\n// carries the geometry ref, MeshRenderer only carries the material handle;\n// the pair is independently composable. D-Q7 default-material policy\n// (feat-20260517-merge-mesh-renderer-material-renderer plan-strategy §2.2):\n// case A (entity carries MeshFilter without MeshRenderer) -> archetype\n// query never matches -> entity is silently absent from the\n// RenderableSnapshot[] (NO default-material fallback, NO onError fire);\n// case B (MeshRenderer.material omitted at spawn) -> mid-grey\n// defaultMaterialSnapshot fallback (no onError); case C (material handle\n// unresolved) -> RhiError 'asset-not-registered' (mirrors the\n// MeshFilter.assetHandle dangling-ref path).\n//\n// charter mapping: proposition 1 (single import) + proposition 4 (explicit\n// failure: missing or unregistered handle fires onError 'asset-not-registered'\n// with .detail = { assetHandle } + cross-asset brand mismatch is a TS\n// compile-time error) + proposition 5 (consistent abstraction: the schema\n// vocab `'shared<T>'` is the SSOT for AssetRegistry-owned handles across\n// the engine).\n\nimport { defineComponent } from '@forgeax/engine-ecs';\n\n/**\n * Mesh filter (geometry asset reference).\n *\n * `assetHandle` carries a `Handle<'MeshAsset', 'shared'>` (u32-stored)\n * pointing into `engine.assets: AssetRegistry`. Use the predefined\n * constants `HANDLE_CUBE` / `HANDLE_TRIANGLE` exported from\n * `@forgeax/engine-render`; custom-mesh registration is OOS in MVP (see\n * feat-future-asset-system).\n *\n * Error path: if the handle is not registered at draw time, RenderSystem\n * fires `Renderer.onError` with\n * `RhiError({ code: 'asset-not-registered', detail: { assetHandle } })`\n * and skips this entity (other entities continue rendering; charter\n * proposition 9 graceful degradation).\n *\n * @example Spawn an entity referencing the builtin cube mesh:\n * import { MeshFilter } from '@forgeax/engine-render';\n * import { HANDLE_CUBE } from '@forgeax/engine-assets-runtime';\n * world.spawn({ component: MeshFilter, data: { assetHandle: HANDLE_CUBE } });\n */\nexport const MeshFilter = defineComponent('MeshFilter', {\n assetHandle: { type: 'shared<MeshAsset>' },\n});\n","// @forgeax/engine-render - MeshRenderer (multi-material array slot).\n//\n// feat-20260608-mesh-multi-section-primitive-multi-material-slot M2 / w7:\n// the single `material` field is replaced with `materials` — an\n// `array<shared<MaterialAsset>>` indexed by MeshAsset.materialSlots. Entries\n// are sparse positional overrides: a missing/zero entry inherits the Mesh\n// slot default, while a defaultless slot inherits the neutral engine material.\n//\n// The schema-vocab keyword `'array<shared<MaterialAsset>>'` (feat-20260614\n// M5 -- migrated from `'array<handle<MaterialAsset>>'`) stores as a u32\n// column slot array; the brand prevents cross-asset assignment at compile\n// time. The `'shared<T>'` arm routes element retain/release through\n// SharedRefStore (M4 / w13) on overwrite / archetype migration.\n//\n// charter mapping: proposition 1 (single import — `MeshRenderer` is the\n// only material-binding component AI users see); proposition 3\n// (machine-readable schema > prose); proposition 4 (explicit failure: the\n// TS brand on `Handle<'MaterialAsset','shared'>` rejects cross-variant\n// assignment at compile time); proposition 5 (consistent abstraction:\n// shading model classification (`'unlit'` / `'standard'`) lives ONLY on\n// the asset discriminant, NOT on the component name).\n//\n// RenderSystem consumption: `render-system-extract.ts` runs ONE archetype\n// query (`world.query(MeshRenderer)`) and routes per entity by\n// `mat.materialShaderId` (shader identity) to the unlit.wgsl or pbr.wgsl\n// pipeline tag.\n\nimport { defineComponent } from '@forgeax/engine-ecs';\n\n/**\n * Mesh renderer (ECS component, multi-material array).\n *\n * Stores `materials: readonly Handle<'MaterialAsset','shared'>[]`\n * (u32-stored array, indexed by MeshAsset.materialSlots). Submeshes point to\n * those stable slots through `Submesh.materialSlot`. The asset's `passes[].shader`\n * identity is the SSOT for which pipeline RenderSystem routes the entity\n * to (record stage dispatches on `materialShaderId`).\n *\n * Defaults map carries `materials: []`: every slot inherits its Mesh-owned\n * default, or the neutral engine material when that slot is intentionally\n * defaultless.\n *\n * @example Spawn while inheriting every Mesh slot default:\n * world.spawn({ component: MeshRenderer, data: {} });\n *\n * @example Spawn an unlit-targeted entity:\n * import { MeshRenderer, Materials } from '@forgeax/engine-render';\n * const matPayload = engine.assets.catalog(matGuid, Materials.unlit([1, 0, 0, 1])).value;\n * const matHandle = world.allocSharedRef('MaterialAsset', matPayload);\n * world.spawn({ component: MeshRenderer, data: { materials: [matHandle] } });\n *\n * @example Spawn a standard (PBR) entity:\n * const matPayload = engine.assets.catalog(matGuid, Materials.standard({\n * baseColor: [0.5, 0.5, 0.5, 1], metallic: 0, roughness: 0.4,\n * })).value;\n * const matHandle = world.allocSharedRef('MaterialAsset', matPayload);\n * world.spawn({ component: MeshRenderer, data: { materials: [matHandle] } });\n */\nexport const MeshRenderer = defineComponent('MeshRenderer', {\n materials: { type: 'array<shared<MaterialAsset>>', default: [] },\n});\n","// @forgeax/engine-render - PointLight (omnidirectional point-light parameters).\n//\n// Schema: color array<f32,3> + intensity f32 + range f32 (feat-20260709 M2:\n// color collapsed from 3 per-axis scalar columns to one inline array<f32,3>).\n// `position` comes from the Transform component; PointLight requires a\n// companion Transform on the same entity (ECS query: `[Transform, PointLight]`).\n// `range` units are meters; defaults to `10.0`. Runtime finite-range\n// attenuation is the Three r184 squared window; the KHR unsquared curve is an\n// import/reference boundary only. See `light-helpers.ts` for the host-side\n// range projection.\n//\n// 0 light + standard material -> physically correct black render\n// (feat-20260518-pbr-direct-lighting-mvp). The runtime once-warn channel\n// (M5 / w25) collapses to \"directionalCount + pointCount + spotCount === 0\";\n// see packages/runtime/README.md section Common pitfalls for the AI-user\n// guidance after the M5 docs land.\n//\n// Naming convention: bare entity name aligned with Bevy ECS conventions\n// (feat-20260513-component-naming-bevy-align). DirectionalLight / SpotLight\n// follow the same single-semantic-entity rule (charter proposition 5).\n//\n// charter mapping: proposition 1 (single import + IDE autocomplete on\n// payload.range) + proposition 3 (silent failure -> explicit failure;\n// spawn-time fail-fast on range<0 / NaN with structured EcsError) +\n// proposition 5 (consistent abstraction: shared payload shape with\n// SpotLight; `range` semantics aligned with KHR_lights_punctual).\n\nimport { defineComponent } from '@forgeax/engine-ecs';\n\n/**\n * Omnidirectional point light (KHR_lights_punctual `point` type).\n *\n * `position` source: the companion `Transform` component on the same entity.\n * Spawn via `world.spawn({ component: Transform, data: ... }, { component: PointLight, data: ... })`\n * so the render system extract path can join the two via the\n * `[Transform, PointLight]` ECS query.\n *\n * `color` is linear-space rgb in `[0, 1]` per channel; `intensity` is candela;\n * `range` is in meters and defaults to `10.0`\n * (KHR convention; `+Infinity` is the KHR no-truncation value, retained\n * for KHR_lights_punctual `range: 0` bridging via `light-helpers.ts`).\n * The runtime window is `clamp(1 - (d / range)^4, 0, 1)^2`; its no-cutoff\n * boundary uses inverse-square decay with the shader safety floor. Exposure is\n * applied after lighting and never multiplied into intensity.\n *\n * @example Spawn a single point light at (5, 3, 5):\n * world.spawn(\n * { component: Transform, data: { pos: [5, 3, 5] } },\n * { component: PointLight, data: { intensity: 8, range: 25 } },\n * );\n *\n * @example Minimal spawn -- defaults give neutral white at full strength, range 10m:\n * world.spawn(\n * { component: Transform, data: { pos: [0, 1, 0] } },\n * { component: PointLight, data: {} },\n * );\n * // resolves to color=[1, 1, 1], intensity=1, range=10.0.\n */\nexport const PointLight = defineComponent('PointLight', {\n // color carries an explicit layer-2 default [1,1,1] (white); the array\n // layer-3 fallback is all-zero, so the default MUST be explicit (D-5).\n color: { type: 'array<f32, 3>', default: new Float32Array([1, 1, 1]) },\n intensity: { type: 'f32', default: 1 },\n range: { type: 'f32', default: 10.0 },\n});\n","// @forgeax/engine-runtime — PointLightShadow (omnidirectional point-light\n// shadow mapping parameters; cube-array atlas variant).\n//\n// Schema: 6 f32 columns — mapSize (u32, stored as f32), depthBias, normalBias,\n// nearPlane, farPlane, pcfKernelSize (u32, stored as f32). No fixed-extent\n// field — cube map uses fov=90 perspective, not ortho.\n//\n// Atlas capacity is a renderer policy; ECS does not enforce a component\n// cardinality bound.\n// Co-located with DirectionalLight in components/ (research L1.2).\n//\n// Naming convention: bare entity name (AGENTS.md §Component naming), no\n// Component suffix. Same defineComponent pattern and ShadowInvalidConfigError\n// surface as the shadow fields merged into DirectionalLight (feat-20260621 M1).\n\nimport { defineComponent } from '@forgeax/engine-ecs';\n\n/**\n * Omnidirectional point-light shadow mapping parameters.\n *\n * The renderer may batch this component into a bounded atlas, but the ECS\n * component remains a plain storage schema and carries no cardinality policy.\n *\n * @example Spawn a point light with default shadow config:\n * world.spawn(\n * { component: Transform, data: { pos: [0, 4, 0] } },\n * { component: PointLight, data: { range: 25 } },\n * { component: PointLightShadow, data: {} }, // 6 fields filled from defaults\n * );\n *\n * @example Spawn with explicit map size and bias:\n * world.spawn(\n * { component: PointLightShadow, data: { mapSize: 1024, depthBias: 0.01 } },\n * );\n */\nexport const PointLightShadow = defineComponent('PointLightShadow', {\n mapSize: { type: 'f32', default: 512 },\n depthBias: { type: 'f32', default: 0.005 },\n normalBias: { type: 'f32', default: 0.05 },\n nearPlane: { type: 'f32', default: 0.1 },\n farPlane: { type: 'f32', default: 25 },\n pcfKernelSize: { type: 'f32', default: 3 },\n});\n","// @forgeax/engine-render - PostProcessParams component.\n//\n// Schema: { shader: 'string', data: 'buffer' }.\n//\n// `shader` identifies the fullscreen post-process shader (the same id as passed to\n// the feature host registers for `id`). `data` carries the per-frame params\n// bytes as a variable-byte ECS managed buffer slot. The write side (world.spawn /\n// world.set) accepts every AllowSharedBufferSource (Float32Array / ArrayBuffer /\n// Uint8Array / typed array) and the ECS normalizes it to Uint8Array bytes\n// (feat-20260621 V2 / AC-A4); the read side (world.get) returns Uint8Array, which\n// satisfies AllowSharedBufferSource for GPU queue.writeBuffer ingestion.\n//\n// Single-semantic component — drops the `Component` suffix\n// (AGENTS.md §Component naming rule #1). The `data` field uses the ECS 'buffer'\n// vocab keyword (plan-strategy D-7) for variable-byte storage.\n//\n// Data flow (plan-strategy D-1):\n// extract -> iterate PostProcessParams-bearing entities ->\n// collect into Map<shaderId, AllowSharedBufferSource> snapshot ->\n// dispatchFullscreenPass picks up per-shader data -> writeBuffer to eager UBO.\n//\n// Decision anchors:\n// - requirements AC-A2 (data-driven, no imperative renderer.setParams mutator)\n// - requirements AC-A4 (AllowSharedBufferSource type on the data field)\n// - plan-strategy D-1 (new single-semantic ECS component for params)\n// - plan-strategy D-7 (ECS 'buffer' vocab keyword for variable-byte storage)\n\nimport { defineComponent } from '@forgeax/engine-ecs';\n\nconst schema = {\n shader: 'string',\n data: 'buffer',\n} as const;\n\nexport const PostProcessParams = defineComponent('PostProcessParams', schema);\n","// @forgeax/engine-runtime - SceneInstance component (feat-20260608-\n// scene-nesting-ecs-fication M2 / w16).\n//\n// Single ECS fat component carrying everything that used to live on the\n// old `SceneInstance` + `SceneInstanceContainer` pair (deleted\n// in M3). Schema fits the ECS schema vocab (3 single-identifier fields):\n//\n// { source: 'shared<SceneAsset>',\n// mapping: 'array<entity>',\n// state: 'unique<SceneInstanceState>' }\n//\n// `source` carries the SceneAsset handle the synthetic root entity was\n// instantiated from (Tier-A AssetUnion handle, AGENTS.md §Assets submodule).\n// `mapping` is the LocalEntityId -> Entity table indexed positionally\n// (mapping[localId] = spawned Entity); the ECS variable-array column gives\n// us a SoA-friendly read path for query<SceneInstance> scans without\n// touching the dynamic Map/Set state.\n// `state` is a `ref<SceneInstanceState>` slot — World holds the live\n// SceneInstanceState payload in its UniqueRefStore and World despawn\n// auto-releases the ref u32 (the same path used by audio / physics\n// payloads, plan-strategy §D-2). Each instantiateScene call calls\n// `world.allocUniqueRef('SceneInstanceState', state)` once and stores the\n// returned u32 in this column.\n//\n// Decision anchors:\n// - plan-strategy §D-2 (single ref wraps SceneInstanceState dynamic\n// structure; ECS schema vocab `\\w+` rejects `ref<Map<...>>`)\n// - plan-strategy §3.2 sequence (sequence diagram step 'set ... state:\n// {entityToLocalId, detached, overrides, rootEntities}')\n// - AGENTS.md §Component naming rule #1 (single-semantic component drops\n// `Component` suffix)\n// - charter F1 (single-import barrel: SceneInstance lives in\n// `@forgeax/engine-render` next to Transform / Camera / DirectionalLight)\n// - charter P3 (machine-readable schema: 3 closed fields)\n// - charter P4 (consistent abstraction: instance == entity carrying\n// SceneInstance — same `world.query({ read: [SceneInstance] })` /\n// `world.get(root, SceneInstance)` path as another component)\n\nimport { defineComponent, type EntityHandle } from '@forgeax/engine-ecs';\nimport type { LocalEntityId, MountOverride } from '@forgeax/engine-types';\n\n/**\n * Per-component, per-field override record carried in `SceneInstanceState`.\n *\n * Composite key `<componentName>:<fieldName>` keeps the override map flat\n * (single Map nesting level) so AI users walking `state.overrides` see one\n * iteration depth — D-2 prefers a single SSOT over multi-Map nesting that\n * would make iteration order ambiguous (charter F1: single mental model).\n *\n * The value is `unknown` because the per-component schema vocab lives in the\n * ECS layer; runtime fail-fast via `EcsErrorCode = 'scene-override-type-\n * mismatch'` (plan-strategy §D-9) catches type drift on the apply path.\n *\n * @internal Surface from `state.overrides` only; AI users never construct a\n * record directly. Use `world.setSceneOverride(root, member, comp,\n * field, value)` to write and `world.removeSceneOverride(root, member,\n * comp, field)` to roll back to the source SceneAsset value.\n */\nexport interface SceneInstanceOverrideRecord {\n readonly comp: string;\n /**\n * Component-granular add-or-patch discriminant (feat-20260713 M1 / w4):\n * present -> this record patches a single field; absent -> it adds/upserts\n * the whole `comp` (M2 apply semantics). Mirrors `MountOverride.field`.\n */\n readonly field?: string;\n readonly value: unknown;\n}\n\n/**\n * Dynamic state payload for one SceneInstance — held in the World's\n * UniqueRefStore behind a `ref<SceneInstanceState>` slot on the synthetic\n * root entity. Mirrors the old class-based layout (plan-strategy §D-2 +\n * design doc §11.2 internal-state-table) but flattened into plain JS Maps/Sets so\n * AI users can iterate without indirection.\n *\n * Lifecycle:\n * - allocated by `world.instantiateScene(handle, parent?)` — the W in\n * `world.allocUniqueRef('SceneInstanceState', state)` returns the u32\n * slot id stored in the SceneInstance.state column;\n * - released by `world.despawn(root)` (the standard `ref<T>` release loop)\n * or explicitly via `world.despawnScene(root)` / `world.despawnDescendants(root)`.\n *\n * AI users typically reach this struct only through the read path\n * `world.get(root, SceneInstance).state.overrides.get(<comp>:<field>)` for\n * inspection; mutation happens through the 8 World methods.\n */\nexport interface SceneInstanceState {\n /**\n * SceneAsset handle the instance was instantiated from. Mirrors the\n * SceneInstance.source column for AI users who already have a\n * SceneInstanceState in hand and want the source handle without a second\n * `world.get` round-trip.\n */\n readonly source: import('@forgeax/engine-types').Handle<'SceneAsset', 'shared'>;\n\n /**\n * Reverse mapping (live `Entity` -> source `LocalEntityId`). Used by\n * `world.setSceneOverride` to validate the member entity belongs to this\n * instance + by `world.detachSceneMember` / `world.reattachSceneMember`\n * to translate caller-side Entity values back to their authored localId.\n */\n readonly entityToLocalId: Map<EntityHandle, LocalEntityId>;\n\n /**\n * LocalEntityIds of members the AI user marked detached via\n * `world.detachSceneMember(root, member)`. The member entities stay\n * alive in the World; only the bookkeeping marker moves so\n * `world.despawnDescendants(root, { keepDetached: true })` can skip them.\n * Detach is idempotent (set semantics).\n */\n readonly detachedLocalIds: Set<LocalEntityId>;\n\n /**\n * Per-member runtime overrides keyed by `<localId>` -> `<comp>:<field>`\n * -> override record. mount-time overrides (authored on the SceneAsset's\n * `mounts[].overrides`) populate this map at instantiate-time so AI\n * users see them via the same read path as runtime\n * `setSceneOverride` writes (single SSOT).\n */\n readonly overrides: Map<LocalEntityId, Map<string, SceneInstanceOverrideRecord>>;\n\n /**\n * Top-level root entities of the materialised tree (members without a\n * ChildOf parent at instantiate time). The synthetic root entity that\n * carries the SceneInstance component itself is NOT in this list — it is\n * the parent of every member entity (plan-strategy §3.2 sequence step 7).\n */\n readonly rootEntities: EntityHandle[];\n\n /** Synthetic roots of recursively mounted SceneAssets owned by this instance. */\n readonly mountRoots: EntityHandle[];\n\n /**\n * Total slot count `entities.length + sum(mounts[].memberCount)` —\n * captured at instantiate-time so cycle / count consistency checks\n * (e.g. `world.getSceneAssetForInstance(root)` symmetry) can compare\n * against the live SceneInstance.mapping column without re-reading the\n * SceneAsset.\n */\n readonly totalSlots: number;\n\n /**\n * Mount-time override authored on the parent SceneAsset's `mounts[]`\n * record. Captured at instantiate-time so `world.removeSceneOverride`\n * can fall back to the mount-time value before going to the source\n * SceneAsset value (plan-strategy §D-2).\n */\n readonly mountTimeOverrides: readonly MountOverride[];\n}\n\n/**\n * SceneInstance ECS component — synthetic root entity payload for one\n * materialised SceneAsset (instance == entity carrying SceneInstance,\n * charter P4).\n *\n * AI users discover the component via IDE autocomplete on\n * `@forgeax/engine-render` (single-import barrel; AGENTS.md §Components):\n *\n * ```ts\n * import { SceneInstance } from '@forgeax/engine-render';\n * for (const row of world.query({ read: [SceneInstance] }).unwrap()) {\n * console.log(`root=${row.entity} source=${row.get(SceneInstance).source}`);\n * }\n *\n * const inst = world.get(root, SceneInstance).value;\n * const memberEntity = inst.mapping[localId]; // Uint32Array snapshot\n * for (const detached of inst.state.detachedLocalIds) { /* ... *\\/ }\n * ```\n *\n * The component does not register a relationship — synthetic root +\n * members are wired through the standard `ChildOf` (member -> root) so\n * the existing `world.iterDescendants(root)` / Children mirror code paths\n * apply unchanged (plan-strategy §D-5).\n *\n * @example Materialise + inspect a SceneAsset:\n * const { root, diagnostics } = world.instantiateScene(handle).value;\n * const inst = world.get(root, SceneInstance).value;\n * // inst.source === handle, inst.mapping is Uint32Array(totalSlots)\n * // inst.state holds entityToLocalId / detachedLocalIds / overrides /\n * // rootEntities / mountRoots / totalSlots / mountTimeOverrides.\n */\nexport const SceneInstance = defineComponent(\n 'SceneInstance',\n {\n source: { type: 'shared<SceneAsset>' },\n mapping: { type: 'array<entity>' },\n // The unique slot is the ECS storage seam for the instance's structured\n // runtime payload; keep the nested semantic visible to schema consumers.\n state: { type: 'unique<SceneInstanceState>', shape: 'nested' },\n },\n { transient: true },\n);\n","// @forgeax/engine-runtime - SkyboxBackground (environment cubemap render background).\n//\n// Schema: 3 fields -- equirect (Handle<EquirectAsset>, u32-stored handle)\n// + mode (f32 enum column, discriminator) + rotation (quaternion xyzw,\n// default identity). Naming convention follows the\n// single-semantic component rule: no Component suffix (AGENTS.md rule #1).\n//\n// feat-20260630-equirect-kind-internalized-ibl-declarative-skyligh M3 / w16:\n// the field is `equirect: shared<EquirectAsset>` (the retired\n// `cubemap: shared<CubeTextureAsset>` is gone). The skybox reuses the same\n// equirect handle as the Skylight; the cubemap projection is internal and\n// driven by the render-system record arm (no user upload call).\n//\n// Plan-strategy D-5: mode is a f32 enum column (ECS columns are POD, no\n// string unions in archetype storage). Consumer-facing type safety is\n// provided by the `SkyboxMode` literal union + `skyboxModeFromF32` mapper\n// (same pattern as cameraProjectionFromF32 in camera.ts:51,105,145,221-237).\n//\n// OOS-1: `mode: 'atmosphere'` implementation is deferred -- the union\n// shape leaves room for additive growth without breaking the f32 enum\n// encoding.\n//\n// AI user minimum spawn (charter P4):\n// world.spawn({ component: SkyboxBackground, data: { equirect, mode: 0 } });\n//\n// Single component activates the skybox render pass transparently when a\n// Skylight + tonemap active camera is present (skybox reuses the same\n// equirect handle as Skylight).\n\nimport { defineComponent } from '@forgeax/engine-ecs';\n\n/**\n * Skybox mode literal union (AC-02 narrowing surface).\n * Currently only 'cubemap' -- future 'atmosphere' deferred per OOS-1.\n */\nexport type SkyboxMode = 'cubemap';\n\n/** Numeric encoding of cubemap skybox mode (schema value for `mode`). */\nexport const SKYBOX_MODE_CUBEMAP = 0;\n\n/**\n * Map a `SkyboxBackground.mode` numeric value to the closed `SkyboxMode`\n * string-literal union. Only member currently is 'cubemap'; the exhaustive\n * switch is ready for future mode additions (e.g. 'atmosphere', OOS-1).\n */\nexport function skyboxModeFromF32(value: number): SkyboxMode {\n switch (value) {\n case SKYBOX_MODE_CUBEMAP:\n return 'cubemap';\n // Future: case SKYBOX_MODE_ATMOSPHERE: return 'atmosphere';\n }\n // Unrecognised value: fall back to 'cubemap' (charter P3 -- no silent\n // exception; the fallback preserves rendering on stale numerics while\n // the exhaustive switch shape ensures AI users catch new members at\n // compile time when a mode is added).\n return 'cubemap';\n}\n\n/**\n * SkyboxBackground: full-screen environment cubemap background.\n *\n * Renders a fullscreen triangle that reconstructs world-space view\n * direction from the camera's inverseViewProj matrix (View UBO),\n * samples a cubemap with the view direction, and writes an HDR color\n * to the hdrColor render target (before the main geometry pass).\n *\n * The skybox pass is automatically activated when a SkyboxBackground\n * entity exists, a Skylight entity provides the equirect source, and the\n * active Camera has tonemap active. The skybox reuses the same equirect\n * handle as the Skylight entity -- two independent components sharing the\n * same GPU resource (the internally-projected cubemap).\n *\n * `equirect` is a `Handle<EquirectAsset>` resolved from a vite pack-index\n * GUID via `engine.assets.loadByGuid<EquirectAsset>(guid)`. The cubemap\n * projection is internal and driven by the render-system record arm; there\n * is no user upload call.\n *\n * `mode` is a numeric discriminator column (`'f32'`). Use\n * `SKYBOX_MODE_CUBEMAP` for the cubemap render path.\n *\n * `rotation` is an environment-space quaternion in `[x, y, z, w]` order.\n * It rotates the sampled cubemap without changing the camera or rebaking the\n * source equirectangular asset.\n *\n * @example Minimum spawn (defaults mode=0):\n * // equirectHandle resolved from loadByGuid<EquirectAsset> (same handle as\n * // the Skylight).\n * world.spawn({ component: SkyboxBackground, data: { equirect: equirectHandle } });\n *\n * @example Single handle shared between Skylight (IBL) and SkyboxBackground:\n * const hdrRes = await engine.assets.loadByGuid<EquirectAsset>(guid);\n * if (!hdrRes.ok) throw hdrRes.error;\n * world.spawn({ component: Skylight, data: { equirect: hdrRes.value } });\n * world.spawn({ component: SkyboxBackground, data: { equirect: hdrRes.value } });\n */\nexport const SkyboxBackground = defineComponent('SkyboxBackground', {\n // The GPU skybox projection is render-owned presentation state; restore\n // keeps the portable mode/rotation controls and lets the owner re-resolve\n // the environment asset on the target world.\n equirect: { type: 'shared<EquirectAsset>', simulationTransient: true },\n mode: { type: 'f32', default: SKYBOX_MODE_CUBEMAP },\n rotation: { type: 'array<f32, 4>', default: new Float32Array([0, 0, 0, 1]) },\n});\n","// @forgeax/engine-runtime - Skylight (ambient environment light).\n//\n// Schema: 4 fields -- equirect (Handle<EquirectAsset>, u32-stored handle,\n// OPTIONAL) + color (array<f32,3>, default [1,1,1] = white) + intensity (f32,\n// default 1.0) + rotation (quaternion xyzw, default identity). feat-20260709 M2 collapsed colorR/G/B into one inline\n// array<f32,3> column. Naming convention follows the DirectionalLight / PointLight /\n// SpotLight family: no Component suffix (AGENTS.md rule #1).\n//\n// Plan-strategy D-6: Skylight component schema is registered but no\n// independent ECS system is created. All Skylight processing happens inside\n// RenderSystem's extract/record phases.\n//\n// AI user minimum spawn (AC-13, charter P4):\n// world.spawn({ component: Skylight, data: {} }); // instant white ambient\n//\n// A single Skylight activates ambient lighting transparently. The `equirect`\n// field is OPTIONAL: omit it for a constant solid-color ambient that needs NO\n// async GPU precompute -- the engine binds a 1x1 white irradiance cube, so\n// ambient = color * intensity * albedo is live on the very first frame (this\n// is the fix for the downstream \"scene is black until the async IBL cubemap\n// finishes uploading\" gap). Supply an equirect to upgrade to full image-based\n// lighting (diffuse irradiance + specular prefilter); `color` * `intensity`\n// still scales the IBL result, so both are live dynamic dials either way.\n//\n// feat-20260630-equirect-kind-internalized-ibl-declarative-skyligh M3 / w16:\n// the field is `equirect: shared<EquirectAsset>` (the retired\n// `cubemap: shared<CubeTextureAsset>` is gone). AI users declare the equirect\n// source directly; the render-system record arm projects the cubemap + IBL\n// chain internally and lazily (no user upload call) -- there is no\n// uploadCubemapFromEquirect on the public surface.\n//\n// Edge cases handled by w18 / w19:\n// - Multi-Skylight: first archetype hit wins, console.warn in dev+prod\n// - intensity=0: ambient term = 0, mathematically valid, no warn\n// - No equirect: solid-color ambient via the white fallback cube (no async)\n\nimport { defineComponent } from '@forgeax/engine-ecs';\n\n/**\n * Skylight ambient environment light.\n *\n * A single Skylight entity provides ambient lighting for all StandardMaterial\n * surfaces. Two modes share one component:\n *\n * - **Solid-color ambient (no equirect).** Omit `equirect` for a constant\n * ambient = `color` * `intensity` applied immediately, with no async GPU\n * precompute. The engine samples a built-in 1x1 white irradiance cube, so a\n * freshly-loaded scene is lit on its first frame instead of being black\n * until an IBL upload finishes.\n * - **Image-based lighting (with equirect).** Supply `equirect` to upgrade to\n * diffuse irradiance + specular prefilter IBL. The engine runs the full\n * precompute (equirect->cubemap, irradiance convolution, prefilter mip\n * chain, BRDF LUT) transparently inside the render-system record arm;\n * `color` * `intensity` then scale the IBL ambient.\n *\n * `equirect` is an OPTIONAL `Handle<EquirectAsset>` resolved from a vite\n * pack-index GUID via `engine.assets.loadByGuid<EquirectAsset>(guid)`. The\n * cubemap projection is internal and idempotent: the same equirect source\n * always resolves to the same GPU cubemap (no user upload call).\n *\n * `color` is the linear-space ambient tint (default white [1, 1, 1]).\n * `intensity` is a linear multiplier (default 1.0; 0 disables ambient). Both\n * are read every frame, so they are live dynamic dials.\n *\n * `rotation` is an environment-space quaternion in `[x, y, z, w]` order.\n * The default identity quaternion leaves the source orientation unchanged;\n * changing it rotates the IBL at sample time without rebaking the source.\n *\n * @example Instant white ambient (no asset, no async):\n * world.spawn({ component: Skylight, data: {} });\n *\n * @example Dim warm ambient:\n * world.spawn({ component: Skylight, data: { color: [1, 0.9, 0.8], intensity: 0.3 } }); // color is [r, g, b]\n *\n * @example Full IBL from an HDR equirect (declarative -- no upload call):\n * // 1. resolve GUID from vite pack-index (see forgeax-engine-vite-plugin-pack)\n * import { AssetGuid } from '@forgeax/engine-pack/guid';\n * const guidRes = AssetGuid.parse('019e4a26-3c29-7420-af5d-20f2724a16b0');\n * if (!guidRes.ok) throw guidRes.error;\n * // 2. load the HDR equirect via the GUID-addressed pack route\n * const hdrRes = await engine.assets.loadByGuid<EquirectAsset>(guidRes.value);\n * if (!hdrRes.ok) throw hdrRes.error;\n * // 3. spawn the Skylight — the equirect handle activates the full IBL path;\n * // the cubemap + IBL precompute happen lazily inside the record arm.\n * world.spawn({ component: Skylight, data: { equirect: hdrRes.value } });\n */\nexport const Skylight = defineComponent('Skylight', {\n // The GPU equirect-to-cubemap projection is render-owned presentation\n // state. Record/restore preserves the portable lighting controls while the\n // render owner resolves this asset again on the target world.\n equirect: { type: 'shared<EquirectAsset>', simulationTransient: true },\n // color carries an explicit layer-2 default [1,1,1] (white); the array\n // layer-3 fallback is all-zero, so the default MUST be explicit (D-5).\n color: { type: 'array<f32, 3>', default: new Float32Array([1, 1, 1]) },\n intensity: { type: 'f32', default: 1.0 },\n rotation: { type: 'array<f32, 4>', default: new Float32Array([0, 0, 0, 1]) },\n});\n","// @forgeax/engine-render - SortKey (per-entity transparent sort override, f32).\n//\n// Schema: 1 f32 column (value). Entity-level sort override consumed by\n// `transparent-sort.ts` (M-3 w23): when present, SortKey.value REPLACES\n// the mode-formula result for this entity in the (layer, sortValue)\n// composite ordering. Override priority is higher than every\n// TransparentSortConfig mode computation (horizontal-z / Y-sort /\n// Y-Z-blend) so AI users can pin a single entity above / below the\n// procedural ordering without reshaping the entire scene.\n//\n// Like Layer, SortKey is a generic ECS renderer component — NOT a\n// 2D-only special. 3D entities may also carry SortKey to bias their\n// position inside the transparent bucket (charter P4 consistent\n// abstraction: same component drives both pipelines).\n//\n// AC-19 derivation audit row (4) names this file as the\n// `defineComponent` factory derivation. JSDoc surfaces the override\n// priority via @derives (ECS factory) annotation; the consumer side\n// of the override behaviour is implemented + verified in M-3 w16 +\n// w23, not here.\n//\n// @derives defineComponent factory (packages/ecs/src/component.ts) —\n// f32 is a legacy scalar type; its intrinsic properties (byteSize / viewCtor\n// / storage) live in TYPE_METADATA['f32'] (feat-20260602 M1).\n//\n// charter mapping: F1 (single-import barrel - joins Layer / Transform\n// / MeshFilter / MeshRenderer / Camera / DirectionalLight) +\n// P3 (explicit default 0.0 surfaces via the 4-layer fallback chain) +\n// P4 (consistent abstraction — generic render component, not 2D-only).\n\nimport { defineComponent } from '@forgeax/engine-ecs';\n\n/**\n * Per-entity transparent-sort override.\n *\n * When attached to an entity inside the transparent bucket (M-3 w22),\n * `SortKey.value` REPLACES the result of `TransparentSortConfig.mode`\n * formula evaluation for this entity. Sort key range is the host's\n * choice — typical use is small (e.g. -10 ... +10) to keep the\n * composite (layer asc, sortValue asc) ordering predictable; large\n * magnitudes work but cross-layer biasing should usually go through\n * `Layer` instead.\n *\n * Override priority (M-3 w23 transparent-sort algorithm):\n *\n * if (world.has(entity, SortKey)) {\n * sortValue = world.get(entity, SortKey).value;\n * } else {\n * sortValue = modeFormula(mode, transform, sprite);\n * }\n *\n * SortKey lives alongside Layer in the (layer, sortValue) composite\n * key — Layer remains the primary key, SortKey replaces the secondary\n * key. This means a `Layer { value: 100 }` foreground entity with\n * `SortKey { value: -99 }` still draws AFTER a `Layer { value: 0 }`\n * background entity; SortKey biases WITHIN a layer, not ACROSS layers.\n *\n * @example Minimal spawn (no override, use mode formula):\n * world.spawn({ component: SortKey, data: {} }); // yields value=0\n *\n * @example Bias one sprite above the JRPG Y-sort formula:\n * world.spawn(\n * { component: Transform, data: { pos: [0, 1, 0],\n * quat: [0, 0, 0, 1], scale: [1, 1, 1] } },\n * { component: Layer, data: { value: 0 } },\n * { component: SortKey, data: { value: -100 } },\n * );\n *\n * @example Spawn payload omitting SortKey — the 4-layer fallback fills 0:\n * // entity reads back SortKey only if explicitly attached;\n * // otherwise the mode-formula path is taken by transparent-sort.ts.\n */\nexport const SortKey = defineComponent('SortKey', {\n value: { type: 'f32', default: 0 },\n});\n","// @forgeax/engine-render - SpotLight (cone-restricted spot-light parameters).\n//\n// Schema: direction array<f32,3> + color array<f32,3> + intensity + range +\n// innerConeDeg + outerConeDeg (feat-20260709 M2: direction/color collapsed from\n// 6 per-axis scalar columns to two inline array<f32,3> columns). `position`\n// comes from the\n// Transform component; SpotLight requires a companion Transform on the same\n// entity (ECS query: `[Transform, SpotLight]`). Cone units are degrees on\n// the Component API surface (charter F1 prior-knowledge alignment with\n// Three.js); the host-side extract step pre-converts to `cosInner` /\n// `cosOuter` before GPU upload (plan-strategy section 8.2 naming convention\n// + D-3 host-side cone conversion).\n//\n// `range` units are meters; defaults to `10.0`. Runtime finite-range\n// attenuation is the Three r184 squared window; the KHR unsquared curve is an\n// import/reference boundary only. The cone falloff is layered on top with\n// `smoothstep(cosOuter, cosInner, dot(L, -direction))` (plan-strategy D-S4).\n//\n// 0 light + standard material -> physically correct black render. The\n// once-warn channel collapses to \"directionalCount + pointCount +\n// spotCount === 0\" (M5 / w25).\n//\n// feat-20260625-spot-light-shadow-mapping M1 w4: embedded castShadow (default true)\n// + 6 shadow fields (mapSize / depthBias / normalBias / nearPlane / farPlane /\n// pcfKernelSize) aligned with DirectionalLight vocabulary (plan-strategy D-6;\n// charter P4 consistent abstraction). Zero-config spawns cast spot shadows;\n// set castShadow:false to opt out (validate short-circuits on false, AC-03).\n// Shadow atlas cap of 4 is enforced at extract stage, not component layer (OOS-5).\n//\n// charter mapping: proposition 1 (single import + IDE autocomplete on\n// payload.outerConeDeg with no `as` cast) + proposition 3 (silent failure\n// -> explicit failure; spawn-time fail-fast on three bound violations) +\n// proposition 5 (consistent abstraction: outgoing direction semantics\n// shared with DirectionalLight; cone deg API + cos shader uniform\n// pre-conversion mirrors directional path's host-side `lightDir x intensity`\n// pre-multiplication).\n\nimport { defineComponent } from '@forgeax/engine-ecs';\n\n/**\n * Cone-restricted spot light (KHR_lights_punctual `spot` type). Casts shadows\n * by default (castShadow defaults to true) — zero-config spawns project hard\n * PCF shadows through an independent spot depth atlas.\n *\n * `direction` @semantics outgoing -- points FROM light source TO the\n * scene (consistent with `DirectionalLight`; the shader internally negates\n * this vector to obtain the L vector for BRDF evaluation:\n * `let l = normalize(-light.direction)`). Extract owns the single\n * normalization step for the snapshot; URP and HDRP consume that normalized\n * value without re-normalizing. `position` source: the companion\n * `Transform` component on the same entity.\n *\n * `innerConeDeg` is the half-angle of the saturated bright region (cone\n * fully bright at the axis); `outerConeDeg` is the half-angle of the\n * falloff edge (cone fully dark beyond). Unit is **degrees**;\n * `outerConeDeg in (innerConeDeg, 90]` (KHR upper bound; spawn-time\n * validation rejects `outer > 90` and `outer <= inner`). The host-side\n * extract step converts both to `cos*` before GPU upload so the shader\n * only sees pre-computed cosines (plan-strategy D-S2 byte freeze; charter\n * P4 host pre-multiplication parity).\n *\n * `color` is linear-space rgb in `[0, 1]` per channel; `intensity` is candela;\n * `range` is in meters and defaults to `10.0`. Exposure is a camera output\n * operation and does not change the stored intensity.\n *\n * Shadow fields (embedded, aligned with DirectionalLight):\n * castShadow ∈ {true, false} — shadow opt-out gate (default true)\n * mapSize >= 1 — shadow map resolution per tile (default 2048)\n * depthBias — shadow acne bias (default 0.005)\n * normalBias — shadow acne normal offset (default 0.05)\n * nearPlane — shadow-camera near (default 0.1)\n * farPlane — shadow-camera far (default 50)\n * pcfKernelSize odd >= 1 — PCF kernel width (default 3)\n *\n * Atlas capacity is capped at 4 castShadow spot lights by the extract stage;\n * the 5th light onward keeps direct illumination but shadowAtlasTile = -1\n * (AC-05: clip is programmatically detectable, light stays visible).\n *\n * @example Spawn a single spot light casting shadows (zero-config):\n * world.spawn(\n * { component: Transform, data: { pos: [0, 5, 0] } },\n * { component: SpotLight, data: { direction: [0, -1, 0] } }, // [x, y, z]\n * );\n *\n * @example Opt out of shadows:\n * world.spawn(\n * { component: Transform, data: { pos: [0, 5, 0] } },\n * { component: SpotLight, data: { direction: [0, -1, 0], castShadow: false } },\n * );\n *\n * @example Explicit shadow config:\n * world.spawn(\n * { component: Transform, data: { pos: [0, 5, 0] } },\n * { component: SpotLight, data: { direction: [0, -1, 0], depthBias: 0.01, normalBias: 0.08, mapSize: 1024 } },\n * );\n *\n * @example Minimal spawn -- defaults give neutral white at full strength, range 10m, KHR pi/4 cone:\n * world.spawn(\n * { component: Transform, data: { pos: [0, 5, 0] } },\n * { component: SpotLight, data: { direction: [0, -1, 0] } }, // [x, y, z]\n * );\n * // resolves to color=[1,1,1], intensity=1, range=10.0,\n * // innerConeDeg=0, outerConeDeg=45 (KHR pi/4 equivalent).\n */\nexport const SpotLight = defineComponent('SpotLight', {\n // direction has no default (D-5): omitting it lands the array layer-3\n // all-zero, which validate() rejects. color carries an explicit layer-2\n // default [1,1,1] (white); the array layer-3 fallback is all-zero.\n direction: { type: 'array<f32, 3>' },\n color: { type: 'array<f32, 3>', default: new Float32Array([1, 1, 1]) },\n intensity: { type: 'f32', default: 1 },\n range: { type: 'f32', default: 10.0 },\n innerConeDeg: { type: 'f32', default: 0 },\n outerConeDeg: { type: 'f32', default: 45 },\n // Shadow opt-out gate: defaults to true so zero-config spawns cast shadows.\n castShadow: { type: 'bool', default: true },\n // 6 shadow fields aligned with DirectionalLight (plan-strategy D-6).\n mapSize: { type: 'f32', default: 2048 },\n depthBias: { type: 'f32', default: 0.005 },\n normalBias: { type: 'f32', default: 0.05 },\n nearPlane: { type: 'f32', default: 0.1 },\n farPlane: { type: 'f32', default: 50 },\n pcfKernelSize: { type: 'f32', default: 3 },\n});\n","import { defineComponent } from '@forgeax/engine-ecs';\n\n/** Numeric labels stored by the public Visibility enum field. */\nexport const VisibilityStateValue = Object.freeze({\n inherited: 0,\n hidden: 1,\n visible: 2,\n} as const);\n\n/** Author visibility intent accepted by the Visibility component. */\nexport type VisibilityState = keyof typeof VisibilityStateValue;\n\n/**\n * Decode the stored u32 without treating an unknown value as a valid state.\n * Invalid values are rejected at ECS write boundaries; undefined remains an\n * explicit signal for readers that inspect externally corrupted columns.\n */\nexport function visibilityStateFromU32(value: number): VisibilityState | undefined {\n switch (value) {\n case VisibilityStateValue.inherited:\n return 'inherited';\n case VisibilityStateValue.hidden:\n return 'hidden';\n case VisibilityStateValue.visible:\n return 'visible';\n default:\n return undefined;\n }\n}\n\n/**\n * Author intent for render participation. Omitted state is inherited and is\n * therefore visible when no valid parent intent is available.\n */\nexport const Visibility = defineComponent(\n 'Visibility',\n {\n state: {\n type: 'enum',\n default: VisibilityStateValue.inherited,\n labels: VisibilityStateValue,\n },\n },\n {\n meta: {\n quickStart: 'Attach Visibility to author render participation intent.',\n diagnostics: 'Compare the ECS state with resolveVisibility and renderer.visibilityStats.',\n recovery: 'Use structured ECS write errors and repair the owning scene relation.',\n boundaries:\n 'Visibility does not own camera, picking, lifecycle, assets, or VFX shadow policy.',\n },\n },\n);\n","export const STANDARD_PIPELINE_ID = 'forgeax::standard' as const;\nexport const STANDARD_LIGHT_COUNTS = [1, 32, 256] as const;\n/** Shared clustered-lighting facts consumed by pipeline, record, and buffers. */\nexport const DEFAULT_CLUSTER_GRID = { x: 16, y: 9, z: 24 } as const;\nexport const CLUSTER_GRID_STRIDE_U32 = 2;\nexport const LIGHT_INDEX_LIST_CAPACITY = 1048576;\nexport const MAX_LIGHTS = 256;\n\nexport type StandardLightCount = (typeof STANDARD_LIGHT_COUNTS)[number];\nexport type StandardLightingLane = 'direct' | 'clustered';\nexport type StandardFallbackLane = 'native' | 'cpu-webgl2';\nexport type StandardShadowMode = 'off' | 'hard' | 'filtered';\nexport type StandardTone =\n | 'none'\n | 'aces-filmic'\n | 'agx'\n | 'cineon'\n | 'linear'\n | 'neutral'\n | 'reinhard'\n | 'reinhard-extended';\nexport type StandardAntialias = 'none' | 'fxaa' | 'msaa';\nexport type StandardPostStage = 'transparent-blend' | 'bloom' | 'tone' | 'fxaa' | 'output';\n\nexport interface StandardProfile {\n readonly pipelineId: typeof STANDARD_PIPELINE_ID;\n readonly lightCount: StandardLightCount;\n readonly lighting: StandardLightingLane;\n readonly shadows: StandardShadowMode;\n readonly pbr: boolean;\n readonly ibl: boolean;\n readonly ssao: boolean;\n readonly bloom: boolean;\n readonly tone: StandardTone;\n readonly antialias: StandardAntialias;\n readonly sky: boolean;\n readonly fallback: StandardFallbackLane;\n readonly postStages: readonly ['transparent-blend', 'bloom', 'tone', 'fxaa', 'output'];\n}\n\nexport const DEFAULT_STANDARD_PROFILE: StandardProfile = Object.freeze({\n pipelineId: STANDARD_PIPELINE_ID,\n lightCount: 32,\n lighting: 'direct',\n shadows: 'filtered',\n pbr: true,\n ibl: true,\n ssao: false,\n bloom: true,\n tone: 'aces-filmic',\n antialias: 'fxaa',\n sky: true,\n fallback: 'native',\n postStages: ['transparent-blend', 'bloom', 'tone', 'fxaa', 'output'] as const,\n});\n\nexport type StandardLane = StandardLightingLane | 'cpu-webgl2';\n\nexport function resolveStandardLane(\n profile: Pick<StandardProfile, 'lighting'>,\n capabilities: { readonly compute: boolean; readonly storageBuffer: boolean },\n): StandardLane {\n if (!capabilities.compute || !capabilities.storageBuffer) return 'cpu-webgl2';\n return profile.lighting;\n}\n","import type { AssetRuntimeError } from '@forgeax/engine-assets-runtime';\nimport type { World } from '@forgeax/engine-ecs';\nimport type { RenderReadLease } from '@forgeax/engine-ecs/projection';\nimport type { Vec3 } from '@forgeax/engine-math';\nimport type { ProfileFrameToken, Profiler } from '@forgeax/engine-profiler';\nimport type { RenderGraphError } from '@forgeax/engine-render-graph';\nimport type {\n BindGroupLayout,\n Buffer,\n Result,\n RhiCaps,\n RhiCommandEncoder,\n RhiDevice,\n RhiError,\n RhiInstance,\n RhiRenderPassEncoder,\n RenderPipeline as RhiRenderPipeline,\n Texture,\n TextureFormat,\n TextureView,\n} from '@forgeax/engine-rhi';\nimport type { SkinError } from '@forgeax/engine-skinning';\nimport type { ImageError } from '@forgeax/engine-types';\nimport type { RhiBackendInstrumentation } from './assembly/backend-contract';\nimport type { Antialias, BloomEnabled, Tonemap } from './components/camera';\nimport type { RenderError } from './errors/render';\nimport type { RenderFeature } from './features/types';\nimport type { PostProcessShaderEntry } from './fullscreen-post-process-pass';\nimport type { RenderSceneInspection } from './inspection-types';\nimport type { MeshMaterialBindingObservation } from './mesh-material-bindings';\nimport type { StandardProfile } from './pipeline/standard-profile';\nimport type { PostProcessError } from './post-process-errors';\n\nexport type RenderResult<T, E> =\n | { readonly ok: true; readonly value: T }\n | { readonly ok: false; readonly error: E };\n\n/** The only World data boundary accepted by the M6 renderer contract. */\nexport type RenderWorldLease = RenderReadLease;\n\n/**\n * Camera facts shared by extract, record, and scene owners.\n *\n * The extract stage resolves ECS camera state once and publishes this closed\n * POD contract. Keeping it beside the render lifecycle contracts prevents\n * extract from depending on the record implementation owner.\n */\nexport interface CameraSnapshot {\n /** Stable ECS identity used by the frame plan's camera authority fact. */\n readonly entityKey?: number;\n /** World-space camera translation (mat4.getTranslation of Transform.world). */\n readonly position: Vec3;\n /** Resolved world-space camera mat4, copied from Transform.world. */\n readonly world: Float32Array;\n readonly fov: number;\n readonly aspect: number;\n readonly near: number;\n readonly far: number;\n /** Camera projection variant used by view and shadow matrix builders. */\n readonly projection: 'perspective' | 'orthographic';\n readonly orthoLeft: number;\n readonly orthoRight: number;\n readonly orthoBottom: number;\n readonly orthoTop: number;\n readonly tonemap: Tonemap;\n readonly exposure: number;\n readonly whitePoint: number;\n readonly antialias: Antialias;\n readonly bloom: BloomEnabled;\n readonly bloomThreshold: number;\n readonly bloomIntensity: number;\n readonly bloomBlurRadius: number;\n readonly clearColor: readonly [number, number, number, number];\n}\n\n/** Stable identity owned by the Standard feature host for built-in tonemap. */\nexport const STANDARD_TONEMAP_FEATURE_ID = 'forgeax::standard::tonemap';\n\n/**\n * Render-graph state exposed to pipeline declarations. Concrete pipeline\n * caches stay in the assembly owner; graph code only needs the two target\n * formats that affect fullscreen descriptors.\n */\ninterface RenderPipelineStateView {\n readonly format: TextureFormat;\n readonly colorAttachmentFormat: TextureFormat;\n}\n\n/**\n * Narrow runtime capability consumed by typed graph primitives. The concrete\n * RenderSystemRuntime structurally satisfies this contract, but the public\n * frame surface does not depend on that assembly owner.\n */\ninterface RenderPipelineRuntime {\n readonly device: RhiDevice;\n readonly errorRegistry: { fire(error: RendererError): void };\n readonly debugOverlay?: RenderDebugOverlay | undefined;\n readonly lookupPostProcess?: (id: string) => PostProcessShaderEntry | undefined;\n readonly getPostProcessParamsBuffer?: (id: string) => Buffer | undefined;\n readonly getPostProcessPipeline?: (\n id: string,\n bgl: BindGroupLayout,\n colorFormat: GPUTextureFormat,\n ) => RhiRenderPipeline | null;\n}\n\n/**\n * Render-owned declaration for an app-provided debug overlay. App owns the\n * concrete DebugDraw instance and its GPU lifecycle; Render only records this\n * capability in the typed graph.\n */\nexport interface RenderDebugOverlay {\n encode(\n pass: RhiRenderPassEncoder,\n viewProj: import('@forgeax/engine-math').Mat4,\n ): Result<void, unknown>;\n}\n\n/**\n * The leaf frame context consumed by RenderPipeline and graph primitives.\n * Concrete assembly data is added only by the internal record extension in\n * render-system.ts, so this contract never imports the RenderSystem owner.\n */\nexport interface RenderPipelineContext {\n readonly pipelineState: RenderPipelineStateView;\n readonly runtime: RenderPipelineRuntime;\n readonly encoder: RhiCommandEncoder;\n readonly view: TextureView;\n readonly clear: readonly [number, number, number, number] | number[];\n readonly targetW: number;\n readonly targetH: number;\n readonly currentTexture: Texture;\n readonly camera: CameraSnapshot;\n /** Per-frame post-process parameter bytes keyed by shader id. */\n readonly postProcessParams: ReadonlyMap<string, Uint8Array>;\n readonly msaaActive: boolean;\n readonly geometryColorResolveView: TextureView | null;\n readonly ldrSpriteColorView: TextureView | null;\n}\n\n/**\n * The sole public renderer contract. Concrete assembly stays behind the host;\n * callers retain only leases, receipts, inspection PODs, and recovery Result.\n */\nexport interface Renderer {\n attach(world: World): RenderResult<RenderWorldLease, RenderError>;\n draw(input: RenderFrameInput): RenderResult<FrameReceipt, RenderError>;\n setProfile(profile: RenderProfile): RenderResult<void, RenderError>;\n state(): RendererState;\n inspect(): RenderInspection;\n observe(\n receipt: FrameReceipt,\n request: FrameObservationRequest,\n ): Promise<RenderResult<FrameReceiptObservation, RenderError>>;\n subscribe(listener: (event: RendererEvent) => void): () => void;\n releaseSurface(): RenderResult<void, RenderError>;\n restoreSurface(): RenderResult<void, RenderError>;\n recover(): Promise<RenderResult<void, RenderError>>;\n dispose(): Promise<RenderResult<void, RenderError>>;\n}\n\n/** The single public renderer lifecycle authority. */\nexport type RendererState = 'alive' | 'device-lost' | 'recovering' | 'faulted' | 'disposed';\n\n/** Immutable, inspectable quality and budget policy for the Standard pipeline. */\nexport type RenderProfile = StandardProfile;\n\n/** Ordered events projected from renderer state and expected-operation failures. */\nexport type RendererEvent =\n | {\n readonly kind: 'state-changed';\n readonly previous: RendererState;\n readonly current: RendererState;\n }\n | { readonly kind: 'error'; readonly error: RenderError };\n\n/** Package-local listener alias used by the renderer assembly implementation. */\nexport type RendererEventListener = (event: RendererEvent) => void;\n\n/**\n * Camera authority supplied for one frame; Render owns the lease contract and\n * the host releases it with the frame lifecycle. It is intentionally bound to\n * the attached scene lease and never accepts a backend object.\n */\nexport interface FrameCamera {\n readonly lease: RenderWorldLease;\n readonly entityKey?: number;\n}\n\n/**\n * Environment authority supplied for one frame. The scene/environment owner\n * supplies the identity; the host validates it against the attached lease.\n */\nexport interface FrameEnvironment {\n readonly lease: RenderWorldLease;\n readonly identity?: string;\n}\n\n/**\n * Receipt-bound observation selection. The request is consumed by `observe`\n * only with the matching FrameReceipt and returns a structured Result error\n * when the generation or frame identity is stale.\n */\nexport interface FrameObservationRequest {\n readonly include: readonly ('timings' | 'draws' | 'bindings')[];\n}\n\n/**\n * Immutable synchronous proof that the sole host-owned submit reached the\n * queue. It is the only successful synchronous draw signal and is required by\n * every observation request.\n */\nexport interface FrameReceipt {\n readonly frameId: number;\n readonly deviceGeneration: number;\n readonly completed: Promise<RenderResult<void, RenderError>>;\n}\n\n/**\n * The sole public draw input; World objects never cross this boundary. Render\n * owns extraction and graph interpretation, while the host owns encoding,\n * finish, submit, and recovery of the attached lease generation.\n */\nexport interface RenderFrameInput {\n readonly leases: readonly RenderWorldLease[];\n readonly camera: FrameCamera;\n readonly environment: FrameEnvironment;\n /** Optional profiler correlation token owned by App and consumed by Render. */\n readonly profileFrame?: ProfileFrameToken;\n}\n\n/** Detached, bounded observation metadata tied to one FrameReceipt and request. */\nexport interface FrameReceiptObservation {\n readonly frameId: number;\n readonly deviceGeneration: number;\n readonly include: readonly ('timings' | 'draws' | 'bindings')[];\n}\n\n/**\n * Detached renderer facts for host diagnostics and capability selection.\n *\n * The snapshot contains only POD values. It never exposes a device, queue,\n * registry, encoder, or live renderer-owned collection.\n */\nexport interface RenderInspection {\n readonly state: RendererState;\n readonly surface: 'available' | 'released';\n readonly profile: RenderProfile;\n readonly capabilities: Readonly<RhiCaps>;\n readonly frame: {\n readonly frameId: number;\n readonly deviceGeneration: number;\n };\n readonly features: readonly string[];\n readonly frustumStats: { readonly culled: number; readonly total: number };\n readonly visibilityStats: { readonly explicitlyHidden: number };\n readonly renderScene: RenderSceneInspection;\n readonly meshMaterialBindings: readonly MeshMaterialBindingObservation[];\n readonly perFramePassNames: readonly string[];\n readonly bindGroupCounts: {\n readonly createBindGroup: number;\n readonly keys: readonly string[];\n };\n}\n\n/** Backend marker — single-element union preserved for future extensibility (D-2). */\n/**\n * `renderer.draw(worlds, options)` owner options\n * (feat-20260709-editor-world-partition M1 / w6, plan-strategy §2 D-3).\n *\n * The single `owner` index that previously served BOTH the surfaced cameras\n * and the singleton render resources (skylight / skybox / postProcessParams) is\n * split into two independent indices:\n * - `cameraOwner` — the world whose cameras are surfaced.\n * - `resourceOwner` — the world whose skylight / skybox / postProcessParams\n * are surfaced.\n *\n * Both owners are always explicit. Single-world callers pass zero for both;\n * composite callers may select different worlds.\n */\nexport type DrawOwnerOptions = {\n readonly cameraOwner: number;\n readonly resourceOwner: number;\n readonly profileFrame?: ProfileFrameToken;\n};\n\n/** Information attached to a device-loss notification. */\nexport interface RendererLostInfo {\n /** Concise machine-readable cause, mapped to a single vocabulary. */\n reason: string;\n /** Free-form description (UA / extension / driver text). */\n message: string;\n}\n\n/** Listener registered through `Renderer.onLost`. */\nexport type RendererLostListener = (info: RendererLostInfo) => void;\n\n/**\n * Composite error type retained for package-local renderer diagnostics. The\n * public Renderer projects expected operation failures through the single\n * `subscribe` event stream and its `error` event; this type does NOT define a\n * error; it only references the cluster unions whose members can arrive here.\n * As such it is an external wire alias (AGENTS.md Change stance add-only wire\n * exception), NOT the eliminated cross-cluster `RuntimeError` SSOT (D-3).\n *\n * Composition = `RhiError | RenderError | AssetRuntimeError | SkinError |\n * PostProcessError`. This equals the pre-decomposition\n * `RhiError | RuntimeError | PostProcessError` exactly: `RuntimeError` was\n * `RenderError | AssetRuntimeError | SkinError` (27 classes). `RecoverError`\n * and `EngineEnvironmentError` are intentionally excluded — neither is ever\n * emitted through the Renderer event stream (`RecoverError` returns from `recover()`,\n * `EngineEnvironmentError` throws at construction), matching the original\n * `RuntimeError` union which excluded both (OOS-3 behavior equivalence).\n *\n * AI consumers do `switch (err.code)` over the union: the disjoint\n * `RhiErrorCode` / `RenderErrorCode` / `AssetRuntimeErrorCode` / `SkinErrorCode`\n * / `PostProcessErrorCode` literal sets let TS narrow each arm to the concrete\n * class (charter P3 union discoverability — every fan-out member is reachable\n * in an exhaustive switch, no untyped escape). Example:\n *\n * ```ts\n * renderer.subscribe((event) => {\n * if (event.kind !== 'error') return;\n * switch (event.error.code) {\n * case 'asset-not-registered': // AssetRuntimeError arm, err narrowed here\n * return report(err.hint);\n * // ...one arm per RhiErrorCode | RenderErrorCode | AssetRuntimeErrorCode\n * // | SkinErrorCode | PostProcessErrorCode member; no default needed,\n * // TS flags every unhandled code at compile time.\n * }\n * });\n * ```\n */\nexport type RendererError =\n | RhiError\n | RenderGraphError\n | ImageError\n | RenderError\n | AssetRuntimeError\n | SkinError\n | PostProcessError;\n\n/**\n * Package-local error listener retained for host assembly. Public consumers\n * use `Renderer.subscribe` and receive `RendererEvent` instead.\n */\nexport type RendererErrorListener = (error: RendererError) => void;\n\n/**\n * Built-in graph pass owners used as nested children of `record/graph-execute`.\n * Unknown extension pass names use the explicit `other` bucket so custom\n * pipelines retain a bounded catalog while the default render workload gets\n * actionable owner attribution.\n */\nexport const RENDER_GRAPH_EXECUTION_PHASE_CATALOG = [\n 'record/graph-execute/point-shadow',\n 'record/graph-execute/cluster-binner-upload',\n 'record/graph-execute/g-buffer',\n 'record/graph-execute/g-buffer/geometry-loop',\n 'record/graph-execute/g-buffer/material-bind-groups',\n 'record/graph-execute/g-buffer/pipeline-selection',\n 'record/graph-execute/g-buffer/draw-submit',\n 'record/graph-execute/ssao-calc',\n 'record/graph-execute/ssao-blur',\n 'record/graph-execute/lighting',\n 'record/graph-execute/forward',\n 'record/graph-execute/forward/geometry-loop',\n 'record/graph-execute/forward/material-bind-groups',\n 'record/graph-execute/forward/pipeline-selection',\n 'record/graph-execute/forward/draw-submit',\n 'record/graph-execute/tonemap',\n 'record/graph-execute/debug-overlay',\n 'record/graph-execute/shadow',\n 'record/graph-execute/spot-shadow',\n 'record/graph-execute/skybox',\n 'record/graph-execute/main',\n 'record/graph-execute/fxaa',\n 'record/graph-execute/bloom-bright',\n 'record/graph-execute/bloom-blur-h',\n 'record/graph-execute/bloom-blur-v',\n 'record/graph-execute/bloom-composite',\n 'record/graph-execute/other',\n] as const;\n\nexport type RenderGraphExecutionPhase = (typeof RENDER_GRAPH_EXECUTION_PHASE_CATALOG)[number];\n\n/** Bounded producer-owned children of the scene-state record owner. */\nexport const RENDER_HDRP_BINNER_PHASE_CATALOG = [\n 'record/scene-state/hdrp-cluster/binner/light-bounds-and-occupancy',\n 'record/scene-state/hdrp-cluster/binner/light-bounds-and-occupancy/light-aabb',\n 'record/scene-state/hdrp-cluster/binner/light-bounds-and-occupancy/cluster-occupancy',\n 'record/scene-state/hdrp-cluster/binner/cluster-reserve',\n 'record/scene-state/hdrp-cluster/binner/input-preparation',\n 'record/scene-state/hdrp-cluster/binner/bin-core',\n 'record/scene-state/hdrp-cluster/binner/light-index-write',\n 'record/scene-state/hdrp-cluster/binner/light-index-write/bounds-read',\n 'record/scene-state/hdrp-cluster/binner/light-index-write/cluster-write',\n] as const;\n\nexport const RENDER_HDRP_CLUSTER_PHASE_CATALOG = [\n 'record/scene-state/hdrp-cluster/binner',\n ...RENDER_HDRP_BINNER_PHASE_CATALOG,\n 'record/scene-state/hdrp-cluster/payload-packing',\n 'record/scene-state/hdrp-cluster/buffer-upload',\n] as const;\n\nexport type RenderHdrpClusterPhase = (typeof RENDER_HDRP_CLUSTER_PHASE_CATALOG)[number];\n\nexport const RENDER_SCENE_STATE_PHASE_CATALOG = [\n 'record/scene-state/fold-buckets',\n 'record/scene-state/lighting-prep',\n 'record/scene-state/ambient-resolution',\n 'record/scene-state/directional-shadow-cache',\n 'record/scene-state/hdrp-cluster',\n ...RENDER_HDRP_CLUSTER_PHASE_CATALOG,\n] as const;\n\n/**\n * Opt-in boundaries inside one `Renderer.draw` call. The observer is a\n * diagnostics seam only: it receives wall-time boundaries and must never be\n * required for rendering correctness. The stage names mirror the existing\n * engine-owned Extract / Prepare / Record orchestration so a host can measure\n * attribution without guessing from RHI command counts.\n */\nexport const RENDER_RECORD_PHASE_CATALOG = [\n 'record/scene-state',\n ...RENDER_SCENE_STATE_PHASE_CATALOG,\n 'record/swapchain',\n 'record/render-graph',\n 'record/target-views',\n 'record/validation',\n 'record/dispatch-plan',\n 'record/uploads',\n 'record/bind-groups',\n 'record/graph-execute',\n ...RENDER_GRAPH_EXECUTION_PHASE_CATALOG,\n] as const;\n\nexport type RenderRecordPhase = (typeof RENDER_RECORD_PHASE_CATALOG)[number];\n\nexport const RENDER_PHASE_CATALOG = [\n 'extract',\n 'bind-groups',\n 'features',\n 'sort',\n 'record',\n ...RENDER_RECORD_PHASE_CATALOG,\n] as const;\n\nexport type RenderPhase = (typeof RENDER_PHASE_CATALOG)[number];\n\nexport type RenderPhaseSkipReason =\n | 'feature-host-unavailable'\n | 'feature-host-empty'\n | 'pipeline-state-unavailable'\n | 'camera-unavailable';\n\n/** First-version options bag (intentionally empty; reserved for v0.1). */\nexport interface RendererOptions {\n /** Producer-owned features installed by the renderer host. */\n readonly features?: readonly RenderFeature<unknown>[] | undefined;\n /** Standard profile consumed by the single renderer-owned pipeline. */\n readonly standardProfile?: RenderProfile | undefined;\n /** Explicit profiler capability shared by App and Render. */\n readonly profiler?: Profiler | undefined;\n /** Optional host-owned RHI lifecycle instrumentation, such as recording. */\n readonly rhiInstrumentation?: RhiBackendInstrumentation | undefined;\n // feat-20260608-create-app-param-surface-trim / M1 / AC-02: `clearColor`\n // was deleted as a one-cut breaking change (AGENTS.md Change stance +\n // requirements constraint #1: no deprecation window, no shim). Scene\n // clear color now lives on the Camera entity (`clearColor`, an inline\n // array<f32,4> column as of feat-20260709 M3); zero-Camera fallback uses\n // `ZERO_CAMERA_CLEAR_FALLBACK = [0, 0, 0, 1]` from\n // `render-system-record`. AI users that pass `{ clearColor: [...] }` on\n // RendererOptions still get a TS2353 excess-property error at compile time.\n //\n // feat-20260608-create-app-param-surface-trim / M2 / AC-06 + D-3:\n // `shaderManifestUrl` was deleted from RendererOptions and moved to\n // the third-arg `BundlerOptions` (build-tool injection channel). The\n // fallback literal '/shaders/manifest.json' stays at the createRenderer\n // body site (D-2 q5-A) so the LO 1.1 zero-config takeoff path keeps\n // working without explicit injection. AI users that pass\n // `{ shaderManifestUrl: '...' }` to RendererOptions get a TS2353\n // excess-property error at compile time pointing them to the third\n // arg (charter P1 progressive disclosure -- the message names\n // BundlerOptions, not a free-form string).\n /**\n * M3 D-P4 escape hatch (feat-20260511-rhi-wgpu-impl): explicit\n * `RhiInstance` injection bypasses the dynamic-import auto-select\n * facade. When set, `createRenderer` uses this instance verbatim and\n * neither concrete backend adapter is imported dynamically (charter\n * proposition 5 discoverable opt-in /\n * plan-strategy §6 M3 + §7.4 escape hatch + Bevy\n * `RenderCreation::Manual` partial equivalent).\n *\n * Typical use cases:\n * - Testing / debugging — inject a deterministic stub.\n * - Pinning a specific backend even when the browser adapter is present,\n * for cross-shim regression tests.\n * - Advanced AI users implementing their own `RhiInstance` shim.\n *\n * AI users who want the default behaviour leave this field omitted;\n * `navigator.gpu` presence/absence drives the dynamic import (see the\n * `createRenderer` JSDoc for the full auto-select decision tree).\n */\n readonly rhi?: RhiInstance | undefined;\n}\n\n// ────────────────────────────────────────────────────────────────────────────\n// Renderer health / recover surface (feat-20260621-renderer-health-recover-skeleton M1)\n// ────────────────────────────────────────────────────────────────────────────\n\n/**\n * Closed union of renderer health states.\n *\n * 3 members per plan-strategy D-1:\n * - `'alive'` — healthy baseline (registry not yet fired)\n * - `'device-lost'` — device loss detected\n * - `'internal-fault'` — internal renderer fault\n *\n * AI users exhaustively switch on `HealthReason` without default; TS guards\n * completeness.\n */\nexport type HealthReason = 'alive' | 'device-lost' | 'internal-fault';\n\n/** Detail for `HealthReason 'device-lost'`. */\nexport interface HealthDetailDeviceLost {\n readonly lostReason: 'unknown' | 'destroyed';\n readonly message: string;\n}\n\n/** Detail for `HealthReason 'internal-fault'`. */\nexport interface HealthDetailInternalFault {\n readonly message: string;\n}\n\n/**\n * Pull-style health snapshot — discriminated union by `reason`.\n *\n * Per plan-strategy D-2: `switch(snap.reason)` narrows `snap.detail` to the\n * per-reason detail type automatically, with zero `as` casts. `alive` has no\n * `.detail` field; `device-lost` / `internal-fault` have a required `.detail`\n * of the respective variant.\n *\n * - `recoverable` — explicit snapshot data owned by the lifecycle authority\n */\nexport type HealthSnapshot =\n | { readonly reason: 'alive'; readonly recoverable: boolean }\n | {\n readonly reason: 'device-lost';\n readonly detail: HealthDetailDeviceLost;\n readonly recoverable: boolean;\n }\n | {\n readonly reason: 'internal-fault';\n readonly detail: HealthDetailInternalFault;\n readonly recoverable: boolean;\n };\n\n/**\n * Maps `HealthReason` to recoverable boolean per the derive table\n * (requirements section \"range\"):\n *\n * | reason | recoverable |\n * |:---------------|:-----------|\n * | `'alive'` | false |\n * | `'device-lost'`| true |\n * | `'internal-fault'` | false |\n */\n/** Callback type for `Renderer.onHealthChange`. */\nexport type HealthChangeListener = (snapshot: HealthSnapshot) => void;\n","import { Entity, type EntityHandle, type World } from '@forgeax/engine-ecs';\nimport {\n projectHierarchy,\n type SceneHierarchyDiagnostic,\n type SceneHierarchySnapshot,\n} from '@forgeax/engine-scene';\nimport {\n Visibility,\n type VisibilityState,\n VisibilityStateValue,\n visibilityStateFromU32,\n} from '../components/visibility';\n\nexport type VisibilitySource = 'default' | 'self' | 'parent';\n\nexport interface VisibilityResolution {\n /** The component value authored on this entity before inheritance. */\n readonly intent: VisibilityState;\n /** The state consumed by render candidates after parent resolution. */\n readonly effective: 'hidden' | 'visible';\n /** Explains whether the effective state came from self, parent, or default. */\n readonly source: VisibilitySource;\n}\n\nexport interface VisibilitySnapshot {\n readonly diagnostics: readonly SceneHierarchyDiagnostic[];\n readonly hasAnyIntent: boolean;\n readonly hasAnyHiddenIntent: boolean;\n get(entity: EntityHandle): VisibilityResolution | undefined;\n /** Resolve effective visibility for a render candidate, including inherited defaults. */\n effective(entity: EntityHandle): 'hidden' | 'visible';\n}\n\ninterface ResolutionState {\n readonly intent: VisibilityState;\n readonly effective: 'hidden' | 'visible';\n readonly source: VisibilitySource;\n}\n\n/**\n * Resolve author intent against the scene-owned valid parent projection.\n * Diagnostics are preserved for callers to repair hierarchy input before retry.\n */\nexport function resolveVisibility(\n world: World,\n hierarchy: SceneHierarchySnapshot = projectHierarchy(world),\n): VisibilitySnapshot {\n const query = world.query({ read: [Entity], optional: [Visibility] });\n let intentCount = 0;\n let hasAnyHiddenIntent = false;\n if (query.ok) {\n for (const row of query.value) {\n const visibility = row.get(Visibility);\n if (visibility === undefined) continue;\n intentCount += 1;\n if (visibility.state === VisibilityStateValue.hidden) {\n hasAnyHiddenIntent = true;\n }\n }\n }\n\n let resolveEntity: ((entity: EntityHandle) => ResolutionState | undefined) | undefined;\n let hasIntent: ((entity: EntityHandle) => boolean) | undefined;\n const ensureResolver = (): void => {\n if (resolveEntity !== undefined) return;\n const intentByEntity = new Map<EntityHandle, VisibilityState>();\n if (query.ok) {\n for (const row of query.value) {\n const raw = row.get(Visibility);\n const intent = raw === undefined ? undefined : visibilityStateFromU32(raw.state);\n if (intent !== undefined) {\n intentByEntity.set(row.entity, intent);\n }\n }\n }\n hasIntent = (entity: EntityHandle): boolean => intentByEntity.has(entity);\n const resolved = new Map<EntityHandle, ResolutionState>();\n const resolving = new Set<EntityHandle>();\n resolveEntity = (entity: EntityHandle): ResolutionState | undefined => {\n const existing = resolved.get(entity);\n if (existing !== undefined) return existing;\n if (resolving.has(entity)) return undefined;\n\n const intent = intentByEntity.get(entity) ?? 'inherited';\n resolving.add(entity);\n\n let result: ResolutionState;\n if (intent === 'hidden') {\n result = { intent, effective: 'hidden', source: 'self' };\n } else if (intent === 'visible') {\n result = { intent, effective: 'visible', source: 'self' };\n } else {\n const parent = hierarchy.getParent(entity);\n const parentResult = parent === undefined ? undefined : resolveEntity?.(parent);\n result =\n parentResult === undefined\n ? { intent, effective: 'visible', source: 'default' }\n : { intent, effective: parentResult.effective, source: 'parent' };\n }\n\n resolving.delete(entity);\n resolved.set(entity, result);\n return result;\n };\n };\n\n return {\n diagnostics: hierarchy.diagnostics,\n hasAnyIntent: intentCount > 0,\n hasAnyHiddenIntent,\n get(entity: EntityHandle): VisibilityResolution | undefined {\n ensureResolver();\n return hasIntent?.(entity) ? resolveEntity?.(entity) : undefined;\n },\n effective(entity: EntityHandle): 'hidden' | 'visible' {\n ensureResolver();\n return resolveEntity?.(entity)?.effective ?? 'visible';\n },\n };\n}\n"]}