@threenative/core 0.3.1 → 0.3.3

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.
package/capabilities.json CHANGED
@@ -1,5 +1,26 @@
1
1
  {
2
2
  "entries": [
3
+ {
4
+ "aliases": [],
5
+ "constraints": [
6
+ "deterministic by construction: the packing order is derived from the sources, never from directory order, so a rebuild places every source at the same pixel",
7
+ "a source the scene samples outside [0, 1] is excluded and reported, never clamped onto a shared page",
8
+ "page bounds hold regardless of input order; padding keeps a mip tap from reaching the next source"
9
+ ],
10
+ "example": "const { pages, transforms, excluded } = packAtlas(sources, { pageSize: 4096, padding: 4 });",
11
+ "importPath": "@threenative/assets",
12
+ "kind": "function",
13
+ "overrides": [],
14
+ "package": "@threenative/assets",
15
+ "signature": "export function atlasManifest(result: IAtlasResult): string { … }",
16
+ "situations": [
17
+ "a merge found nothing to collapse because each imported part owns its own texture",
18
+ "cut the material count of an imported model pack at build time"
19
+ ],
20
+ "summary": "Pack texture sources into deterministic atlas pages and answer each source's UV transform, so a build can stop giving every material a private texture.",
21
+ "supersedes": [],
22
+ "symbol": "atlasManifest"
23
+ },
3
24
  {
4
25
  "aliases": [],
5
26
  "constraints": [
@@ -22,6 +43,27 @@
22
43
  "supersedes": [],
23
44
  "symbol": "audioPass"
24
45
  },
46
+ {
47
+ "aliases": [],
48
+ "constraints": [
49
+ "reads geometry and materials only: no GPU, no runtime, no game",
50
+ "a texture whose size the document does not state is reported excluded, never assumed square",
51
+ "a model the reader cannot open is named, never skipped silently"
52
+ ],
53
+ "example": "pnpm census:content public/assets",
54
+ "importPath": "@threenative/assets",
55
+ "kind": "function",
56
+ "overrides": [],
57
+ "package": "@threenative/assets",
58
+ "signature": "export function censusDocument( model: string, document: Document, pageSize = 4_096, ): IContentCensus { … }",
59
+ "situations": [
60
+ "find out whether \"fewer objects\" is available in this content before promising it",
61
+ "explain why a per-material merge collapsed nothing"
62
+ ],
63
+ "summary": "Census one glTF document, or a directory of them: the merge buckets a scene has now, and the buckets it would have once its atlasable textures shared pages.",
64
+ "supersedes": [],
65
+ "symbol": "censusDocument"
66
+ },
25
67
  {
26
68
  "aliases": [],
27
69
  "constraints": [
@@ -42,6 +84,26 @@
42
84
  "supersedes": [],
43
85
  "symbol": "compileAssets"
44
86
  },
87
+ {
88
+ "aliases": [],
89
+ "constraints": [
90
+ "the signature ignores the material name, which is what made every imported part a singleton, and keeps materials apart on any field it does not understand",
91
+ "the census reads geometry and materials only: no GPU, no runtime, no game"
92
+ ],
93
+ "example": "pnpm census:content public/assets",
94
+ "importPath": "@threenative/assets",
95
+ "kind": "function",
96
+ "overrides": [],
97
+ "package": "@threenative/assets",
98
+ "signature": "export function dedupeMaterials(materials: readonly IMaterialState[]): { … }",
99
+ "situations": [
100
+ "decide whether fewer objects is actually available in this content",
101
+ "report why a per-material merge collapsed nothing"
102
+ ],
103
+ "summary": "Collapse materials that became identical once their textures shared an atlas page, and count the buckets a merge would find — before and after — so the promise can be checked rather than made.",
104
+ "supersedes": [],
105
+ "symbol": "dedupeMaterials"
106
+ },
45
107
  {
46
108
  "aliases": [],
47
109
  "constraints": ["an empty row list produces no report lines"],
@@ -148,6 +210,47 @@
148
210
  "supersedes": [],
149
211
  "symbol": "lightmapPass"
150
212
  },
213
+ {
214
+ "aliases": [],
215
+ "constraints": [
216
+ "the signature ignores the material name, which is what made every imported part a singleton, and keeps materials apart on any field it does not understand",
217
+ "the census reads geometry and materials only: no GPU, no runtime, no game"
218
+ ],
219
+ "example": "pnpm census:content public/assets",
220
+ "importPath": "@threenative/assets",
221
+ "kind": "function",
222
+ "overrides": [],
223
+ "package": "@threenative/assets",
224
+ "signature": "export function materialSignature(material: IMaterialState): string { … }",
225
+ "situations": [
226
+ "decide whether fewer objects is actually available in this content",
227
+ "report why a per-material merge collapsed nothing"
228
+ ],
229
+ "summary": "Collapse materials that became identical once their textures shared an atlas page, and count the buckets a merge would find — before and after — so the promise can be checked rather than made.",
230
+ "supersedes": [],
231
+ "symbol": "materialSignature"
232
+ },
233
+ {
234
+ "aliases": [],
235
+ "constraints": [
236
+ "reads geometry and materials only: no GPU, no runtime, no game",
237
+ "a texture whose size the document does not state is reported excluded, never assumed square",
238
+ "a model the reader cannot open is named, never skipped silently"
239
+ ],
240
+ "example": "pnpm census:content public/assets",
241
+ "importPath": "@threenative/assets",
242
+ "kind": "function",
243
+ "overrides": [],
244
+ "package": "@threenative/assets",
245
+ "signature": "export function materialStateOf(material: Material): IMaterialState { … }",
246
+ "situations": [
247
+ "find out whether \"fewer objects\" is available in this content before promising it",
248
+ "explain why a per-material merge collapsed nothing"
249
+ ],
250
+ "summary": "Census one glTF document, or a directory of them: the merge buckets a scene has now, and the buckets it would have once its atlasable textures shared pages.",
251
+ "supersedes": [],
252
+ "symbol": "materialStateOf"
253
+ },
151
254
  {
152
255
  "aliases": [],
153
256
  "constraints": [
@@ -167,6 +270,27 @@
167
270
  "supersedes": [],
168
271
  "symbol": "modelPass"
169
272
  },
273
+ {
274
+ "aliases": [],
275
+ "constraints": [
276
+ "deterministic by construction: the packing order is derived from the sources, never from directory order, so a rebuild places every source at the same pixel",
277
+ "a source the scene samples outside [0, 1] is excluded and reported, never clamped onto a shared page",
278
+ "page bounds hold regardless of input order; padding keeps a mip tap from reaching the next source"
279
+ ],
280
+ "example": "const { pages, transforms, excluded } = packAtlas(sources, { pageSize: 4096, padding: 4 });",
281
+ "importPath": "@threenative/assets",
282
+ "kind": "function",
283
+ "overrides": [],
284
+ "package": "@threenative/assets",
285
+ "signature": "export function packAtlas( sources: readonly IAtlasSource[], options: IAtlasOptions = { … }",
286
+ "situations": [
287
+ "a merge found nothing to collapse because each imported part owns its own texture",
288
+ "cut the material count of an imported model pack at build time"
289
+ ],
290
+ "summary": "Pack texture sources into deterministic atlas pages and answer each source's UV transform, so a build can stop giving every material a private texture.",
291
+ "supersedes": [],
292
+ "symbol": "packAtlas"
293
+ },
170
294
  {
171
295
  "aliases": [],
172
296
  "constraints": [
@@ -225,6 +349,48 @@
225
349
  "supersedes": [],
226
350
  "symbol": "resolveBasisTranscoder"
227
351
  },
352
+ {
353
+ "aliases": [],
354
+ "constraints": [
355
+ "a surface is tiling when its own UVs leave [0, 1]; glTF's default wrap is REPEAT, so the sampler alone excludes almost everything and is the wrong test",
356
+ "the rewrite is in place, and a buffer that does not hold pairs throws rather than rewriting half a coordinate",
357
+ "`resolveSourceTexel` is the inverse, so a build can round-trip a texel instead of asserting the arithmetic against itself"
358
+ ],
359
+ "example": "if (!uvsTile(uv)) rewriteUvs(uv, transforms.get(source)!);",
360
+ "importPath": "@threenative/assets",
361
+ "kind": "function",
362
+ "overrides": [],
363
+ "package": "@threenative/assets",
364
+ "signature": "export function resolveSourceTexel( atlasUv: readonly [number, number], transform: IAtlasTransform, source: { … }",
365
+ "situations": [
366
+ "rewrite a model's texture coordinates after packing its images into an atlas",
367
+ "tell a surface that tiles from one that merely has a repeating sampler"
368
+ ],
369
+ "summary": "Move a mesh's UVs onto its atlas page, and decide from the geometry which meshes may not go.",
370
+ "supersedes": [],
371
+ "symbol": "resolveSourceTexel"
372
+ },
373
+ {
374
+ "aliases": [],
375
+ "constraints": [
376
+ "a surface is tiling when its own UVs leave [0, 1]; glTF's default wrap is REPEAT, so the sampler alone excludes almost everything and is the wrong test",
377
+ "the rewrite is in place, and a buffer that does not hold pairs throws rather than rewriting half a coordinate",
378
+ "`resolveSourceTexel` is the inverse, so a build can round-trip a texel instead of asserting the arithmetic against itself"
379
+ ],
380
+ "example": "if (!uvsTile(uv)) rewriteUvs(uv, transforms.get(source)!);",
381
+ "importPath": "@threenative/assets",
382
+ "kind": "function",
383
+ "overrides": [],
384
+ "package": "@threenative/assets",
385
+ "signature": "export function rewriteUvs(uv: Float32Array, transform: IAtlasTransform): Float32Array { … }",
386
+ "situations": [
387
+ "rewrite a model's texture coordinates after packing its images into an atlas",
388
+ "tell a surface that tiles from one that merely has a repeating sampler"
389
+ ],
390
+ "summary": "Move a mesh's UVs onto its atlas page, and decide from the geometry which meshes may not go.",
391
+ "supersedes": [],
392
+ "symbol": "rewriteUvs"
393
+ },
228
394
  {
229
395
  "aliases": [],
230
396
  "constraints": [
@@ -265,6 +431,48 @@
265
431
  "supersedes": [],
266
432
  "symbol": "texturePass"
267
433
  },
434
+ {
435
+ "aliases": [],
436
+ "constraints": [
437
+ "reads geometry and materials only: no GPU, no runtime, no game",
438
+ "a texture whose size the document does not state is reported excluded, never assumed square",
439
+ "a model the reader cannot open is named, never skipped silently"
440
+ ],
441
+ "example": "pnpm census:content public/assets",
442
+ "importPath": "@threenative/assets",
443
+ "kind": "function",
444
+ "overrides": [],
445
+ "package": "@threenative/assets",
446
+ "signature": "export function totalCensus(entries: readonly IContentCensus[]): Omit<IContentCensus, \"model\"> { … }",
447
+ "situations": [
448
+ "find out whether \"fewer objects\" is available in this content before promising it",
449
+ "explain why a per-material merge collapsed nothing"
450
+ ],
451
+ "summary": "Census one glTF document, or a directory of them: the merge buckets a scene has now, and the buckets it would have once its atlasable textures shared pages.",
452
+ "supersedes": [],
453
+ "symbol": "totalCensus"
454
+ },
455
+ {
456
+ "aliases": [],
457
+ "constraints": [
458
+ "a surface is tiling when its own UVs leave [0, 1]; glTF's default wrap is REPEAT, so the sampler alone excludes almost everything and is the wrong test",
459
+ "the rewrite is in place, and a buffer that does not hold pairs throws rather than rewriting half a coordinate",
460
+ "`resolveSourceTexel` is the inverse, so a build can round-trip a texel instead of asserting the arithmetic against itself"
461
+ ],
462
+ "example": "if (!uvsTile(uv)) rewriteUvs(uv, transforms.get(source)!);",
463
+ "importPath": "@threenative/assets",
464
+ "kind": "function",
465
+ "overrides": [],
466
+ "package": "@threenative/assets",
467
+ "signature": "export function uvsTile(uv: ArrayLike<number>): boolean { … }",
468
+ "situations": [
469
+ "rewrite a model's texture coordinates after packing its images into an atlas",
470
+ "tell a surface that tiles from one that merely has a repeating sampler"
471
+ ],
472
+ "summary": "Move a mesh's UVs onto its atlas page, and decide from the geometry which meshes may not go.",
473
+ "supersedes": [],
474
+ "symbol": "uvsTile"
475
+ },
268
476
  {
269
477
  "aliases": [],
270
478
  "constraints": [
@@ -284,6 +492,47 @@
284
492
  "supersedes": [],
285
493
  "symbol": "watchAssets"
286
494
  },
495
+ {
496
+ "aliases": [],
497
+ "constraints": [
498
+ "the signature ignores the material name, which is what made every imported part a singleton, and keeps materials apart on any field it does not understand",
499
+ "the census reads geometry and materials only: no GPU, no runtime, no game"
500
+ ],
501
+ "example": "pnpm census:content public/assets",
502
+ "importPath": "@threenative/assets",
503
+ "kind": "function",
504
+ "overrides": [],
505
+ "package": "@threenative/assets",
506
+ "signature": "export function withAtlasTextures( materials: readonly IMaterialState[], pageOf: (texture: string) => string | undefined, ): IMaterialState[] { … }",
507
+ "situations": [
508
+ "decide whether fewer objects is actually available in this content",
509
+ "report why a per-material merge collapsed nothing"
510
+ ],
511
+ "summary": "Collapse materials that became identical once their textures shared an atlas page, and count the buckets a merge would find — before and after — so the promise can be checked rather than made.",
512
+ "supersedes": [],
513
+ "symbol": "withAtlasTextures"
514
+ },
515
+ {
516
+ "aliases": [],
517
+ "constraints": [
518
+ "a surface is tiling when its own UVs leave [0, 1]; glTF's default wrap is REPEAT, so the sampler alone excludes almost everything and is the wrong test",
519
+ "the rewrite is in place, and a buffer that does not hold pairs throws rather than rewriting half a coordinate",
520
+ "`resolveSourceTexel` is the inverse, so a build can round-trip a texel instead of asserting the arithmetic against itself"
521
+ ],
522
+ "example": "if (!uvsTile(uv)) rewriteUvs(uv, transforms.get(source)!);",
523
+ "importPath": "@threenative/assets",
524
+ "kind": "function",
525
+ "overrides": [],
526
+ "package": "@threenative/assets",
527
+ "signature": "export function wrapTiles(wrapS: number | undefined, wrapT: number | undefined): boolean { … }",
528
+ "situations": [
529
+ "rewrite a model's texture coordinates after packing its images into an atlas",
530
+ "tell a surface that tiles from one that merely has a repeating sampler"
531
+ ],
532
+ "summary": "Move a mesh's UVs onto its atlas page, and decide from the geometry which meshes may not go.",
533
+ "supersedes": [],
534
+ "symbol": "wrapTiles"
535
+ },
287
536
  {
288
537
  "aliases": [],
289
538
  "constraints": [
@@ -307,6 +556,49 @@
307
556
  "supersedes": [],
308
557
  "symbol": "addInSlices"
309
558
  },
559
+ {
560
+ "aliases": [],
561
+ "constraints": [
562
+ "off by default and installed by `TN_FRAME_SPANS=1`; unset, every call site is one guarded return",
563
+ "the tree is closed against the frame budget's own render phase, so `TN_FRAME_SPANS` and `TN_FRAME_BUDGET` describe the same frames",
564
+ "measurement only: no span changes what is drawn, in what order, or with which renderer"
565
+ ],
566
+ "example": "if (spansRequested()) setSpanRecorder(new SpanRecorder());",
567
+ "importPath": "@threenative/core",
568
+ "kind": "function",
569
+ "overrides": [],
570
+ "package": "@threenative/core",
571
+ "signature": "export function addSpan(id: SpanId, ms: number): void { … }",
572
+ "situations": [
573
+ "find out what inside the render phase is actually costing the frame",
574
+ "tell a shadow pass's traversal from the main pass's, with the residual computed",
575
+ "price an optimisation against a measured part of the phase rather than the whole of it"
576
+ ],
577
+ "summary": "Attribute the render phase to 100% with a nested span tree, off unless `TN_FRAME_SPANS` asks. Every non-leaf reports its own time minus the spans inside it, so an unmeasured part shows up as a residual instead of being filed under \"other\"; a child that outlives its parent reports a negative residual rather than a clamped zero.",
578
+ "supersedes": [],
579
+ "symbol": "addSpan"
580
+ },
581
+ {
582
+ "aliases": [],
583
+ "constraints": [
584
+ "every mass, area, power and inertia value comes from the game's airframe",
585
+ "damage, stores and configuration arrive as the game's own modifier sample"
586
+ ],
587
+ "example": "const model = new FlightModel({ airframe: sbd, state: aircraft, wind: seaWind });\nmodel.step(1 / 60, { turn: -1, pitch: 0.4 });",
588
+ "importPath": "@threenative/core",
589
+ "kind": "function",
590
+ "overrides": [],
591
+ "package": "@threenative/core",
592
+ "signature": "export function aerodynamicCoefficients( alpha: number, flaps = 0, gear = 0, brakes = 0, ): { … }",
593
+ "situations": [
594
+ "fly an airplane with lift, drag, stall and control authority",
595
+ "launch an aircraft off a moving carrier deck",
596
+ "apply component damage or a loadout to an aircraft's performance"
597
+ ],
598
+ "summary": "Fly a fixed-wing aircraft with a real force balance instead of a steered velocity.",
599
+ "supersedes": [],
600
+ "symbol": "aerodynamicCoefficients"
601
+ },
310
602
  {
311
603
  "aliases": [],
312
604
  "constraints": ["register from a scene context; callbacks are cleared when that scene exits"],
@@ -324,6 +616,74 @@
324
616
  "supersedes": [],
325
617
  "symbol": "afterPhysics"
326
618
  },
619
+ {
620
+ "aliases": [],
621
+ "constraints": [
622
+ "every mass, area, power and inertia value comes from the game's airframe",
623
+ "damage, stores and configuration arrive as the game's own modifier sample"
624
+ ],
625
+ "example": "const model = new FlightModel({ airframe: sbd, state: aircraft, wind: seaWind });\nmodel.step(1 / 60, { turn: -1, pitch: 0.4 });",
626
+ "importPath": "@threenative/core",
627
+ "kind": "function",
628
+ "overrides": [],
629
+ "package": "@threenative/core",
630
+ "signature": "export function aircraftMass(state: IFlightState, airframe: IAircraftAirframe): number { … }",
631
+ "situations": [
632
+ "fly an airplane with lift, drag, stall and control authority",
633
+ "launch an aircraft off a moving carrier deck",
634
+ "apply component damage or a loadout to an aircraft's performance"
635
+ ],
636
+ "summary": "Fly a fixed-wing aircraft with a real force balance instead of a steered velocity.",
637
+ "supersedes": [],
638
+ "symbol": "aircraftMass"
639
+ },
640
+ {
641
+ "aliases": [],
642
+ "constraints": [
643
+ "every mass, area, power and inertia value comes from the game's airframe",
644
+ "damage, stores and configuration arrive as the game's own modifier sample"
645
+ ],
646
+ "example": "const model = new FlightModel({ airframe: sbd, state: aircraft, wind: seaWind });\nmodel.step(1 / 60, { turn: -1, pitch: 0.4 });",
647
+ "importPath": "@threenative/core",
648
+ "kind": "function",
649
+ "overrides": [],
650
+ "package": "@threenative/core",
651
+ "signature": "export function airDensity(y: number): number { … }",
652
+ "situations": [
653
+ "fly an airplane with lift, drag, stall and control authority",
654
+ "launch an aircraft off a moving carrier deck",
655
+ "apply component damage or a loadout to an aircraft's performance"
656
+ ],
657
+ "summary": "Fly a fixed-wing aircraft with a real force balance instead of a steered velocity.",
658
+ "supersedes": [],
659
+ "symbol": "airDensity"
660
+ },
661
+ {
662
+ "aliases": [],
663
+ "constraints": [
664
+ "the marker is per object and is reported as `exemptMarked` in the projection window",
665
+ "`renderer.minimumProjectedPixels: false` leaves the scene drawn and keeps the measurement on",
666
+ "the marker is per object and survives scene rebuilds only as long as the object does",
667
+ "disabling the gate (`renderer.minimumProjectedPixels: false`) keeps its measurement on"
668
+ ],
669
+ "example": "import { alwaysRender } from \"@threenative/core\";\nalwaysRender(ctx.camera.children[0]); // a camera-attached cockpit stays drawn",
670
+ "importPath": "@threenative/core",
671
+ "kind": "function",
672
+ "overrides": [
673
+ "renderer.minimumProjectedPixels sets the projected-pixel threshold, default 0.5"
674
+ ],
675
+ "package": "@threenative/core",
676
+ "signature": "export function alwaysRender(object: Object3D, enabled = true): void { … }",
677
+ "situations": [
678
+ "keep a small object drawn when the engine would skip it as too far to resolve",
679
+ "stop my cockpit, marker or player model popping out at distance",
680
+ "a tiny object disappeared at range and I need it always visible",
681
+ "widen or narrow the projected-size cull with a named threshold"
682
+ ],
683
+ "summary": "Keep an object drawn even when the render camera cannot resolve it. The engine's projected-size gate is on by default: an object whose world bounding sphere projects to fewer than 0.5 raster pixels in the camera about to render it is not submitted, per camera. Mark the player's own cockpit, a nameplate, a quest marker, or anything a game never wants to pop out of the frame. `alwaysRender(object, false)` removes the marker. Camera-attached objects and shadow casters are already kept, and the number of marked objects is reported beside the cull in the `TN_PROJECTION` window rather than hidden. The threshold itself is `renderer.minimumProjectedPixels` — a larger number cuts more aggressively, `false` leaves every object drawn while still measuring.",
684
+ "supersedes": [],
685
+ "symbol": "alwaysRender"
686
+ },
327
687
  {
328
688
  "aliases": [],
329
689
  "constraints": [
@@ -409,6 +769,27 @@
409
769
  "supersedes": [],
410
770
  "symbol": "attachToBone"
411
771
  },
772
+ {
773
+ "aliases": [],
774
+ "constraints": [
775
+ "every mass, area, power and inertia value comes from the game's airframe",
776
+ "damage, stores and configuration arrive as the game's own modifier sample"
777
+ ],
778
+ "example": "const model = new FlightModel({ airframe: sbd, state: aircraft, wind: seaWind });\nmodel.step(1 / 60, { turn: -1, pitch: 0.4 });",
779
+ "importPath": "@threenative/core",
780
+ "kind": "function",
781
+ "overrides": [],
782
+ "package": "@threenative/core",
783
+ "signature": "export function attitudeAxes(state: IFlightState): IFlightAxes { … }",
784
+ "situations": [
785
+ "fly an airplane with lift, drag, stall and control authority",
786
+ "launch an aircraft off a moving carrier deck",
787
+ "apply component damage or a loadout to an aircraft's performance"
788
+ ],
789
+ "summary": "Fly a fixed-wing aircraft with a real force balance instead of a steered velocity.",
790
+ "supersedes": [],
791
+ "symbol": "attitudeAxes"
792
+ },
412
793
  {
413
794
  "aliases": [],
414
795
  "constraints": [
@@ -431,6 +812,44 @@
431
812
  "supersedes": ["new Audio("],
432
813
  "symbol": "AudioBus"
433
814
  },
815
+ {
816
+ "aliases": [],
817
+ "constraints": [],
818
+ "example": "const geometry = baseGeometryOf(mesh);",
819
+ "importPath": "@threenative/core",
820
+ "kind": "function",
821
+ "overrides": [],
822
+ "package": "@threenative/core",
823
+ "signature": "export function baseGeometryOf(mesh: Mesh): BufferGeometry { … }",
824
+ "situations": [
825
+ "collide or ray-test the authored geometry of a mesh whose render detail changes with distance"
826
+ ],
827
+ "summary": "The full-detail geometry of a mesh the loader gave an automatic LOD chain. Selection swaps `mesh.geometry`, so a ray test or a collision body built from the current geometry would change with the camera. Framework picking and gameplay collide against this instead: the authored LOD0, which never changes as the camera moves.",
828
+ "supersedes": [],
829
+ "symbol": "baseGeometryOf"
830
+ },
831
+ {
832
+ "aliases": [],
833
+ "constraints": [
834
+ "off by default and installed by `TN_FRAME_SPANS=1`; unset, every call site is one guarded return",
835
+ "the tree is closed against the frame budget's own render phase, so `TN_FRAME_SPANS` and `TN_FRAME_BUDGET` describe the same frames",
836
+ "measurement only: no span changes what is drawn, in what order, or with which renderer"
837
+ ],
838
+ "example": "if (spansRequested()) setSpanRecorder(new SpanRecorder());",
839
+ "importPath": "@threenative/core",
840
+ "kind": "function",
841
+ "overrides": [],
842
+ "package": "@threenative/core",
843
+ "signature": "export function beginSpan(id: SpanId): void { … }",
844
+ "situations": [
845
+ "find out what inside the render phase is actually costing the frame",
846
+ "tell a shadow pass's traversal from the main pass's, with the residual computed",
847
+ "price an optimisation against a measured part of the phase rather than the whole of it"
848
+ ],
849
+ "summary": "Attribute the render phase to 100% with a nested span tree, off unless `TN_FRAME_SPANS` asks. Every non-leaf reports its own time minus the spans inside it, so an unmeasured part shows up as a residual instead of being filed under \"other\"; a child that outlives its parent reports a negative residual rather than a clamped zero.",
850
+ "supersedes": [],
851
+ "symbol": "beginSpan"
852
+ },
434
853
  {
435
854
  "aliases": [],
436
855
  "constraints": [
@@ -676,6 +1095,27 @@
676
1095
  "supersedes": [],
677
1096
  "symbol": "ComputeDrivenRegistry"
678
1097
  },
1098
+ {
1099
+ "aliases": [],
1100
+ "constraints": [
1101
+ "counts command-encoder and queue methods only; `mapAsync` and the presentation path are named, not folded in",
1102
+ "`gpuBytes` is `queue.writeBuffer` exactly, so it reconciles against a driver; texture uploads are not included",
1103
+ "`jsAllocBytes` needs `performance.memory` and stays absent where the platform lacks it"
1104
+ ],
1105
+ "example": "const counters = FrameCounters.install(counterDeviceOf(renderer.raw));",
1106
+ "importPath": "@threenative/core",
1107
+ "kind": "function",
1108
+ "overrides": [],
1109
+ "package": "@threenative/core",
1110
+ "signature": "export function counterDeviceOf(raw: unknown): unknown { … }",
1111
+ "situations": [
1112
+ "decide whether a CPU-bound frame is paying for the V8-to-host boundary",
1113
+ "measure how many bytes a frame writes into GPU buffers, and how many commands it issues"
1114
+ ],
1115
+ "summary": "Count the frame's host-boundary crossings and the bytes it writes into GPU buffers, off unless `TN_FRAME_SPANS` asks for them. On the frame budget's own window as `counters`, so a crossing count and a millisecond split describe the same frames.",
1116
+ "supersedes": [],
1117
+ "symbol": "counterDeviceOf"
1118
+ },
679
1119
  {
680
1120
  "aliases": ["different props in each area", "first playable screen external assets"],
681
1121
  "constraints": [
@@ -778,6 +1218,48 @@
778
1218
  "supersedes": [],
779
1219
  "symbol": "defineGame"
780
1220
  },
1221
+ {
1222
+ "aliases": [],
1223
+ "constraints": [
1224
+ "the rule is derived from the frame's own numbers; the display period comes from the host's presentation cap or the game's declared target, never a constant",
1225
+ "no verdict without a measured GPU reading and a pass census — an absent measurement is not evidence",
1226
+ "the record carries objects considered, draws per pass, triangles per draw and shadow-exempt casters, so the reader is not dependent on the ranking"
1227
+ ],
1228
+ "example": "defineGame({ display: { maxFps: 60 }, scenes: { Play } });",
1229
+ "importPath": "@threenative/core",
1230
+ "kind": "function",
1231
+ "overrides": [],
1232
+ "package": "@threenative/core",
1233
+ "signature": "export function describeSceneShape( window: IFrameBudgetWindow, cull: IRenderCameraCullReport | undefined, ): ISceneShape | undefined { … }",
1234
+ "situations": [
1235
+ "find out whether a slow frame is the scene's shape or the device",
1236
+ "tell an authoring agent what to reduce before it promises a merge"
1237
+ ],
1238
+ "summary": "Warn the agent that built the scene before a human plays it: a frame whose GPU is idle while its JS render phase is longer than the display's own period is a scene-shape problem, and the engine already has the shape. On by default, printed at most once per reported window as `TN_SCENE_WARNING`, and silent on a scene that is honestly GPU-bound.",
1239
+ "supersedes": [],
1240
+ "symbol": "describeSceneShape"
1241
+ },
1242
+ {
1243
+ "aliases": [],
1244
+ "constraints": [
1245
+ "the rule is derived from the frame's own numbers; the display period comes from the host's presentation cap or the game's declared target, never a constant",
1246
+ "no verdict without a measured GPU reading and a pass census — an absent measurement is not evidence",
1247
+ "the record carries objects considered, draws per pass, triangles per draw and shadow-exempt casters, so the reader is not dependent on the ranking"
1248
+ ],
1249
+ "example": "defineGame({ display: { maxFps: 60 }, scenes: { Play } });",
1250
+ "importPath": "@threenative/core",
1251
+ "kind": "function",
1252
+ "overrides": [],
1253
+ "package": "@threenative/core",
1254
+ "signature": "export function describeSceneWarning(warning: ISceneWarning): string { … }",
1255
+ "situations": [
1256
+ "find out whether a slow frame is the scene's shape or the device",
1257
+ "tell an authoring agent what to reduce before it promises a merge"
1258
+ ],
1259
+ "summary": "Warn the agent that built the scene before a human plays it: a frame whose GPU is idle while its JS render phase is longer than the display's own period is a scene-shape problem, and the engine already has the shape. On by default, printed at most once per reported window as `TN_SCENE_WARNING`, and silent on a scene that is honestly GPU-bound.",
1260
+ "supersedes": [],
1261
+ "symbol": "describeSceneWarning"
1262
+ },
781
1263
  {
782
1264
  "aliases": [],
783
1265
  "constraints": ["pass a non-zero direction; coefficients and radii come from the game"],
@@ -806,6 +1288,49 @@
806
1288
  "supersedes": [],
807
1289
  "symbol": "directionFromSolarPosition"
808
1290
  },
1291
+ {
1292
+ "aliases": [],
1293
+ "constraints": [
1294
+ "the rule is derived from the frame's own numbers; the display period comes from the host's presentation cap or the game's declared target, never a constant",
1295
+ "no verdict without a measured GPU reading and a pass census — an absent measurement is not evidence",
1296
+ "the record carries objects considered, draws per pass, triangles per draw and shadow-exempt casters, so the reader is not dependent on the ranking"
1297
+ ],
1298
+ "example": "defineGame({ display: { maxFps: 60 }, scenes: { Play } });",
1299
+ "importPath": "@threenative/core",
1300
+ "kind": "function",
1301
+ "overrides": [],
1302
+ "package": "@threenative/core",
1303
+ "signature": "export function displayPeriodMs( declaredTargetFps: number | undefined, ): { … }",
1304
+ "situations": [
1305
+ "find out whether a slow frame is the scene's shape or the device",
1306
+ "tell an authoring agent what to reduce before it promises a merge"
1307
+ ],
1308
+ "summary": "Warn the agent that built the scene before a human plays it: a frame whose GPU is idle while its JS render phase is longer than the display's own period is a scene-shape problem, and the engine already has the shape. On by default, printed at most once per reported window as `TN_SCENE_WARNING`, and silent on a scene that is honestly GPU-bound.",
1309
+ "supersedes": [],
1310
+ "symbol": "displayPeriodMs"
1311
+ },
1312
+ {
1313
+ "aliases": [],
1314
+ "constraints": [
1315
+ "off by default and installed by `TN_FRAME_SPANS=1`; unset, every call site is one guarded return",
1316
+ "the tree is closed against the frame budget's own render phase, so `TN_FRAME_SPANS` and `TN_FRAME_BUDGET` describe the same frames",
1317
+ "measurement only: no span changes what is drawn, in what order, or with which renderer"
1318
+ ],
1319
+ "example": "if (spansRequested()) setSpanRecorder(new SpanRecorder());",
1320
+ "importPath": "@threenative/core",
1321
+ "kind": "function",
1322
+ "overrides": [],
1323
+ "package": "@threenative/core",
1324
+ "signature": "export function endSpan(id: SpanId): void { … }",
1325
+ "situations": [
1326
+ "find out what inside the render phase is actually costing the frame",
1327
+ "tell a shadow pass's traversal from the main pass's, with the residual computed",
1328
+ "price an optimisation against a measured part of the phase rather than the whole of it"
1329
+ ],
1330
+ "summary": "Attribute the render phase to 100% with a nested span tree, off unless `TN_FRAME_SPANS` asks. Every non-leaf reports its own time minus the spans inside it, so an unmeasured part shows up as a residual instead of being filed under \"other\"; a child that outlives its parent reports a negative residual rather than a clamped zero.",
1331
+ "supersedes": [],
1332
+ "symbol": "endSpan"
1333
+ },
809
1334
  {
810
1335
  "aliases": [],
811
1336
  "constraints": [
@@ -828,6 +1353,27 @@
828
1353
  "supersedes": [],
829
1354
  "symbol": "ensureVelocityOutput"
830
1355
  },
1356
+ {
1357
+ "aliases": [],
1358
+ "constraints": [
1359
+ "every mass, area, power and inertia value comes from the game's airframe",
1360
+ "damage, stores and configuration arrive as the game's own modifier sample"
1361
+ ],
1362
+ "example": "const model = new FlightModel({ airframe: sbd, state: aircraft, wind: seaWind });\nmodel.step(1 / 60, { turn: -1, pitch: 0.4 });",
1363
+ "importPath": "@threenative/core",
1364
+ "kind": "class",
1365
+ "overrides": [],
1366
+ "package": "@threenative/core",
1367
+ "signature": "export class FlightModel<TState extends IFlightState = IFlightState> { … }",
1368
+ "situations": [
1369
+ "fly an airplane with lift, drag, stall and control authority",
1370
+ "launch an aircraft off a moving carrier deck",
1371
+ "apply component damage or a loadout to an aircraft's performance"
1372
+ ],
1373
+ "summary": "Fly a fixed-wing aircraft with a real force balance instead of a steered velocity.",
1374
+ "supersedes": [],
1375
+ "symbol": "FlightModel"
1376
+ },
831
1377
  {
832
1378
  "aliases": [],
833
1379
  "constraints": [
@@ -857,7 +1403,73 @@
857
1403
  {
858
1404
  "aliases": [],
859
1405
  "constraints": [
860
- "on by default and printed as TN_FRAME_BUDGET; defineGame({ frameBudget: false }) silences the marker, not the measurement"
1406
+ "the rule is derived from the frame's own numbers; the display period comes from the host's presentation cap or the game's declared target, never a constant",
1407
+ "no verdict without a measured GPU reading and a pass census — an absent measurement is not evidence",
1408
+ "the record carries objects considered, draws per pass, triangles per draw and shadow-exempt casters, so the reader is not dependent on the ranking"
1409
+ ],
1410
+ "example": "defineGame({ display: { maxFps: 60 }, scenes: { Play } });",
1411
+ "importPath": "@threenative/core",
1412
+ "kind": "function",
1413
+ "overrides": [],
1414
+ "package": "@threenative/core",
1415
+ "signature": "export function formatSceneWarning(warning: ISceneWarning): string { … }",
1416
+ "situations": [
1417
+ "find out whether a slow frame is the scene's shape or the device",
1418
+ "tell an authoring agent what to reduce before it promises a merge"
1419
+ ],
1420
+ "summary": "Warn the agent that built the scene before a human plays it: a frame whose GPU is idle while its JS render phase is longer than the display's own period is a scene-shape problem, and the engine already has the shape. On by default, printed at most once per reported window as `TN_SCENE_WARNING`, and silent on a scene that is honestly GPU-bound.",
1421
+ "supersedes": [],
1422
+ "symbol": "formatSceneWarning"
1423
+ },
1424
+ {
1425
+ "aliases": [],
1426
+ "constraints": [
1427
+ "off by default and installed by `TN_FRAME_SPANS=1`; unset, every call site is one guarded return",
1428
+ "the tree is closed against the frame budget's own render phase, so `TN_FRAME_SPANS` and `TN_FRAME_BUDGET` describe the same frames",
1429
+ "measurement only: no span changes what is drawn, in what order, or with which renderer"
1430
+ ],
1431
+ "example": "if (spansRequested()) setSpanRecorder(new SpanRecorder());",
1432
+ "importPath": "@threenative/core",
1433
+ "kind": "function",
1434
+ "overrides": [],
1435
+ "package": "@threenative/core",
1436
+ "signature": "export function formatSpansWindow(window: ISpanWindow): string { … }",
1437
+ "situations": [
1438
+ "find out what inside the render phase is actually costing the frame",
1439
+ "tell a shadow pass's traversal from the main pass's, with the residual computed",
1440
+ "price an optimisation against a measured part of the phase rather than the whole of it"
1441
+ ],
1442
+ "summary": "Attribute the render phase to 100% with a nested span tree, off unless `TN_FRAME_SPANS` asks. Every non-leaf reports its own time minus the spans inside it, so an unmeasured part shows up as a residual instead of being filed under \"other\"; a child that outlives its parent reports a negative residual rather than a clamped zero.",
1443
+ "supersedes": [],
1444
+ "symbol": "formatSpansWindow"
1445
+ },
1446
+ {
1447
+ "aliases": [],
1448
+ "constraints": [
1449
+ "off by default and expensive by construction — it does the work it is checking, twice",
1450
+ "it throws on the first divergence rather than logging; a validation mode that continues is one nobody reads",
1451
+ "it proves the frames it ran on and nothing else"
1452
+ ],
1453
+ "example": "if (renderListValidationRequested()) console.log(formatValidationReport(report));",
1454
+ "importPath": "@threenative/core",
1455
+ "kind": "function",
1456
+ "overrides": [],
1457
+ "package": "@threenative/core",
1458
+ "signature": "export function formatValidationReport(report: IValidationReport): string { … }",
1459
+ "situations": [
1460
+ "prove a static freeze did not leave a stale transform on screen",
1461
+ "gate a scene in CI against silent transform divergence"
1462
+ ],
1463
+ "summary": "Prove that nothing a cache skipped changed the picture: `TN_RENDERLIST_VALIDATE=1` recomputes every world matrix the long way, every frame, and throws on the first element that disagrees with what the frame is about to draw.",
1464
+ "supersedes": [],
1465
+ "symbol": "formatValidationReport"
1466
+ },
1467
+ {
1468
+ "aliases": [],
1469
+ "constraints": [
1470
+ "on by default and printed as TN_FRAME_BUDGET; defineGame({ frameBudget: false }) silences the marker, not the measurement",
1471
+ "per-pass numbers are attributed to the innermost active render call, so nested shadow and reflection passes do not read as main",
1472
+ "GPU is a mean/p50/p95/max series over resolved frames (`gpu`) with `gpuStale` counting frames that had no fresh reading; absent means no timestamps, never zero"
861
1473
  ],
862
1474
  "example": "defineGame({ frameBudget: { reportEvery: 120 }, scenes: { Play } });",
863
1475
  "importPath": "@threenative/core",
@@ -867,12 +1479,57 @@
867
1479
  "signature": "export class FrameBudget { … }",
868
1480
  "situations": [
869
1481
  "find out why a game runs slowly on a phone",
870
- "attribute a frame to present wait, simulation, three.js render, or overlay"
1482
+ "attribute a frame to present wait, simulation, three.js render, or overlay",
1483
+ "tell whether the GPU is the frame's constraint from a per-frame series, not one lagged timestamp",
1484
+ "split a frame's draw calls and triangles per render pass (main, shadow, reflection)",
1485
+ "tell a shadow or reflection pass's cost from the main colour pass"
871
1486
  ],
872
- "summary": "Read where the frame's milliseconds went, per presented frame, on any platform.",
1487
+ "summary": "Read where the frame's milliseconds went, per presented frame, on any platform; each `TN_FRAME_BUDGET` window also carries the GPU time per resolved frame and the draw calls and triangles each render pass submitted.",
873
1488
  "supersedes": [],
874
1489
  "symbol": "FrameBudget"
875
1490
  },
1491
+ {
1492
+ "aliases": [],
1493
+ "constraints": [
1494
+ "counts command-encoder and queue methods only; `mapAsync` and the presentation path are named, not folded in",
1495
+ "`gpuBytes` is `queue.writeBuffer` exactly, so it reconciles against a driver; texture uploads are not included",
1496
+ "`jsAllocBytes` needs `performance.memory` and stays absent where the platform lacks it"
1497
+ ],
1498
+ "example": "const counters = FrameCounters.install(counterDeviceOf(renderer.raw));",
1499
+ "importPath": "@threenative/core",
1500
+ "kind": "class",
1501
+ "overrides": [],
1502
+ "package": "@threenative/core",
1503
+ "signature": "export class FrameCounters { … }",
1504
+ "situations": [
1505
+ "decide whether a CPU-bound frame is paying for the V8-to-host boundary",
1506
+ "measure how many bytes a frame writes into GPU buffers, and how many commands it issues"
1507
+ ],
1508
+ "summary": "Count the frame's host-boundary crossings and the bytes it writes into GPU buffers, off unless `TN_FRAME_SPANS` asks for them. On the frame budget's own window as `counters`, so a crossing count and a millisecond split describe the same frames.",
1509
+ "supersedes": [],
1510
+ "symbol": "FrameCounters"
1511
+ },
1512
+ {
1513
+ "aliases": [],
1514
+ "constraints": [
1515
+ "every mass, area, power and inertia value comes from the game's airframe",
1516
+ "damage, stores and configuration arrive as the game's own modifier sample"
1517
+ ],
1518
+ "example": "const model = new FlightModel({ airframe: sbd, state: aircraft, wind: seaWind });\nmodel.step(1 / 60, { turn: -1, pitch: 0.4 });",
1519
+ "importPath": "@threenative/core",
1520
+ "kind": "function",
1521
+ "overrides": [],
1522
+ "package": "@threenative/core",
1523
+ "signature": "export function gearClearance(state: IFlightState): number { … }",
1524
+ "situations": [
1525
+ "fly an airplane with lift, drag, stall and control authority",
1526
+ "launch an aircraft off a moving carrier deck",
1527
+ "apply component damage or a loadout to an aircraft's performance"
1528
+ ],
1529
+ "summary": "Fly a fixed-wing aircraft with a real force balance instead of a steered velocity.",
1530
+ "supersedes": [],
1531
+ "symbol": "gearClearance"
1532
+ },
876
1533
  {
877
1534
  "aliases": [],
878
1535
  "constraints": [
@@ -978,6 +1635,27 @@
978
1635
  "supersedes": [],
979
1636
  "symbol": "GroundSnap"
980
1637
  },
1638
+ {
1639
+ "aliases": [],
1640
+ "constraints": [
1641
+ "returns an uninstall that restores every wrapper, asserted by test",
1642
+ "a renderer whose internals have moved loses that span rather than throwing, and it is absent from the report rather than zero",
1643
+ "only the outermost `_projectObject` opens a span, because three's recurses per child"
1644
+ ],
1645
+ "example": "const uninstall = installSpanProbes(renderer.raw, scene);",
1646
+ "importPath": "@threenative/core",
1647
+ "kind": "function",
1648
+ "overrides": [],
1649
+ "package": "@threenative/core",
1650
+ "signature": "export function installSpanProbes(target: ISpanProbeTarget, root: Object3D): () => void { … }",
1651
+ "situations": [
1652
+ "measure which part of three's render path costs the frame",
1653
+ "attach the span tree to a renderer a test or a tool constructed itself"
1654
+ ],
1655
+ "summary": "Attach the span probes to a renderer: three's `render`, `_projectObject`, the render list's `sort`, the per-draw submission, and the scene-graph walk. Installed for you when `TN_FRAME_SPANS` asks; exported so a harness can wrap a renderer it owns.",
1656
+ "supersedes": [],
1657
+ "symbol": "installSpanProbes"
1658
+ },
981
1659
  {
982
1660
  "aliases": ["landmarks points of interest", "obstacles collectibles increasing pace"],
983
1661
  "constraints": [
@@ -1003,6 +1681,27 @@
1003
1681
  "supersedes": [],
1004
1682
  "symbol": "InstancedBatch"
1005
1683
  },
1684
+ {
1685
+ "aliases": [],
1686
+ "constraints": [
1687
+ "staticness is authored, never guessed; no heuristic watches gameplay and decides for you",
1688
+ "it deletes the local and world matrix composes, not the walk itself — three 0.185 recurses into every child regardless",
1689
+ "a write deeper inside a frozen subtree must call `invalidateStatic`; `TN_RENDERLIST_VALIDATE=1` is what proves it happened"
1690
+ ],
1691
+ "example": "markStatic(island); invalidateStatic(drawbridge);",
1692
+ "importPath": "@threenative/core",
1693
+ "kind": "function",
1694
+ "overrides": [],
1695
+ "package": "@threenative/core",
1696
+ "signature": "export function invalidateStatic(object: Object3D): number | undefined { … }",
1697
+ "situations": [
1698
+ "cut the per-frame matrix work of terrain, buildings, props and other scenery that never moves",
1699
+ "keep a frozen subtree correct when the game does move it after all"
1700
+ ],
1701
+ "summary": "Stop recomposing the transforms of a subtree nobody moves. `markStatic(root)` composes the subtree once and freezes it; the engine re-arms a root whose own transform the game changes, and `invalidateStatic(object)` announces a write deeper inside one.",
1702
+ "supersedes": [],
1703
+ "symbol": "invalidateStatic"
1704
+ },
1006
1705
  {
1007
1706
  "aliases": [],
1008
1707
  "constraints": [
@@ -1045,6 +1744,27 @@
1045
1744
  "supersedes": [],
1046
1745
  "symbol": "isNative"
1047
1746
  },
1747
+ {
1748
+ "aliases": [],
1749
+ "constraints": [
1750
+ "staticness is authored, never guessed; no heuristic watches gameplay and decides for you",
1751
+ "it deletes the local and world matrix composes, not the walk itself — three 0.185 recurses into every child regardless",
1752
+ "a write deeper inside a frozen subtree must call `invalidateStatic`; `TN_RENDERLIST_VALIDATE=1` is what proves it happened"
1753
+ ],
1754
+ "example": "markStatic(island); invalidateStatic(drawbridge);",
1755
+ "importPath": "@threenative/core",
1756
+ "kind": "function",
1757
+ "overrides": [],
1758
+ "package": "@threenative/core",
1759
+ "signature": "export function isStatic(root: Object3D): boolean { … }",
1760
+ "situations": [
1761
+ "cut the per-frame matrix work of terrain, buildings, props and other scenery that never moves",
1762
+ "keep a frozen subtree correct when the game does move it after all"
1763
+ ],
1764
+ "summary": "Stop recomposing the transforms of a subtree nobody moves. `markStatic(root)` composes the subtree once and freezes it; the engine re-arms a root whose own transform the game changes, and `invalidateStatic(object)` announces a write deeper inside one.",
1765
+ "supersedes": [],
1766
+ "symbol": "isStatic"
1767
+ },
1048
1768
  {
1049
1769
  "aliases": [],
1050
1770
  "constraints": [
@@ -1109,6 +1829,50 @@
1109
1829
  "supersedes": [],
1110
1830
  "symbol": "loadAll"
1111
1831
  },
1832
+ {
1833
+ "aliases": [],
1834
+ "constraints": [
1835
+ "staticness is authored, never guessed; no heuristic watches gameplay and decides for you",
1836
+ "it deletes the local and world matrix composes, not the walk itself — three 0.185 recurses into every child regardless",
1837
+ "a write deeper inside a frozen subtree must call `invalidateStatic`; `TN_RENDERLIST_VALIDATE=1` is what proves it happened"
1838
+ ],
1839
+ "example": "markStatic(island); invalidateStatic(drawbridge);",
1840
+ "importPath": "@threenative/core",
1841
+ "kind": "function",
1842
+ "overrides": [],
1843
+ "package": "@threenative/core",
1844
+ "signature": "export function markStatic(root: Object3D): number { … }",
1845
+ "situations": [
1846
+ "cut the per-frame matrix work of terrain, buildings, props and other scenery that never moves",
1847
+ "keep a frozen subtree correct when the game does move it after all"
1848
+ ],
1849
+ "summary": "Stop recomposing the transforms of a subtree nobody moves. `markStatic(root)` composes the subtree once and freezes it; the engine re-arms a root whose own transform the game changes, and `invalidateStatic(object)` announces a write deeper inside one.",
1850
+ "supersedes": [],
1851
+ "symbol": "markStatic"
1852
+ },
1853
+ {
1854
+ "aliases": [],
1855
+ "constraints": [
1856
+ "a game that reads a hidden object's matrixWorld directly must use getWorldPosition or updateWorldMatrix(true, false) first",
1857
+ "`renderer.matrixWorld: \"all\"` visits every node; `TN_PROJECTION` reports the visited count either way"
1858
+ ],
1859
+ "example": "import { MatrixWorldPass } from \"@threenative/core\";\nconst pass = new MatrixWorldPass(); // renderer.matrixWorld defaults to \"visible\"",
1860
+ "importPath": "@threenative/core",
1861
+ "kind": "class",
1862
+ "overrides": [
1863
+ "renderer.matrixWorld: \"all\" runs three's full walk instead of the visible-only default"
1864
+ ],
1865
+ "package": "@threenative/core",
1866
+ "signature": "export class MatrixWorldPass { … }",
1867
+ "situations": [
1868
+ "the per-frame world matrix walk is hot in a profile",
1869
+ "stop multiplying matrices for hidden models, LOD levels and merged stand-ins",
1870
+ "a game needs every node walked, exactly as three's own updateMatrixWorld does"
1871
+ ],
1872
+ "summary": "Walk the scene graph's world matrices each frame without recursing into a hidden subtree. On by default as `renderer.matrixWorld: \"visible\"`. three's `updateMatrixWorld` recurses into every child whatever its `visible` flag, so a hidden LOD body, a merged stand-in and a parked model cost a world-matrix multiply each while nothing under them can draw. This pass mirrors three exactly for every visible node and defers a hidden node's subtree until the frame it shows again. A class that overrides `updateMatrixWorld` (`SkinnedMesh`, `Camera`) runs its own, and a hidden node that holds bones is walked, so no skeleton or view matrix goes stale.",
1873
+ "supersedes": [],
1874
+ "symbol": "MatrixWorldPass"
1875
+ },
1112
1876
  {
1113
1877
  "aliases": [],
1114
1878
  "constraints": ["precise per-vertex measurement is opt-in and not for frame loops"],
@@ -1126,24 +1890,30 @@
1126
1890
  {
1127
1891
  "aliases": [],
1128
1892
  "constraints": [
1129
- "every part is de-indexed and stripped to position, so UVs and authored normals do not survive; normals are recomputed from the merged buffer",
1893
+ "every part is de-indexed; without preserve it is stripped to position and normals are recomputed from the merged buffer",
1894
+ "preserve keeps the listed channels, transforming position and normal by the part's placement matrix while UV values are retained unchanged",
1895
+ "a part that does not carry a listed preserve channel throws naming the label, the part and the channel",
1130
1896
  "either every part names a color or none does, and a mix throws",
1131
1897
  "an empty part list throws, and a merge three.js refuses throws naming the label"
1132
1898
  ],
1133
- "example": "const wall = new Mesh(mergeParts(pieces, { label: \"gatehouse\" }), stone);\n// pieces are meshes, or { geometry, matrix, color } when the colour is per piece:\nconst banner = mergeParts([{ color: 0x8b2f1a, geometry: cloth, matrix: placement }], { label: \"banner\" });",
1899
+ "example": "const wall = new Mesh(mergeParts(pieces, { label: \"gatehouse\" }), stone);\n// pieces are meshes, or { geometry, matrix, color } when the colour is per piece:\nconst banner = mergeParts([{ color: 0x8b2f1a, geometry: cloth, matrix: placement }], { label: \"banner\" });\n// keep a model's texture UVs and authored normals while baking its transforms:\nconst hull = mergeParts(hullParts, { label: \"hull\", preserve: [\"uv\", \"normal\"] });",
1134
1900
  "importPath": "@threenative/core",
1135
1901
  "kind": "function",
1136
1902
  "overrides": [
1137
- "color is per part and optional; without it no colour attribute is written and the surface alone decides"
1903
+ "color is per part and optional; without it no colour attribute is written and the surface alone decides",
1904
+ "preserve is optional and empty by default: position-only merge with recomputed normals, exactly as before"
1138
1905
  ],
1139
1906
  "package": "@threenative/core",
1140
1907
  "signature": "export function mergeParts( parts: Iterable<IMergePart>, options: IMergePartsOptions, ): BufferGeometry { … }",
1141
1908
  "situations": [
1142
1909
  "bake a building, ship or character authored out of primitives into one draw call",
1910
+ "merge multiple static Three.js meshes into one mesh per material",
1911
+ "consolidate the static parts of an imported glTF model into one buffer",
1912
+ "preserve texture UV coordinates and authored normals while baking object transforms",
1143
1913
  "merge many small geometries and keep each piece's own colour",
1144
1914
  "stop mergeGeometries from silently returning null on an extruded shape"
1145
1915
  ],
1146
- "summary": "Merge pieces a game authored out of primitives into one buffer, keeping each piece's own colour. `InstancedBatch` collapses many copies of one shape; this collapses many *different* shapes that never move relative to each other — a building, a ship, a character built from boxes. Two things go wrong every time and neither is about how any of it looks. `mergeGeometries` hands back `null` on mismatched inputs instead of throwing, and the usual mismatch is invisible: one `ExtrudeGeometry` is non-indexed while every other primitive is indexed, so the merge fails at the first piece and the scene never loads. And a merged buffer draws with one surface, so per-piece colour is gone unless every piece carries a flat `color` attribute written before the merge. Both are mechanical. Geometry, placement, colour and the surface it draws with all stay the game's.",
1916
+ "summary": "Merge pieces a game authored out of primitives into one buffer, keeping each piece's own look. `InstancedBatch` collapses many copies of one shape; this collapses many *different* shapes that never move relative to each other — a building, a ship, a character built from boxes, or the static parts of an imported glTF model. Two things go wrong every time and neither is about how any of it looks. `mergeGeometries` hands back `null` on mismatched inputs instead of throwing, and the usual mismatch is invisible: one `ExtrudeGeometry` is non-indexed while every other primitive is indexed, so the merge fails at the first piece and the scene never loads. And a merged buffer draws with one surface, so per-piece colour is gone unless every piece carries a flat `color` attribute written before the merge. Both are mechanical. By default every part is stripped to position and the normals are recomputed from the merged buffer; pass `preserve` to keep authored normals and texture UVs while baking each part's object transform. Geometry, placement, colour and the surface it draws with all stay the game's.",
1147
1917
  "supersedes": [],
1148
1918
  "symbol": "mergeParts"
1149
1919
  },
@@ -1166,6 +1936,23 @@
1166
1936
  "supersedes": ["new Box3().setFromObject("],
1167
1937
  "symbol": "normaliseToMetres"
1168
1938
  },
1939
+ {
1940
+ "aliases": [],
1941
+ "constraints": [],
1942
+ "example": "const off = onLaunchFailure((failure) => shell.loading({ failure: failure.message }));",
1943
+ "importPath": "@threenative/core",
1944
+ "kind": "function",
1945
+ "overrides": [],
1946
+ "package": "@threenative/core",
1947
+ "signature": "export function onLaunchFailure(listener: (failure: ILaunchFailure) => void): () => void { … }",
1948
+ "situations": [
1949
+ "show the player why the game stopped loading instead of leaving the loading screen up",
1950
+ "report a stalled launch or a lost GPU device in the game's own UI"
1951
+ ],
1952
+ "summary": "Called for every launch failure the engine notices, with the message to show the player.",
1953
+ "supersedes": [],
1954
+ "symbol": "onLaunchFailure"
1955
+ },
1169
1956
  {
1170
1957
  "aliases": [],
1171
1958
  "constraints": ["recordings are version 1; the parser fails closed with TN_REPLAY_* codes"],
@@ -1453,26 +2240,89 @@
1453
2240
  "supersedes": [],
1454
2241
  "symbol": "reconcileMirroredClips"
1455
2242
  },
2243
+ {
2244
+ "aliases": [],
2245
+ "constraints": [
2246
+ "staticness is authored, never guessed; no heuristic watches gameplay and decides for you",
2247
+ "it deletes the local and world matrix composes, not the walk itself — three 0.185 recurses into every child regardless",
2248
+ "a write deeper inside a frozen subtree must call `invalidateStatic`; `TN_RENDERLIST_VALIDATE=1` is what proves it happened"
2249
+ ],
2250
+ "example": "markStatic(island); invalidateStatic(drawbridge);",
2251
+ "importPath": "@threenative/core",
2252
+ "kind": "function",
2253
+ "overrides": [],
2254
+ "package": "@threenative/core",
2255
+ "signature": "export function refreshStaticTransforms(): void { … }",
2256
+ "situations": [
2257
+ "cut the per-frame matrix work of terrain, buildings, props and other scenery that never moves",
2258
+ "keep a frozen subtree correct when the game does move it after all"
2259
+ ],
2260
+ "summary": "Stop recomposing the transforms of a subtree nobody moves. `markStatic(root)` composes the subtree once and freezes it; the engine re-arms a root whose own transform the game changes, and `invalidateStatic(object)` announces a write deeper inside one.",
2261
+ "supersedes": [],
2262
+ "symbol": "refreshStaticTransforms"
2263
+ },
1456
2264
  {
1457
2265
  "aliases": [],
1458
2266
  "constraints": [
1459
2267
  "stage factories own colour, strength, and all other appearance choices",
1460
2268
  "authored stages declare exactly one before or after anchor"
1461
2269
  ],
1462
- "example": "const chain = new RenderChain(renderer, { input: colour, stages, request: { stages: [\"bloom\"], tier: \"auto\" } });",
2270
+ "example": "const chain = new RenderChain(renderer, { input: colour, stages, request: { stages: [\"bloom\"], tier: \"auto\" } });",
2271
+ "importPath": "@threenative/core",
2272
+ "kind": "class",
2273
+ "overrides": [],
2274
+ "package": "@threenative/core",
2275
+ "signature": "export class RenderChain { … }",
2276
+ "situations": [
2277
+ "compose screen-space effects in a canonical order",
2278
+ "insert a game-authored post stage into the measured render chain",
2279
+ "report which render tier and velocity route actually ran"
2280
+ ],
2281
+ "summary": "Compose game-provided render nodes in a measured, fail-closed chain. Authored stages use an opaque id and anchor before or after a built-in or another supplied stage; the engine does not need to know the effect's visual vocabulary.",
2282
+ "supersedes": [],
2283
+ "symbol": "RenderChain"
2284
+ },
2285
+ {
2286
+ "aliases": [],
2287
+ "constraints": [
2288
+ "off by default and expensive by construction — it does the work it is checking, twice",
2289
+ "it throws on the first divergence rather than logging; a validation mode that continues is one nobody reads",
2290
+ "it proves the frames it ran on and nothing else"
2291
+ ],
2292
+ "example": "if (renderListValidationRequested()) console.log(formatValidationReport(report));",
2293
+ "importPath": "@threenative/core",
2294
+ "kind": "function",
2295
+ "overrides": [],
2296
+ "package": "@threenative/core",
2297
+ "signature": "export function renderListValidationRequested(): boolean { … }",
2298
+ "situations": [
2299
+ "prove a static freeze did not leave a stale transform on screen",
2300
+ "gate a scene in CI against silent transform divergence"
2301
+ ],
2302
+ "summary": "Prove that nothing a cache skipped changed the picture: `TN_RENDERLIST_VALIDATE=1` recomputes every world matrix the long way, every frame, and throws on the first element that disagrees with what the frame is about to draw.",
2303
+ "supersedes": [],
2304
+ "symbol": "renderListValidationRequested"
2305
+ },
2306
+ {
2307
+ "aliases": [],
2308
+ "constraints": [
2309
+ "off by default and expensive by construction — it does the work it is checking, twice",
2310
+ "it throws on the first divergence rather than logging; a validation mode that continues is one nobody reads",
2311
+ "it proves the frames it ran on and nothing else"
2312
+ ],
2313
+ "example": "if (renderListValidationRequested()) console.log(formatValidationReport(report));",
1463
2314
  "importPath": "@threenative/core",
1464
2315
  "kind": "class",
1465
2316
  "overrides": [],
1466
2317
  "package": "@threenative/core",
1467
- "signature": "export class RenderChain { … }",
2318
+ "signature": "export class RenderListValidator { … }",
1468
2319
  "situations": [
1469
- "compose screen-space effects in a canonical order",
1470
- "insert a game-authored post stage into the measured render chain",
1471
- "report which render tier and velocity route actually ran"
2320
+ "prove a static freeze did not leave a stale transform on screen",
2321
+ "gate a scene in CI against silent transform divergence"
1472
2322
  ],
1473
- "summary": "Compose game-provided render nodes in a measured, fail-closed chain. Authored stages use an opaque id and anchor before or after a built-in or another supplied stage; the engine does not need to know the effect's visual vocabulary.",
2323
+ "summary": "Prove that nothing a cache skipped changed the picture: `TN_RENDERLIST_VALIDATE=1` recomputes every world matrix the long way, every frame, and throws on the first element that disagrees with what the frame is about to draw.",
1474
2324
  "supersedes": [],
1475
- "symbol": "RenderChain"
2325
+ "symbol": "RenderListValidator"
1476
2326
  },
1477
2327
  {
1478
2328
  "aliases": ["fixed seed fixed-step simulation"],
@@ -1493,6 +2343,43 @@
1493
2343
  "supersedes": [],
1494
2344
  "symbol": "replay"
1495
2345
  },
2346
+ {
2347
+ "aliases": [],
2348
+ "constraints": [],
2349
+ "example": "resetAudioCueLedger();",
2350
+ "importPath": "@threenative/core",
2351
+ "kind": "function",
2352
+ "overrides": [],
2353
+ "package": "@threenative/core",
2354
+ "signature": "export function resetAudioCueLedger(): void { … }",
2355
+ "situations": [
2356
+ "clear the recorded audio cue counts between tests so one test cannot read another's plays"
2357
+ ],
2358
+ "summary": "Forgets every recorded cue.",
2359
+ "supersedes": [],
2360
+ "symbol": "resetAudioCueLedger"
2361
+ },
2362
+ {
2363
+ "aliases": [],
2364
+ "constraints": [
2365
+ "staticness is authored, never guessed; no heuristic watches gameplay and decides for you",
2366
+ "it deletes the local and world matrix composes, not the walk itself — three 0.185 recurses into every child regardless",
2367
+ "a write deeper inside a frozen subtree must call `invalidateStatic`; `TN_RENDERLIST_VALIDATE=1` is what proves it happened"
2368
+ ],
2369
+ "example": "markStatic(island); invalidateStatic(drawbridge);",
2370
+ "importPath": "@threenative/core",
2371
+ "kind": "function",
2372
+ "overrides": [],
2373
+ "package": "@threenative/core",
2374
+ "signature": "export function resetStaticTransforms(): void { … }",
2375
+ "situations": [
2376
+ "cut the per-frame matrix work of terrain, buildings, props and other scenery that never moves",
2377
+ "keep a frozen subtree correct when the game does move it after all"
2378
+ ],
2379
+ "summary": "Stop recomposing the transforms of a subtree nobody moves. `markStatic(root)` composes the subtree once and freezes it; the engine re-arms a root whose own transform the game changes, and `invalidateStatic(object)` announces a write deeper inside one.",
2380
+ "supersedes": [],
2381
+ "symbol": "resetStaticTransforms"
2382
+ },
1496
2383
  {
1497
2384
  "aliases": [],
1498
2385
  "constraints": [
@@ -1525,10 +2412,37 @@
1525
2412
  "supersedes": [],
1526
2413
  "symbol": "resolveAtmosphereParameters"
1527
2414
  },
2415
+ {
2416
+ "aliases": [],
2417
+ "constraints": [
2418
+ "it draws nothing; the game supplies the mesh, the material and every colour",
2419
+ "add its height to an analytic swell, never in place of one",
2420
+ "the patch is finite and its rim absorbs; call recenter to keep it over the action",
2421
+ "there is no obstacle mask, because a mask is only correct for a body that never moves"
2422
+ ],
2423
+ "example": "const ripples = new RippleField({ resolution: 128, size: 400 });\nripples.impulse(hit.x, hit.z, 6, -40, 0.5);\nripples.advance(dt);\nconst lift = ripples.heightAt(boat.x, boat.z);",
2424
+ "importPath": "@threenative/core",
2425
+ "kind": "class",
2426
+ "overrides": [
2427
+ "speed, damping, foamHalfLife, current, step and maxSteps tune the solve; the default"
2428
+ ],
2429
+ "package": "@threenative/core",
2430
+ "signature": "export class RippleField { … }",
2431
+ "situations": [
2432
+ "make a splash or explosion ripple outward across water",
2433
+ "show the sea reacting to a bomb, shell, or torpedo hitting it",
2434
+ "leave a foam trail behind something moving through water",
2435
+ "disturb a water surface the player can see respond",
2436
+ "spread and drift foam on a water surface over time"
2437
+ ],
2438
+ "summary": "Propagate a disturbance across a patch of water surface and let it fade. step is 1/60s or the CFL stability limit for the given resolution and speed, whichever is smaller",
2439
+ "supersedes": [],
2440
+ "symbol": "RippleField"
2441
+ },
1528
2442
  {
1529
2443
  "aliases": [],
1530
2444
  "constraints": ["scene code must stay portable across web and native"],
1531
- "example": "class Play extends Scene { update(ctx, dt) {} }",
2445
+ "example": "class Play extends Scene { update(ctx, dt) {} }\nctx.beforeRender(() => packBatches()); // cleared on scene change and stop, like ctx.afterPhysics",
1532
2446
  "importPath": "@threenative/core",
1533
2447
  "kind": "class",
1534
2448
  "overrides": [],
@@ -1536,7 +2450,8 @@
1536
2450
  "signature": "export abstract class Scene< TState extends Record<string, unknown> = Record<string, unknown>, TPhysics = undefined, > { … }",
1537
2451
  "situations": [
1538
2452
  "add a playable level or menu scene",
1539
- "move scene setup and per-frame gameplay out of the entry point"
2453
+ "move scene setup and per-frame gameplay out of the entry point",
2454
+ "run scene work once per actual world draw, after the frame's last fixed update and before the projection packs"
1540
2455
  ],
1541
2456
  "summary": "Implement a portable Godot-shaped game scene lifecycle.",
1542
2457
  "supersedes": [],
@@ -1559,6 +2474,27 @@
1559
2474
  "supersedes": ["new Raycaster("],
1560
2475
  "symbol": "ScenePicker"
1561
2476
  },
2477
+ {
2478
+ "aliases": [],
2479
+ "constraints": [
2480
+ "the rule is derived from the frame's own numbers; the display period comes from the host's presentation cap or the game's declared target, never a constant",
2481
+ "no verdict without a measured GPU reading and a pass census — an absent measurement is not evidence",
2482
+ "the record carries objects considered, draws per pass, triangles per draw and shadow-exempt casters, so the reader is not dependent on the ranking"
2483
+ ],
2484
+ "example": "defineGame({ display: { maxFps: 60 }, scenes: { Play } });",
2485
+ "importPath": "@threenative/core",
2486
+ "kind": "function",
2487
+ "overrides": [],
2488
+ "package": "@threenative/core",
2489
+ "signature": "export function sceneWarning( window: IFrameBudgetWindow, shape: ISceneShape | undefined, declaredTargetFps: number | undefined, ): ISceneWarning | undefined { … }",
2490
+ "situations": [
2491
+ "find out whether a slow frame is the scene's shape or the device",
2492
+ "tell an authoring agent what to reduce before it promises a merge"
2493
+ ],
2494
+ "summary": "Warn the agent that built the scene before a human plays it: a frame whose GPU is idle while its JS render phase is longer than the display's own period is a scene-shape problem, and the engine already has the shape. On by default, printed at most once per reported window as `TN_SCENE_WARNING`, and silent on a scene that is honestly GPU-bound.",
2495
+ "supersedes": [],
2496
+ "symbol": "sceneWarning"
2497
+ },
1562
2498
  {
1563
2499
  "aliases": ["tower defense game", "spawn waves"],
1564
2500
  "constraints": [
@@ -1580,6 +2516,49 @@
1580
2516
  "supersedes": [],
1581
2517
  "symbol": "Scheduler"
1582
2518
  },
2519
+ {
2520
+ "aliases": [],
2521
+ "constraints": [
2522
+ "every mass, area, power and inertia value comes from the game's airframe",
2523
+ "damage, stores and configuration arrive as the game's own modifier sample"
2524
+ ],
2525
+ "example": "const model = new FlightModel({ airframe: sbd, state: aircraft, wind: seaWind });\nmodel.step(1 / 60, { turn: -1, pitch: 0.4 });",
2526
+ "importPath": "@threenative/core",
2527
+ "kind": "function",
2528
+ "overrides": [],
2529
+ "package": "@threenative/core",
2530
+ "signature": "export function setAttitude( state: IFlightState, heading = 0, pitch = 0, roll = 0, ): IFlightQuaternion { … }",
2531
+ "situations": [
2532
+ "fly an airplane with lift, drag, stall and control authority",
2533
+ "launch an aircraft off a moving carrier deck",
2534
+ "apply component damage or a loadout to an aircraft's performance"
2535
+ ],
2536
+ "summary": "Fly a fixed-wing aircraft with a real force balance instead of a steered velocity.",
2537
+ "supersedes": [],
2538
+ "symbol": "setAttitude"
2539
+ },
2540
+ {
2541
+ "aliases": [],
2542
+ "constraints": [
2543
+ "off by default and installed by `TN_FRAME_SPANS=1`; unset, every call site is one guarded return",
2544
+ "the tree is closed against the frame budget's own render phase, so `TN_FRAME_SPANS` and `TN_FRAME_BUDGET` describe the same frames",
2545
+ "measurement only: no span changes what is drawn, in what order, or with which renderer"
2546
+ ],
2547
+ "example": "if (spansRequested()) setSpanRecorder(new SpanRecorder());",
2548
+ "importPath": "@threenative/core",
2549
+ "kind": "function",
2550
+ "overrides": [],
2551
+ "package": "@threenative/core",
2552
+ "signature": "export function setSpanRecorder(next: SpanRecorder | undefined): void { … }",
2553
+ "situations": [
2554
+ "find out what inside the render phase is actually costing the frame",
2555
+ "tell a shadow pass's traversal from the main pass's, with the residual computed",
2556
+ "price an optimisation against a measured part of the phase rather than the whole of it"
2557
+ ],
2558
+ "summary": "Attribute the render phase to 100% with a nested span tree, off unless `TN_FRAME_SPANS` asks. Every non-leaf reports its own time minus the spans inside it, so an unmeasured part shows up as a residual instead of being filed under \"other\"; a child that outlives its parent reports a negative residual rather than a clamped zero.",
2559
+ "supersedes": [],
2560
+ "symbol": "setSpanRecorder"
2561
+ },
1583
2562
  {
1584
2563
  "aliases": [],
1585
2564
  "constraints": [
@@ -1704,6 +2683,94 @@
1704
2683
  "supersedes": [],
1705
2684
  "symbol": "solarPositionAt"
1706
2685
  },
2686
+ {
2687
+ "aliases": [],
2688
+ "constraints": [
2689
+ "off by default and installed by `TN_FRAME_SPANS=1`; unset, every call site is one guarded return",
2690
+ "the tree is closed against the frame budget's own render phase, so `TN_FRAME_SPANS` and `TN_FRAME_BUDGET` describe the same frames",
2691
+ "measurement only: no span changes what is drawn, in what order, or with which renderer"
2692
+ ],
2693
+ "example": "if (spansRequested()) setSpanRecorder(new SpanRecorder());",
2694
+ "importPath": "@threenative/core",
2695
+ "kind": "function",
2696
+ "overrides": [],
2697
+ "package": "@threenative/core",
2698
+ "signature": "export function spanNow(): number { … }",
2699
+ "situations": [
2700
+ "find out what inside the render phase is actually costing the frame",
2701
+ "tell a shadow pass's traversal from the main pass's, with the residual computed",
2702
+ "price an optimisation against a measured part of the phase rather than the whole of it"
2703
+ ],
2704
+ "summary": "Attribute the render phase to 100% with a nested span tree, off unless `TN_FRAME_SPANS` asks. Every non-leaf reports its own time minus the spans inside it, so an unmeasured part shows up as a residual instead of being filed under \"other\"; a child that outlives its parent reports a negative residual rather than a clamped zero.",
2705
+ "supersedes": [],
2706
+ "symbol": "spanNow"
2707
+ },
2708
+ {
2709
+ "aliases": [],
2710
+ "constraints": [
2711
+ "off by default and installed by `TN_FRAME_SPANS=1`; unset, every call site is one guarded return",
2712
+ "the tree is closed against the frame budget's own render phase, so `TN_FRAME_SPANS` and `TN_FRAME_BUDGET` describe the same frames",
2713
+ "measurement only: no span changes what is drawn, in what order, or with which renderer"
2714
+ ],
2715
+ "example": "if (spansRequested()) setSpanRecorder(new SpanRecorder());",
2716
+ "importPath": "@threenative/core",
2717
+ "kind": "function",
2718
+ "overrides": [],
2719
+ "package": "@threenative/core",
2720
+ "signature": "export function spanRecorder(): SpanRecorder | undefined { … }",
2721
+ "situations": [
2722
+ "find out what inside the render phase is actually costing the frame",
2723
+ "tell a shadow pass's traversal from the main pass's, with the residual computed",
2724
+ "price an optimisation against a measured part of the phase rather than the whole of it"
2725
+ ],
2726
+ "summary": "Attribute the render phase to 100% with a nested span tree, off unless `TN_FRAME_SPANS` asks. Every non-leaf reports its own time minus the spans inside it, so an unmeasured part shows up as a residual instead of being filed under \"other\"; a child that outlives its parent reports a negative residual rather than a clamped zero.",
2727
+ "supersedes": [],
2728
+ "symbol": "spanRecorder"
2729
+ },
2730
+ {
2731
+ "aliases": [],
2732
+ "constraints": [
2733
+ "off by default and installed by `TN_FRAME_SPANS=1`; unset, every call site is one guarded return",
2734
+ "the tree is closed against the frame budget's own render phase, so `TN_FRAME_SPANS` and `TN_FRAME_BUDGET` describe the same frames",
2735
+ "measurement only: no span changes what is drawn, in what order, or with which renderer"
2736
+ ],
2737
+ "example": "if (spansRequested()) setSpanRecorder(new SpanRecorder());",
2738
+ "importPath": "@threenative/core",
2739
+ "kind": "class",
2740
+ "overrides": [],
2741
+ "package": "@threenative/core",
2742
+ "signature": "export class SpanRecorder { … }",
2743
+ "situations": [
2744
+ "find out what inside the render phase is actually costing the frame",
2745
+ "tell a shadow pass's traversal from the main pass's, with the residual computed",
2746
+ "price an optimisation against a measured part of the phase rather than the whole of it"
2747
+ ],
2748
+ "summary": "Attribute the render phase to 100% with a nested span tree, off unless `TN_FRAME_SPANS` asks. Every non-leaf reports its own time minus the spans inside it, so an unmeasured part shows up as a residual instead of being filed under \"other\"; a child that outlives its parent reports a negative residual rather than a clamped zero.",
2749
+ "supersedes": [],
2750
+ "symbol": "SpanRecorder"
2751
+ },
2752
+ {
2753
+ "aliases": [],
2754
+ "constraints": [
2755
+ "off by default and installed by `TN_FRAME_SPANS=1`; unset, every call site is one guarded return",
2756
+ "the tree is closed against the frame budget's own render phase, so `TN_FRAME_SPANS` and `TN_FRAME_BUDGET` describe the same frames",
2757
+ "measurement only: no span changes what is drawn, in what order, or with which renderer"
2758
+ ],
2759
+ "example": "if (spansRequested()) setSpanRecorder(new SpanRecorder());",
2760
+ "importPath": "@threenative/core",
2761
+ "kind": "function",
2762
+ "overrides": [],
2763
+ "package": "@threenative/core",
2764
+ "signature": "export function spansRequested(): boolean { … }",
2765
+ "situations": [
2766
+ "find out what inside the render phase is actually costing the frame",
2767
+ "tell a shadow pass's traversal from the main pass's, with the residual computed",
2768
+ "price an optimisation against a measured part of the phase rather than the whole of it"
2769
+ ],
2770
+ "summary": "Attribute the render phase to 100% with a nested span tree, off unless `TN_FRAME_SPANS` asks. Every non-leaf reports its own time minus the spans inside it, so an unmeasured part shows up as a residual instead of being filed under \"other\"; a child that outlives its parent reports a negative residual rather than a clamped zero.",
2771
+ "supersedes": [],
2772
+ "symbol": "spansRequested"
2773
+ },
1707
2774
  {
1708
2775
  "aliases": [],
1709
2776
  "constraints": [
@@ -1748,6 +2815,27 @@
1748
2815
  "supersedes": [],
1749
2816
  "symbol": "SpriteAnimator3D"
1750
2817
  },
2818
+ {
2819
+ "aliases": [],
2820
+ "constraints": [
2821
+ "staticness is authored, never guessed; no heuristic watches gameplay and decides for you",
2822
+ "it deletes the local and world matrix composes, not the walk itself — three 0.185 recurses into every child regardless",
2823
+ "a write deeper inside a frozen subtree must call `invalidateStatic`; `TN_RENDERLIST_VALIDATE=1` is what proves it happened"
2824
+ ],
2825
+ "example": "markStatic(island); invalidateStatic(drawbridge);",
2826
+ "importPath": "@threenative/core",
2827
+ "kind": "function",
2828
+ "overrides": [],
2829
+ "package": "@threenative/core",
2830
+ "signature": "export function staticTransformCensus(): IStaticTransformCensus { … }",
2831
+ "situations": [
2832
+ "cut the per-frame matrix work of terrain, buildings, props and other scenery that never moves",
2833
+ "keep a frozen subtree correct when the game does move it after all"
2834
+ ],
2835
+ "summary": "Stop recomposing the transforms of a subtree nobody moves. `markStatic(root)` composes the subtree once and freezes it; the engine re-arms a root whose own transform the game changes, and `invalidateStatic(object)` announces a write deeper inside one.",
2836
+ "supersedes": [],
2837
+ "symbol": "staticTransformCensus"
2838
+ },
1751
2839
  {
1752
2840
  "aliases": [],
1753
2841
  "constraints": [
@@ -1768,6 +2856,27 @@
1768
2856
  "supersedes": [],
1769
2857
  "symbol": "TracerPool3D"
1770
2858
  },
2859
+ {
2860
+ "aliases": [],
2861
+ "constraints": [
2862
+ "staticness is authored, never guessed; no heuristic watches gameplay and decides for you",
2863
+ "it deletes the local and world matrix composes, not the walk itself — three 0.185 recurses into every child regardless",
2864
+ "a write deeper inside a frozen subtree must call `invalidateStatic`; `TN_RENDERLIST_VALIDATE=1` is what proves it happened"
2865
+ ],
2866
+ "example": "markStatic(island); invalidateStatic(drawbridge);",
2867
+ "importPath": "@threenative/core",
2868
+ "kind": "function",
2869
+ "overrides": [],
2870
+ "package": "@threenative/core",
2871
+ "signature": "export function unmarkStatic(root: Object3D): void { … }",
2872
+ "situations": [
2873
+ "cut the per-frame matrix work of terrain, buildings, props and other scenery that never moves",
2874
+ "keep a frozen subtree correct when the game does move it after all"
2875
+ ],
2876
+ "summary": "Stop recomposing the transforms of a subtree nobody moves. `markStatic(root)` composes the subtree once and freezes it; the engine re-arms a root whose own transform the game changes, and `invalidateStatic(object)` announces a write deeper inside one.",
2877
+ "supersedes": [],
2878
+ "symbol": "unmarkStatic"
2879
+ },
1771
2880
  {
1772
2881
  "aliases": [],
1773
2882
  "constraints": ["patches cannot introduce omitted, negative, or non-finite physical values"],
@@ -1796,6 +2905,52 @@
1796
2905
  "supersedes": [],
1797
2906
  "symbol": "updateClusteredMeshes"
1798
2907
  },
2908
+ {
2909
+ "aliases": [],
2910
+ "constraints": [
2911
+ "the chain is baked by the asset cook; there is no runtime generation and no runtime flag",
2912
+ "`assets.lod: false` opts out globally and `assets.lod.overrides` per asset, with no runtime controller installed",
2913
+ "only static indexed triangles are eligible; skinned, morphed, alpha-blended or authored-LOD meshes keep full detail",
2914
+ "the real gate is measured benefit: a level must save at least `minSaving` (20% default) of its predecessor, and a mesh that cannot is skipped, not forced"
2915
+ ],
2916
+ "example": "// Nothing to call: the loader returns a mesh with the chain and the engine selects every frame.\nconst hull = await ctx.assets.model(\"hull.glb\");\nctx.scene.add(hull.scene);",
2917
+ "importPath": "@threenative/core",
2918
+ "kind": "function",
2919
+ "overrides": [
2920
+ "`assets.lod.generation.maxLevels`, `.minTriangles`, `.minTrianglesScope`, `.minSaving` and `.errorTargets` move the bake's ceiling, pre-filter, its scope, its saving rule and its error ladder; `assets.lod.runtime.maxPixelError` and `.hysteresis` move the runtime budget and coarsen band; all by project, preset or asset"
2921
+ ],
2922
+ "package": "@threenative/core",
2923
+ "signature": "export function updateModelLods( root: { … }",
2924
+ "situations": [
2925
+ "draw a vehicle or hull built from many small meshes at distance without its full triangles",
2926
+ "stop a distant model from costing its authored mesh count and triangle count",
2927
+ "one source model, right detail by default, no hand-authored LOD files"
2928
+ ],
2929
+ "summary": "Draw a model at the detail its projected geometric error earns, from a chain the asset cook baked. **This is engine-owned, and a game does not call it.** `assets.lod: {}` opts in — the default-on front door opens after qualification — the `model` pass bakes `TN_discrete_lod` into eligible models, the loader registers the reader, and the engine runs the selection every frame before it renders. Each frame the mesh picks the cheapest baked level whose measured error projects to fewer than the resolved pixel budget, taking the camera's own projection, zoom, viewport and a conservative nearest depth into account. Refinement is immediate; coarsening waits for the resolved hysteresis. A mesh with no baked chain draws its full geometry.",
2930
+ "supersedes": [],
2931
+ "symbol": "updateModelLods"
2932
+ },
2933
+ {
2934
+ "aliases": [],
2935
+ "constraints": [
2936
+ "off by default and expensive by construction — it does the work it is checking, twice",
2937
+ "it throws on the first divergence rather than logging; a validation mode that continues is one nobody reads",
2938
+ "it proves the frames it ran on and nothing else"
2939
+ ],
2940
+ "example": "if (renderListValidationRequested()) console.log(formatValidationReport(report));",
2941
+ "importPath": "@threenative/core",
2942
+ "kind": "function",
2943
+ "overrides": [],
2944
+ "package": "@threenative/core",
2945
+ "signature": "export function validateWorldMatrices(root: Object3D): { … }",
2946
+ "situations": [
2947
+ "prove a static freeze did not leave a stale transform on screen",
2948
+ "gate a scene in CI against silent transform divergence"
2949
+ ],
2950
+ "summary": "Prove that nothing a cache skipped changed the picture: `TN_RENDERLIST_VALIDATE=1` recomputes every world matrix the long way, every frame, and throws on the first element that disagrees with what the frame is about to draw.",
2951
+ "supersedes": [],
2952
+ "symbol": "validateWorldMatrices"
2953
+ },
1799
2954
  {
1800
2955
  "aliases": [],
1801
2956
  "constraints": [
@@ -1888,10 +3043,12 @@
1888
3043
  "it draws nothing; the game supplies the mesh, the material and every colour",
1889
3044
  "the material must be transparent so the frame beneath it is already drawn",
1890
3045
  "thickness is metres, saturating at maxThickness; sky behind the surface reads deep",
1891
- "one reflection is a second draw of the world, so resolutionScale is the whole cost",
3046
+ "one reflection is a second draw of the world; resolutionScale is its pixels only",
3047
+ "on a crowded scene the mirrored pass is draw-bound: name reflection.layers or pay twice",
3048
+ "reflection.refreshInterval is in presented frames; 1 is every frame, and the default",
1892
3049
  "the mirror plane is level, from level alone; do not parent target to a scaled mesh"
1893
3050
  ],
1894
- "example": "const surface = new WaterSurface3D({ level: 0, maxThickness: 3, reflection: { resolutionScale: 0.5 } });\nmaterial.colorNode = mix(surface.refractionAt(offset), surface.reflectionAt(offset), fresnel);",
3051
+ "example": "const REFLECTED = 1; // the layer the big silhouettes sit on\nconst surface = new WaterSurface3D({ level: 0, maxThickness: 3, reflection: { resolutionScale: 0.5, layers: (1 << 0) | (1 << REFLECTED) } });\nmaterial.colorNode = mix(surface.refractionAt(offset), surface.reflectionAt(offset), fresnel);",
1895
3052
  "importPath": "@threenative/core",
1896
3053
  "kind": "class",
1897
3054
  "overrides": [],
@@ -1901,7 +3058,9 @@
1901
3058
  "reflect the sky and the shoreline in a lake, pond or river",
1902
3059
  "see the bed through the water and have the shallows fade at the shore",
1903
3060
  "know how deep the water is under a pixel without a second render pass",
1904
- "stop a water surface repeating in visible bands or stripes"
3061
+ "stop a water surface repeating in visible bands or stripes",
3062
+ "keep a crowd of small actors out of the water's reflection so the frame can afford it",
3063
+ "stop the water reflection redrawing the whole world every frame"
1905
3064
  ],
1906
3065
  "summary": "Give a horizontal water surface the world mirrored in it, the world beneath it, and the metres of water between them.",
1907
3066
  "supersedes": [],
@@ -1911,9 +3070,10 @@
1911
3070
  "aliases": [],
1912
3071
  "constraints": [
1913
3072
  "supply every wave amplitude, wavelength, direction, speed and warp value",
1914
- "call setTime for the default graph clock when the game advances its own time"
3073
+ "call setTime for the default graph clock when the game advances its own time",
3074
+ "sample allocates a result and a normal vector; ask heightAt when only the height is"
1915
3075
  ],
1916
- "example": "const field = new WaveField({ waves });\nconst { height, normal } = field.sample(x, z, elapsed);",
3076
+ "example": "const field = new WaveField({ waves });\nconst { height, normal } = field.sample(x, z, elapsed);\nconst lift = field.heightAt(x, z, elapsed);",
1917
3077
  "importPath": "@threenative/core",
1918
3078
  "kind": "class",
1919
3079
  "overrides": [],
@@ -1924,7 +3084,7 @@
1924
3084
  "make water move",
1925
3085
  "find the water surface height at a point"
1926
3086
  ],
1927
- "summary": "Evaluate analytic waves on CPU and displace game-owned vertices with the matching TSL graph.",
3087
+ "summary": "Evaluate analytic waves on CPU and displace game-owned vertices with the matching TSL graph. wanted, and the warp jacobian, every slope and both allocations are skipped",
1928
3088
  "supersedes": [],
1929
3089
  "symbol": "WaveField"
1930
3090
  },
@@ -2194,7 +3354,7 @@
2194
3354
  {
2195
3355
  "aliases": ["journal objective panel", "readable HUD"],
2196
3356
  "constraints": [
2197
- "publishes at the store's throttled cadence, and not at all with no UI listening"
3357
+ "publishes once per rendered frame unless stateFlushMs selects a slower interval, and not at all with no UI listening"
2198
3358
  ],
2199
3359
  "example": "publishUiState(bridge, game.state);",
2200
3360
  "importPath": "@threenative/core/ui-layer",
@@ -2206,7 +3366,7 @@
2206
3366
  "show score or health in a UI rendered over the game surface",
2207
3367
  "keep a HUD in step with the game without re-rendering on the loop",
2208
3368
  "publish game state to a HUD in another realm",
2209
- "keep a web and native UI mirror on the same throttled state stream"
3369
+ "keep a web and native UI mirror on the same coalesced state stream"
2210
3370
  ],
2211
3371
  "summary": "Publish the game's state so a UI in another process can mirror it.",
2212
3372
  "supersedes": [],
@@ -2672,18 +3832,15 @@
2672
3832
  },
2673
3833
  {
2674
3834
  "aliases": [],
2675
- "constraints": ["unknown scenario keys fail closed"],
2676
- "example": "const scenario = await loadPlaytestScenario(project, file);",
3835
+ "constraints": ["returns an error; the caller must throw it"],
3836
+ "example": "import { invalidScenario } from \"@threenative/playtest\";\nthrow invalidScenario(\"smoke.playtest.json\", \"Expected a non-empty assertion set\");",
2677
3837
  "importPath": "@threenative/playtest",
2678
3838
  "kind": "function",
2679
3839
  "overrides": [],
2680
3840
  "package": "@threenative/playtest",
2681
3841
  "signature": "export function invalidScenario(scenarioPath: string, message: string): PlaytestScenarioError { … }",
2682
- "situations": [
2683
- "create a browser or device playtest scenario",
2684
- "wait or hold a game for a deterministic number of ticks"
2685
- ],
2686
- "summary": "Load and validate a scenario, then control its tick steps.",
3842
+ "situations": ["construct a validation error for malformed scenario input"],
3843
+ "summary": "Construct a named invalid-scenario error without loading or executing a scenario.",
2687
3844
  "supersedes": [],
2688
3845
  "symbol": "invalidScenario"
2689
3846
  },
@@ -2707,8 +3864,11 @@
2707
3864
  },
2708
3865
  {
2709
3866
  "aliases": [],
2710
- "constraints": ["unknown scenario keys fail closed"],
2711
- "example": "const scenario = await loadPlaytestScenario(project, file);",
3867
+ "constraints": [
3868
+ "unknown scenario keys and missing referenced evidence fail closed",
3869
+ "loading validates the fixture; use the runner to execute it"
3870
+ ],
3871
+ "example": "import { loadPlaytestScenario } from \"@threenative/playtest\";\nconst scenario = await loadPlaytestScenario(process.cwd(), \"playtests/smoke.playtest.json\");",
2712
3872
  "importPath": "@threenative/playtest",
2713
3873
  "kind": "function",
2714
3874
  "overrides": [],
@@ -2716,9 +3876,9 @@
2716
3876
  "signature": "export async function loadPlaytestScenario(projectPath: string, scenarioPath: string): Promise<IPlaytestScenario> { … }",
2717
3877
  "situations": [
2718
3878
  "create a browser or device playtest scenario",
2719
- "wait or hold a game for a deterministic number of ticks"
3879
+ "load a deterministic tick-based playtest scenario"
2720
3880
  ],
2721
- "summary": "Load and validate a scenario, then control its tick steps.",
3881
+ "summary": "Load and validate a scenario and its referenced evidence before running it.",
2722
3882
  "supersedes": [],
2723
3883
  "symbol": "loadPlaytestScenario"
2724
3884
  },
@@ -2838,69 +3998,59 @@
2838
3998
  },
2839
3999
  {
2840
4000
  "aliases": [],
2841
- "constraints": ["unknown scenario keys fail closed"],
2842
- "example": "const scenario = await loadPlaytestScenario(project, file);",
4001
+ "constraints": [
4002
+ "the diagnostic describes a failed load, not a successfully executed scenario"
4003
+ ],
4004
+ "example": "import { PlaytestScenarioError } from \"@threenative/playtest\";\nconst error = new PlaytestScenarioError({ code: \"TN_PLAYTEST_SCENARIO_INVALID\", message: \"Invalid fixture\", severity: \"error\", suggestion: \"Fix the fixture\" });",
2843
4005
  "importPath": "@threenative/playtest",
2844
4006
  "kind": "class",
2845
4007
  "overrides": [],
2846
4008
  "package": "@threenative/playtest",
2847
4009
  "signature": "export class PlaytestScenarioError extends Error { … }",
2848
- "situations": [
2849
- "create a browser or device playtest scenario",
2850
- "wait or hold a game for a deterministic number of ticks"
2851
- ],
2852
- "summary": "Load and validate a scenario, then control its tick steps.",
4010
+ "situations": ["catch a structured playtest scenario validation error"],
4011
+ "summary": "Carry a structured scenario validation diagnostic as an error.",
2853
4012
  "supersedes": [],
2854
4013
  "symbol": "PlaytestScenarioError"
2855
4014
  },
2856
4015
  {
2857
4016
  "aliases": [],
2858
- "constraints": ["unknown scenario keys fail closed"],
2859
- "example": "const scenario = await loadPlaytestScenario(project, file);",
4017
+ "constraints": ["reads the duration only; the runner advances the simulation"],
4018
+ "example": "import { playtestStepHoldTicks } from \"@threenative/playtest\";\nconst ticks = playtestStepHoldTicks({ kind: \"input\", press: \"KeyW\", holdTicks: 30, release: true });",
2860
4019
  "importPath": "@threenative/playtest",
2861
4020
  "kind": "function",
2862
4021
  "overrides": [],
2863
4022
  "package": "@threenative/playtest",
2864
4023
  "signature": "export function playtestStepHoldTicks(step: IPlaytestStep, fallback = 1): number { … }",
2865
- "situations": [
2866
- "create a browser or device playtest scenario",
2867
- "wait or hold a game for a deterministic number of ticks"
2868
- ],
2869
- "summary": "Load and validate a scenario, then control its tick steps.",
4024
+ "situations": ["read the deterministic number of ticks to hold a playtest input"],
4025
+ "summary": "Read a validated step's input-hold duration in simulation ticks.",
2870
4026
  "supersedes": [],
2871
4027
  "symbol": "playtestStepHoldTicks"
2872
4028
  },
2873
4029
  {
2874
4030
  "aliases": [],
2875
- "constraints": ["unknown scenario keys fail closed"],
2876
- "example": "const scenario = await loadPlaytestScenario(project, file);",
4031
+ "constraints": ["reads the wait duration only; the runner advances the simulation"],
4032
+ "example": "import { playtestStepWaitTicks } from \"@threenative/playtest\";\nconst ticks = playtestStepWaitTicks({ kind: \"wait\", waitTicks: 30, release: true });",
2877
4033
  "importPath": "@threenative/playtest",
2878
4034
  "kind": "function",
2879
4035
  "overrides": [],
2880
4036
  "package": "@threenative/playtest",
2881
4037
  "signature": "export function playtestStepWaitTicks(step: IPlaytestStep): number { … }",
2882
- "situations": [
2883
- "create a browser or device playtest scenario",
2884
- "wait or hold a game for a deterministic number of ticks"
2885
- ],
2886
- "summary": "Load and validate a scenario, then control its tick steps.",
4038
+ "situations": ["wait or hold a game for a deterministic number of ticks"],
4039
+ "summary": "Read a validated step's no-input duration in simulation ticks.",
2887
4040
  "supersedes": [],
2888
4041
  "symbol": "playtestStepWaitTicks"
2889
4042
  },
2890
4043
  {
2891
4044
  "aliases": [],
2892
- "constraints": ["unknown scenario keys fail closed"],
2893
- "example": "const scenario = await loadPlaytestScenario(project, file);",
4045
+ "constraints": ["throws an invalid-scenario error on the first unknown key"],
4046
+ "example": "import { rejectUnknownKeys } from \"@threenative/playtest\";\nrejectUnknownKeys({ name: \"smoke\" }, [\"name\"], \"smoke.playtest.json\", \"scenario\");",
2894
4047
  "importPath": "@threenative/playtest",
2895
4048
  "kind": "function",
2896
4049
  "overrides": [],
2897
4050
  "package": "@threenative/playtest",
2898
4051
  "signature": "export function rejectUnknownKeys( value: Record<string, unknown>, allowedKeys: readonly string[], scenarioPath: string, objectPath: string, ): void { … }",
2899
- "situations": [
2900
- "create a browser or device playtest scenario",
2901
- "wait or hold a game for a deterministic number of ticks"
2902
- ],
2903
- "summary": "Load and validate a scenario, then control its tick steps.",
4052
+ "situations": ["reject an unknown field while validating a scenario object"],
4053
+ "summary": "Reject object keys outside the explicitly allowed scenario fields.",
2904
4054
  "supersedes": [],
2905
4055
  "symbol": "rejectUnknownKeys"
2906
4056
  },
@@ -3734,18 +4884,17 @@
3734
4884
  },
3735
4885
  {
3736
4886
  "aliases": [],
3737
- "constraints": ["inspect the adapter name before claiming GPU proof"],
3738
- "example": "const args = resolveBrowserArguments(undefined);",
4887
+ "constraints": [
4888
+ "returns changes only; the caller dispatches them and retains the next snapshot"
4889
+ ],
4890
+ "example": "import { reconcileBrowserPointers } from \"@threenative/playtest/runner\";\nconst changes = reconcileBrowserPointers(new Map(), [{ id: 1, x: 20, y: 30 }]);",
3739
4891
  "importPath": "@threenative/playtest/runner",
3740
4892
  "kind": "function",
3741
4893
  "overrides": [],
3742
4894
  "package": "@threenative/playtest",
3743
4895
  "signature": "export function reconcileBrowserPointers( previous: ReadonlyMap<number, Required<IPlaytestPointer>>, next: readonly IPlaytestPointer[], ): IBrowserPointerChange[] { … }",
3744
- "situations": [
3745
- "run a browser playtest with Vulkan WebGPU",
3746
- "reject a SwiftShader adapter as evidence"
3747
- ],
3748
- "summary": "Select safe Chromium arguments for WebGPU playtests.",
4896
+ "situations": ["reconcile pointer contacts into down move and up events"],
4897
+ "summary": "Compare pointer snapshots and produce down, move, and up transitions.",
3749
4898
  "supersedes": [],
3750
4899
  "symbol": "reconcileBrowserPointers"
3751
4900
  },
@@ -3785,18 +4934,18 @@
3785
4934
  },
3786
4935
  {
3787
4936
  "aliases": [],
3788
- "constraints": ["inspect the adapter name before claiming GPU proof"],
3789
- "example": "const args = resolveBrowserArguments(undefined);",
4937
+ "constraints": [
4938
+ "pass WEBGPU_BROWSER_ARGS explicitly; undefined selects no additional arguments",
4939
+ "inspect the observed adapter before claiming hardware GPU evidence"
4940
+ ],
4941
+ "example": "import { resolveBrowserArguments, WEBGPU_BROWSER_ARGS } from \"@threenative/playtest/runner\";\nconst args = resolveBrowserArguments(WEBGPU_BROWSER_ARGS);",
3790
4942
  "importPath": "@threenative/playtest/runner",
3791
4943
  "kind": "function",
3792
4944
  "overrides": [],
3793
4945
  "package": "@threenative/playtest",
3794
4946
  "signature": "export function resolveBrowserArguments(browserArgs: readonly string[] | undefined): string[] { … }",
3795
- "situations": [
3796
- "run a browser playtest with Vulkan WebGPU",
3797
- "reject a SwiftShader adapter as evidence"
3798
- ],
3799
- "summary": "Select safe Chromium arguments for WebGPU playtests.",
4947
+ "situations": ["run a browser playtest with Vulkan WebGPU"],
4948
+ "summary": "Copy the selected Chromium arguments without silently enabling a rendering recipe.",
3800
4949
  "supersedes": [],
3801
4950
  "symbol": "resolveBrowserArguments"
3802
4951
  },
@@ -3952,18 +5101,17 @@
3952
5101
  },
3953
5102
  {
3954
5103
  "aliases": [],
3955
- "constraints": ["inspect the adapter name before claiming GPU proof"],
3956
- "example": "const args = resolveBrowserArguments(undefined);",
5104
+ "constraints": [
5105
+ "undefined means no software name was found, not proof of a hardware adapter"
5106
+ ],
5107
+ "example": "import { softwareAdapterName } from \"@threenative/playtest/runner\";\nconst software = softwareAdapterName({ architecture: \"swiftshader\" });",
3957
5108
  "importPath": "@threenative/playtest/runner",
3958
5109
  "kind": "function",
3959
5110
  "overrides": [],
3960
5111
  "package": "@threenative/playtest",
3961
5112
  "signature": "export function softwareAdapterName(adapter: Readonly<Record<string, string>> | undefined): string | undefined { … }",
3962
- "situations": [
3963
- "run a browser playtest with Vulkan WebGPU",
3964
- "reject a SwiftShader adapter as evidence"
3965
- ],
3966
- "summary": "Select safe Chromium arguments for WebGPU playtests.",
5113
+ "situations": ["reject a SwiftShader adapter as evidence"],
5114
+ "summary": "Identify a software renderer in the fields reported by adapter.info.",
3967
5115
  "supersedes": [],
3968
5116
  "symbol": "softwareAdapterName"
3969
5117
  },
@@ -4122,6 +5270,25 @@
4122
5270
  "supersedes": [],
4123
5271
  "symbol": "viewportRestoreCommands"
4124
5272
  },
5273
+ {
5274
+ "aliases": [],
5275
+ "constraints": [
5276
+ "browser only; requires a scenario and runtime.startup; does not execute scenario steps or assertions",
5277
+ "cancellation is checked between resource acquisitions; lock waiting retains its own bounded queue policy",
5278
+ "use session.screenshot for nonblank PNGs; private-display captures are not FPS evidence",
5279
+ "use threenative-playtest trace --url <url> for slow-frame attribution instead of creating another profiler"
5280
+ ],
5281
+ "example": "import { parseStandalonePlaytestArgs, withBrowserCapture } from \"@threenative/playtest/runner\";\nconst config = parseStandalonePlaytestArgs([\"--scenario\", \"playtests/smoke.playtest.json\", \"--url\", \"http://127.0.0.1:5173\"]);\nawait withBrowserCapture(config, async (session) => session.screenshot(\"ready\"));",
5282
+ "importPath": "@threenative/playtest/runner",
5283
+ "kind": "function",
5284
+ "overrides": [],
5285
+ "package": "@threenative/playtest",
5286
+ "signature": "export async function withBrowserCapture<T>( config: IStandalonePlaytestConfig, capture: (session: IBrowserCaptureSession) => Promise<T>, signal?: AbortSignal, ): Promise<T> { … }",
5287
+ "situations": ["write a custom browser capture without owning Xvfb or Chromium cleanup"],
5288
+ "summary": "Capture a ready ThreeNative game with the runner's display, lock, server and browser ownership.",
5289
+ "supersedes": [],
5290
+ "symbol": "withBrowserCapture"
5291
+ },
4125
5292
  {
4126
5293
  "aliases": [],
4127
5294
  "constraints": ["missing observations and malformed assertions fail closed"],
@@ -4853,7 +6020,10 @@
4853
6020
  },
4854
6021
  {
4855
6022
  "aliases": [],
4856
- "constraints": [],
6023
+ "constraints": [
6024
+ "the Geometry tab captures only on an explicit press; nothing is collected while idle",
6025
+ "per-object numbers are measured submissions reconciled against the frame's own pass totals, and the remainder is reported rather than hidden"
6026
+ ],
4857
6027
  "example": "<DebugOverlay />",
4858
6028
  "importPath": "@threenative/ui",
4859
6029
  "kind": "function",
@@ -4862,9 +6032,11 @@
4862
6032
  "signature": "export function DebugOverlay() { … }",
4863
6033
  "situations": [
4864
6034
  "display runtime and playtest diagnostics in a React HUD",
4865
- "inspect a game without changing its scene"
6035
+ "inspect a game without changing its scene",
6036
+ "find out which scene object is submitting the frame's triangles",
6037
+ "tell a cheap foreground character from an expensive distant prop"
4866
6038
  ],
4867
- "summary": "Show framework diagnostics while developing a game.",
6039
+ "summary": "Show framework diagnostics while developing a game. Backtick opens it; the Entities tab lists registered entity fields, and the Geometry tab captures one frame and ranks the objects that submitted its triangles beside their projected size on screen.",
4868
6040
  "supersedes": [],
4869
6041
  "symbol": "DebugOverlay"
4870
6042
  },
@@ -4915,7 +6087,7 @@
4915
6087
  "bind a HUD component to game state",
4916
6088
  "select a slice of state for a React panel"
4917
6089
  ],
4918
- "summary": "Read throttled game state from React.",
6090
+ "summary": "Read the game's coalesced frame snapshot from React.",
4919
6091
  "supersedes": [],
4920
6092
  "symbol": "useGameState"
4921
6093
  },
@@ -4953,6 +6125,90 @@
4953
6125
  "supersedes": [],
4954
6126
  "symbol": "useUiState"
4955
6127
  },
6128
+ {
6129
+ "symbol": "renderer.matrixWorld",
6130
+ "package": "@threenative/core",
6131
+ "importPath": "src/game.ts",
6132
+ "kind": "function",
6133
+ "signature": "renderer.matrixWorld?: \"visible\" | \"all\"",
6134
+ "summary": "The per-frame world-matrix walk, on by default as `\"visible\"`: a hidden subtree — a full-detail body behind a merged stand-in, a hidden LOD level, a parked or hangared model — is not recursed into, so nothing that cannot draw pays a world-matrix multiply. Every visible node is composed exactly as three does, a class that overrides `updateMatrixWorld` (`SkinnedMesh`, `Camera`) runs its own, and a hidden node that holds bones is still walked. `\"all\"` restores three's every-node walk.",
6135
+ "situations": [
6136
+ "updateMatrixWorld and multiplyMatrices are hot in a profile",
6137
+ "the per-frame matrix walk is slow with many hidden LOD bodies or paired full/hull models",
6138
+ "stop paying for matrices of models nothing can draw",
6139
+ "a skinned mesh or camera goes stale because its world matrix was not refreshed",
6140
+ "restore three's own full updateMatrixWorld walk",
6141
+ "compare how many scene nodes the engine walks per frame"
6142
+ ],
6143
+ "example": "renderer: { matrixWorld: \"all\" } // in threenative.config.ts; omit for the visible-only default",
6144
+ "constraints": [
6145
+ "Unset is the shipping behaviour: `\"visible\"`, which does not recurse into a hidden subtree. `\"all\"` visits every node exactly as three's own `updateMatrixWorld` does.",
6146
+ "A game that reads a hidden object's `matrixWorld` directly must not rely on the walk reaching it: use `getWorldPosition`/`getWorldQuaternion`/`getWorldScale` or call `object.updateWorldMatrix(true, false)` first.",
6147
+ "The walk is the engine's either way, so three's renderer never walks the scene a second time; the count of nodes visited is reported as `matrixWorld` in every `TN_PROJECTION` window.",
6148
+ "Bones are never skipped: a hidden node that holds a `Bone` is walked, because a visible `SkinnedMesh` draws with its skeleton's matrices wherever the armature sits."
6149
+ ],
6150
+ "overrides": [
6151
+ "renderer.matrixWorld: \"all\" restores three's every-node walk; the visited count still reports"
6152
+ ],
6153
+ "supersedes": [],
6154
+ "aliases": []
6155
+ },
6156
+ {
6157
+ "symbol": "renderer.minimumProjectedPixels",
6158
+ "package": "@threenative/core",
6159
+ "importPath": "src/game.ts",
6160
+ "kind": "function",
6161
+ "signature": "renderer.minimumProjectedPixels?: number | false",
6162
+ "summary": "Do not submit what the render camera cannot resolve. On by default at a conservative 0.5 projected pixel; an object below it is skipped per render camera. Raise the number to cull more, set `false` to leave every object drawn — the count of what was skipped still reports in `TN_PROJECTION`.",
6163
+ "situations": [
6164
+ "my frame is slow with many distant objects",
6165
+ "draw count is high but the screen is mostly empty",
6166
+ "far away models, aircraft, boats or props cost draw calls but are specks",
6167
+ "a large roster or fleet drops the frame rate while barely visible",
6168
+ "stop submitting objects smaller than a pixel to the camera",
6169
+ "cull by how big something looks to the camera rather than how far it is from the player",
6170
+ "tune how aggressively distant objects are skipped",
6171
+ "a small object I need disappeared at range"
6172
+ ],
6173
+ "example": "renderer: { minimumProjectedPixels: 2 } // in threenative.config.ts",
6174
+ "constraints": [
6175
+ "Unset is the shipping behaviour: the gate runs at 0.5 px. A game that wants the cut a shipped title tuned names 2; `false` leaves every object drawn.",
6176
+ "The decision reads the render camera's projection and viewport, not the player's distance — a camera far from the player still culls its own specks.",
6177
+ "It writes only `object.visible`, which the projection's batch key ignores; `castShadow`, `layers` and `frustumCulled` are never flipped, because that churns batch grouping.",
6178
+ "Shadow casters and objects attached to the render camera are never dropped on the main view alone. Exempt any other object with `alwaysRender`.",
6179
+ "Turning the gate off with `false` does not turn its measurement off: `TN_PROJECTION` still reports considered and skipped counts as `cull`."
6180
+ ],
6181
+ "overrides": [
6182
+ "alwaysRender(object) keeps one object drawn whatever the render camera resolves",
6183
+ "renderer.minimumProjectedPixels: false leaves the scene drawn and keeps the measurement on"
6184
+ ],
6185
+ "supersedes": [],
6186
+ "aliases": []
6187
+ },
6188
+ {
6189
+ "symbol": "renderer.projection",
6190
+ "package": "@threenative/core",
6191
+ "importPath": "src/game.ts",
6192
+ "kind": "function",
6193
+ "signature": "renderer.projection?: boolean",
6194
+ "summary": "The engine's scene-render projection — an internal mirror that collapses repeated draws — on by default. Set `renderer.projection: false` to decline it.",
6195
+ "situations": [
6196
+ "the game got slower after the projection engaged",
6197
+ "turn off the render projection, batching, or the instanced mirror",
6198
+ "draw count fell but frame time did not",
6199
+ "a multi-second freeze when the mirror first engages",
6200
+ "opt out of an engine render optimizer"
6201
+ ],
6202
+ "example": "renderer: { projection: false } // in threenative.config.ts",
6203
+ "constraints": [
6204
+ "Unset is the shipping behaviour: the projection runs. Only an explicit `false` declines it.",
6205
+ "An opted-out game builds no mirror and runs no eligibility scan; the authored scene is what renders, so declining costs nothing rather than being re-judged each frame.",
6206
+ "TN_RENDER_PROJECTION still reports the verdict, with reasonCode `disabled` rather than one of the measured declines."
6207
+ ],
6208
+ "overrides": [],
6209
+ "supersedes": [],
6210
+ "aliases": []
6211
+ },
4956
6212
  {
4957
6213
  "symbol": "WorldEnvironment",
4958
6214
  "package": "three",