@threenative/core 0.3.3 → 0.3.4
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 +555 -21
- package/dist/{assets-CYKk2WTu.d.ts → assets-CqvE429w.d.ts} +45 -3
- package/dist/{canvas-layer-C1SnMoJ-.d.ts → canvas-layer-DDmC_VVF.d.ts} +1 -1
- package/dist/{game-D_6r-k4Y.d.ts → game-CljaDv4D.d.ts} +144 -10
- package/dist/{gpu-readback-CMklJs6r.d.ts → gpu-readback-CqJEfWNQ.d.ts} +16 -6
- package/dist/hot.d.ts +4 -4
- package/dist/index.d.ts +467 -35
- package/dist/index.js +3208 -257
- package/dist/playtest.d.ts +11 -5
- package/dist/playtest.js +55 -5
- package/dist/react.d.ts +2 -2
- package/dist/{renderer-Cy4qeBOA.d.ts → renderer-CfsS2hxi.d.ts} +190 -2
- package/dist/ui-layer.d.ts +5 -3
- package/dist/world.d.ts +900 -14
- package/dist/world.js +10876 -126
- package/gpl/fixtures/make_world_fixture.py +184 -0
- package/gpl/recipes/_common.py +11 -0
- package/gpl/recipes/decimate.py +11 -5
- package/gpl/recipes/export_world.py +682 -0
- package/mcp/blender-server.mjs +55 -1
- package/mcp/engine-server.mjs +54 -20
- package/mcp/servers.mjs +3 -3
- package/package.json +6 -6
- package/patches/three@0.185.1.patch +2101 -138
- package/scripts/apply-three-patch.mjs +61 -37
package/capabilities.json
CHANGED
|
@@ -930,6 +930,26 @@
|
|
|
930
930
|
"supersedes": [],
|
|
931
931
|
"symbol": "boneLengths"
|
|
932
932
|
},
|
|
933
|
+
{
|
|
934
|
+
"aliases": [],
|
|
935
|
+
"constraints": [
|
|
936
|
+
"call rebuild() after a scene transform or geometry change; the snapshot is static by default",
|
|
937
|
+
"rebuild() is an explicit CPU SAH build proportional to selected triangles; process() is a no-op, and the game pays upstream traversal per shader ray"
|
|
938
|
+
],
|
|
939
|
+
"example": "const bvh = ctx.add(new GPUSceneBVH(ctx.scene, { include: (object) => object.userData.traceable === true }));",
|
|
940
|
+
"importPath": "@threenative/core",
|
|
941
|
+
"kind": "function",
|
|
942
|
+
"overrides": [],
|
|
943
|
+
"package": "@threenative/core",
|
|
944
|
+
"signature": "bvhIntersectFirstHit = upstream.bvhIntersectFirstHit",
|
|
945
|
+
"situations": [
|
|
946
|
+
"trace thousands of scene rays inside a TSL kernel",
|
|
947
|
+
"build a contact-occlusion or visibility query over loaded meshes"
|
|
948
|
+
],
|
|
949
|
+
"summary": "Pack a selected static scene into TSL storage nodes for an upstream BVH ray query.",
|
|
950
|
+
"supersedes": [],
|
|
951
|
+
"symbol": "bvhIntersectFirstHit"
|
|
952
|
+
},
|
|
933
953
|
{
|
|
934
954
|
"aliases": [],
|
|
935
955
|
"constraints": [
|
|
@@ -967,6 +987,26 @@
|
|
|
967
987
|
"supersedes": [],
|
|
968
988
|
"symbol": "CanvasLayer"
|
|
969
989
|
},
|
|
990
|
+
{
|
|
991
|
+
"aliases": [],
|
|
992
|
+
"constraints": [
|
|
993
|
+
"the browser grants capture only from a user gesture and a refusal is reported, never swallowed",
|
|
994
|
+
"a relative binding requests capture on canvas click unless `captureOnClick: false` opts out"
|
|
995
|
+
],
|
|
996
|
+
"example": "canvas.addEventListener(\"click\", () => captureMouse(canvas));",
|
|
997
|
+
"importPath": "@threenative/core",
|
|
998
|
+
"kind": "function",
|
|
999
|
+
"overrides": [],
|
|
1000
|
+
"package": "@threenative/core",
|
|
1001
|
+
"signature": "export function captureMouse(target: EventTarget): Promise<void> | undefined { … }",
|
|
1002
|
+
"situations": [
|
|
1003
|
+
"lock the mouse pointer so first-person mouse look keeps the cursor out of the way",
|
|
1004
|
+
"stop the cursor leaving the window in the middle of a turn"
|
|
1005
|
+
],
|
|
1006
|
+
"summary": "Lock the pointer to the game's surface: the capture every first-person mouse look needs. A browser grants capture only from a user gesture, so call it from a click or a pointerdown handler. A relative binding such as `look: { pointerRelative: true }` already requests capture on the first canvas click; this is the same request for a game that starts capture from another named gesture, and `ctx.input.captureMouse()` is the map's own way to ask.",
|
|
1007
|
+
"supersedes": [],
|
|
1008
|
+
"symbol": "captureMouse"
|
|
1009
|
+
},
|
|
970
1010
|
{
|
|
971
1011
|
"aliases": [],
|
|
972
1012
|
"constraints": ["a track that binds nothing counts as driving nothing"],
|
|
@@ -1164,6 +1204,7 @@
|
|
|
1164
1204
|
"package": "@threenative/core",
|
|
1165
1205
|
"signature": "export function createRandom(seed?: number): IRandom { … }",
|
|
1166
1206
|
"situations": [
|
|
1207
|
+
"get a seeded deterministic random number generator — the same mulberry32 a game would hand-roll",
|
|
1167
1208
|
"seed enemy patrol choices",
|
|
1168
1209
|
"reproduce the same procedural level in a playtest"
|
|
1169
1210
|
],
|
|
@@ -1190,6 +1231,47 @@
|
|
|
1190
1231
|
"supersedes": [],
|
|
1191
1232
|
"symbol": "createReplayDriver"
|
|
1192
1233
|
},
|
|
1234
|
+
{
|
|
1235
|
+
"aliases": [],
|
|
1236
|
+
"constraints": [
|
|
1237
|
+
"every value is required; there is no default sun, sky, haze or exposure",
|
|
1238
|
+
"`skySize` must keep the sky box's corners inside the camera's far plane",
|
|
1239
|
+
"shadowExtents follow `VirtualShadowNode`: half-widths, finest first, strictly increasing"
|
|
1240
|
+
],
|
|
1241
|
+
"example": "const daylight = new Daylight({ follow: ctx.camera, sunDirection, sunColor, sunIntensity: 4, shadowExtents: [24, 96, 320], sky: { turbidity: 3, rayleigh: 1.4, mieCoefficient: 0.004, mieDirectionalG: 0.8 }, fill: { sky, ground, intensity: 1.1 }, haze: { color: horizon, density: 0.0011 }, exposure: 2 ** -0.6, skySize: 1600 });\nctx.add(daylight);",
|
|
1242
|
+
"importPath": "@threenative/core",
|
|
1243
|
+
"kind": "class",
|
|
1244
|
+
"overrides": [
|
|
1245
|
+
"sky uniforms stay live on `daylight.sky`; the light and fill are `daylight.sun` and `daylight.fill`"
|
|
1246
|
+
],
|
|
1247
|
+
"package": "@threenative/core",
|
|
1248
|
+
"signature": "export class Daylight extends Group implements IComputeDriven { … }",
|
|
1249
|
+
"situations": [
|
|
1250
|
+
"daytime sky, sun and shadows for a large outdoor map",
|
|
1251
|
+
"distant terrain should fade into the sky instead of a coloured wall",
|
|
1252
|
+
"match a Blender look-dev scene's sun, sky and exposure in the game"
|
|
1253
|
+
],
|
|
1254
|
+
"summary": "An outdoor daylight rig: physical sky, one sun with open-world shadows that follow the eye, hemisphere fill, sky-coloured haze and the AgX tone curve. Every value is the game's.",
|
|
1255
|
+
"supersedes": [],
|
|
1256
|
+
"symbol": "Daylight"
|
|
1257
|
+
},
|
|
1258
|
+
{
|
|
1259
|
+
"aliases": [],
|
|
1260
|
+
"constraints": [
|
|
1261
|
+
"a name in camelCase becomes UPPER_SNAKE: `debugFlag(\"freeCam\")` reads `?freeCam` or `TN_DEBUG_FREE_CAM`",
|
|
1262
|
+
"`0` and `false` are off, so a saved URL cannot turn a switch back on"
|
|
1263
|
+
],
|
|
1264
|
+
"example": "import { debugFlag } from \"@threenative/core\";\nif (debugFlag(\"freeCam\")) camera.flyMode = true;",
|
|
1265
|
+
"importPath": "@threenative/core",
|
|
1266
|
+
"kind": "function",
|
|
1267
|
+
"overrides": [],
|
|
1268
|
+
"package": "@threenative/core",
|
|
1269
|
+
"signature": "export function debugFlag(name: string): boolean { … }",
|
|
1270
|
+
"situations": ["read a debug toggle from the URL or an environment variable"],
|
|
1271
|
+
"summary": "Read a debug switch from the URL, or from `TN_DEBUG_*` in the environment on a native launch.",
|
|
1272
|
+
"supersedes": [],
|
|
1273
|
+
"symbol": "debugFlag"
|
|
1274
|
+
},
|
|
1193
1275
|
{
|
|
1194
1276
|
"aliases": [
|
|
1195
1277
|
"firing line nearest target crosshair",
|
|
@@ -1353,6 +1435,20 @@
|
|
|
1353
1435
|
"supersedes": [],
|
|
1354
1436
|
"symbol": "ensureVelocityOutput"
|
|
1355
1437
|
},
|
|
1438
|
+
{
|
|
1439
|
+
"aliases": [],
|
|
1440
|
+
"constraints": ["development builds only; a production build publishes nothing"],
|
|
1441
|
+
"example": "import { exposeDebug } from \"@threenative/core\";\nexposeDebug(\"player\", player);\n// then from the console: __THREENATIVE__.debug.player",
|
|
1442
|
+
"importPath": "@threenative/core",
|
|
1443
|
+
"kind": "function",
|
|
1444
|
+
"overrides": [],
|
|
1445
|
+
"package": "@threenative/core",
|
|
1446
|
+
"signature": "export function exposeDebug(name: string, value: unknown): void { … }",
|
|
1447
|
+
"situations": ["expose a game object to a capture script or the console in dev builds"],
|
|
1448
|
+
"summary": "Publish one game object under `__THREENATIVE__.debug` for a capture script or the console.",
|
|
1449
|
+
"supersedes": [],
|
|
1450
|
+
"symbol": "exposeDebug"
|
|
1451
|
+
},
|
|
1356
1452
|
{
|
|
1357
1453
|
"aliases": [],
|
|
1358
1454
|
"constraints": [
|
|
@@ -1478,6 +1574,7 @@
|
|
|
1478
1574
|
"package": "@threenative/core",
|
|
1479
1575
|
"signature": "export class FrameBudget { … }",
|
|
1480
1576
|
"situations": [
|
|
1577
|
+
"show an on-screen frame time meter with p50, p95 and p99 percentiles",
|
|
1481
1578
|
"find out why a game runs slowly on a phone",
|
|
1482
1579
|
"attribute a frame to present wait, simulation, three.js render, or overlay",
|
|
1483
1580
|
"tell whether the GPU is the frame's constraint from a per-frame series, not one lagged timestamp",
|
|
@@ -1635,6 +1732,27 @@
|
|
|
1635
1732
|
"supersedes": [],
|
|
1636
1733
|
"symbol": "GroundSnap"
|
|
1637
1734
|
},
|
|
1735
|
+
{
|
|
1736
|
+
"aliases": [],
|
|
1737
|
+
"constraints": [
|
|
1738
|
+
"`buttons` is the gamepad and `mouseButtons` the mouse; `up`/`down`/`left`/`right` are the directions of `vector(name)`, not the keys that press it",
|
|
1739
|
+
"scroll, pinch and pointer-relative sources are declared on the binding and read through `axis(name)`; a game adds no window listener of its own"
|
|
1740
|
+
],
|
|
1741
|
+
"example": "const game = defineGame({ input: { move: { up: [\"KeyW\"], down: [\"KeyS\"], left: [\"KeyA\"], right: [\"KeyD\"] } }, scenes: { Play } });\n// inside the scene, per frame: ctx.input.axis(\"move\") is 0 at rest and 1 at full tilt",
|
|
1742
|
+
"importPath": "@threenative/core",
|
|
1743
|
+
"kind": "class",
|
|
1744
|
+
"overrides": [],
|
|
1745
|
+
"package": "@threenative/core",
|
|
1746
|
+
"signature": "export class InputMap { … }",
|
|
1747
|
+
"situations": [
|
|
1748
|
+
"map WASD keys to a movement axis instead of reading the held key set in the update loop",
|
|
1749
|
+
"read a jump, a fire or a reload as one named action bound to key, gamepad button and mouse button together",
|
|
1750
|
+
"read mouse look, wheel zoom or a two-finger pinch as an axis the frame loop already ticks"
|
|
1751
|
+
],
|
|
1752
|
+
"summary": "The map a game reads input through: named actions, 2D vectors and scalar axes resolved from keyboard, gamepad, mouse, wheel, pinch and touch. `defineGame({ input })` builds one and hands it to the running game as `ctx.input`, so the usual route is a binding in the config and `ctx.input.axis(\"move\")` in the update. Construct one directly to drive a menu, a replay or a test outside a running game.",
|
|
1753
|
+
"supersedes": [],
|
|
1754
|
+
"symbol": "InputMap"
|
|
1755
|
+
},
|
|
1638
1756
|
{
|
|
1639
1757
|
"aliases": [],
|
|
1640
1758
|
"constraints": [
|
|
@@ -1688,7 +1806,7 @@
|
|
|
1688
1806
|
"it deletes the local and world matrix composes, not the walk itself — three 0.185 recurses into every child regardless",
|
|
1689
1807
|
"a write deeper inside a frozen subtree must call `invalidateStatic`; `TN_RENDERLIST_VALIDATE=1` is what proves it happened"
|
|
1690
1808
|
],
|
|
1691
|
-
"example": "
|
|
1809
|
+
"example": "invalidateStatic(drawbridge);",
|
|
1692
1810
|
"importPath": "@threenative/core",
|
|
1693
1811
|
"kind": "function",
|
|
1694
1812
|
"overrides": [],
|
|
@@ -1751,7 +1869,7 @@
|
|
|
1751
1869
|
"it deletes the local and world matrix composes, not the walk itself — three 0.185 recurses into every child regardless",
|
|
1752
1870
|
"a write deeper inside a frozen subtree must call `invalidateStatic`; `TN_RENDERLIST_VALIDATE=1` is what proves it happened"
|
|
1753
1871
|
],
|
|
1754
|
-
"example": "
|
|
1872
|
+
"example": "invalidateStatic(drawbridge);",
|
|
1755
1873
|
"importPath": "@threenative/core",
|
|
1756
1874
|
"kind": "function",
|
|
1757
1875
|
"overrides": [],
|
|
@@ -1829,11 +1947,30 @@
|
|
|
1829
1947
|
"supersedes": [],
|
|
1830
1948
|
"symbol": "loadAll"
|
|
1831
1949
|
},
|
|
1950
|
+
{
|
|
1951
|
+
"aliases": [],
|
|
1952
|
+
"constraints": [
|
|
1953
|
+
"a non-positive viewport height, a non-positive frustum height or an unprojectable camera throws",
|
|
1954
|
+
"a non-positive depth has no projected scale and returns Infinity"
|
|
1955
|
+
],
|
|
1956
|
+
"example": "const pixels = lodPixelScale(camera, canvas.clientHeight, mesh.position.distanceTo(camera.position));",
|
|
1957
|
+
"importPath": "@threenative/core",
|
|
1958
|
+
"kind": "function",
|
|
1959
|
+
"overrides": [],
|
|
1960
|
+
"package": "@threenative/core",
|
|
1961
|
+
"signature": "export function lodPixelScale(camera: Camera, viewportHeight: number, depth: number): number { … }",
|
|
1962
|
+
"situations": [
|
|
1963
|
+
"pick a level of detail from an object's projected size in pixels on screen",
|
|
1964
|
+
"know how many screen pixels a world-space error covers at a given distance"
|
|
1965
|
+
],
|
|
1966
|
+
"summary": "The screen pixels one world unit covers at `depth` for this camera and viewport. Perspective divides the projected scale by the depth; orthographic has no depth term and uses the frustum height instead. This is the number a level of detail is chosen against: multiply a level's world-space error by it and you have the on-screen error a player can see, which is the comparison `updateModelLods` makes from the baked chain.",
|
|
1967
|
+
"supersedes": [],
|
|
1968
|
+
"symbol": "lodPixelScale"
|
|
1969
|
+
},
|
|
1832
1970
|
{
|
|
1833
1971
|
"aliases": [],
|
|
1834
1972
|
"constraints": [
|
|
1835
1973
|
"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
1974
|
"a write deeper inside a frozen subtree must call `invalidateStatic`; `TN_RENDERLIST_VALIDATE=1` is what proves it happened"
|
|
1838
1975
|
],
|
|
1839
1976
|
"example": "markStatic(island); invalidateStatic(drawbridge);",
|
|
@@ -1843,10 +1980,10 @@
|
|
|
1843
1980
|
"package": "@threenative/core",
|
|
1844
1981
|
"signature": "export function markStatic(root: Object3D): number { … }",
|
|
1845
1982
|
"situations": [
|
|
1846
|
-
"
|
|
1847
|
-
"
|
|
1983
|
+
"freeze a static mesh or subtree so its matrices are not recomputed every frame",
|
|
1984
|
+
"stop the engine recomposing the transforms of props, terrain and buildings each frame"
|
|
1848
1985
|
],
|
|
1849
|
-
"summary": "
|
|
1986
|
+
"summary": "Freeze a subtree nobody moves: `markStatic(root)` composes its transforms once and stops the per-frame recompose. It is the call a game makes on scenery, terrain, buildings and props. The engine re-arms a root whose own transform the game changes; a write deeper inside a frozen subtree is announced with `invalidateStatic(object)`.",
|
|
1850
1987
|
"supersedes": [],
|
|
1851
1988
|
"symbol": "markStatic"
|
|
1852
1989
|
},
|
|
@@ -1865,6 +2002,7 @@
|
|
|
1865
2002
|
"package": "@threenative/core",
|
|
1866
2003
|
"signature": "export class MatrixWorldPass { … }",
|
|
1867
2004
|
"situations": [
|
|
2005
|
+
"update the world matrices of the visible objects each frame instead of the whole scene",
|
|
1868
2006
|
"the per-frame world matrix walk is hot in a profile",
|
|
1869
2007
|
"stop multiplying matrices for hidden models, LOD levels and merged stand-ins",
|
|
1870
2008
|
"a game needs every node walked, exactly as three's own updateMatrixWorld does"
|
|
@@ -1887,6 +2025,28 @@
|
|
|
1887
2025
|
"supersedes": [],
|
|
1888
2026
|
"symbol": "measureThreePose"
|
|
1889
2027
|
},
|
|
2028
|
+
{
|
|
2029
|
+
"aliases": [],
|
|
2030
|
+
"constraints": [
|
|
2031
|
+
"the material is the game's own instance and the split follows the materials the game",
|
|
2032
|
+
"the meshes come back in root's local space and unparented, with the originals still in the",
|
|
2033
|
+
"a skinned or instanced mesh, and a mesh with several materials, is left out — its",
|
|
2034
|
+
"a group where only some meshes carry uv throws naming the label rather than losing the"
|
|
2035
|
+
],
|
|
2036
|
+
"example": "const [hull, deck] = mergeByMaterial(ship, { label: \"ship\" });\n// a piece that must keep moving at run time:\nconst [steady] = mergeByMaterial(ship, { label: \"ship\", skip: (mesh) => mesh.name === \"radar\" });",
|
|
2037
|
+
"importPath": "@threenative/core",
|
|
2038
|
+
"kind": "function",
|
|
2039
|
+
"overrides": ["skip leaves one mesh out of its group and out of the result"],
|
|
2040
|
+
"package": "@threenative/core",
|
|
2041
|
+
"signature": "export function mergeByMaterial(root: Object3D, options: IMergeByMaterialOptions): Mesh[] { … }",
|
|
2042
|
+
"situations": [
|
|
2043
|
+
"collapse a building or ship of dozens of boxes into one draw call per material",
|
|
2044
|
+
"consolidate the static parts of a group before adding it to the scene"
|
|
2045
|
+
],
|
|
2046
|
+
"summary": "Bake a hierarchy's static meshes into one mesh per material, with their transforms baked in. already made; nothing here decides appearance tree: add them to root and remove the sources yourself, or both draw vertices are not its own to bake texture mapping; a missing normal is recomputed",
|
|
2047
|
+
"supersedes": [],
|
|
2048
|
+
"symbol": "mergeByMaterial"
|
|
2049
|
+
},
|
|
1890
2050
|
{
|
|
1891
2051
|
"aliases": [],
|
|
1892
2052
|
"constraints": [
|
|
@@ -2247,7 +2407,7 @@
|
|
|
2247
2407
|
"it deletes the local and world matrix composes, not the walk itself — three 0.185 recurses into every child regardless",
|
|
2248
2408
|
"a write deeper inside a frozen subtree must call `invalidateStatic`; `TN_RENDERLIST_VALIDATE=1` is what proves it happened"
|
|
2249
2409
|
],
|
|
2250
|
-
"example": "
|
|
2410
|
+
"example": "invalidateStatic(drawbridge);",
|
|
2251
2411
|
"importPath": "@threenative/core",
|
|
2252
2412
|
"kind": "function",
|
|
2253
2413
|
"overrides": [],
|
|
@@ -2366,7 +2526,7 @@
|
|
|
2366
2526
|
"it deletes the local and world matrix composes, not the walk itself — three 0.185 recurses into every child regardless",
|
|
2367
2527
|
"a write deeper inside a frozen subtree must call `invalidateStatic`; `TN_RENDERLIST_VALIDATE=1` is what proves it happened"
|
|
2368
2528
|
],
|
|
2369
|
-
"example": "
|
|
2529
|
+
"example": "invalidateStatic(drawbridge);",
|
|
2370
2530
|
"importPath": "@threenative/core",
|
|
2371
2531
|
"kind": "function",
|
|
2372
2532
|
"overrides": [],
|
|
@@ -2412,6 +2572,22 @@
|
|
|
2412
2572
|
"supersedes": [],
|
|
2413
2573
|
"symbol": "resolveAtmosphereParameters"
|
|
2414
2574
|
},
|
|
2575
|
+
{
|
|
2576
|
+
"aliases": [],
|
|
2577
|
+
"constraints": [
|
|
2578
|
+
"pass the measured display rate when you have one; without it the answer is the 60 fallback and says so"
|
|
2579
|
+
],
|
|
2580
|
+
"example": "resolveTargetFps(config, getPlatform()).targetFps;",
|
|
2581
|
+
"importPath": "@threenative/core",
|
|
2582
|
+
"kind": "function",
|
|
2583
|
+
"overrides": [],
|
|
2584
|
+
"package": "@threenative/core",
|
|
2585
|
+
"signature": "export function resolveTargetFps( config: ITargetFpsConfig | undefined, platform: ITargetFpsPlatform | undefined, measuredRefreshHz?: number, ): ITargetFps { … }",
|
|
2586
|
+
"situations": ["my game does frame-rate-dependent work and must not hardcode 60"],
|
|
2587
|
+
"summary": "Read what frame rate a game gets when its config does not say, and why. `display.maxFps` follows the display — capped at 120 on desktop and web, 60 on mobile — rather than the 60 every template used to ship, and an explicit number still wins with `0` still uncapping. The engine calls this itself; a game calls it when it needs the same number for its own frame-rate-dependent work, and `TN_FRAME_BUDGET` reports the resolved target and its source on every window.",
|
|
2588
|
+
"supersedes": [],
|
|
2589
|
+
"symbol": "resolveTargetFps"
|
|
2590
|
+
},
|
|
2415
2591
|
{
|
|
2416
2592
|
"aliases": [],
|
|
2417
2593
|
"constraints": [
|
|
@@ -2602,6 +2778,22 @@
|
|
|
2602
2778
|
"supersedes": [],
|
|
2603
2779
|
"symbol": "skeletonBones"
|
|
2604
2780
|
},
|
|
2781
|
+
{
|
|
2782
|
+
"aliases": [],
|
|
2783
|
+
"constraints": [
|
|
2784
|
+
"pass the measured display rate when you have one; without it the answer is the 60 fallback and says so"
|
|
2785
|
+
],
|
|
2786
|
+
"example": "resolveTargetFps(config, getPlatform()).targetFps;",
|
|
2787
|
+
"importPath": "@threenative/core",
|
|
2788
|
+
"kind": "function",
|
|
2789
|
+
"overrides": [],
|
|
2790
|
+
"package": "@threenative/core",
|
|
2791
|
+
"signature": "export function snapRefreshRate(refreshHz: number): number { … }",
|
|
2792
|
+
"situations": ["my game does frame-rate-dependent work and must not hardcode 60"],
|
|
2793
|
+
"summary": "Read what frame rate a game gets when its config does not say, and why. `display.maxFps` follows the display — capped at 120 on desktop and web, 60 on mobile — rather than the 60 every template used to ship, and an explicit number still wins with `0` still uncapping. The engine calls this itself; a game calls it when it needs the same number for its own frame-rate-dependent work, and `TN_FRAME_BUDGET` reports the resolved target and its source on every window.",
|
|
2794
|
+
"supersedes": [],
|
|
2795
|
+
"symbol": "snapRefreshRate"
|
|
2796
|
+
},
|
|
2605
2797
|
{
|
|
2606
2798
|
"aliases": [],
|
|
2607
2799
|
"constraints": [
|
|
@@ -2822,7 +3014,7 @@
|
|
|
2822
3014
|
"it deletes the local and world matrix composes, not the walk itself — three 0.185 recurses into every child regardless",
|
|
2823
3015
|
"a write deeper inside a frozen subtree must call `invalidateStatic`; `TN_RENDERLIST_VALIDATE=1` is what proves it happened"
|
|
2824
3016
|
],
|
|
2825
|
-
"example": "
|
|
3017
|
+
"example": "invalidateStatic(drawbridge);",
|
|
2826
3018
|
"importPath": "@threenative/core",
|
|
2827
3019
|
"kind": "function",
|
|
2828
3020
|
"overrides": [],
|
|
@@ -2863,7 +3055,7 @@
|
|
|
2863
3055
|
"it deletes the local and world matrix composes, not the walk itself — three 0.185 recurses into every child regardless",
|
|
2864
3056
|
"a write deeper inside a frozen subtree must call `invalidateStatic`; `TN_RENDERLIST_VALIDATE=1` is what proves it happened"
|
|
2865
3057
|
],
|
|
2866
|
-
"example": "
|
|
3058
|
+
"example": "invalidateStatic(drawbridge);",
|
|
2867
3059
|
"importPath": "@threenative/core",
|
|
2868
3060
|
"kind": "function",
|
|
2869
3061
|
"overrides": [],
|
|
@@ -2999,7 +3191,8 @@
|
|
|
2999
3191
|
"constraints": [
|
|
3000
3192
|
"the light must be a DirectionalLight with `castShadow` and a target in the scene",
|
|
3001
3193
|
"clipExtents are half-widths in world units, finest first, strictly increasing",
|
|
3002
|
-
"call `trackCaster(object)` for movers; it enables layer `VIRTUAL_SHADOW_MOVER_LAYER` on the object and its descendants, tracking or untracking refreshes cached levels once, and subsequent mover movement refreshes only when a window moves"
|
|
3194
|
+
"call `trackCaster(object)` for movers; it enables layer `VIRTUAL_SHADOW_MOVER_LAYER` on the object and its descendants, tracking or untracking refreshes cached levels once, and subsequent mover movement refreshes only when a window moves",
|
|
3195
|
+
"call `object.layers.set(VIRTUAL_SHADOW_CASTER_LAYER)` for a mesh that exists only to cast; the level cameras already render that layer and the main camera never does"
|
|
3003
3196
|
],
|
|
3004
3197
|
"example": "const sun = new DirectionalLight(0xffffff, 3);\nsun.castShadow = true;\nsun.shadow.shadowNode = new VirtualShadowNode(sun, { clipExtents: [12, 40, 120] });",
|
|
3005
3198
|
"importPath": "@threenative/core",
|
|
@@ -3409,6 +3602,22 @@
|
|
|
3409
3602
|
"supersedes": [],
|
|
3410
3603
|
"symbol": "subscribeUiState"
|
|
3411
3604
|
},
|
|
3605
|
+
{
|
|
3606
|
+
"aliases": [],
|
|
3607
|
+
"constraints": [
|
|
3608
|
+
"the returned view aliases the caller's buffer; writing to it mutates the source"
|
|
3609
|
+
],
|
|
3610
|
+
"example": "const records = cellPlacements(buffer, { asset: \"tree\", offset: 0, count: 120 });",
|
|
3611
|
+
"importPath": "@threenative/core/world",
|
|
3612
|
+
"kind": "function",
|
|
3613
|
+
"overrides": [],
|
|
3614
|
+
"package": "@threenative/core",
|
|
3615
|
+
"signature": "export function cellPlacements(placements: ArrayBuffer, run: IWorldRun): Float32Array { … }",
|
|
3616
|
+
"situations": ["feed one cell's instance transforms into a batch without copying"],
|
|
3617
|
+
"summary": "Borrow the run's placement records as a live view over the placement buffer.",
|
|
3618
|
+
"supersedes": [],
|
|
3619
|
+
"symbol": "cellPlacements"
|
|
3620
|
+
},
|
|
3412
3621
|
{
|
|
3413
3622
|
"aliases": [],
|
|
3414
3623
|
"constraints": [
|
|
@@ -3455,17 +3664,73 @@
|
|
|
3455
3664
|
"supersedes": [],
|
|
3456
3665
|
"symbol": "Heightfield"
|
|
3457
3666
|
},
|
|
3667
|
+
{
|
|
3668
|
+
"aliases": [],
|
|
3669
|
+
"constraints": [
|
|
3670
|
+
"the sampler reads the game's data; the framework never selects a terrain shape"
|
|
3671
|
+
],
|
|
3672
|
+
"example": "const sampleHeight = heightSamplerFromHeightmap(terrain, extent, await loadWorldHeightmap(url));",
|
|
3673
|
+
"importPath": "@threenative/core/world",
|
|
3674
|
+
"kind": "function",
|
|
3675
|
+
"overrides": [],
|
|
3676
|
+
"package": "@threenative/core",
|
|
3677
|
+
"signature": "export function heightSamplerFromHeightmap( terrain: IWorldTerrain, extent: IWorldExtent, data: Uint16Array, ): (x: number, z: number) => number { … }",
|
|
3678
|
+
"situations": [
|
|
3679
|
+
"turn an exported raw heightmap into terrain collision and rendering",
|
|
3680
|
+
"query ground height from a Blender-authored world package"
|
|
3681
|
+
],
|
|
3682
|
+
"summary": "Build a game-usable `sampleHeight` from a raw v1 heightmap. The returned function interpolates bilinearly in world units and clamps to the map edges, so it plugs straight into `Heightfield.fromSampler` and `TerrainTiles`. Height is `heightMin + v / 65535 * (heightMax - heightMin)` at vertex `(column, row)`.",
|
|
3683
|
+
"supersedes": [],
|
|
3684
|
+
"symbol": "heightSamplerFromHeightmap"
|
|
3685
|
+
},
|
|
3686
|
+
{
|
|
3687
|
+
"aliases": [],
|
|
3688
|
+
"constraints": [
|
|
3689
|
+
"the package's world.json must carry `terrain.layers.table` and `terrain.layers.splat`, written by the `export_terrain_layers.py` recipe",
|
|
3690
|
+
"WebGPU allows 16 sampled textures per stage: planes + diffuse maps + normal maps must fit"
|
|
3691
|
+
],
|
|
3692
|
+
"example": "const surface = await loadTerrainSplat({ assets: ctx.assets, url: \"world/world.json\" });\nconst world = await WorldCells.load({ assets: ctx.assets, url: \"world/world.json\", surface, follow, ring: 2 });",
|
|
3693
|
+
"importPath": "@threenative/core/world",
|
|
3694
|
+
"kind": "function",
|
|
3695
|
+
"overrides": [
|
|
3696
|
+
"every value comes from the package's table; the returned material is the game's to adjust"
|
|
3697
|
+
],
|
|
3698
|
+
"package": "@threenative/core",
|
|
3699
|
+
"signature": "export async function loadTerrainSplat(options: ILoadTerrainSplatOptions): Promise<Material> { … }",
|
|
3700
|
+
"situations": [
|
|
3701
|
+
"terrain textured by splat masks exported from Blender with the world package",
|
|
3702
|
+
"the game's terrain should match the DCC's terrain material without a second copy"
|
|
3703
|
+
],
|
|
3704
|
+
"summary": "The splat terrain surface a world package describes, for `WorldCells.load({ surface })`. Layers blend over a base by mask channels read as linear data (the masks ship raw, beside the heightmap, so no cook moves a blend threshold), with noise-broken edges and macro brightness variation. Texture sets tile in world metres on the package's ground plane (x, -z: a Z-up authoring tool's x and y), cliffs can be triplanar, and the base plus any layer that asks carries a normal map. Nothing here is a look choice: textures, tiles, tints, thresholds and noise scales all come from the package's table, which the game authors once and its DCC shares.",
|
|
3705
|
+
"supersedes": [],
|
|
3706
|
+
"symbol": "loadTerrainSplat"
|
|
3707
|
+
},
|
|
3708
|
+
{
|
|
3709
|
+
"aliases": [],
|
|
3710
|
+
"constraints": ["a non-OK response throws; bytes are byte-swapped only on a big-endian host"],
|
|
3711
|
+
"example": "const data = await loadWorldHeightmap(\"/world/terrain/heightmap.u16\");",
|
|
3712
|
+
"importPath": "@threenative/core/world",
|
|
3713
|
+
"kind": "function",
|
|
3714
|
+
"overrides": [],
|
|
3715
|
+
"package": "@threenative/core",
|
|
3716
|
+
"signature": "export async function loadWorldHeightmap(url: string): Promise<Uint16Array> { … }",
|
|
3717
|
+
"situations": ["load a world package's heightmap once before building terrain"],
|
|
3718
|
+
"summary": "Fetch a raw little-endian uint16 heightmap and expose it as samples.",
|
|
3719
|
+
"supersedes": [],
|
|
3720
|
+
"symbol": "loadWorldHeightmap"
|
|
3721
|
+
},
|
|
3458
3722
|
{
|
|
3459
3723
|
"aliases": ["stream terrain across chunks"],
|
|
3460
3724
|
"constraints": [
|
|
3461
3725
|
"sampleHeight and surface are required game choices; no landform or surface preset is installed",
|
|
3462
|
-
"residentTileBudget and residentByteBudget are hard caps; a tile that cannot fit throws"
|
|
3726
|
+
"residentTileBudget and residentByteBudget are hard caps; a tile that cannot fit throws",
|
|
3727
|
+
"seam gap, LOD pop and the rendered-vertex finiteness scan are measurements that are off by default; TN_TERRAIN_VALIDATE=1, ?tnTerrainValidate=1 or validate: true runs them, and maxSeamGap, maxVisualSeamGap and maxLodPop report undefined while they are off"
|
|
3463
3728
|
],
|
|
3464
3729
|
"example": "const tiles = new TerrainTiles({ sampleHeight, surface: gameSurface(), tileSize: 256, tileResolution: 129, residentTileBudget: 25, residentByteBudget: 32_000_000 });",
|
|
3465
3730
|
"importPath": "@threenative/core/world",
|
|
3466
3731
|
"kind": "class",
|
|
3467
3732
|
"overrides": [
|
|
3468
|
-
"tileSize, tileResolution, lodFactors, lodDistances, skirtDepth, streamRadius, and budgets"
|
|
3733
|
+
"tileSize, tileResolution, lodFactors, lodDistances, skirtDepth, streamRadius, colliderRadius, validate, and budgets"
|
|
3469
3734
|
],
|
|
3470
3735
|
"package": "@threenative/core",
|
|
3471
3736
|
"signature": "export class TerrainTiles extends Object3D implements IComputeDriven { … }",
|
|
@@ -3478,9 +3743,177 @@
|
|
|
3478
3743
|
"supersedes": [],
|
|
3479
3744
|
"symbol": "TerrainTiles"
|
|
3480
3745
|
},
|
|
3746
|
+
{
|
|
3747
|
+
"aliases": [],
|
|
3748
|
+
"constraints": ["off by default: it is the work it checks, every frame"],
|
|
3749
|
+
"example": "// Reads its own the way `renderListValidationRequested` does: a native launch sets the\n// environment variable, a browser asks with the query string, a test or harness sets the\n// global. `0` and `false` are off, so a saved URL that enabled it still says \"off\".\nconst tiles = new TerrainTiles({ ...options, validate: terrainValidationRequested() });",
|
|
3750
|
+
"importPath": "@threenative/core/world",
|
|
3751
|
+
"kind": "function",
|
|
3752
|
+
"overrides": [],
|
|
3753
|
+
"package": "@threenative/core",
|
|
3754
|
+
"signature": "export function terrainValidationRequested(): boolean { … }",
|
|
3755
|
+
"situations": [
|
|
3756
|
+
"turn terrain's per-frame seam, LOD pop and vertex checks on for one run",
|
|
3757
|
+
"assert the terrain geometry a game streams before it ships"
|
|
3758
|
+
],
|
|
3759
|
+
"summary": "Whether `TN_TERRAIN_VALIDATE` asks for terrain validation on this launch.",
|
|
3760
|
+
"supersedes": [],
|
|
3761
|
+
"symbol": "terrainValidationRequested"
|
|
3762
|
+
},
|
|
3763
|
+
{
|
|
3764
|
+
"aliases": [],
|
|
3765
|
+
"constraints": [
|
|
3766
|
+
"validation only checks structure and ranges; it never fetches the heightmap or GLBs"
|
|
3767
|
+
],
|
|
3768
|
+
"example": "const { ok, errors } = validateWorldPackage(json, { placementsByteLength: buffer.byteLength });",
|
|
3769
|
+
"importPath": "@threenative/core/world",
|
|
3770
|
+
"kind": "function",
|
|
3771
|
+
"overrides": [],
|
|
3772
|
+
"package": "@threenative/core",
|
|
3773
|
+
"signature": "export function validateWorldPackage( manifest: unknown, options: IWorldPackageValidationOptions, ): { … }",
|
|
3774
|
+
"situations": [
|
|
3775
|
+
"check a Blender-exported world package before the runtime attaches anything",
|
|
3776
|
+
"report why a world package cannot be streamed"
|
|
3777
|
+
],
|
|
3778
|
+
"summary": "Validate a `world.json` manifest against the v1 contract. Never throws on garbage input: a non-object manifest is `WORLD_MALFORMED`. Every problem is collected, so an exporter sees the complete list at once.",
|
|
3779
|
+
"supersedes": [],
|
|
3780
|
+
"symbol": "validateWorldPackage"
|
|
3781
|
+
},
|
|
3782
|
+
{
|
|
3783
|
+
"aliases": [],
|
|
3784
|
+
"constraints": [
|
|
3785
|
+
"surface is the game's; this class creates no material, colour or geometry",
|
|
3786
|
+
"budgets are hard caps that report pressure instead of over-committing",
|
|
3787
|
+
"model loads are bounded by `concurrency` (default 12) across every resident cell, not per cell",
|
|
3788
|
+
"refilters are bounded by `rebuildsPerUpdate` (default 16) per update, nearest cell first",
|
|
3789
|
+
"admission is bounded by `admissionBudgetMs` (default 2) per update across every path, plus at most one unit each for terrain and props; while props are queued terrain takes at most half, so neither starves the other, and a deferred cell keeps drawing what it has",
|
|
3790
|
+
"SkinnedMesh parts are skipped; an instanced copy would draw one rest pose",
|
|
3791
|
+
"a baked chain's switch distances are measured against `autoLod` (default 4 px of error over 60° and 1080 raster rows), because an instanced draw cannot select a level per instance; an asset with authored `lods` never consults it",
|
|
3792
|
+
"`prewarmed` resolves once every prewarmed shared batch has been drawn; a game with a loading screen waits on it, and `stats().pendingPrewarm` is the same gate as a number",
|
|
3793
|
+
"every `asset:level:part` is one InstancedMesh for the main pass, plus one caster InstancedMesh per world-grid square of `clusterSize` on the shadow caster layer, so the main pass draws one mesh per key and a shadow level submits only the squares it covers",
|
|
3794
|
+
"two definitions the asset loader resolves to one model — the same cooked `glb`, the same `lods` at the same distances, the same `maxDistance` and bounds — are one asset under the lexicographically smallest id: one model load, one set of `asset:level:part` keys, one prewarm and one refcount, released when the last cell holding any member of the group leaves the ring; `TN_WORLD_ASSET_ALIAS` reports how many of the package's assets are really distinct",
|
|
3795
|
+
"the main pass mesh draws only the squares the render camera's frustum covers — on by default, narrowed once per frame for every main batch by the engine's render-cadence dispatch, never for an orthographic camera — a batch with nothing to draw is hidden rather than submitted at `count 0`, and `TN_WORLD_MAIN_CULL` reports both every five seconds",
|
|
3796
|
+
"a loaded chunk is merged by material before it is added, so it submits one draw per material rather than one per node; a skinned, multi-material or morph-target mesh, one carrying a baked AutoLOD chain, and an instanced mesh past `chunkMergeMaxTriangles` (default 43,690 triangles) or with a shape over 2,048 triangles, all keep their own geometry; a material group crossing 131,072 vertices (4 MiB of position + normal + uv) is split into several meshes in traversal order instead of one giant upload, indexed parts keep their index, and `TN_WORLD_CHUNK_MERGE` reports what the merge did and the bytes it left",
|
|
3797
|
+
"`shadows.castDistance` is accepted and ignored (clusters replaced it); `shadows.invalidate` is called at most once a second after streamed records changed"
|
|
3798
|
+
],
|
|
3799
|
+
"example": "const world = await WorldCells.load({ url: \"/world/world.json\", surface, follow, ring: 1, budgets: { residentCells: 25, instances: 20000, bytes: 8000000 } });\nscene.add(world);\nworld.update();",
|
|
3800
|
+
"importPath": "@threenative/core/world",
|
|
3801
|
+
"kind": "class",
|
|
3802
|
+
"overrides": [
|
|
3803
|
+
"ring, budgets, terrain tile size/resolution, terrain stream and collider radius, `transparentScatter`, `clusterSize` and `shadows.invalidate`, load `concurrency`, `rebuildsPerUpdate`, `admissionBudgetMs` and the package's per-asset maxDistance"
|
|
3804
|
+
],
|
|
3805
|
+
"package": "@threenative/core",
|
|
3806
|
+
"signature": "export class WorldCells extends Group implements IComputeDriven { … }",
|
|
3807
|
+
"situations": [
|
|
3808
|
+
"stream a large Blender-authored world by cell instead of one huge GLB",
|
|
3809
|
+
"keep scattered props and hand-placed chunks resident around a moving player",
|
|
3810
|
+
"honour per-asset draw distances and hard streaming budgets without a mid-frame throw"
|
|
3811
|
+
],
|
|
3812
|
+
"summary": "Stream a Blender-authored world package by cell and keep it resident around a followed point. The class composes `TerrainTiles` for the package's heightmap, builds one `InstancedBatch` per resident cell asset run, distance level and mesh part, and loads hand-placed chunk GLBs through `loadAll` + `addInSlices`. Ring residency, per-asset `maxDistance` filtering, the per-asset `lods` levels, hard budgets and generation-tokened cancellation all live here; every geometry, material and surface still comes from the package's GLBs and the game. An asset is drawn per part, not per model: a GLB with several primitives is one `InstancedBatch` each, and a scattered part whose own material is transparent draws as an alpha cutout unless the game asks for blending, because an `InstancedMesh` cannot sort its instances. An asset whose package entry names no `lods` is drawn at the levels its own model carries a baked AutoLOD chain for: the levels an instanced draw cannot reach by itself, switched at the distance their error projects over the `autoLod` viewport. Authored `lods` win; a chain is only a fallback.",
|
|
3813
|
+
"supersedes": [],
|
|
3814
|
+
"symbol": "WorldCells"
|
|
3815
|
+
},
|
|
3816
|
+
{
|
|
3817
|
+
"aliases": [],
|
|
3818
|
+
"constraints": [
|
|
3819
|
+
"absolute paths, Windows drive letters, backslashes and any `..` segment throw MetaHumanAssetError with TN_MH_PATH_ESCAPE before the path is joined onto a root"
|
|
3820
|
+
],
|
|
3821
|
+
"example": "assertAssetPath(\"metahuman/specimen.glb\");",
|
|
3822
|
+
"importPath": "@threenative/metahuman",
|
|
3823
|
+
"kind": "function",
|
|
3824
|
+
"overrides": [],
|
|
3825
|
+
"package": "@threenative/metahuman",
|
|
3826
|
+
"requires": ["npm i @threenative/metahuman"],
|
|
3827
|
+
"signature": "export function assertAssetPath(path: string): string { … }",
|
|
3828
|
+
"situations": ["reject a MetaHuman asset path that would read outside the game's asset root"],
|
|
3829
|
+
"summary": "Keeps a caller-supplied asset path inside the asset directory.",
|
|
3830
|
+
"supersedes": [],
|
|
3831
|
+
"symbol": "assertAssetPath"
|
|
3832
|
+
},
|
|
3833
|
+
{
|
|
3834
|
+
"aliases": [],
|
|
3835
|
+
"constraints": [
|
|
3836
|
+
"the model is loaded through the game's own asset loader and cloned per instance, so",
|
|
3837
|
+
"the rig's own GUI-to-raw mapping runs; the adapter never re-derives it, and a LOD",
|
|
3838
|
+
"an undeclared control, an out-of-domain value, an undeclared LOD and any call after",
|
|
3839
|
+
"in a native host that installed the MetaHuman resident the C++ evaluator runs and"
|
|
3840
|
+
],
|
|
3841
|
+
"example": "const human = await loadMetaHuman({ assets: ctx.assets, model: \"metahuman/head.glb\",\n dna: \"metahuman/head.dna\", bindings: \"metahuman/bindings.json\" });\n human.setControls({ jawOpen: 0.4 });\n// in the scene update, after any body animation\n human.update();",
|
|
3842
|
+
"importPath": "@threenative/metahuman",
|
|
3843
|
+
"kind": "function",
|
|
3844
|
+
"overrides": [],
|
|
3845
|
+
"package": "@threenative/metahuman",
|
|
3846
|
+
"requires": ["npm i @threenative/metahuman"],
|
|
3847
|
+
"signature": "loadMetaHuman = (options: ILoadMetaHumanOptions): Promise<IMetaHuman> => createMetaHuman( { … }",
|
|
3848
|
+
"situations": [
|
|
3849
|
+
"put a MetaHuman head in a browser game without an Unreal import or a baked clip"
|
|
3850
|
+
],
|
|
3851
|
+
"summary": "Load a prepared MetaHuman head and drive its expression from the browser's WASM evaluator: declared faceboard controls in, joint deltas and morph weights out, applied to an ordinary Three.js object graph. two characters never write each other's face and nothing is disposed that `ctx.assets` still owns switch re-evaluates the current controls before the replacement mesh is shown `dispose()` throw, each with a stable `code`; nothing is clamped or coerced `diagnostics().backend` reads \"native\"; the game code does not change",
|
|
3852
|
+
"supersedes": [],
|
|
3853
|
+
"symbol": "loadMetaHuman"
|
|
3854
|
+
},
|
|
3855
|
+
{
|
|
3856
|
+
"aliases": [],
|
|
3857
|
+
"constraints": ["`code` is part of the public surface; renaming one is a breaking change"],
|
|
3858
|
+
"example": "if (error instanceof MetaHumanAssetError && error.code === \"TN_MH_HASH_MISMATCH\") refetch();",
|
|
3859
|
+
"importPath": "@threenative/metahuman",
|
|
3860
|
+
"kind": "class",
|
|
3861
|
+
"overrides": [],
|
|
3862
|
+
"package": "@threenative/metahuman",
|
|
3863
|
+
"requires": ["npm i @threenative/metahuman"],
|
|
3864
|
+
"signature": "export class MetaHumanAssetError extends Error { … }",
|
|
3865
|
+
"situations": ["branch on why a MetaHuman asset or evaluator call was refused"],
|
|
3866
|
+
"summary": "The rejection every check in this package raises, carrying a stable machine-readable code.",
|
|
3867
|
+
"supersedes": [],
|
|
3868
|
+
"symbol": "MetaHumanAssetError"
|
|
3869
|
+
},
|
|
3870
|
+
{
|
|
3871
|
+
"aliases": [],
|
|
3872
|
+
"constraints": [
|
|
3873
|
+
"the binary's SHA-256 is checked against the shipped manifest before it is",
|
|
3874
|
+
"every returned array is a copy, so no view survives a memory growth; a disposed",
|
|
3875
|
+
"a native host that installed the MetaHuman resident gets its C++ evaluator from"
|
|
3876
|
+
],
|
|
3877
|
+
"example": "const rig = await RigEvaluator.create(dna); rig.setGuiControls(gui); rig.evaluate(true); rig.jointOutputs();",
|
|
3878
|
+
"importPath": "@threenative/metahuman",
|
|
3879
|
+
"kind": "class",
|
|
3880
|
+
"overrides": [],
|
|
3881
|
+
"package": "@threenative/metahuman",
|
|
3882
|
+
"requires": ["npm i @threenative/metahuman"],
|
|
3883
|
+
"signature": "export class RigEvaluator implements IRigEvaluator { … }",
|
|
3884
|
+
"situations": [
|
|
3885
|
+
"drive a prepared MetaHuman head's expression from the browser without an Unreal import"
|
|
3886
|
+
],
|
|
3887
|
+
"summary": "One MetaHuman head rig over the checksum-verified browser WASM build of the shared OpenRigLogic ABI: faceboard GUI controls in, joint deltas, blend shape weights and animated map weights out. instantiated, and nothing is fetched from a CDN evaluator throws instead of reading freed memory `create` and the WASM is never fetched; `RigEvaluator.backend()` says which one runs",
|
|
3888
|
+
"supersedes": [],
|
|
3889
|
+
"symbol": "RigEvaluator"
|
|
3890
|
+
},
|
|
3891
|
+
{
|
|
3892
|
+
"aliases": [],
|
|
3893
|
+
"constraints": [
|
|
3894
|
+
"fails closed: a missing key, a wrong type, a hash that differs from the bytes on",
|
|
3895
|
+
"reads the rig's real names and the GLB's real node, mesh and target counts, so it"
|
|
3896
|
+
],
|
|
3897
|
+
"example": "const bindings = validateMetaHumanAssets({ bindings: parsed, dnaSha256, glbSha256, rig, gltf });",
|
|
3898
|
+
"importPath": "@threenative/metahuman",
|
|
3899
|
+
"kind": "function",
|
|
3900
|
+
"overrides": [],
|
|
3901
|
+
"package": "@threenative/metahuman",
|
|
3902
|
+
"requires": ["npm i @threenative/metahuman"],
|
|
3903
|
+
"signature": "export function validateMetaHumanAssets(input: IMetaHumanAssetInput): IMetaHumanBindings { … }",
|
|
3904
|
+
"situations": [
|
|
3905
|
+
"refuse a MetaHuman specimen whose bindings point at joints, nodes, blend shape"
|
|
3906
|
+
],
|
|
3907
|
+
"summary": "Checks one prepared specimen — bindings sidecar, DNA and GLB — against the rig it claims to drive, and returns the bindings only when every name, index, domain and hash holds up. channels, morph targets or LODs the loaded files do not contain disk and an index past the end of its array are rejections, each with a stable code cannot pass a hand-written sidecar the rig cannot drive",
|
|
3908
|
+
"supersedes": [],
|
|
3909
|
+
"symbol": "validateMetaHumanAssets"
|
|
3910
|
+
},
|
|
3481
3911
|
{
|
|
3482
3912
|
"aliases": ["pick up item"],
|
|
3483
3913
|
"constraints": ["add the area to the physics context before stepping the world"],
|
|
3914
|
+
"deprecated": [
|
|
3915
|
+
"Constructor option `world` is deprecated; pass an IPhysicsContext as `physics` instead. Area3D itself is not deprecated."
|
|
3916
|
+
],
|
|
3484
3917
|
"example": "const goal = new Area3D({ physics: ctx.physics, shape: CollisionShape3D.sphere(1.2), position: { x: 0, y: 0.5, z: -8 } });",
|
|
3485
3918
|
"importPath": "@threenative/physics",
|
|
3486
3919
|
"kind": "class",
|
|
@@ -3538,6 +3971,9 @@
|
|
|
3538
3971
|
"run jump coins goal"
|
|
3539
3972
|
],
|
|
3540
3973
|
"constraints": ["use moveAndSlide inside the physics update"],
|
|
3974
|
+
"deprecated": [
|
|
3975
|
+
"Constructor option `world` is deprecated; pass an IPhysicsContext as `physics` instead. CharacterBody3D itself is not deprecated."
|
|
3976
|
+
],
|
|
3541
3977
|
"example": "const body = new CharacterBody3D({ object: hero, physics: ctx.physics, shape: CollisionShape3D.capsule(0.5, 0.35) });",
|
|
3542
3978
|
"importPath": "@threenative/physics",
|
|
3543
3979
|
"kind": "class",
|
|
@@ -3589,6 +4025,9 @@
|
|
|
3589
4025
|
{
|
|
3590
4026
|
"aliases": [],
|
|
3591
4027
|
"constraints": ["both bodies must belong to the same physics context"],
|
|
4028
|
+
"deprecated": [
|
|
4029
|
+
"Constructor option `world` is deprecated; pass an IPhysicsContext as `physics` instead. Joint3D itself is not deprecated."
|
|
4030
|
+
],
|
|
3592
4031
|
"example": "const hinge = Joint3D.hinge({ physics: ctx.physics, bodyA: beam, bodyB: bob, anchorA: { x: 0, y: 0, z: 0 }, anchorB: { x: 0, y: 2.4, z: 0 }, axis: { x: 1, y: 0, z: 0 } });",
|
|
3593
4032
|
"importPath": "@threenative/physics",
|
|
3594
4033
|
"kind": "class",
|
|
@@ -3641,6 +4080,9 @@
|
|
|
3641
4080
|
{
|
|
3642
4081
|
"aliases": [],
|
|
3643
4082
|
"constraints": ["register rapier() in the game plugin list before using bodies"],
|
|
4083
|
+
"deprecated": [
|
|
4084
|
+
"Constructor option `world` is deprecated; pass an IPhysicsContext as `physics` instead. RigidBody3D itself is not deprecated."
|
|
4085
|
+
],
|
|
3644
4086
|
"example": "const crate = new RigidBody3D({ object, physics: ctx.physics, shape: CollisionShape3D.box(1, 1, 1), mass: 8 });",
|
|
3645
4087
|
"importPath": "@threenative/physics",
|
|
3646
4088
|
"kind": "class",
|
|
@@ -3680,6 +4122,34 @@
|
|
|
3680
4122
|
"supersedes": [],
|
|
3681
4123
|
"symbol": "softBodyCollision"
|
|
3682
4124
|
},
|
|
4125
|
+
{
|
|
4126
|
+
"aliases": [
|
|
4127
|
+
"racing car racing kart drift vehicle go-kart",
|
|
4128
|
+
"suspension wheel traction tyre grip",
|
|
4129
|
+
"accelerator pedal handbrake steering wheel",
|
|
4130
|
+
"rescue respawn flip back on track"
|
|
4131
|
+
],
|
|
4132
|
+
"constraints": [
|
|
4133
|
+
"write engineForce, brake and steering every physics update; a car with no input does not move",
|
|
4134
|
+
"suspensionStiffness is a frequency squared, not newtons per metre; 100 is a road car and 20 bottoms out"
|
|
4135
|
+
],
|
|
4136
|
+
"example": "const car = new VehicleBody3D({ object: chassis, physics: ctx.physics, shape: CollisionShape3D.box(1.6, 0.5, 3.6), mass: 900, wheels: [{ position: { x: 0.8, y: -0.15, z: -1.2 }, wheelRadius: 0.34, suspensionRestLength: 0.3, suspensionStiffness: 100, dampingCompression: 2.3, dampingRelaxation: 4.4, wheelFrictionSlip: 10.5, maxSuspensionTravel: 0.3, useAsSteering: true, useAsTraction: false }] });",
|
|
4137
|
+
"importPath": "@threenative/physics",
|
|
4138
|
+
"kind": "class",
|
|
4139
|
+
"overrides": [
|
|
4140
|
+
"a wheel ray never hits the chassis it hangs from, and it honours the chassis collision mask",
|
|
4141
|
+
"continuousCollision is on for the chassis, so a fast car cannot tunnel through a wall"
|
|
4142
|
+
],
|
|
4143
|
+
"package": "@threenative/physics",
|
|
4144
|
+
"signature": "export class VehicleBody3D extends RigidBody3D { … }",
|
|
4145
|
+
"situations": [
|
|
4146
|
+
"drive a car, truck or bike around a track",
|
|
4147
|
+
"make a vehicle roll over kerbs, brake into a corner or stop at a wall"
|
|
4148
|
+
],
|
|
4149
|
+
"summary": "Drive a car on ray-cast suspension instead of faking speed and heading.",
|
|
4150
|
+
"supersedes": [],
|
|
4151
|
+
"symbol": "VehicleBody3D"
|
|
4152
|
+
},
|
|
3683
4153
|
{
|
|
3684
4154
|
"aliases": ["close engagement range"],
|
|
3685
4155
|
"constraints": [
|
|
@@ -4139,6 +4609,23 @@
|
|
|
4139
4609
|
"supersedes": [],
|
|
4140
4610
|
"symbol": "assertCaptureNotBlank"
|
|
4141
4611
|
},
|
|
4612
|
+
{
|
|
4613
|
+
"aliases": [],
|
|
4614
|
+
"constraints": ["the assertion throws instead of returning a false pass"],
|
|
4615
|
+
"example": "assertFrameShowsSomething(png, \"first frame\");",
|
|
4616
|
+
"importPath": "@threenative/playtest/capture",
|
|
4617
|
+
"kind": "function",
|
|
4618
|
+
"overrides": [],
|
|
4619
|
+
"package": "@threenative/playtest",
|
|
4620
|
+
"signature": "assertFrameShowsSomething = assertCaptureNotBlank",
|
|
4621
|
+
"situations": [
|
|
4622
|
+
"guard a visual playtest against a blank frame",
|
|
4623
|
+
"prove a screenshot contains more than a loading surface"
|
|
4624
|
+
],
|
|
4625
|
+
"summary": "Fail closed when a screenshot is blank or uniform.",
|
|
4626
|
+
"supersedes": [],
|
|
4627
|
+
"symbol": "assertFrameShowsSomething"
|
|
4628
|
+
},
|
|
4142
4629
|
{
|
|
4143
4630
|
"aliases": [],
|
|
4144
4631
|
"constraints": [],
|
|
@@ -4409,6 +4896,20 @@
|
|
|
4409
4896
|
"supersedes": [],
|
|
4410
4897
|
"symbol": "connectPlaytestBridgeTransport"
|
|
4411
4898
|
},
|
|
4899
|
+
{
|
|
4900
|
+
"aliases": [],
|
|
4901
|
+
"constraints": ["a private Xvfb is software, so a rate read there measures the X server"],
|
|
4902
|
+
"example": "import { decideDisplayStrategy } from \"@threenative/playtest/runner\";\nconst lane = decideDisplayStrategy({ env: process.env, platform: \"linux\" });\nif (lane.kind === \"private-xvfb\") throw new Error(\"refuse to judge this frame rate\");",
|
|
4903
|
+
"importPath": "@threenative/playtest/runner",
|
|
4904
|
+
"kind": "function",
|
|
4905
|
+
"overrides": [],
|
|
4906
|
+
"package": "@threenative/playtest",
|
|
4907
|
+
"signature": "export function decideDisplayStrategy(input: IDisplayDecisionInput): IDisplayStrategy { … }",
|
|
4908
|
+
"situations": ["judge whether a measured frame rate came from a display that can carry one"],
|
|
4909
|
+
"summary": "Decide which display a pixel-producing run paints on, the same decision the runner makes.",
|
|
4910
|
+
"supersedes": [],
|
|
4911
|
+
"symbol": "decideDisplayStrategy"
|
|
4912
|
+
},
|
|
4412
4913
|
{
|
|
4413
4914
|
"aliases": [],
|
|
4414
4915
|
"constraints": ["the mailbox lifecycle must be disposed after the run"],
|
|
@@ -6190,22 +6691,33 @@
|
|
|
6190
6691
|
"package": "@threenative/core",
|
|
6191
6692
|
"importPath": "src/game.ts",
|
|
6192
6693
|
"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.",
|
|
6694
|
+
"signature": "renderer.projection?: boolean | { materialChecks?: 'spread' | 'everyFrame' }",
|
|
6695
|
+
"summary": "The engine's scene-render projection — an internal mirror that collapses repeated draws, including animated skinned rigs that share a geometry and material into one palette draw per pass — on by default. Set `renderer.projection: false` to decline it, or `projection: { materialChecks: 'everyFrame' }` to keep it and pay for a per-material check on every frame.",
|
|
6195
6696
|
"situations": [
|
|
6697
|
+
"a crowd of animated characters draws slowly",
|
|
6698
|
+
"many SkinnedMesh copies of one rig, each its own draw call",
|
|
6196
6699
|
"the game got slower after the projection engaged",
|
|
6197
6700
|
"turn off the render projection, batching, or the instanced mirror",
|
|
6198
6701
|
"draw count fell but frame time did not",
|
|
6199
6702
|
"a multi-second freeze when the mirror first engages",
|
|
6200
|
-
"opt out of an engine render optimizer"
|
|
6703
|
+
"opt out of an engine render optimizer",
|
|
6704
|
+
"thousands of props each with their own material, one colour apart",
|
|
6705
|
+
"a material edit takes a few frames to show up",
|
|
6706
|
+
"check every batched material every frame anyway"
|
|
6201
6707
|
],
|
|
6202
6708
|
"example": "renderer: { projection: false } // in threenative.config.ts",
|
|
6203
6709
|
"constraints": [
|
|
6204
6710
|
"Unset is the shipping behaviour: the projection runs. Only an explicit `false` declines it.",
|
|
6205
6711
|
"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."
|
|
6712
|
+
"TN_RENDER_PROJECTION still reports the verdict, with reasonCode `disabled` rather than one of the measured declines.",
|
|
6713
|
+
"`materialChecks: 'spread'` is the default: a bounded slice of the batched materials is proved per frame instead of all of them, so a frame of 4,096 colour-only materials costs 512 checks rather than 4,096. A base-colour edit is never delayed — that write is O(1) per member.",
|
|
6714
|
+
"The price of `spread` is staleness on every other material edit: a material that gains a roughness, a map or a define still leaves its group and is drawn exactly, up to `materialCheckStaleFrames` frames later. TN_RENDER_PROJECTION reports that bound; `materialChecks: 'everyFrame'` sets it to 0 and restores the per-member, per-frame check.",
|
|
6715
|
+
"Any other `materialChecks` value throws at startup rather than falling back to a default."
|
|
6716
|
+
],
|
|
6717
|
+
"overrides": [
|
|
6718
|
+
"renderer.projection: false declines the whole mirror and costs nothing to decline",
|
|
6719
|
+
"renderer: { projection: { materialChecks: 'everyFrame' } } proves every batched material every frame instead of the default bounded slice"
|
|
6207
6720
|
],
|
|
6208
|
-
"overrides": [],
|
|
6209
6721
|
"supersedes": [],
|
|
6210
6722
|
"aliases": []
|
|
6211
6723
|
},
|
|
@@ -6520,7 +7032,7 @@
|
|
|
6520
7032
|
],
|
|
6521
7033
|
"notOwned": [
|
|
6522
7034
|
{
|
|
6523
|
-
"guidance": "The framework owns no save/load system. Write a save module in your project's src/ using your own plain state shape (for example, ctx.state), and read agent-docs/gameplay-recipes.md for the template recipe.",
|
|
7035
|
+
"guidance": "The framework owns no save/load system. Write a save module in your project's src/ using your own plain state shape (for example, ctx.state), and read node_modules/create-threenative/agent-docs/references/gameplay-recipes.md for the template recipe.",
|
|
6524
7036
|
"id": "save-load",
|
|
6525
7037
|
"situations": [
|
|
6526
7038
|
"persist a player's progress between sessions",
|
|
@@ -6529,12 +7041,12 @@
|
|
|
6529
7041
|
]
|
|
6530
7042
|
},
|
|
6531
7043
|
{
|
|
6532
|
-
"guidance": "The framework owns no inventory system. Write inventory state in your project's src/ with plain objects under ctx.state, and read agent-docs/gameplay-recipes.md for the template recipe.",
|
|
7044
|
+
"guidance": "The framework owns no inventory system. Write inventory state in your project's src/ with plain objects under ctx.state, and read node_modules/create-threenative/agent-docs/references/gameplay-recipes.md for the template recipe.",
|
|
6533
7045
|
"id": "inventory",
|
|
6534
7046
|
"situations": ["inventory system", "manage inventory contents"]
|
|
6535
7047
|
},
|
|
6536
7048
|
{
|
|
6537
|
-
"guidance": "The framework owns no dialogue system. Write the conversation data and state in your project's src/; render it with the template UI (starter uses src/ui/), and read agent-docs/gameplay-recipes.md.",
|
|
7049
|
+
"guidance": "The framework owns no dialogue system. Write the conversation data and state in your project's src/; render it with the template UI (starter uses src/ui/), and read node_modules/create-threenative/agent-docs/references/gameplay-recipes.md.",
|
|
6538
7050
|
"id": "dialogue",
|
|
6539
7051
|
"situations": ["NPC dialogue system", "write conversation choices for an NPC"]
|
|
6540
7052
|
},
|
|
@@ -6542,6 +7054,28 @@
|
|
|
6542
7054
|
"guidance": "The framework owns the optional authenticated transport seam at @threenative/core/net. Import connect with an HTTPS endpoint and a game-issued credential; it validates channels, message sizes, and bounded queues, but reliable overflow returns false and native qualification depends on the installed bridge (iOS remains unverified). Write authoritative replication, snapshots, prediction, interpolation, and rejoin policy in your project's src/ and server code.",
|
|
6543
7055
|
"id": "networked-multiplayer",
|
|
6544
7056
|
"situations": ["authoritative replication", "client prediction"]
|
|
7057
|
+
},
|
|
7058
|
+
{
|
|
7059
|
+
"guidance": "The framework owns no vegetation system. Copy the MIT source in examples/integrations/vegetation/src/ (https://github.com/ThreeNativeHQ/threenative/tree/develop/examples/integrations/vegetation) into your src/ and follow its README: generate seeded, vertex-budgeted EZ Tree variants offline (tree.ts, from the pinned ez-tree source, not the npm 1.1.0 bundle), write them with treeToGlb into assets/, cook them with assets.models.passes.prune: false (prune strips the uv and _WIND weight your materials read), load with ctx.assets.model and re-material by glTF material name. render/wind.ts is editable TSL wind in world metres; call expandBounds(geometry, minWorldScale) per variant geometry. assets.lod bakes bark levels and keeps alpha-masked leaves at LOD0. Worked sample: the grove game in github.com/ThreeNativeHQ/examples.",
|
|
7060
|
+
"id": "procedural-vegetation",
|
|
7061
|
+
"situations": [
|
|
7062
|
+
"procedural trees",
|
|
7063
|
+
"swaying trees",
|
|
7064
|
+
"tree foliage wind sway",
|
|
7065
|
+
"generate a forest of trees"
|
|
7066
|
+
]
|
|
7067
|
+
},
|
|
7068
|
+
{
|
|
7069
|
+
"guidance": "The framework owns no IK system. Copy the MIT source in examples/integrations/ik/src/ (https://github.com/ThreeNativeHQ/threenative/tree/develop/examples/integrations/ik) into your src/ and follow its README: it needs the closed-chain-ik/core dependency its own package.json pins, three keeps the only rendered pose, and new ConstrainedIK({root, joints: [{bone, axes, min, max}], effectors: [{bone, orientation?}], iterations, positionTolerance, rotationTolerance}) admits direct Bone hierarchies under rigid or positive-uniform-scale parents (shear, reflection, non-uniform or singular scale throws before the pose is touched). Call ik.update(targets) once per frame after AnimationPlayer/mixer and before render, with one world-space metre position per effector in order plus an optional quaternion only where the effector declared orientation: true; it never starts a loop, never moves the root and installs no bone translation or root-motion controller. Joint limits are X/Y/Z offsets relative to the animation pose supplied that call, so reapply the animation pose before solving to avoid accumulation, and the solver only rotates: bone lengths hold to under 1e-15 m. Iterations are 1-128; an unreachable goal returns converged false with finite residuals and never throws, a solver failure restores the original pose, and dispose() at scene teardown. Worked demo: the rifle grip game in examples/constrained-ik/, which plays on web, desktop native and Android.",
|
|
7070
|
+
"id": "constrained-ik",
|
|
7071
|
+
"situations": [
|
|
7072
|
+
"two-handed grip on a prop",
|
|
7073
|
+
"grip shared by two hands",
|
|
7074
|
+
"rifle grip follows sway",
|
|
7075
|
+
"inverse kinematics for a hand on a target",
|
|
7076
|
+
"hand IK pose correction",
|
|
7077
|
+
"elbow limits during an aiming pose"
|
|
7078
|
+
]
|
|
6545
7079
|
}
|
|
6546
7080
|
],
|
|
6547
7081
|
"version": 2
|