@threenative/core 0.3.0 → 0.3.2

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 (39) hide show
  1. package/README.md +10 -0
  2. package/capabilities.json +1881 -131
  3. package/dist/assets-kyoF7JlJ.d.ts +103 -0
  4. package/dist/{audio-Dp2mXpD3.d.ts → audio-BFiGneTL.d.ts} +62 -0
  5. package/dist/canvas-layer-BLVijiUJ.d.ts +62 -0
  6. package/dist/{game-CYIaKhgl.d.ts → game-XGrTzapq.d.ts} +350 -164
  7. package/dist/gpu-readback-D2iRvoe9.d.ts +112 -0
  8. package/dist/hot.d.ts +5 -3
  9. package/dist/hot.js +5 -1
  10. package/dist/index.d.ts +813 -143
  11. package/dist/index.js +4548 -941
  12. package/dist/net.d.ts +65 -0
  13. package/dist/net.js +643 -0
  14. package/dist/playtest.d.ts +29 -5
  15. package/dist/playtest.js +181 -35
  16. package/dist/react.d.ts +4 -2
  17. package/dist/{canvas-layer-CtrZHgIh.d.ts → renderer-C6hqZpoG.d.ts} +237 -75
  18. package/dist/world.d.ts +203 -4
  19. package/dist/world.js +2536 -25
  20. package/gpl/LICENSE.GPL +117 -0
  21. package/gpl/convert.py +192 -0
  22. package/gpl/recipes/_common.py +169 -0
  23. package/gpl/recipes/bake_ao.py +111 -0
  24. package/gpl/recipes/decimate.py +64 -0
  25. package/gpl/recipes/retarget.py +131 -0
  26. package/gpl/recipes/unwrap.py +71 -0
  27. package/mcp/blender-server.mjs +632 -0
  28. package/mcp/blender.mjs +27 -0
  29. package/mcp/engine-server.mjs +271 -23
  30. package/mcp/engine.mjs +15 -7
  31. package/mcp/install.d.mts +37 -0
  32. package/mcp/install.mjs +94 -26
  33. package/mcp/servers.d.mts +34 -0
  34. package/mcp/servers.mjs +110 -9
  35. package/package.json +36 -8
  36. package/patches/three@0.185.1.patch +249 -14
  37. package/scripts/ensure-mcp.mjs +20 -13
  38. package/scripts/bundle-engine-mcp.mjs +0 -15
  39. package/scripts/generate-version.mjs +0 -13
@@ -0,0 +1,103 @@
1
+ import { Texture, Object3D, AnimationClip } from 'three';
2
+
3
+ interface IAssetLoaderOptions {
4
+ readonly basePath?: string;
5
+ /**
6
+ * URL of the manifest written by the asset compile step (`public/assets.manifest.json`).
7
+ * Defaults to `assets.manifest.json` resolved against `basePath`. A logical path is resolved
8
+ * to its compiled output through it; a manifest that is absent — 404 or unfetchable — falls
9
+ * back to loading every path verbatim, while one that is served malformed or with an unknown
10
+ * version throws.
11
+ */
12
+ readonly manifest?: string;
13
+ /**
14
+ * Directory the game's uncompiled assets live in, relative to `basePath`.
15
+ *
16
+ * Only consulted when there is no manifest, and only after the verbatim path has been tried. It
17
+ * is what makes the delete-test possible: remove everything the asset pipeline produced and the
18
+ * game still finds `assets/rock.png` where the author put it, just slower and uncompressed.
19
+ * Defaults to the compile step's own default source directory.
20
+ */
21
+ readonly sourcePath?: string;
22
+ readonly model?: (url: string) => Promise<unknown>;
23
+ readonly texture?: (url: string) => Promise<Texture>;
24
+ readonly audio?: (url: string) => Promise<AudioBuffer>;
25
+ /**
26
+ * The live renderer (`IRendererLike.raw`), handed to `KTX2Loader.detectSupport()` exactly
27
+ * once so compiled KTX2 textures transcode to a format this machine's GPU actually supports.
28
+ * Required for games whose textures compile to `.ktx2`; without it such a load throws rather
29
+ * than silently uploading decoded RGBA.
30
+ */
31
+ readonly renderer?: unknown;
32
+ }
33
+ /** The structural slice of three's `KTX2Loader` core depends on. */
34
+ interface IKTX2LoaderLike {
35
+ detectSupport(renderer: unknown): unknown;
36
+ load(url: string, onLoad: (texture: Texture) => void, onProgress?: (event: ProgressEvent<EventTarget>) => void, onError?: (error: unknown) => void): unknown;
37
+ setTranscoderPath(path: string): unknown;
38
+ }
39
+ /**
40
+ * Present only when a renderer was handed to the asset loader. `ready` settles once support
41
+ * detection ran; it rejects naming the renderer and platform when no compressed format is
42
+ * supported, and `defineGame` awaits it during boot so such a target fails at construction.
43
+ */
44
+ interface ICompressedTextureSupport {
45
+ /**
46
+ * The one shared instance, also handed to `GLTFLoader.setKTX2Loader()` for models. Resolves
47
+ * `undefined` when the renderer exposed no surface to probe (see `createKtx2Loader`).
48
+ */
49
+ readonly loader: Promise<IKTX2LoaderLike | undefined>;
50
+ readonly ready: Promise<void>;
51
+ }
52
+ interface IAssetLoader {
53
+ readonly compressedTextures?: ICompressedTextureSupport;
54
+ model<T = unknown>(path: string): Promise<T>;
55
+ texture(path: string): Promise<Texture>;
56
+ audio(path: string): Promise<AudioBuffer>;
57
+ release(kind: "audio" | "model" | "texture", path: string): boolean;
58
+ /**
59
+ * Where a logical path is served from, in the order worth trying — the manifest's
60
+ * content-addressed output when a manifest exists, otherwise the verbatim and source paths.
61
+ * For loaders this surface does not wrap (an HDR sky, a font, a data file) — a game must never
62
+ * hard-code a hashed output name, which changes on every rebuild.
63
+ */
64
+ resolve(path: string): Promise<readonly string[]>;
65
+ /**
66
+ * How many loads this loader has been asked for and how many have settled (resolved or
67
+ * rejected). A loading view reads the ratio; the runtime folds it into `ctx.startup.progress`
68
+ * while the start scene loads, so the bar moves with the bytes instead of jumping 0 to 1.
69
+ *
70
+ * `requestedBytes` and `settledBytes` are the same ledger weighed by the `bytes` the compile
71
+ * step already records for every manifest entry, and they are what a loading bar should read.
72
+ * A file count treats a 710 MB model and a 4 KB icon alike: one real game's bar reached 92%
73
+ * on eleven small assets and then stood still for the entire download of the twelfth. Both
74
+ * stay 0 for a game with no manifest, where no size is knowable before the bytes arrive.
75
+ */
76
+ readonly progress: {
77
+ readonly requested: number;
78
+ readonly requestedBytes: number;
79
+ readonly settled: number;
80
+ readonly settledBytes: number;
81
+ };
82
+ clear(): void;
83
+ }
84
+ /**
85
+ * Repair an exported rig whose clips are Z-mirrored against its own bind pose, in place.
86
+ *
87
+ * The signature (PRD-324, the whole Wildwood animal pack): the file's bind faces its own +Z, but
88
+ * every animation track is expressed in a Z-mirrored frame — position tracks hold `(x, y, −z)`
89
+ * where the bind holds `(x, y, z)`, and quaternion tracks hold `(−x, −y, z, w)`, the conjugation
90
+ * of the same mirror. Played as authored, every animal faces backwards with its spine folded:
91
+ * head behind pelvis, healthy bone lengths, zero errors anywhere.
92
+ *
93
+ * An exporter writes exactly this when it converts a rig's bind to one convention and forgets its
94
+ * animation tracks. The loader repairs it before the game ever sees it: detection votes per
95
+ * tracked bone on whether the clip's translation Z sits negated against the bind, and only an
96
+ * overwhelming vote (at least `MIRROR_VOTE_MINIMUM` bones, `MIRROR_VOTE_SHARE` of them) converts
97
+ * every track — positions negate Z, quaternions negate X and Y, once, across all clips. A file
98
+ * that does not carry the signature is left byte-identical.
99
+ */
100
+ declare function reconcileMirroredClips(root: Object3D, clips: readonly AnimationClip[]): boolean;
101
+ declare function createAssetLoader(options?: IAssetLoaderOptions): IAssetLoader;
102
+
103
+ export { type IAssetLoader as I, type IAssetLoaderOptions as a, createAssetLoader as c, reconcileMirroredClips as r };
@@ -53,6 +53,19 @@ interface IAudioRuntimeSnapshot {
53
53
  readonly voices: number;
54
54
  /** Retired voices held for reuse. Bounded by peak concurrency, never by session length. */
55
55
  readonly pooled: number;
56
+ /**
57
+ * Voices stopped mid-cue by `pause` and holding their position. Nonzero here while a game
58
+ * claims to be running is a menu that muted the world and forgot to give it back.
59
+ */
60
+ readonly paused: number;
61
+ /**
62
+ * Cue-shaping options this runtime accepted and could not honour, sorted and de-duplicated.
63
+ *
64
+ * The native host binds neither a biquad filter nor a schedulable `detune`, so `lowpassHz` and
65
+ * `detune` are silently dropped there. A mix tuned on the web and shipped to a phone is flat in
66
+ * a way nothing else reports, and a build can be failed on this.
67
+ */
68
+ readonly unsupported: readonly string[];
56
69
  }
57
70
  declare class AudioBus {
58
71
  #private;
@@ -65,6 +78,41 @@ declare class AudioBus {
65
78
  * now bounded by how many cues have ever sounded at once, so a gate can pin it.
66
79
  */
67
80
  get pooled(): number;
81
+ /** Voices holding their position across a `pause`. */
82
+ get pausedVoices(): number;
83
+ /** True between `pause` and `resume`. */
84
+ get paused(): boolean;
85
+ /** The master volume last asked for — the target, not a point some ramp is passing through. */
86
+ get volume(): number;
87
+ /** @see IAudioRuntimeSnapshot.unsupported */
88
+ get unsupported(): readonly string[];
89
+ /**
90
+ * The whole bus's level, which is what a volume slider and a duck both move.
91
+ *
92
+ * One bus per category — ambience, effects, music — makes this the mixer: ducking the wood
93
+ * under a discovery cue is `ambience.setVolume(0.35, 0.4)` and `setVolume(1, 0.8)` after. It
94
+ * rides the listener's gain, so it costs nothing per voice and applies to cues already sounding.
95
+ *
96
+ * A bus constructed with a shared `listener` shares that listener's master with every other bus
97
+ * on it; give each category its own bus (the default) to mix them apart.
98
+ *
99
+ * @param volume Linear gain, 0 or greater.
100
+ * @param fade Seconds to reach it. Defaults to a 15 ms settle, which is a level change rather
101
+ * than a fade — the shortest move that does not click.
102
+ */
103
+ setVolume(volume: number, fade?: number): void;
104
+ /**
105
+ * Stop every sounding voice where it stands and hold its position.
106
+ *
107
+ * The difference from `stop` is what happens next: a paused bed resumes mid-bar, a stopped one
108
+ * starts over. Cues asked for while paused stay queued and sound on `resume`, so a menu opened
109
+ * during a burst does not fire the backlog at the player when they close it.
110
+ *
111
+ * The audio context keeps running — suspending it would silence every other bus sharing it.
112
+ */
113
+ pause(): void;
114
+ /** Sound the held voices again from where they stopped, then release anything queued. */
115
+ resume(): void;
68
116
  setCamera(camera: Object3D): void;
69
117
  reparent(camera: Object3D): void;
70
118
  unlock(): Promise<void>;
@@ -86,6 +134,20 @@ declare class AudioBus {
86
134
  */
87
135
  playAt(buffer: AudioBuffer, source: Object3D | Vector3, options?: IAudioPlayOptions): PositionalAudio;
88
136
  music(buffer: AudioBuffer, options?: IAudioPlayOptions): Audio;
137
+ /**
138
+ * Stop one voice and give it back to the pool.
139
+ *
140
+ * The missing primitive: `stop()` silenced every voice or none, and stopping the handle
141
+ * directly does not work — three's `Audio.stop()` detaches `onended` before stopping the node,
142
+ * so the bus's reclaim hook never fires and the voice leaks out of the pool while staying in
143
+ * the scene. A game that wants one entity's loop to end needs exactly this, and `playAt` with
144
+ * `loop` is the obvious way to reach for it.
145
+ *
146
+ * Returns false for a voice this bus is not sounding — a stale handle from a voice already
147
+ * recycled, or one belonging to another bus. Reported rather than thrown: a caller stopping a
148
+ * sound that already ended has made no mistake.
149
+ */
150
+ stopVoice(voice: Audio<AudioNode>): boolean;
89
151
  stop(): void;
90
152
  dispose(): void;
91
153
  }
@@ -0,0 +1,62 @@
1
+ import * as three from 'three';
2
+ import { Camera, Vector2, Vector3, Scene, OrthographicCamera } from 'three';
3
+ import { I as IRendererLike } from './renderer-C6hqZpoG.js';
4
+
5
+ interface IViewportSize {
6
+ readonly aspect: number;
7
+ readonly height: number;
8
+ readonly width: number;
9
+ }
10
+ interface IViewportInsets {
11
+ readonly bottom: number;
12
+ readonly left: number;
13
+ readonly right: number;
14
+ readonly top: number;
15
+ }
16
+ interface IViewportSafeArea extends IViewportInsets {
17
+ /** The safe rectangle in drawable pixel coordinates, with y measured from the top edge. */
18
+ readonly height: number;
19
+ readonly source: "full-drawable-fallback" | "measured";
20
+ readonly width: number;
21
+ readonly x: number;
22
+ readonly y: number;
23
+ }
24
+ interface IViewportOptions {
25
+ readonly camera: Camera;
26
+ readonly renderer: IRendererLike;
27
+ readonly source?: IViewportPlatformSource;
28
+ }
29
+ type ViewportResizeHandler = (size: IViewportSize) => void;
30
+ interface IViewportPlatformSource {
31
+ observeResize(canvas: HTMLCanvasElement, resize: () => void): () => void;
32
+ readSize(canvas: HTMLCanvasElement): IViewportSize;
33
+ readSafeArea?(canvas: HTMLCanvasElement, size: IViewportSize): IViewportInsets | undefined;
34
+ }
35
+ declare class Viewport {
36
+ #private;
37
+ readonly camera: Camera;
38
+ readonly renderer: IRendererLike;
39
+ constructor(options: IViewportOptions);
40
+ get size(): IViewportSize;
41
+ get safeArea(): IViewportSafeArea;
42
+ projectPosition(screen: Vector2, z?: number, target?: Vector3): Vector3;
43
+ unprojectPosition(world: Vector3, target?: Vector2): Vector2;
44
+ onResize(handler: ViewportResizeHandler): () => void;
45
+ resize(): void;
46
+ dispose(): void;
47
+ }
48
+
49
+ /** A Godot-shaped render surface that is independent of the world camera and post pipeline. */
50
+ declare class CanvasLayer {
51
+ #private;
52
+ readonly scene: Scene<three.Object3DEventMap>;
53
+ readonly camera: OrthographicCamera;
54
+ /** Declares that this layer covers the framebuffer, allowing the world pass to be skipped. */
55
+ opaque: boolean;
56
+ constructor(viewport: Pick<Viewport, "onResize" | "size">);
57
+ dispose(): void;
58
+ /** Subscribe to drawable-size changes after this layer has updated its camera. */
59
+ onResize(handler: (size: IViewportSize) => void): () => void;
60
+ }
61
+
62
+ export { CanvasLayer as C, type IViewportOptions as I, Viewport as V };