@threenative/core 0.2.0 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +55 -0
  3. package/capabilities.json +5292 -0
  4. package/dist/assets-kyoF7JlJ.d.ts +103 -0
  5. package/dist/audio-BFiGneTL.d.ts +156 -0
  6. package/dist/canvas-layer-BLVijiUJ.d.ts +62 -0
  7. package/dist/game-XGrTzapq.d.ts +1166 -0
  8. package/dist/gpu-readback-D2iRvoe9.d.ts +112 -0
  9. package/dist/hot.d.ts +20 -2
  10. package/dist/hot.js +14 -2
  11. package/dist/index.d.ts +2108 -55
  12. package/dist/index.js +15645 -2222
  13. package/dist/net.d.ts +65 -0
  14. package/dist/net.js +643 -0
  15. package/dist/playtest.d.ts +37 -4
  16. package/dist/playtest.js +246 -546
  17. package/dist/react.d.ts +177 -0
  18. package/dist/react.js +635 -0
  19. package/dist/renderer-C6hqZpoG.d.ts +770 -0
  20. package/dist/ui-layer.d.ts +306 -0
  21. package/dist/ui-layer.js +425 -0
  22. package/dist/world.d.ts +254 -0
  23. package/dist/world.js +2686 -0
  24. package/gpl/LICENSE.GPL +117 -0
  25. package/gpl/convert.py +192 -0
  26. package/gpl/recipes/_common.py +169 -0
  27. package/gpl/recipes/bake_ao.py +111 -0
  28. package/gpl/recipes/decimate.py +64 -0
  29. package/gpl/recipes/retarget.py +131 -0
  30. package/gpl/recipes/unwrap.py +71 -0
  31. package/mcp/assets.mjs +5 -0
  32. package/mcp/blender-server.mjs +632 -0
  33. package/mcp/blender.mjs +27 -0
  34. package/mcp/engine-server.mjs +501 -0
  35. package/mcp/engine.mjs +31 -0
  36. package/mcp/install.d.mts +37 -0
  37. package/mcp/install.mjs +145 -0
  38. package/mcp/launch.mjs +72 -0
  39. package/mcp/sculpt.mjs +5 -0
  40. package/mcp/servers.d.mts +34 -0
  41. package/mcp/servers.mjs +160 -0
  42. package/package.json +76 -6
  43. package/patches/three@0.185.1.patch +522 -0
  44. package/scripts/apply-three-patch.mjs +297 -0
  45. package/scripts/ensure-mcp.mjs +43 -0
  46. package/scripts/postinstall.mjs +6 -0
  47. package/dist/audio-CEAw0w5y.d.ts +0 -35
  48. package/dist/game-DRt1Qhq3.d.ts +0 -429
@@ -0,0 +1,112 @@
1
+ import { Object3D } from 'three';
2
+ import { I as IRendererLike } from './renderer-C6hqZpoG.js';
3
+
4
+ /**
5
+ * The lifecycle contract for a game-owned GPU simulation.
6
+ *
7
+ * A compute-driven object owns its kernels, buffers, and appearance. The framework only attaches
8
+ * the active renderer, warms the kernels before the world is shown, dispatches process calls at
9
+ * the object's declared cadence, and releases the object when its scene ends.
10
+ */
11
+ interface IComputeDriven {
12
+ /** Kernels to compile before the world is shown. Read once, at attach. */
13
+ readonly warmupNodes: readonly unknown[];
14
+ attachRenderer(renderer: IRendererLike): void;
15
+ /**
16
+ * The loop phase that dispatches `process`. Defaults to fixed-step; render cadence preserves the
17
+ * existing behavior of consumers whose simulation is intentionally tied to presentation.
18
+ */
19
+ readonly processCadence?: "fixed" | "render";
20
+ /** Dispatched once per fixed step, in scene-add order unless render cadence is declared. */
21
+ process(renderer: IRendererLike): void;
22
+ detach(): void;
23
+ readonly released: boolean;
24
+ }
25
+ /** The ordered registry used by the game loop for all compute-driven scene objects. */
26
+ declare class ComputeDrivenRegistry {
27
+ #private;
28
+ get size(): number;
29
+ /** Attach and remember one object. Re-adding the same object is idempotent. */
30
+ add(object: Object3D & IComputeDriven, renderer: IRendererLike): void;
31
+ /** Release one object without disturbing the order of the remaining objects. */
32
+ remove(driven: IComputeDriven): void;
33
+ /** Kernels in the same order as their objects were added to the scene. */
34
+ get warmupNodes(): readonly unknown[];
35
+ /** Dispatch fixed-step objects once; detached scene children are released before dispatch. */
36
+ process(renderer: IRendererLike): void;
37
+ /** Dispatch render-cadence objects once; detached scene children are released before dispatch. */
38
+ processRender(renderer: IRendererLike): void;
39
+ /** Release every registered object, continuing after a failure so no resource is stranded. */
40
+ clear(): void;
41
+ }
42
+
43
+ interface IGPUReadbackOptions {
44
+ /** The GPU storage attribute to copy — a TSL storage node's `.value`. */
45
+ readonly attribute: unknown;
46
+ /**
47
+ * Frames between readback requests. `1` asks every frame; larger values throttle.
48
+ *
49
+ * A copy off the GPU costs a queue submission and a mapped buffer, so a game reading a field it
50
+ * only consults for physics asks for it every few frames and pays the staleness instead.
51
+ */
52
+ readonly everyFrames: number;
53
+ }
54
+ /** A landed copy of GPU memory, with the age of the frame that produced it. */
55
+ interface IGPUReadbackSample {
56
+ readonly data: Float32Array;
57
+ /**
58
+ * Frames between the frame whose GPU state these bytes hold and the frame reading them.
59
+ *
60
+ * This is the number a caller must not be allowed to ignore. A buoyancy solver that treats a
61
+ * 4-frame-old surface as this frame's surface floats a hull through the water it is drawn on,
62
+ * and nothing in the frame says so.
63
+ */
64
+ readonly staleFrames: number;
65
+ }
66
+ /**
67
+ * A throttled, fire-and-forget copy of a GPU buffer into CPU memory.
68
+ *
69
+ * Every sample it hands back carries its own age. That is the whole point: the copy is
70
+ * asynchronous, so the bytes are always some frames behind the GPU, and a class that hid that
71
+ * would let a caller mistake stale data for live data with no way to find out.
72
+ *
73
+ * `request()` never awaits and never blocks the frame. One copy is in flight at a time; requests
74
+ * made while one is pending are dropped rather than queued, because a backlog of copies of a field
75
+ * that has already moved on is latency with no information in it.
76
+ */
77
+ declare class GPUReadback {
78
+ #private;
79
+ readonly everyFrames: number;
80
+ constructor(options: IGPUReadbackOptions);
81
+ get released(): boolean;
82
+ /** True while a copy is in flight. A game that wants to pace its own work can read it. */
83
+ get pending(): boolean;
84
+ /** The newest landed bytes, or `undefined` before the first copy lands. */
85
+ get data(): Float32Array | undefined;
86
+ /**
87
+ * How many frames old the landed bytes are.
88
+ *
89
+ * Before anything has landed this is the number of frames since construction, which grows
90
+ * without bound on purpose: "no data yet" and "data from frame zero" must not read the same.
91
+ */
92
+ get staleFrames(): number;
93
+ /** The newest bytes with their age attached, or `undefined` before the first copy lands. */
94
+ get sample(): IGPUReadbackSample | undefined;
95
+ /** Requests, landings and failures, for a report that has to say why a sample is old. */
96
+ get stats(): {
97
+ readonly requests: number;
98
+ readonly lands: number;
99
+ readonly failures: number;
100
+ };
101
+ /**
102
+ * Advances the frame clock and starts a copy when the throttle allows one.
103
+ *
104
+ * Safe to call every frame. It returns before the GPU has answered — awaiting it is the one
105
+ * thing that would turn this class into the stall it exists to avoid.
106
+ */
107
+ request(renderer: IRendererLike): void;
108
+ /** Drops the attribute reference and the landed bytes. Further requests throw. */
109
+ dispose(): void;
110
+ }
111
+
112
+ export { ComputeDrivenRegistry as C, GPUReadback as G, type IComputeDriven as I, type IGPUReadbackSample as a, type IGPUReadbackOptions as b };
package/dist/hot.d.ts CHANGED
@@ -1,6 +1,10 @@
1
- import { a as audioRuntimeSnapshot } from './audio-CEAw0w5y.js';
2
- import { I as IGame } from './game-DRt1Qhq3.js';
1
+ import { a as audioRuntimeSnapshot } from './audio-BFiGneTL.js';
2
+ import { I as IGame } from './game-XGrTzapq.js';
3
3
  import 'three';
4
+ import './assets-kyoF7JlJ.js';
5
+ import './renderer-C6hqZpoG.js';
6
+ import 'three/webgpu';
7
+ import './canvas-layer-BLVijiUJ.js';
4
8
  import 'zustand/vanilla';
5
9
 
6
10
  interface IHotDiagnostics {
@@ -11,7 +15,21 @@ interface IHotDiagnostics {
11
15
  readonly audio: ReturnType<typeof audioRuntimeSnapshot>;
12
16
  readonly physics: number | null;
13
17
  }
18
+ /**
19
+ * Validate that hot-reload state can cross the Vite boundary.
20
+ * @situation preserve JSON-shaped state during hot reload
21
+ * @situation reject a non-portable game state before reload
22
+ * @constraint state must contain finite numbers and plain objects only
23
+ * @example assertPortableState(game.state.getState());
24
+ */
14
25
  declare function assertPortableState(state: unknown): void;
26
+ /**
27
+ * Register hot-reload state preservation for a game.
28
+ * @situation keep game state while editing source in development
29
+ * @situation diagnose state shape changes during hot reload
30
+ * @constraint use only in the web development entry
31
+ * @example acceptHotUpdate(game, import.meta.hot);
32
+ */
15
33
  declare function acceptHotUpdate<TState extends Record<string, unknown>, TPhysics>(game: IGame<TState, TPhysics>, hot: IImportMeta["hot"]): void;
16
34
  interface IImportMeta {
17
35
  readonly env?: {
package/dist/hot.js CHANGED
@@ -5,11 +5,17 @@ var buses = /* @__PURE__ */ new Set();
5
5
  function audioRuntimeSnapshot() {
6
6
  let queued = 0;
7
7
  let voices = 0;
8
+ let pooled = 0;
9
+ let paused = 0;
10
+ const unsupported = /* @__PURE__ */ new Set();
8
11
  for (const bus of buses) {
9
12
  queued += bus.queued;
10
13
  voices += bus.voices;
14
+ pooled += bus.pooled;
15
+ paused += bus.pausedVoices;
16
+ for (const option of bus.unsupported) unsupported.add(option);
11
17
  }
12
- return { queued, voices };
18
+ return { paused, pooled, queued, unsupported: [...unsupported].sort(), voices };
13
19
  }
14
20
 
15
21
  // src/hot.ts
@@ -23,6 +29,12 @@ function acceptHotUpdate(game, hot) {
23
29
  const carried = hot.data.threenative;
24
30
  const reloads = carried?.reloads ?? 0;
25
31
  if (carried !== void 0) restoreState(game, carried.state);
32
+ if (carried?.sceneName !== void 0) {
33
+ try {
34
+ game.resumeScene(carried.sceneName);
35
+ } catch {
36
+ }
37
+ }
26
38
  if (isDev && typeof window !== "undefined") {
27
39
  const host = window;
28
40
  host.__THREENATIVE__ = {
@@ -39,7 +51,7 @@ function acceptHotUpdate(game, hot) {
39
51
  game.state.flush();
40
52
  const state = game.state.getState();
41
53
  assertPortableState(state);
42
- data.threenative = { reloads: reloads + 1, state };
54
+ data.threenative = { reloads: reloads + 1, sceneName: game.sceneName, state };
43
55
  } catch (error) {
44
56
  data.threenative = void 0;
45
57
  hot.invalidate(error instanceof Error ? error.message : String(error));