murow 0.0.72 → 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 (115) 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/core/sparse-batcher/sparse-batcher.js +1 -1
  13. package/dist/cjs/ecs/component.js +1 -1
  14. package/dist/cjs/ecs/system-builder.js +1 -1
  15. package/dist/cjs/ecs/world.js +1 -1
  16. package/dist/cjs/game/loop/loop.js +1 -1
  17. package/dist/cjs/net/adapters/bun-websocket.js +1 -1
  18. package/dist/cjs/renderer/base/renderer-3d.js +1 -1
  19. package/dist/cjs/renderer/prefab-bucket/concrete.js +1 -1
  20. package/dist/cjs/renderer/prefab-bucket/index.js +1 -1
  21. package/dist/cjs/renderer/prefab-bucket/parsers.js +1 -1
  22. package/dist/cjs/renderer/prefab-bucket/specs.js +1 -1
  23. package/dist/esm/core/binary-codec/binary-codec.js +1 -1
  24. package/dist/esm/core/driver/drivers/immediate.js +1 -1
  25. package/dist/esm/core/driver/drivers/raf.js +1 -1
  26. package/dist/esm/core/driver/drivers/timeout.js +1 -1
  27. package/dist/esm/core/input/index.js +1 -1
  28. package/dist/esm/core/input/mouse-look/index.js +1 -0
  29. package/dist/esm/core/input/mouse-look/mouse-look.js +1 -0
  30. package/dist/esm/core/input/scroll-zoom/index.js +1 -0
  31. package/dist/esm/core/input/scroll-zoom/scroll-zoom.js +1 -0
  32. package/dist/esm/core/sparse-batcher/sparse-batcher.js +1 -1
  33. package/dist/esm/ecs/component.js +1 -1
  34. package/dist/esm/ecs/system-builder.js +1 -1
  35. package/dist/esm/ecs/world.js +1 -1
  36. package/dist/esm/game/loop/loop.js +1 -1
  37. package/dist/esm/net/adapters/bun-websocket.js +1 -1
  38. package/dist/esm/renderer/base/renderer-3d.js +1 -1
  39. package/dist/esm/renderer/prefab-bucket/concrete.js +1 -1
  40. package/dist/esm/renderer/prefab-bucket/index.js +1 -1
  41. package/dist/esm/renderer/prefab-bucket/parsers.js +1 -1
  42. package/dist/netcode/cjs/index.js +1552 -0
  43. package/dist/netcode/esm/index.js +1530 -0
  44. package/dist/netcode/types/client/game-client.d.ts +125 -0
  45. package/dist/netcode/types/client/index.d.ts +1 -0
  46. package/dist/netcode/types/client/interpolation-buffer.d.ts +37 -0
  47. package/dist/netcode/types/client/interpolation-buffer.test.d.ts +1 -0
  48. package/dist/netcode/types/codec/delta-codec.d.ts +17 -0
  49. package/dist/netcode/types/codec/delta-codec.test.d.ts +1 -0
  50. package/dist/netcode/types/codec/index.d.ts +1 -0
  51. package/dist/netcode/types/components/index.d.ts +1 -0
  52. package/dist/netcode/types/components/sync-spec.d.ts +43 -0
  53. package/dist/netcode/types/components/sync-spec.test.d.ts +1 -0
  54. package/dist/netcode/types/ctx.d.ts +105 -0
  55. package/dist/netcode/types/ctx.test.d.ts +1 -0
  56. package/dist/netcode/types/handlers/define-handlers.d.ts +47 -0
  57. package/dist/netcode/types/handlers/index.d.ts +1 -0
  58. package/dist/netcode/types/index.d.ts +11 -0
  59. package/dist/netcode/types/integration.test.d.ts +1 -0
  60. package/dist/netcode/types/intents/define-intents.d.ts +53 -0
  61. package/dist/netcode/types/intents/define-intents.test.d.ts +1 -0
  62. package/dist/netcode/types/intents/index.d.ts +1 -0
  63. package/dist/netcode/types/network/base.d.ts +120 -0
  64. package/dist/netcode/types/network/index.d.ts +2 -0
  65. package/dist/netcode/types/network/transport.d.ts +1 -0
  66. package/dist/netcode/types/packets/convergence.test.d.ts +1 -0
  67. package/dist/netcode/types/packets/harness.d.ts +103 -0
  68. package/dist/netcode/types/packets/index.d.ts +2 -0
  69. package/dist/netcode/types/packets/intermittent-intents.test.d.ts +1 -0
  70. package/dist/netcode/types/packets/pathological.test.d.ts +1 -0
  71. package/dist/netcode/types/packets/peer-interpolation.test.d.ts +1 -0
  72. package/dist/netcode/types/packets/reordering.test.d.ts +1 -0
  73. package/dist/netcode/types/packets/virtual-network.d.ts +65 -0
  74. package/dist/netcode/types/predictions/define-predictions.d.ts +45 -0
  75. package/dist/netcode/types/predictions/define-predictions.test.d.ts +1 -0
  76. package/dist/netcode/types/predictions/index.d.ts +1 -0
  77. package/dist/netcode/types/reconciliation.test.d.ts +1 -0
  78. package/dist/netcode/types/rpcs/define-rpcs.d.ts +44 -0
  79. package/dist/netcode/types/rpcs/define-rpcs.test.d.ts +1 -0
  80. package/dist/netcode/types/rpcs/index.d.ts +1 -0
  81. package/dist/netcode/types/server/game-server.d.ts +77 -0
  82. package/dist/netcode/types/server/index.d.ts +2 -0
  83. package/dist/netcode/types/server/plugins/aoi-grid.d.ts +34 -0
  84. package/dist/netcode/types/server/plugins/index.d.ts +3 -0
  85. package/dist/netcode/types/server/plugins/lag-compensation.d.ts +34 -0
  86. package/dist/netcode/types/server/plugins/plugin.d.ts +24 -0
  87. package/dist/netcode/types/tick-rate.test.d.ts +1 -0
  88. package/dist/netcode/types/transports/index.d.ts +1 -0
  89. package/dist/netcode/types/transports/memory-transport.d.ts +51 -0
  90. package/dist/netcode/types/types.test.d.ts +1 -0
  91. package/dist/types/core/binary-codec/binary-codec.d.ts +89 -31
  92. package/dist/types/core/driver/driver.d.ts +8 -8
  93. package/dist/types/core/driver/drivers/immediate.d.ts +4 -4
  94. package/dist/types/core/driver/drivers/raf.d.ts +17 -6
  95. package/dist/types/core/driver/drivers/timeout.d.ts +4 -4
  96. package/dist/types/core/input/index.d.ts +2 -0
  97. package/dist/types/core/input/mouse-look/index.d.ts +1 -0
  98. package/dist/types/core/input/mouse-look/mouse-look.d.ts +139 -0
  99. package/dist/types/core/input/scroll-zoom/index.d.ts +1 -0
  100. package/dist/types/core/input/scroll-zoom/scroll-zoom.d.ts +38 -0
  101. package/dist/types/ecs/component.d.ts +67 -11
  102. package/dist/types/ecs/entity-handle.d.ts +5 -5
  103. package/dist/types/ecs/system-builder.d.ts +13 -0
  104. package/dist/types/ecs/world.d.ts +72 -4
  105. package/dist/types/game/loop/loop.d.ts +21 -2
  106. package/dist/types/net/adapters/bun-websocket.d.ts +19 -3
  107. package/dist/types/renderer/base/renderer-3d.d.ts +1 -1
  108. package/dist/types/renderer/prefab-bucket/concrete.d.ts +42 -2
  109. package/dist/types/renderer/prefab-bucket/index.d.ts +12 -2
  110. package/dist/types/renderer/prefab-bucket/specs.d.ts +46 -3
  111. package/dist/types/renderer/types.d.ts +5 -3
  112. package/dist/webgpu/cjs/index.js +591 -40
  113. package/dist/webgpu/esm/index.js +591 -40
  114. package/dist/webgpu/types/3d/renderer.d.ts +111 -5
  115. package/package.json +6 -1
@@ -96,7 +96,7 @@ export declare class EntityHandle {
96
96
  * const velocityVx = entity.field(Velocity, 'vx');
97
97
  *
98
98
  * // Direct array access - same as RAW API!
99
- * transformX[entity.id] += velocityVx[entity.id] * dt;
99
+ * transformX[entity.id] += velocityVx[entity.id] * deltaTime;
100
100
  * ```
101
101
  */
102
102
  field<T extends object, K extends keyof T>(component: Component<T>, field: K): Float32Array | Int32Array | Uint32Array | Uint16Array | Uint8Array;
@@ -149,8 +149,8 @@ export declare class EntityHandle {
149
149
  * const velocity = entity.get(Velocity); // Uses cached data
150
150
  *
151
151
  * entity
152
- * .setField(Transform, 'x', transform.x + velocity.vx * dt)
153
- * .setField(Transform, 'y', transform.y + velocity.vy * dt)
152
+ * .setField(Transform, 'x', transform.x + velocity.vx * deltaTime)
153
+ * .setField(Transform, 'y', transform.y + velocity.vy * deltaTime)
154
154
  * .flush();
155
155
  * ```
156
156
  */
@@ -189,8 +189,8 @@ export declare class EntityHandle {
189
189
  * ```typescript
190
190
  * // Mutate fields directly
191
191
  * entity.setFields(Transform, function (t) {
192
- * t.x += velocity.vx * dt;
193
- * t.y += velocity.vy * dt;
192
+ * t.x += velocity.vx * deltaTime;
193
+ * t.y += velocity.vy * deltaTime;
194
194
  * });
195
195
  *
196
196
  * // Conditional mutation
@@ -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
  }
@@ -4,7 +4,7 @@
4
4
  import { BaseRenderer } from "./renderer";
5
5
  import type { Camera3DState, Renderer3DOptions } from "../types";
6
6
  export declare abstract class Base3DRenderer extends BaseRenderer<Renderer3DOptions> {
7
- readonly maxModels: number;
7
+ readonly maxInstances: number;
8
8
  abstract readonly camera: Camera3DState;
9
9
  constructor(canvas: HTMLCanvasElement, options: Renderer3DOptions);
10
10
  }
@@ -17,7 +17,7 @@
17
17
  * ```
18
18
  */
19
19
  import { BasePrefabBucket, type StringOr } from './index';
20
- import type { Prefab2D, Prefab2DSpec, Prefab3D, Prefab3DSpec, PrefabFor } from './specs';
20
+ import type { CompositePrefab, PartOffset, Prefab2D, Prefab2DSpec, Prefab3D, Prefab3DSpec, PrefabFor } from './specs';
21
21
  type SpecForMode<M> = M extends '3d' ? Prefab3DSpec : Prefab2DSpec;
22
22
  type PrefabUnionForMode<M> = M extends '3d' ? Prefab3D : Prefab2D;
23
23
  /**
@@ -48,8 +48,48 @@ export declare class PrefabBucket<M extends '2d' | '3d' = '3d', Specs extends Re
48
48
  * Accepts any string at runtime but autocompletes known ids.
49
49
  */
50
50
  get<K extends keyof Specs & string, R = PrefabFor<Specs[K]>>(id: StringOr<K>): R;
51
+ /**
52
+ * Register a named group of part specs. Each part is added as a top-level
53
+ * prefab with id `<groupName>.<partId>` (or `<groupName>.<auto-hex>` when
54
+ * the part omitted `id`). A `composite` prefab is also created at id
55
+ * `groupName`, with the parts wired up via their `offset` for spawning.
56
+ *
57
+ * ```ts
58
+ * bucket.addGroup('campfire', [
59
+ * { type: 'gltf', id: 'logs', src: '/logs.glb' },
60
+ * { type: 'cube', id: 'flame', size: 0.3, offset: { position: [0, 0.3, 0] } },
61
+ * { type: 'cube', size: 0.5, offset: { position: [0, 0.8, 0] } }, // auto id
62
+ * ]);
63
+ *
64
+ * await bucket.load();
65
+ *
66
+ * bucket.get('campfire'); // composite (spawnable as one)
67
+ * bucket.get('campfire.logs'); // the part directly
68
+ * bucket.getGroup('campfire').prefabs; // both parts
69
+ * ```
70
+ */
71
+ addGroup(name: string, parts: readonly GroupPartInput<M>[]): this;
72
+ /**
73
+ * Look up a group registered via `addGroup`. Returns the parts in order
74
+ * and an `asComposite()` helper that returns the composite prefab created
75
+ * for the group (equivalent to `bucket.get(groupName)`).
76
+ */
77
+ getGroup(name: string): {
78
+ prefabs: Prefab3D[];
79
+ asComposite(): CompositePrefab;
80
+ };
51
81
  }
82
+ /**
83
+ * Part spec accepted by `addGroup`: a regular 3D spec with `id` optional
84
+ * (auto-generated when absent) and a new `offset` field.
85
+ */
86
+ type GroupPartInput<M extends '2d' | '3d'> = M extends '3d' ? {
87
+ [K in Prefab3DSpec as K['type']]: Omit<K, 'id'> & {
88
+ id?: string;
89
+ offset?: PartOffset;
90
+ };
91
+ }[Prefab3DSpec['type']] : never;
52
92
  /** Convenience aliases for explicit mode typing (e.g. function params). */
53
93
  export type PrefabBucket2D<Specs extends Record<string, Prefab2DSpec> = {}> = PrefabBucket<'2d', Specs>;
54
94
  export type PrefabBucket3D<Specs extends Record<string, Prefab3DSpec> = {}> = PrefabBucket<'3d', Specs>;
55
- export type { GltfPrefab, GltfSpec, GridPrefab, GridSpec, Prefab2D, Prefab2DSpec, Prefab3D, Prefab3DSpec, PrefabFor, SpritesheetPrefab, SpritesheetSpec, } from './specs';
95
+ export type { CompositePrefab, CompositeSpec, CubePrefab, CubeSpec, GltfPrefab, GltfSpec, GridPrefab, GridSpec, PartOffset, Prefab2D, Prefab2DSpec, Prefab3D, Prefab3DSpec, PrefabFor, SpritesheetPrefab, SpritesheetSpec, } from './specs';
@@ -58,17 +58,27 @@ export type PrefabParser<Spec extends PrefabSpecBase = PrefabSpecBase, Prefab ex
58
58
  export type PrefabParserMap<Spec extends PrefabSpecBase, Prefab extends PrefabBase> = Record<string, PrefabParser<Spec, Prefab>>;
59
59
  export declare class BasePrefabBucket<M extends PrefabMode = PrefabMode, Spec extends PrefabSpecBase = PrefabSpecBase, Prefab extends PrefabBase = PrefabBase, Specs extends Record<string, Spec> = {}> {
60
60
  readonly mode: M;
61
- /** Shared notification channel see `PrefabBucketEvents`. */
61
+ /** Shared notification channel - see `PrefabBucketEvents`. */
62
62
  readonly events: EventSystem<PrefabBucketEvents>;
63
63
  private parsers;
64
64
  private pending;
65
65
  private pendingIds;
66
66
  private prefabs;
67
+ /** `groupName -> ordered list of final part ids`. Populated by `addGroup` on subclasses. */
68
+ protected groups: Map<string, string[]>;
67
69
  constructor(mode: M, parsers?: PrefabParserMap<Spec, Prefab>);
68
- /** Add a single spec. Throws if id is a duplicate or if the bucket is already loaded. */
70
+ /** Add a single spec. Throws if id is a duplicate, contains '.', or if the bucket is already loaded. */
69
71
  add<const S extends Spec>(spec: S): BasePrefabBucket<M, Spec, Prefab, Specs & {
70
72
  [K in S['id']]: S;
71
73
  }>;
74
+ /**
75
+ * Internal add that skips the '.' validation. Used by `addGroup` on
76
+ * subclasses to register parts under group-path ids (e.g. `'campfire.logs'`)
77
+ * after the group itself has validated user input.
78
+ */
79
+ protected addUnchecked<const S extends Spec>(spec: S): BasePrefabBucket<M, Spec, Prefab, Specs & {
80
+ [K in S['id']]: S;
81
+ }>;
72
82
  /**
73
83
  * Add multiple specs. Validates the whole batch (ids unique, not loaded) before
74
84
  * committing, so the bucket never lands in a half-applied state.
@@ -53,7 +53,35 @@ export interface GridSpec {
53
53
  /** Optional user-defined sidecar data (scale, speed, gameplay hints, etc). */
54
54
  readonly metadata?: Record<string, unknown>;
55
55
  }
56
- export type Prefab3DSpec = GltfSpec | GridSpec;
56
+ /** Unit cube prefab centered at origin. Use the instance's `scale` to size it. */
57
+ export interface CubeSpec {
58
+ readonly type: 'cube';
59
+ readonly id: string;
60
+ /** Edge length. Defaults to 1. */
61
+ readonly size?: number;
62
+ readonly metadata?: Record<string, unknown>;
63
+ }
64
+ /** Local transform offset applied to a part inside a composite/group at spawn time. */
65
+ export interface PartOffset {
66
+ readonly position?: readonly [number, number, number];
67
+ readonly rotation?: readonly [number, number, number];
68
+ }
69
+ /**
70
+ * A prefab that spawns several other prefabs at fixed local offsets. Created
71
+ * by `bucket.addGroup(...)`; not typically written by hand. Spawning a
72
+ * composite instance spawns one child per part, with each part's offset
73
+ * composed onto the instance's position/rotation.
74
+ */
75
+ export interface CompositeSpec {
76
+ readonly type: 'composite';
77
+ readonly id: string;
78
+ readonly parts: readonly {
79
+ readonly partId: string;
80
+ readonly offset?: PartOffset;
81
+ }[];
82
+ readonly metadata?: Record<string, unknown>;
83
+ }
84
+ export type Prefab3DSpec = GltfSpec | GridSpec | CubeSpec | CompositeSpec;
57
85
  /**
58
86
  * Map a spec's `animations` tuple to a record-keyed-by-name. Used to type
59
87
  * `GltfPrefab.animations` so `prefab.animations.Run` is a string literal
@@ -134,7 +162,22 @@ export interface GridPrefab<S extends GridSpec = GridSpec> {
134
162
  readonly lineWidth: number;
135
163
  readonly metadata: MetadataOf<S>;
136
164
  }
137
- export type Prefab3D = GltfPrefab | GridPrefab;
165
+ export interface CubePrefab<S extends CubeSpec = CubeSpec> {
166
+ readonly type: 'cube';
167
+ readonly id: S['id'];
168
+ readonly size: number;
169
+ readonly metadata: MetadataOf<S>;
170
+ }
171
+ export interface CompositePrefab<S extends CompositeSpec = CompositeSpec> {
172
+ readonly type: 'composite';
173
+ readonly id: S['id'];
174
+ readonly parts: readonly {
175
+ readonly partId: string;
176
+ readonly offset?: PartOffset;
177
+ }[];
178
+ readonly metadata: MetadataOf<S>;
179
+ }
180
+ export type Prefab3D = GltfPrefab | GridPrefab | CubePrefab | CompositePrefab;
138
181
  export interface SpritesheetSpec {
139
182
  readonly type: 'spritesheet';
140
183
  readonly id: string;
@@ -162,5 +205,5 @@ export type Prefab2D = SpritesheetPrefab;
162
205
  * bucket's `get()` so `bucket.get('minion')` returns `GltfPrefab` directly,
163
206
  * not the discriminated union.
164
207
  */
165
- export type PrefabFor<S> = S extends GltfSpec ? GltfPrefab<S> : S extends GridSpec ? GridPrefab<S> : S extends SpritesheetSpec ? SpritesheetPrefab<S> : never;
208
+ export type PrefabFor<S> = S extends GltfSpec ? GltfPrefab<S> : S extends GridSpec ? GridPrefab<S> : S extends CubeSpec ? CubePrefab<S> : S extends CompositeSpec ? CompositePrefab<S> : S extends SpritesheetSpec ? SpritesheetPrefab<S> : never;
166
209
  export {};
@@ -14,10 +14,12 @@ export interface Renderer2DOptions extends RendererOptions {
14
14
  }
15
15
  export interface Renderer3DOptions extends RendererOptions {
16
16
  /**
17
- * Non-skinned instance budget. Optional when a prefab bucket is provided
18
- * the renderer will derive a sensible default from the bucket.
17
+ * How many instances you intend to spawn at once. Sizes both the
18
+ * non-skinned instance pool directly and seeds the default for the
19
+ * skinned-instance budget. Optional when a prefab bucket is provided -
20
+ * the renderer derives a sensible default from the bucket.
19
21
  */
20
- maxModels?: number;
22
+ maxInstances?: number;
21
23
  enableLighting?: boolean;
22
24
  }
23
25
  export interface Camera2DState {