murow 0.0.73 → 0.1.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 (99) hide show
  1. package/README.md +15 -1
  2. package/dist/cjs/core/binary-codec/binary-codec.js +1 -1
  3. package/dist/cjs/core/driver/driver.js +1 -1
  4. package/dist/cjs/core/driver/drivers/immediate.js +1 -1
  5. package/dist/cjs/core/driver/drivers/raf.js +1 -1
  6. package/dist/cjs/core/driver/drivers/timeout.js +1 -1
  7. package/dist/cjs/core/input/index.js +1 -1
  8. package/dist/cjs/core/input/mouse-look/index.js +1 -0
  9. package/dist/cjs/core/input/mouse-look/mouse-look.js +1 -0
  10. package/dist/cjs/core/input/scroll-zoom/index.js +1 -0
  11. package/dist/cjs/core/input/scroll-zoom/scroll-zoom.js +1 -0
  12. package/dist/cjs/ecs/component.js +1 -1
  13. package/dist/cjs/ecs/system-builder.js +1 -1
  14. package/dist/cjs/ecs/world.js +1 -1
  15. package/dist/cjs/game/loop/loop.js +1 -1
  16. package/dist/cjs/net/adapters/bun-websocket.js +1 -1
  17. package/dist/esm/core/binary-codec/binary-codec.js +1 -1
  18. package/dist/esm/core/driver/drivers/immediate.js +1 -1
  19. package/dist/esm/core/driver/drivers/raf.js +1 -1
  20. package/dist/esm/core/driver/drivers/timeout.js +1 -1
  21. package/dist/esm/core/input/index.js +1 -1
  22. package/dist/esm/core/input/mouse-look/index.js +1 -0
  23. package/dist/esm/core/input/mouse-look/mouse-look.js +1 -0
  24. package/dist/esm/core/input/scroll-zoom/index.js +1 -0
  25. package/dist/esm/core/input/scroll-zoom/scroll-zoom.js +1 -0
  26. package/dist/esm/ecs/component.js +1 -1
  27. package/dist/esm/ecs/system-builder.js +1 -1
  28. package/dist/esm/ecs/world.js +1 -1
  29. package/dist/esm/game/loop/loop.js +1 -1
  30. package/dist/esm/net/adapters/bun-websocket.js +1 -1
  31. package/dist/netcode/cjs/index.js +1552 -0
  32. package/dist/netcode/esm/index.js +1530 -0
  33. package/dist/netcode/types/client/game-client.d.ts +125 -0
  34. package/dist/netcode/types/client/index.d.ts +1 -0
  35. package/dist/netcode/types/client/interpolation-buffer.d.ts +37 -0
  36. package/dist/netcode/types/client/interpolation-buffer.test.d.ts +1 -0
  37. package/dist/netcode/types/codec/delta-codec.d.ts +17 -0
  38. package/dist/netcode/types/codec/delta-codec.test.d.ts +1 -0
  39. package/dist/netcode/types/codec/index.d.ts +1 -0
  40. package/dist/netcode/types/components/index.d.ts +1 -0
  41. package/dist/netcode/types/components/sync-spec.d.ts +43 -0
  42. package/dist/netcode/types/components/sync-spec.test.d.ts +1 -0
  43. package/dist/netcode/types/ctx.d.ts +105 -0
  44. package/dist/netcode/types/ctx.test.d.ts +1 -0
  45. package/dist/netcode/types/handlers/define-handlers.d.ts +47 -0
  46. package/dist/netcode/types/handlers/index.d.ts +1 -0
  47. package/dist/netcode/types/index.d.ts +11 -0
  48. package/dist/netcode/types/integration.test.d.ts +1 -0
  49. package/dist/netcode/types/intents/define-intents.d.ts +53 -0
  50. package/dist/netcode/types/intents/define-intents.test.d.ts +1 -0
  51. package/dist/netcode/types/intents/index.d.ts +1 -0
  52. package/dist/netcode/types/network/base.d.ts +120 -0
  53. package/dist/netcode/types/network/index.d.ts +2 -0
  54. package/dist/netcode/types/network/transport.d.ts +1 -0
  55. package/dist/netcode/types/packets/convergence.test.d.ts +1 -0
  56. package/dist/netcode/types/packets/harness.d.ts +103 -0
  57. package/dist/netcode/types/packets/index.d.ts +2 -0
  58. package/dist/netcode/types/packets/intermittent-intents.test.d.ts +1 -0
  59. package/dist/netcode/types/packets/pathological.test.d.ts +1 -0
  60. package/dist/netcode/types/packets/peer-interpolation.test.d.ts +1 -0
  61. package/dist/netcode/types/packets/reordering.test.d.ts +1 -0
  62. package/dist/netcode/types/packets/virtual-network.d.ts +65 -0
  63. package/dist/netcode/types/predictions/define-predictions.d.ts +45 -0
  64. package/dist/netcode/types/predictions/define-predictions.test.d.ts +1 -0
  65. package/dist/netcode/types/predictions/index.d.ts +1 -0
  66. package/dist/netcode/types/reconciliation.test.d.ts +1 -0
  67. package/dist/netcode/types/rpcs/define-rpcs.d.ts +44 -0
  68. package/dist/netcode/types/rpcs/define-rpcs.test.d.ts +1 -0
  69. package/dist/netcode/types/rpcs/index.d.ts +1 -0
  70. package/dist/netcode/types/server/game-server.d.ts +77 -0
  71. package/dist/netcode/types/server/index.d.ts +2 -0
  72. package/dist/netcode/types/server/plugins/aoi-grid.d.ts +34 -0
  73. package/dist/netcode/types/server/plugins/index.d.ts +3 -0
  74. package/dist/netcode/types/server/plugins/lag-compensation.d.ts +34 -0
  75. package/dist/netcode/types/server/plugins/plugin.d.ts +24 -0
  76. package/dist/netcode/types/tick-rate.test.d.ts +1 -0
  77. package/dist/netcode/types/transports/index.d.ts +1 -0
  78. package/dist/netcode/types/transports/memory-transport.d.ts +51 -0
  79. package/dist/netcode/types/types.test.d.ts +1 -0
  80. package/dist/types/core/binary-codec/binary-codec.d.ts +89 -31
  81. package/dist/types/core/driver/driver.d.ts +8 -8
  82. package/dist/types/core/driver/drivers/immediate.d.ts +4 -4
  83. package/dist/types/core/driver/drivers/raf.d.ts +6 -6
  84. package/dist/types/core/driver/drivers/timeout.d.ts +4 -4
  85. package/dist/types/core/input/index.d.ts +2 -0
  86. package/dist/types/core/input/mouse-look/index.d.ts +1 -0
  87. package/dist/types/core/input/mouse-look/mouse-look.d.ts +139 -0
  88. package/dist/types/core/input/scroll-zoom/index.d.ts +1 -0
  89. package/dist/types/core/input/scroll-zoom/scroll-zoom.d.ts +38 -0
  90. package/dist/types/ecs/component.d.ts +67 -11
  91. package/dist/types/ecs/entity-handle.d.ts +5 -5
  92. package/dist/types/ecs/system-builder.d.ts +13 -0
  93. package/dist/types/ecs/world.d.ts +72 -4
  94. package/dist/types/game/loop/loop.d.ts +21 -2
  95. package/dist/types/net/adapters/bun-websocket.d.ts +19 -3
  96. package/dist/webgpu/cjs/index.js +120 -5
  97. package/dist/webgpu/esm/index.js +120 -5
  98. package/dist/webgpu/types/3d/renderer.d.ts +38 -0
  99. package/package.json +6 -1
@@ -120,10 +120,23 @@ export declare class ExecutableSystem {
120
120
  private queryMask;
121
121
  private conditionPredicate?;
122
122
  private proxyEntity;
123
+ /**
124
+ * Indices of queried components that have `__sync` metadata. Cached at
125
+ * construction so `execute()` can flip dirty bits without re-checking
126
+ * each tick. Empty for systems whose query touches no synced components,
127
+ * which keeps the hot path zero-overhead.
128
+ */
129
+ private syncedComponentIndices;
123
130
  constructor(world: World, components: Component<any>[], userCallback: (entity: any, deltaTime: number, world: World) => void, fieldDescs: FieldDesc[], queryMaskKey: string, queryMask: number[], conditionPredicate?: (entity: any) => boolean);
124
131
  /**
125
132
  * Execute the system for all matching entities.
126
133
  *
134
+ * If the system's query includes any synced components, every entity
135
+ * touched by the system is marked dirty for those components after the
136
+ * callback runs (coarse strategy. the network layer assumes any
137
+ * entity that flowed through a system whose query included its synced
138
+ * component may have changed).
139
+ *
127
140
  * @param deltaTime - Time delta to pass to system callback
128
141
  */
129
142
  execute(deltaTime: number): void;
@@ -1,3 +1,4 @@
1
+ import type { ArrayFromField, Schema } from "../core/binary-codec";
1
2
  import { Component } from "./component";
2
3
  import { ComponentStore } from "./component-store";
3
4
  import { EntityHandle } from "./entity-handle";
@@ -70,6 +71,21 @@ export declare class World extends WorldSystems {
70
71
  private queryMaskCache;
71
72
  private despawnedBuffer;
72
73
  private despawnedCount;
74
+ /**
75
+ * Per-component dirty bitmask, indexed by [componentIndex][entity>>>5].
76
+ * `null` for components without `__sync` metadata: no overhead when
77
+ * networking isn't in play. Higher-level packages might read these
78
+ * to build per-peer snapshot deltas.
79
+ */
80
+ private dirtyBitsByComponent;
81
+ /**
82
+ * Per-component field bundle: a frozen object whose keys are the
83
+ * component's field names and whose values are the same typed-array
84
+ * references returned by `getFieldArray`. Built once at registration,
85
+ * shared forever — `world.fields(C)` returns the same object on every
86
+ * call (zero garbage). Indexed by `component.__worldIndex`.
87
+ */
88
+ private fieldsByComponent;
73
89
  private worldId;
74
90
  constructor(config: WorldConfig);
75
91
  /**
@@ -150,6 +166,34 @@ export declare class World extends WorldSystems {
150
166
  * Invalidate all query caches (called on archetype changes).
151
167
  */
152
168
  private invalidateQueryCache;
169
+ /**
170
+ * Mark an entity dirty for a given component index. No-op for
171
+ * components without `__sync` metadata. Called internally by every
172
+ * write path (`add`, `set`, `update`, `system-builder` field setters).
173
+ */
174
+ markDirty(entity: Entity, componentIndex: number): void;
175
+ /**
176
+ * Test whether an entity is currently marked dirty for a component.
177
+ * Used by snapshot builders.
178
+ */
179
+ isDirty(entity: Entity, component: Component<any>): boolean;
180
+ /**
181
+ * Clear the dirty bit for an entity/component pair. Called by the
182
+ * snapshot builder after a delta for the entity has been acknowledged
183
+ * by all peers.
184
+ */
185
+ clearDirty(entity: Entity, component: Component<any>): void;
186
+ /**
187
+ * Iterate dirty entities for a synced component. Calls `cb` for each
188
+ * entity whose dirty bit is set. Returns immediately for unsynced
189
+ * components.
190
+ */
191
+ forEachDirty(component: Component<any>, cb: (entity: Entity) => void): void;
192
+ /**
193
+ * Clear all dirty bits across all components. Usually the snapshot
194
+ * pipeline clears bits per-entity as it processes them.
195
+ */
196
+ clearAllDirty(): void;
153
197
  /**
154
198
  * Add a component to an entity with initial data.
155
199
  */
@@ -223,8 +267,8 @@ export declare class World extends WorldSystems {
223
267
  * const t = world.get(entity, Transform);
224
268
  * const v = world.get(entity, Velocity);
225
269
  * world.update(entity, Transform, {
226
- * x: t.x + v.vx * dt,
227
- * y: t.y + v.vy * dt
270
+ * x: t.x + v.vx * deltaTime,
271
+ * y: t.y + v.vy * deltaTime
228
272
  * });
229
273
  * }
230
274
  * ```
@@ -289,12 +333,36 @@ export declare class World extends WorldSystems {
289
333
  * const velocityVy = world.getFieldArray(Velocity, 'vy');
290
334
  *
291
335
  * for (const entity of world.query(Transform, Velocity)) {
292
- * transformX[entity] += velocityVx[entity] * dt;
293
- * transformY[entity] += velocityVy[entity] * dt;
336
+ * transformX[entity] += velocityVx[entity] * deltaTime;
337
+ * transformY[entity] += velocityVy[entity] * deltaTime;
294
338
  * }
295
339
  * ```
296
340
  */
297
341
  getFieldArray<T extends object>(component: Component<T>, fieldName: keyof T): Float32Array | Int32Array | Uint32Array | Uint16Array | Uint8Array;
342
+ /**
343
+ * Get a typed-array bundle for every field of a component.
344
+ *
345
+ * Returns the same frozen object on every call - built once at component
346
+ * registration and shared forever. Each field name maps to its underlying
347
+ * typed array, with the EXACT element type inferred from the schema:
348
+ * `f32 -> Float32Array`, `u8 -> Uint8Array`, `u16 -> Uint16Array`, etc.
349
+ * No casts needed in caller code.
350
+ *
351
+ * Use this when you want RAW-speed per-entity reads/writes without the
352
+ * `world.update({...})` allocation + `for...in` overhead. Bypasses dirty
353
+ * tracking: for networked components, see `ctx.fields()` in `murow/netcode`
354
+ * which auto-marks dirty, or call `world.markDirty(entity, index)` yourself.
355
+ *
356
+ * @example
357
+ * ```ts
358
+ * const pos = world.fields(Position); // pos.x, pos.z typed as Float32Array
359
+ * pos.x[entity] += velocity.x * dt;
360
+ * pos.z[entity] += velocity.z * dt;
361
+ * ```
362
+ */
363
+ fields<T extends object, S extends Schema<T>>(component: Component<T, S>): Readonly<{
364
+ [K in keyof S]: ArrayFromField<S[K]>;
365
+ }>;
298
366
  /**
299
367
  * Create an EntityHandle wrapper for fluent API usage.
300
368
  *
@@ -51,8 +51,8 @@ export declare class GameLoop<T extends GameLoopType = DriverType> {
51
51
  interface GameLoopOptions<T extends GameLoopType> {
52
52
  tickRate: number;
53
53
  type: T;
54
- onTick?: (dt: number, tick: number, input: ReturnType<InputManager["snapshot"]>) => void;
55
- onRender?: (dt: number, alpha: number, input: ReturnType<InputManager["peek"]>) => void;
54
+ onTick?: (deltaTime: number, tick: number, input: ReturnType<InputManager["snapshot"]>) => void;
55
+ onRender?: (deltaTime: number, alpha: number, input: ReturnType<InputManager["peek"]>) => void;
56
56
  }
57
57
  type BaseEvents = [
58
58
  [
@@ -64,6 +64,25 @@ type BaseEvents = [
64
64
  startedAt: number;
65
65
  }
66
66
  ],
67
+ [
68
+ "sync",
69
+ {
70
+ /**
71
+ * Current tick number.
72
+ */
73
+ tick: number;
74
+ /**
75
+ * Delta time since the last tick.
76
+ */
77
+ deltaTime: number;
78
+ /**
79
+ * Input snapshot at the start of the tick.
80
+ *
81
+ * **Only available in client loops.**
82
+ */
83
+ input: ReturnType<InputManager["snapshot"]>;
84
+ }
85
+ ],
67
86
  [
68
87
  "pre-tick",
69
88
  {
@@ -63,7 +63,10 @@ export declare class BunWebSocketServerTransport implements ServerTransportAdapt
63
63
  getPeerIds(): string[];
64
64
  close(): void;
65
65
  /**
66
- * Internal: Register a new peer (called from Bun server fetch handler)
66
+ * Internal: Register a new peer. Records the peer in the internal map
67
+ * but does NOT fire connection handlers: that's `_handlePeerConnection`'s
68
+ * job, called separately by the `open` callback. Splitting registration
69
+ * from notification keeps the handler count to exactly one per connect.
67
70
  */
68
71
  _registerPeer(socket: ServerWebSocket<unknown>): string;
69
72
  /**
@@ -76,7 +79,20 @@ export declare class BunWebSocketServerTransport implements ServerTransportAdapt
76
79
  _handlePeerDisconnection(peerId: string): void;
77
80
  _handlePeerConnection(peerId: string): void;
78
81
  /**
79
- * Static factory method to create a Bun WebSocket server
82
+ * Static factory method to create a Bun WebSocket server.
83
+ *
84
+ * @param port - Port to listen on.
85
+ * @param opts - Optional configuration:
86
+ * - `path`: If set, only requests to this URL pathname are upgraded
87
+ * (e.g., `/ws`). Other paths fall through to `fetch`. Defaults to
88
+ * accepting any path.
89
+ * - `fetch`: Handler for non-upgrade HTTP requests on the same port.
90
+ * Useful for serving a static bundle alongside the WS endpoint so
91
+ * the whole app runs on one port. If omitted, non-upgrade requests
92
+ * get a 400 response (matches the previous WS-only behavior).
80
93
  */
81
- static create(port: number): BunWebSocketServerTransport;
94
+ static create(port: number, opts?: {
95
+ path?: string;
96
+ fetch?: (req: Request, server: Server<unknown>) => Response | Promise<Response>;
97
+ }): BunWebSocketServerTransport;
82
98
  }
@@ -4734,17 +4734,18 @@ var WebGPU3DRenderer = class extends import_renderer4.Base3DRenderer {
4734
4734
  * with another instance (e.g., when spawning all parts of a character).
4735
4735
  */
4736
4736
  addInstance(opts) {
4737
+ const userPrefabId = isPrefab3D(opts.model) ? opts.model.id : null;
4737
4738
  if (isPrefab3D(opts.model) && opts.model.type === "composite") {
4738
4739
  return this.addCompositeInstance(opts, opts.model);
4739
4740
  }
4740
4741
  const modelOrGltf = isPrefab3D(opts.model) ? resolvePrefabHandle(opts.model) : opts.model;
4741
4742
  if ("parts" in modelOrGltf) {
4742
- return this.addGltfInstance(opts, modelOrGltf);
4743
+ return this.addGltfInstance(opts, modelOrGltf, userPrefabId);
4743
4744
  }
4744
4745
  const modelHandle = modelOrGltf;
4745
4746
  const model = this.models[modelHandle.id];
4746
4747
  if (model?.skinned) {
4747
- return this.addSkinnedInstance(opts, modelHandle, model.skinIndex);
4748
+ return this.addSkinnedInstance(opts, modelHandle, model.skinIndex, void 0, userPrefabId);
4748
4749
  }
4749
4750
  const slot = this.freeList.allocate();
4750
4751
  if (slot === -1)
@@ -4777,10 +4778,14 @@ var WebGPU3DRenderer = class extends import_renderer4.Base3DRenderer {
4777
4778
  const staticData = this.staticData;
4778
4779
  const self = this;
4779
4780
  let destroyed = false;
4781
+ const posOut = [0, 0, 0];
4782
+ const rotOut = [0, 0, 0];
4783
+ const sclOut = [0, 0, 0];
4780
4784
  const handle = {
4781
4785
  slot,
4782
4786
  modelId: modelHandle.id,
4783
4787
  skinned: false,
4788
+ prefabId: userPrefabId,
4784
4789
  setPosition(nx, ny, nz) {
4785
4790
  dynamicData[dynBase + DYN_CURR_PX] = nx;
4786
4791
  dynamicData[dynBase + DYN_CURR_PY] = ny;
@@ -4796,6 +4801,32 @@ var WebGPU3DRenderer = class extends import_renderer4.Base3DRenderer {
4796
4801
  staticData[statBase + STAT_SY] = ny;
4797
4802
  staticData[statBase + STAT_SZ] = nz;
4798
4803
  },
4804
+ teleport(nx, ny, nz) {
4805
+ dynamicData[dynBase + DYN_PREV_PX] = nx;
4806
+ dynamicData[dynBase + DYN_PREV_PY] = ny;
4807
+ dynamicData[dynBase + DYN_PREV_PZ] = nz;
4808
+ dynamicData[dynBase + DYN_CURR_PX] = nx;
4809
+ dynamicData[dynBase + DYN_CURR_PY] = ny;
4810
+ dynamicData[dynBase + DYN_CURR_PZ] = nz;
4811
+ },
4812
+ get position() {
4813
+ posOut[0] = dynamicData[dynBase + DYN_CURR_PX];
4814
+ posOut[1] = dynamicData[dynBase + DYN_CURR_PY];
4815
+ posOut[2] = dynamicData[dynBase + DYN_CURR_PZ];
4816
+ return posOut;
4817
+ },
4818
+ get rotation() {
4819
+ rotOut[0] = dynamicData[dynBase + DYN_CURR_RX];
4820
+ rotOut[1] = dynamicData[dynBase + DYN_CURR_RY];
4821
+ rotOut[2] = dynamicData[dynBase + DYN_CURR_RZ];
4822
+ return rotOut;
4823
+ },
4824
+ get scale() {
4825
+ sclOut[0] = staticData[statBase + STAT_SX];
4826
+ sclOut[1] = staticData[statBase + STAT_SY];
4827
+ sclOut[2] = staticData[statBase + STAT_SZ];
4828
+ return sclOut;
4829
+ },
4799
4830
  destroy() {
4800
4831
  if (destroyed)
4801
4832
  return;
@@ -4809,7 +4840,7 @@ var WebGPU3DRenderer = class extends import_renderer4.Base3DRenderer {
4809
4840
  };
4810
4841
  return handle;
4811
4842
  }
4812
- addGltfInstance(opts, gltf) {
4843
+ addGltfInstance(opts, gltf, prefabId) {
4813
4844
  const childHandles = [];
4814
4845
  let firstSkinnedSlot;
4815
4846
  for (const part of gltf.parts) {
@@ -4817,7 +4848,7 @@ var WebGPU3DRenderer = class extends import_renderer4.Base3DRenderer {
4817
4848
  const model = this.models[part.id];
4818
4849
  let handle;
4819
4850
  if (model?.skinned) {
4820
- handle = this.addSkinnedInstance(partOpts, part, model.skinIndex, firstSkinnedSlot);
4851
+ handle = this.addSkinnedInstance(partOpts, part, model.skinIndex, firstSkinnedSlot, prefabId);
4821
4852
  if (firstSkinnedSlot === void 0)
4822
4853
  firstSkinnedSlot = handle.slot;
4823
4854
  } else {
@@ -4826,8 +4857,10 @@ var WebGPU3DRenderer = class extends import_renderer4.Base3DRenderer {
4826
4857
  childHandles.push(handle);
4827
4858
  }
4828
4859
  const skinnedHandle = childHandles.find((h) => h.skinned);
4860
+ const lead = childHandles[0];
4829
4861
  return {
4830
4862
  skinned: gltf.skinned,
4863
+ prefabId,
4831
4864
  setPosition(x, y, z) {
4832
4865
  for (const h of childHandles)
4833
4866
  h.setPosition(x, y, z);
@@ -4840,6 +4873,19 @@ var WebGPU3DRenderer = class extends import_renderer4.Base3DRenderer {
4840
4873
  for (const h of childHandles)
4841
4874
  h.setScale(x, y, z);
4842
4875
  },
4876
+ teleport(x, y, z) {
4877
+ for (const h of childHandles)
4878
+ h.teleport(x, y, z);
4879
+ },
4880
+ get position() {
4881
+ return lead.position;
4882
+ },
4883
+ get rotation() {
4884
+ return lead.rotation;
4885
+ },
4886
+ get scale() {
4887
+ return lead.scale;
4888
+ },
4843
4889
  play: skinnedHandle?.play ? (name, opts2) => {
4844
4890
  skinnedHandle.play(name, opts2);
4845
4891
  } : void 0,
@@ -4876,6 +4922,17 @@ var WebGPU3DRenderer = class extends import_renderer4.Base3DRenderer {
4876
4922
  rz: p.offset?.rotation?.[2] ?? 0
4877
4923
  }));
4878
4924
  const childHandles = [];
4925
+ const posOut = [basePos[0], basePos[1], basePos[2]];
4926
+ const rotOut = [baseRot[0], baseRot[1], baseRot[2]];
4927
+ const sclOut = [1, 1, 1];
4928
+ const initialScale = opts.scale;
4929
+ if (typeof initialScale === "number") {
4930
+ sclOut[0] = sclOut[1] = sclOut[2] = initialScale;
4931
+ } else if (initialScale) {
4932
+ sclOut[0] = initialScale[0];
4933
+ sclOut[1] = initialScale[1];
4934
+ sclOut[2] = initialScale[2];
4935
+ }
4879
4936
  for (let i = 0; i < composite.parts.length; i++) {
4880
4937
  const part = composite.parts[i];
4881
4938
  const off = offsets[i];
@@ -4890,29 +4947,57 @@ var WebGPU3DRenderer = class extends import_renderer4.Base3DRenderer {
4890
4947
  }
4891
4948
  return {
4892
4949
  skinned: childHandles.some((h) => h.skinned),
4950
+ prefabId: composite.id,
4893
4951
  setPosition(x, y, z) {
4952
+ posOut[0] = x;
4953
+ posOut[1] = y;
4954
+ posOut[2] = z;
4894
4955
  for (let i = 0; i < childHandles.length; i++) {
4895
4956
  const o = offsets[i];
4896
4957
  childHandles[i].setPosition(x + o.px, y + o.py, z + o.pz);
4897
4958
  }
4898
4959
  },
4899
4960
  setRotation(x, y, z) {
4961
+ rotOut[0] = x;
4962
+ rotOut[1] = y;
4963
+ rotOut[2] = z;
4900
4964
  for (let i = 0; i < childHandles.length; i++) {
4901
4965
  const o = offsets[i];
4902
4966
  childHandles[i].setRotation(x + o.rx, y + o.ry, z + o.rz);
4903
4967
  }
4904
4968
  },
4905
4969
  setScale(x, y, z) {
4970
+ sclOut[0] = x;
4971
+ sclOut[1] = y;
4972
+ sclOut[2] = z;
4906
4973
  for (const h of childHandles)
4907
4974
  h.setScale(x, y, z);
4908
4975
  },
4976
+ teleport(x, y, z) {
4977
+ posOut[0] = x;
4978
+ posOut[1] = y;
4979
+ posOut[2] = z;
4980
+ for (let i = 0; i < childHandles.length; i++) {
4981
+ const o = offsets[i];
4982
+ childHandles[i].teleport(x + o.px, y + o.py, z + o.pz);
4983
+ }
4984
+ },
4985
+ get position() {
4986
+ return posOut;
4987
+ },
4988
+ get rotation() {
4989
+ return rotOut;
4990
+ },
4991
+ get scale() {
4992
+ return sclOut;
4993
+ },
4909
4994
  destroy() {
4910
4995
  for (const h of childHandles)
4911
4996
  h.destroy();
4912
4997
  }
4913
4998
  };
4914
4999
  }
4915
- addSkinnedInstance(opts, modelHandle, skinIndex, linkedSlot) {
5000
+ addSkinnedInstance(opts, modelHandle, skinIndex, linkedSlot, prefabId = null) {
4916
5001
  const slot = this.skinnedFreeList.allocate();
4917
5002
  if (slot === -1)
4918
5003
  throw new Error(`Max skinned instances (${this.maxSkinnedInstances}) reached`);
@@ -4981,10 +5066,14 @@ var WebGPU3DRenderer = class extends import_renderer4.Base3DRenderer {
4981
5066
  const capturedBoneOffset = boneOffset;
4982
5067
  const capturedSkinIndex = skinIndex;
4983
5068
  let destroyed = false;
5069
+ const posOut = [0, 0, 0];
5070
+ const rotOut = [0, 0, 0];
5071
+ const sclOut = [0, 0, 0];
4984
5072
  return {
4985
5073
  slot,
4986
5074
  modelId: modelHandle.id,
4987
5075
  skinned: true,
5076
+ prefabId,
4988
5077
  setPosition(nx, ny, nz) {
4989
5078
  dynamicData[dynBase + DYN_CURR_PX] = nx;
4990
5079
  dynamicData[dynBase + DYN_CURR_PY] = ny;
@@ -5000,6 +5089,32 @@ var WebGPU3DRenderer = class extends import_renderer4.Base3DRenderer {
5000
5089
  staticData[statBase + SSTAT_SY] = ny;
5001
5090
  staticData[statBase + SSTAT_SZ] = nz;
5002
5091
  },
5092
+ teleport(nx, ny, nz) {
5093
+ dynamicData[dynBase + DYN_PREV_PX] = nx;
5094
+ dynamicData[dynBase + DYN_PREV_PY] = ny;
5095
+ dynamicData[dynBase + DYN_PREV_PZ] = nz;
5096
+ dynamicData[dynBase + DYN_CURR_PX] = nx;
5097
+ dynamicData[dynBase + DYN_CURR_PY] = ny;
5098
+ dynamicData[dynBase + DYN_CURR_PZ] = nz;
5099
+ },
5100
+ get position() {
5101
+ posOut[0] = dynamicData[dynBase + DYN_CURR_PX];
5102
+ posOut[1] = dynamicData[dynBase + DYN_CURR_PY];
5103
+ posOut[2] = dynamicData[dynBase + DYN_CURR_PZ];
5104
+ return posOut;
5105
+ },
5106
+ get rotation() {
5107
+ rotOut[0] = dynamicData[dynBase + DYN_CURR_RX];
5108
+ rotOut[1] = dynamicData[dynBase + DYN_CURR_RY];
5109
+ rotOut[2] = dynamicData[dynBase + DYN_CURR_RZ];
5110
+ return rotOut;
5111
+ },
5112
+ get scale() {
5113
+ sclOut[0] = staticData[statBase + SSTAT_SX];
5114
+ sclOut[1] = staticData[statBase + SSTAT_SY];
5115
+ sclOut[2] = staticData[statBase + SSTAT_SZ];
5116
+ return sclOut;
5117
+ },
5003
5118
  play(name, opts2) {
5004
5119
  const state = animStates[slot];
5005
5120
  if (state)
@@ -4622,17 +4622,18 @@ var WebGPU3DRenderer = class extends Base3DRenderer {
4622
4622
  * with another instance (e.g., when spawning all parts of a character).
4623
4623
  */
4624
4624
  addInstance(opts) {
4625
+ const userPrefabId = isPrefab3D(opts.model) ? opts.model.id : null;
4625
4626
  if (isPrefab3D(opts.model) && opts.model.type === "composite") {
4626
4627
  return this.addCompositeInstance(opts, opts.model);
4627
4628
  }
4628
4629
  const modelOrGltf = isPrefab3D(opts.model) ? resolvePrefabHandle(opts.model) : opts.model;
4629
4630
  if ("parts" in modelOrGltf) {
4630
- return this.addGltfInstance(opts, modelOrGltf);
4631
+ return this.addGltfInstance(opts, modelOrGltf, userPrefabId);
4631
4632
  }
4632
4633
  const modelHandle = modelOrGltf;
4633
4634
  const model = this.models[modelHandle.id];
4634
4635
  if (model?.skinned) {
4635
- return this.addSkinnedInstance(opts, modelHandle, model.skinIndex);
4636
+ return this.addSkinnedInstance(opts, modelHandle, model.skinIndex, void 0, userPrefabId);
4636
4637
  }
4637
4638
  const slot = this.freeList.allocate();
4638
4639
  if (slot === -1)
@@ -4665,10 +4666,14 @@ var WebGPU3DRenderer = class extends Base3DRenderer {
4665
4666
  const staticData = this.staticData;
4666
4667
  const self = this;
4667
4668
  let destroyed = false;
4669
+ const posOut = [0, 0, 0];
4670
+ const rotOut = [0, 0, 0];
4671
+ const sclOut = [0, 0, 0];
4668
4672
  const handle = {
4669
4673
  slot,
4670
4674
  modelId: modelHandle.id,
4671
4675
  skinned: false,
4676
+ prefabId: userPrefabId,
4672
4677
  setPosition(nx, ny, nz) {
4673
4678
  dynamicData[dynBase + DYN_CURR_PX] = nx;
4674
4679
  dynamicData[dynBase + DYN_CURR_PY] = ny;
@@ -4684,6 +4689,32 @@ var WebGPU3DRenderer = class extends Base3DRenderer {
4684
4689
  staticData[statBase + STAT_SY] = ny;
4685
4690
  staticData[statBase + STAT_SZ] = nz;
4686
4691
  },
4692
+ teleport(nx, ny, nz) {
4693
+ dynamicData[dynBase + DYN_PREV_PX] = nx;
4694
+ dynamicData[dynBase + DYN_PREV_PY] = ny;
4695
+ dynamicData[dynBase + DYN_PREV_PZ] = nz;
4696
+ dynamicData[dynBase + DYN_CURR_PX] = nx;
4697
+ dynamicData[dynBase + DYN_CURR_PY] = ny;
4698
+ dynamicData[dynBase + DYN_CURR_PZ] = nz;
4699
+ },
4700
+ get position() {
4701
+ posOut[0] = dynamicData[dynBase + DYN_CURR_PX];
4702
+ posOut[1] = dynamicData[dynBase + DYN_CURR_PY];
4703
+ posOut[2] = dynamicData[dynBase + DYN_CURR_PZ];
4704
+ return posOut;
4705
+ },
4706
+ get rotation() {
4707
+ rotOut[0] = dynamicData[dynBase + DYN_CURR_RX];
4708
+ rotOut[1] = dynamicData[dynBase + DYN_CURR_RY];
4709
+ rotOut[2] = dynamicData[dynBase + DYN_CURR_RZ];
4710
+ return rotOut;
4711
+ },
4712
+ get scale() {
4713
+ sclOut[0] = staticData[statBase + STAT_SX];
4714
+ sclOut[1] = staticData[statBase + STAT_SY];
4715
+ sclOut[2] = staticData[statBase + STAT_SZ];
4716
+ return sclOut;
4717
+ },
4687
4718
  destroy() {
4688
4719
  if (destroyed)
4689
4720
  return;
@@ -4697,7 +4728,7 @@ var WebGPU3DRenderer = class extends Base3DRenderer {
4697
4728
  };
4698
4729
  return handle;
4699
4730
  }
4700
- addGltfInstance(opts, gltf) {
4731
+ addGltfInstance(opts, gltf, prefabId) {
4701
4732
  const childHandles = [];
4702
4733
  let firstSkinnedSlot;
4703
4734
  for (const part of gltf.parts) {
@@ -4705,7 +4736,7 @@ var WebGPU3DRenderer = class extends Base3DRenderer {
4705
4736
  const model = this.models[part.id];
4706
4737
  let handle;
4707
4738
  if (model?.skinned) {
4708
- handle = this.addSkinnedInstance(partOpts, part, model.skinIndex, firstSkinnedSlot);
4739
+ handle = this.addSkinnedInstance(partOpts, part, model.skinIndex, firstSkinnedSlot, prefabId);
4709
4740
  if (firstSkinnedSlot === void 0)
4710
4741
  firstSkinnedSlot = handle.slot;
4711
4742
  } else {
@@ -4714,8 +4745,10 @@ var WebGPU3DRenderer = class extends Base3DRenderer {
4714
4745
  childHandles.push(handle);
4715
4746
  }
4716
4747
  const skinnedHandle = childHandles.find((h) => h.skinned);
4748
+ const lead = childHandles[0];
4717
4749
  return {
4718
4750
  skinned: gltf.skinned,
4751
+ prefabId,
4719
4752
  setPosition(x, y, z) {
4720
4753
  for (const h of childHandles)
4721
4754
  h.setPosition(x, y, z);
@@ -4728,6 +4761,19 @@ var WebGPU3DRenderer = class extends Base3DRenderer {
4728
4761
  for (const h of childHandles)
4729
4762
  h.setScale(x, y, z);
4730
4763
  },
4764
+ teleport(x, y, z) {
4765
+ for (const h of childHandles)
4766
+ h.teleport(x, y, z);
4767
+ },
4768
+ get position() {
4769
+ return lead.position;
4770
+ },
4771
+ get rotation() {
4772
+ return lead.rotation;
4773
+ },
4774
+ get scale() {
4775
+ return lead.scale;
4776
+ },
4731
4777
  play: skinnedHandle?.play ? (name, opts2) => {
4732
4778
  skinnedHandle.play(name, opts2);
4733
4779
  } : void 0,
@@ -4764,6 +4810,17 @@ var WebGPU3DRenderer = class extends Base3DRenderer {
4764
4810
  rz: p.offset?.rotation?.[2] ?? 0
4765
4811
  }));
4766
4812
  const childHandles = [];
4813
+ const posOut = [basePos[0], basePos[1], basePos[2]];
4814
+ const rotOut = [baseRot[0], baseRot[1], baseRot[2]];
4815
+ const sclOut = [1, 1, 1];
4816
+ const initialScale = opts.scale;
4817
+ if (typeof initialScale === "number") {
4818
+ sclOut[0] = sclOut[1] = sclOut[2] = initialScale;
4819
+ } else if (initialScale) {
4820
+ sclOut[0] = initialScale[0];
4821
+ sclOut[1] = initialScale[1];
4822
+ sclOut[2] = initialScale[2];
4823
+ }
4767
4824
  for (let i = 0; i < composite.parts.length; i++) {
4768
4825
  const part = composite.parts[i];
4769
4826
  const off = offsets[i];
@@ -4778,29 +4835,57 @@ var WebGPU3DRenderer = class extends Base3DRenderer {
4778
4835
  }
4779
4836
  return {
4780
4837
  skinned: childHandles.some((h) => h.skinned),
4838
+ prefabId: composite.id,
4781
4839
  setPosition(x, y, z) {
4840
+ posOut[0] = x;
4841
+ posOut[1] = y;
4842
+ posOut[2] = z;
4782
4843
  for (let i = 0; i < childHandles.length; i++) {
4783
4844
  const o = offsets[i];
4784
4845
  childHandles[i].setPosition(x + o.px, y + o.py, z + o.pz);
4785
4846
  }
4786
4847
  },
4787
4848
  setRotation(x, y, z) {
4849
+ rotOut[0] = x;
4850
+ rotOut[1] = y;
4851
+ rotOut[2] = z;
4788
4852
  for (let i = 0; i < childHandles.length; i++) {
4789
4853
  const o = offsets[i];
4790
4854
  childHandles[i].setRotation(x + o.rx, y + o.ry, z + o.rz);
4791
4855
  }
4792
4856
  },
4793
4857
  setScale(x, y, z) {
4858
+ sclOut[0] = x;
4859
+ sclOut[1] = y;
4860
+ sclOut[2] = z;
4794
4861
  for (const h of childHandles)
4795
4862
  h.setScale(x, y, z);
4796
4863
  },
4864
+ teleport(x, y, z) {
4865
+ posOut[0] = x;
4866
+ posOut[1] = y;
4867
+ posOut[2] = z;
4868
+ for (let i = 0; i < childHandles.length; i++) {
4869
+ const o = offsets[i];
4870
+ childHandles[i].teleport(x + o.px, y + o.py, z + o.pz);
4871
+ }
4872
+ },
4873
+ get position() {
4874
+ return posOut;
4875
+ },
4876
+ get rotation() {
4877
+ return rotOut;
4878
+ },
4879
+ get scale() {
4880
+ return sclOut;
4881
+ },
4797
4882
  destroy() {
4798
4883
  for (const h of childHandles)
4799
4884
  h.destroy();
4800
4885
  }
4801
4886
  };
4802
4887
  }
4803
- addSkinnedInstance(opts, modelHandle, skinIndex, linkedSlot) {
4888
+ addSkinnedInstance(opts, modelHandle, skinIndex, linkedSlot, prefabId = null) {
4804
4889
  const slot = this.skinnedFreeList.allocate();
4805
4890
  if (slot === -1)
4806
4891
  throw new Error(`Max skinned instances (${this.maxSkinnedInstances}) reached`);
@@ -4869,10 +4954,14 @@ var WebGPU3DRenderer = class extends Base3DRenderer {
4869
4954
  const capturedBoneOffset = boneOffset;
4870
4955
  const capturedSkinIndex = skinIndex;
4871
4956
  let destroyed = false;
4957
+ const posOut = [0, 0, 0];
4958
+ const rotOut = [0, 0, 0];
4959
+ const sclOut = [0, 0, 0];
4872
4960
  return {
4873
4961
  slot,
4874
4962
  modelId: modelHandle.id,
4875
4963
  skinned: true,
4964
+ prefabId,
4876
4965
  setPosition(nx, ny, nz) {
4877
4966
  dynamicData[dynBase + DYN_CURR_PX] = nx;
4878
4967
  dynamicData[dynBase + DYN_CURR_PY] = ny;
@@ -4888,6 +4977,32 @@ var WebGPU3DRenderer = class extends Base3DRenderer {
4888
4977
  staticData[statBase + SSTAT_SY] = ny;
4889
4978
  staticData[statBase + SSTAT_SZ] = nz;
4890
4979
  },
4980
+ teleport(nx, ny, nz) {
4981
+ dynamicData[dynBase + DYN_PREV_PX] = nx;
4982
+ dynamicData[dynBase + DYN_PREV_PY] = ny;
4983
+ dynamicData[dynBase + DYN_PREV_PZ] = nz;
4984
+ dynamicData[dynBase + DYN_CURR_PX] = nx;
4985
+ dynamicData[dynBase + DYN_CURR_PY] = ny;
4986
+ dynamicData[dynBase + DYN_CURR_PZ] = nz;
4987
+ },
4988
+ get position() {
4989
+ posOut[0] = dynamicData[dynBase + DYN_CURR_PX];
4990
+ posOut[1] = dynamicData[dynBase + DYN_CURR_PY];
4991
+ posOut[2] = dynamicData[dynBase + DYN_CURR_PZ];
4992
+ return posOut;
4993
+ },
4994
+ get rotation() {
4995
+ rotOut[0] = dynamicData[dynBase + DYN_CURR_RX];
4996
+ rotOut[1] = dynamicData[dynBase + DYN_CURR_RY];
4997
+ rotOut[2] = dynamicData[dynBase + DYN_CURR_RZ];
4998
+ return rotOut;
4999
+ },
5000
+ get scale() {
5001
+ sclOut[0] = staticData[statBase + SSTAT_SX];
5002
+ sclOut[1] = staticData[statBase + SSTAT_SY];
5003
+ sclOut[2] = staticData[statBase + SSTAT_SZ];
5004
+ return sclOut;
5005
+ },
4891
5006
  play(name, opts2) {
4892
5007
  const state = animStates[slot];
4893
5008
  if (state)