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
@@ -0,0 +1,103 @@
1
+ import { World, type Entity } from 'murow/ecs';
2
+ import { GameLoop } from 'murow/game';
3
+ import { SimpleRNG } from 'murow/core/simple-rng';
4
+ import { GameServer } from '../server/game-server';
5
+ import { GameClient } from '../client/game-client';
6
+ import type { Peer } from '../ctx';
7
+ import { JitterConfig, VirtualNetwork, VirtualServerTransport } from './virtual-network';
8
+ export declare const MOVE_SPEED = 4;
9
+ export declare const TICK_RATE = 24;
10
+ export declare const STEP_SEC: number;
11
+ export declare const FIXED_DT: number;
12
+ export declare const Position: import("murow/ecs").Component<{
13
+ x: number;
14
+ z: number;
15
+ } & object, {
16
+ x: import("murow/core").Field<number, Float32Array<ArrayBufferLike>>;
17
+ z: import("murow/core").Field<number, Float32Array<ArrayBufferLike>>;
18
+ }>;
19
+ export declare function buildSchema(): {
20
+ intents: import("..").DefinedIntents<{
21
+ move: {
22
+ dx: import("murow/core").Field<number, Float32Array<ArrayBufferLike>>;
23
+ dz: import("murow/core").Field<number, Float32Array<ArrayBufferLike>>;
24
+ };
25
+ }>;
26
+ rpcs: import("..").DefinedRpcs<{}>;
27
+ predictions: import("..").DefinedPredictions<{
28
+ move: {
29
+ dx: import("murow/core").Field<number, Float32Array<ArrayBufferLike>>;
30
+ dz: import("murow/core").Field<number, Float32Array<ArrayBufferLike>>;
31
+ };
32
+ }>;
33
+ };
34
+ export interface PeerSim {
35
+ id: number;
36
+ client: GameClient<any, any>;
37
+ world: World;
38
+ loop: GameLoop<'manual-client'>;
39
+ peer: Peer;
40
+ serverEntity: Entity;
41
+ localEntity: Entity | null;
42
+ dx: number;
43
+ dz: number;
44
+ sentIntents: number;
45
+ serverAppliedIntents: number;
46
+ reconciles: number;
47
+ /** Largest correction magnitude observed across all reconciles. */
48
+ maxCorrection: number;
49
+ /** Largest replay depth observed across all reconciles. */
50
+ maxReplayDepth: number;
51
+ /** Total number of snapshots received. */
52
+ snapshotsReceived: number;
53
+ /** Snapshots whose tick was not strictly greater than the previous. */
54
+ staleSnapshots: number;
55
+ /** Highest snapshot tick observed. */
56
+ lastSnapshotTick: number;
57
+ /** Predicted position captured the moment a snapshot frame arrives. */
58
+ _preReconcilePos: {
59
+ x: number;
60
+ z: number;
61
+ } | null;
62
+ }
63
+ export interface Harness {
64
+ rng: SimpleRNG;
65
+ vnet: VirtualNetwork;
66
+ transport: VirtualServerTransport;
67
+ serverLoop: GameLoop<'manual-server'>;
68
+ serverWorld: World;
69
+ server: GameServer<any, any>;
70
+ sims: PeerSim[];
71
+ serverIntentsByPeer: Map<string, number>;
72
+ }
73
+ export interface HarnessOptions {
74
+ seed: number;
75
+ numClients: number;
76
+ jitter: JitterConfig;
77
+ /** Override per-client unit movement direction. Defaults to evenly-spaced ring. */
78
+ direction?: (i: number, n: number, rng: SimpleRNG) => {
79
+ dx: number;
80
+ dz: number;
81
+ };
82
+ /** Override client GameClient options. */
83
+ interpDelayMs?: number;
84
+ }
85
+ export declare function makeHarness(opts: HarnessOptions): Harness;
86
+ /**
87
+ * Run a no-loss bootstrap so every client receives its assignment, then
88
+ * (optionally) flip back to the jittery config for steady-state traffic.
89
+ * `bootstrap` defaults to "same config but lossChance=0".
90
+ *
91
+ * If `warmupTicks` is set, each client steps a random number of ticks in
92
+ * [0, warmupTicks] BEFORE the main scenario, so clients enter with
93
+ * different `localTick` values (simulating staggered join times).
94
+ */
95
+ export declare function bootstrap(h: Harness, steadyState: JitterConfig, opts?: {
96
+ warmupTicks?: number;
97
+ }): void;
98
+ /** Drain pending packets with loss disabled so post-conditions can settle. */
99
+ export declare function drain(h: Harness, jitter: JitterConfig, ticks?: number): void;
100
+ /** Snapshot per-peer server-applied intent counts into each sim. */
101
+ export declare function captureServerCounts(h: Harness): void;
102
+ /** FNV-1a 32-bit hash over Position fields. Determinism probe. */
103
+ export declare function hashPositions(world: World, entities: Entity[]): number;
@@ -0,0 +1,2 @@
1
+ export * from './virtual-network';
2
+ export * from './harness';
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,65 @@
1
+ import { SimpleRNG } from 'murow/core/simple-rng';
2
+ import type { TransportAdapter, ServerTransportAdapter } from 'murow/net';
3
+ export interface JitterConfig {
4
+ baseLatencyMs: number;
5
+ jitterMs: number;
6
+ lossChance: number;
7
+ /**
8
+ * Chance per packet of having its arrival time perturbed enough to
9
+ * overtake or fall behind nearby packets. Real networks reorder; this
10
+ * stresses the netcode's resilience to out-of-order delivery.
11
+ */
12
+ reorderChance?: number;
13
+ /** Magnitude of the reorder skew, in ms. */
14
+ reorderSkewMs?: number;
15
+ }
16
+ export declare class VirtualNetwork {
17
+ private now;
18
+ private slots;
19
+ private seq;
20
+ nowMs(): number;
21
+ schedule(latencyMs: number, fn: () => void): void;
22
+ advance(ms: number): void;
23
+ pending(): number;
24
+ }
25
+ export declare class VirtualPeerTransport implements TransportAdapter {
26
+ readonly peerId: string;
27
+ private vnet;
28
+ private rng;
29
+ private cfg;
30
+ serverOnMessage: ((data: Uint8Array) => void) | null;
31
+ clientOnMessage: ((data: Uint8Array) => void) | null;
32
+ serverOnClose: (() => void) | null;
33
+ clientOnClose: (() => void) | null;
34
+ clientOnOpen: (() => void) | null;
35
+ closed: boolean;
36
+ constructor(peerId: string, vnet: VirtualNetwork, rng: SimpleRNG, cfg: JitterConfig);
37
+ setConfig(cfg: JitterConfig): void;
38
+ private schedule;
39
+ send(data: Uint8Array): void;
40
+ onOpen(_handler: () => void): void;
41
+ onMessage(handler: (data: Uint8Array) => void): void;
42
+ onClose(handler: () => void): void;
43
+ close(): void;
44
+ clientView(): TransportAdapter;
45
+ openClient(): void;
46
+ }
47
+ export declare class VirtualServerTransport implements ServerTransportAdapter<VirtualPeerTransport> {
48
+ private vnet;
49
+ private rng;
50
+ cfg: JitterConfig;
51
+ private peers;
52
+ private connectionHandler;
53
+ private nextId;
54
+ constructor(vnet: VirtualNetwork, rng: SimpleRNG, cfg: JitterConfig);
55
+ setConfig(cfg: JitterConfig): void;
56
+ onConnection(handler: (peer: VirtualPeerTransport, peerId: string) => void): void;
57
+ onDisconnection(_handler: (peerId: string) => void): void;
58
+ getPeer(peerId: string): VirtualPeerTransport | undefined;
59
+ getPeerIds(): string[];
60
+ close(): void;
61
+ connectClient(): {
62
+ client: TransportAdapter;
63
+ peerId: string;
64
+ };
65
+ }
@@ -0,0 +1,45 @@
1
+ import type { DefinedIntents, IntentPayload, IntentSchemaMap } from '../intents/define-intents';
2
+ import type { PredictionContext } from '../ctx';
3
+ /**
4
+ * A single prediction function. Pure deterministic logic that mutates
5
+ * `ctx.world` given a typed intent payload. The same function runs on
6
+ * the server as the authoritative apply and on the client as the
7
+ * predicted apply (plus rollback replay during reconciliation).
8
+ */
9
+ export type PredictionFn<P> = (payload: P, ctx: PredictionContext) => void;
10
+ /**
11
+ * Bundle of predictions keyed by intent name. Partial: not every intent
12
+ * needs a prediction. Intents without an entry here are server-only and
13
+ * should register through `defineHandlers` instead.
14
+ */
15
+ export type PredictionMap<I extends IntentSchemaMap> = {
16
+ [K in keyof I]?: PredictionFn<IntentPayload<I, K>>;
17
+ };
18
+ /**
19
+ * Tagged bundle returned by `definePredictions`. Pass to both
20
+ * `server.use(predictions)` and `client.use(predictions)`.
21
+ */
22
+ export interface DefinedPredictions<I extends IntentSchemaMap> {
23
+ readonly __kind: 'predictions';
24
+ readonly intents: DefinedIntents<I>;
25
+ readonly map: PredictionMap<I>;
26
+ }
27
+ /**
28
+ * Bundle predictions for an intent schema. The same module is used on
29
+ * both sides; the server applies authoritatively, the client predicts
30
+ * locally and rolls back on disagreement.
31
+ *
32
+ * Predictions must be deterministic: no `Math.random`, no `Date.now`, no
33
+ * I/O. Use `ctx.rng`, `ctx.tick`, `ctx.deltaTime`.
34
+ *
35
+ * @example
36
+ * const predictions = definePredictions(intents, {
37
+ * move: ({ dx, dy }, ctx) => {
38
+ * ctx.world.update(ctx.entity, Velocity, { vx: dx, vy: dy });
39
+ * },
40
+ * });
41
+ *
42
+ * server.use(predictions);
43
+ * client.use(predictions);
44
+ */
45
+ export declare function definePredictions<I extends IntentSchemaMap>(intents: DefinedIntents<I>, map: PredictionMap<I>): DefinedPredictions<I>;
@@ -0,0 +1 @@
1
+ export * from './define-predictions';
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,44 @@
1
+ import { RpcRegistry, defineRPC } from 'murow/protocol';
2
+ /**
3
+ * User-supplied schema map for `defineRpcs`. Each entry maps an RPC
4
+ * method name to its field schema.
5
+ */
6
+ export type RpcSchemaMap = Record<string, Record<string, any>>;
7
+ /** Payload type for a named RPC, inferred from its schema. */
8
+ export type RpcPayload<R extends RpcSchemaMap, K extends keyof R> = {
9
+ [P in keyof R[K]]: R[K][P] extends {
10
+ read(dv: DataView, o: number): infer T;
11
+ } ? T : never;
12
+ };
13
+ /**
14
+ * Result of `defineRpcs`. `registry` plugs into `GameServer`/`GameClient`
15
+ * via the `protocol` option; `__payloads` is a phantom used purely for
16
+ * type inference on `sendRpc`/`broadcastRpc` call sites.
17
+ */
18
+ export interface DefinedRpcs<R extends RpcSchemaMap> {
19
+ /** Underlying `DefinedRPC` objects keyed by method name. */
20
+ readonly defs: {
21
+ readonly [K in keyof R]: ReturnType<typeof defineRPC<R[K]>>;
22
+ };
23
+ /** Pre-populated registry ready to plug into the network layer. */
24
+ readonly registry: RpcRegistry;
25
+ /** Phantom field for type inference, never read at runtime. */
26
+ readonly __payloads: {
27
+ [K in keyof R]: RpcPayload<R, K>;
28
+ };
29
+ }
30
+ /**
31
+ * Declare every RPC the game uses in one map. RPCs are bidirectional;
32
+ * the same registry serves `server.sendRpc`/`server.broadcastRpc` and
33
+ * `client.sendRpc`.
34
+ *
35
+ * @example
36
+ * const rpcs = defineRpcs({
37
+ * matchStart: { countdownSec: u8 },
38
+ * buyItem: { itemId: string(32) },
39
+ * });
40
+ *
41
+ * server.broadcastRpc('matchStart', { countdownSec: 3 });
42
+ * client.on('rpc', ({ name, payload }) => { ... });
43
+ */
44
+ export declare function defineRpcs<R extends RpcSchemaMap>(rpcs: R): DefinedRpcs<R>;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export * from './define-rpcs';
@@ -0,0 +1,77 @@
1
+ import type { Entity, World } from 'murow/ecs';
2
+ import type { GameLoop } from 'murow/game';
3
+ import type { ServerTransportAdapter, TransportAdapter } from 'murow/net';
4
+ import { Network } from '../network/base';
5
+ import type { DefinedIntents, IntentSchemaMap } from '../intents/define-intents';
6
+ import type { DefinedRpcs, RpcPayload, RpcSchemaMap } from '../rpcs/define-rpcs';
7
+ import type { DefinedPredictions } from '../predictions/define-predictions';
8
+ import type { DefinedHandlers } from '../handlers/define-handlers';
9
+ import type { ServerPlugin } from './plugins/plugin';
10
+ import { type Peer } from '../ctx';
11
+ export interface GameServerOptions<I extends IntentSchemaMap, R extends RpcSchemaMap> {
12
+ world: World;
13
+ loop: GameLoop<any>;
14
+ transport: ServerTransportAdapter<TransportAdapter>;
15
+ protocol: {
16
+ intents: DefinedIntents<I>;
17
+ rpcs: DefinedRpcs<R>;
18
+ };
19
+ snapshot?: {
20
+ /** Snapshots per second. Clamped to tickRate. Default 20. */
21
+ rate?: number;
22
+ };
23
+ kick?: {
24
+ /** Ms before forcing transport close after a kick. Default 2000. */
25
+ ackTimeout?: number;
26
+ };
27
+ /** Optional schema fingerprint for component-layout drift checks. */
28
+ schemaFingerprint?: string;
29
+ }
30
+ export declare class GameServer<I extends IntentSchemaMap = IntentSchemaMap, R extends RpcSchemaMap = RpcSchemaMap> extends Network<'server', R> {
31
+ readonly world: World;
32
+ readonly loop: GameLoop<any>;
33
+ readonly transport: ServerTransportAdapter<TransportAdapter>;
34
+ readonly intents: DefinedIntents<I>;
35
+ readonly rpcs: DefinedRpcs<R>;
36
+ private peers;
37
+ private predictionMap;
38
+ private handlerMap;
39
+ private plugins;
40
+ private lagComp;
41
+ private syncedComponents;
42
+ private syncedNumMaskWords;
43
+ private snapshotEvery;
44
+ private tickCounter;
45
+ private kickAckTimeout;
46
+ private rng;
47
+ private scratch;
48
+ private lastDt;
49
+ constructor(opts: GameServerOptions<I, R>);
50
+ private discoverSyncedComponents;
51
+ private wireTransport;
52
+ private handleDisconnect;
53
+ private wireLoop;
54
+ private drainIntentQueues;
55
+ private sendSnapshots;
56
+ private anyNeedsBaseline;
57
+ private handleIncoming;
58
+ private handleIntent;
59
+ private handleRpc;
60
+ use(bundle: DefinedPredictions<I>): this;
61
+ use(bundle: DefinedHandlers<I>): this;
62
+ use(plugin: ServerPlugin): this;
63
+ /**
64
+ * Bind an entity to a peer. Fills `ctx.entity` for the peer's intents
65
+ * and pushes a MSG_ASSIGN_ENTITY frame so the client knows which
66
+ * entity it owns.
67
+ */
68
+ assignEntity(peer: Peer, entityId: Entity): void;
69
+ sendRpc<K extends keyof R & string>(peer: Peer, name: K, payload: RpcPayload<R, K>): void;
70
+ broadcastRpc<K extends keyof R & string>(name: K, payload: RpcPayload<R, K>): void;
71
+ /**
72
+ * Soft-kick: send KICK frame, wait `kick.ackTimeout` for the client's
73
+ * ack, then force the transport closed.
74
+ */
75
+ kick(peer: Peer, reason: string): void;
76
+ private completeKick;
77
+ }
@@ -0,0 +1,2 @@
1
+ export * from './game-server';
2
+ export * from './plugins';
@@ -0,0 +1,34 @@
1
+ import type { Component, Entity, World } from 'murow/ecs';
2
+ import type { Peer } from '../../ctx';
3
+ import type { ServerPlugin } from './plugin';
4
+ export interface AoiGridOptions {
5
+ /** Plugin identifier; matches `sync.interest` strings on components. Default `'aoi'`. */
6
+ name?: string;
7
+ /** Reserved for grid acceleration. v1 does a naive radius scan. */
8
+ cellSize: number;
9
+ /** AOI radius around each peer (world units). */
10
+ radius: number;
11
+ /** Extra slack on the despawn boundary. Default 0. */
12
+ hysteresisRadius?: number;
13
+ /** Component holding `{ x, y }` positions used to compute distance. */
14
+ positionComponent: Component<{
15
+ x: number;
16
+ y: number;
17
+ }>;
18
+ }
19
+ /**
20
+ * Spatial interest plugin. Per-tick, filters dirty entities to those
21
+ * within `radius + hysteresisRadius` of each peer's assigned entity.
22
+ *
23
+ * v1 is O(peers * dirty). A grid accelerator can slot in without
24
+ * changing the public surface.
25
+ */
26
+ export declare class AoiGrid implements ServerPlugin {
27
+ readonly name: string;
28
+ readonly cellSize: number;
29
+ readonly radius: number;
30
+ readonly hysteresisRadius: number;
31
+ private positionComponent;
32
+ constructor(opts: AoiGridOptions);
33
+ filterSnapshot(peer: Peer, world: World, dirtyEntities: ReadonlyArray<Entity>, out: Entity[]): void;
34
+ }
@@ -0,0 +1,3 @@
1
+ export * from './plugin';
2
+ export * from './aoi-grid';
3
+ export * from './lag-compensation';
@@ -0,0 +1,34 @@
1
+ import type { Component, World } from 'murow/ecs';
2
+ import type { ServerPlugin } from './plugin';
3
+ export interface LagCompensationOptions {
4
+ /** Plugin identifier. Default `'lag-compensation'`. */
5
+ name?: string;
6
+ /** How far back to keep state history (ms). Default 500. */
7
+ historyMs?: number;
8
+ /** Tick rate of the simulation; used to size the ring buffer. */
9
+ tickRate: number;
10
+ /** Components whose history is recorded for rewind. */
11
+ components: Component<any>[];
12
+ }
13
+ /**
14
+ * Records the configured components every tick for `historyMs` worth of
15
+ * frames. `rewind(clientTick, fn)` overlays the historical state for the
16
+ * duration of `fn`, then restores. Used inside server-only handlers for
17
+ * hit detection that's fair across players with different pings.
18
+ */
19
+ export declare class LagCompensation implements ServerPlugin {
20
+ readonly name: string;
21
+ readonly historyMs: number;
22
+ readonly tickRate: number;
23
+ private components;
24
+ private componentIndices;
25
+ private ringBuffer;
26
+ private ringSize;
27
+ private ringHead;
28
+ private currentTick;
29
+ private world;
30
+ constructor(opts: LagCompensationOptions);
31
+ onMount(server: any): void;
32
+ onTick(_world: World, _dt: number): void;
33
+ rewind<T>(clientTick: number, fn: () => T): T;
34
+ }
@@ -0,0 +1,24 @@
1
+ import type { Entity, World } from 'murow/ecs';
2
+ import type { Peer, ServerHandlerContext } from '../../ctx';
3
+ export interface ServerPlugin {
4
+ readonly name: string;
5
+ onMount?(server: any): void;
6
+ onUnmount?(server: any): void;
7
+ onTick?(world: World, deltaTime: number): void;
8
+ /** Observe-only. Return value is ignored. */
9
+ onIntent?(peer: Peer, kind: number, name: string, payload: unknown, ctx: ServerHandlerContext): void;
10
+ /**
11
+ * Filter `dirtyEntities` down to those visible to `peer`. Push
12
+ * visible entities onto `out` (pre-cleared by the engine). Plugins
13
+ * compose in registration order on the same scratch array.
14
+ */
15
+ filterSnapshot?(peer: Peer, world: World, dirtyEntities: ReadonlyArray<Entity>, out: Entity[]): void;
16
+ onDisconnect?(peer: Peer): void;
17
+ }
18
+ export interface ClientPlugin {
19
+ readonly name: string;
20
+ onMount?(client: any): void;
21
+ onUnmount?(client: any): void;
22
+ onTick?(world: World, deltaTime: number): void;
23
+ onSnapshot?(tick: number): void;
24
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export * from './memory-transport';
@@ -0,0 +1,51 @@
1
+ import type { TransportAdapter, ServerTransportAdapter } from 'murow/net';
2
+ /**
3
+ * In-process transport pair for tests. Wires a server adapter and one or
4
+ * more client adapters together via microtask delivery. No queueing, no
5
+ * latency simulation; `send` schedules a microtask to deliver to the
6
+ * matching `onMessage`.
7
+ */
8
+ export declare class MemoryServerTransport implements ServerTransportAdapter<MemoryPeerTransport> {
9
+ private peers;
10
+ private connectionHandler;
11
+ private disconnectionHandler;
12
+ private nextPeerId;
13
+ onConnection(handler: (peer: MemoryPeerTransport, peerId: string) => void): void;
14
+ onDisconnection(handler: (peerId: string) => void): void;
15
+ getPeer(peerId: string): MemoryPeerTransport | undefined;
16
+ getPeerIds(): string[];
17
+ /**
18
+ * Simulate a client connection. Returns the client-side adapter and
19
+ * the server-assigned peer id. Fires the server's connection handler
20
+ * synchronously; the client's `onOpen` fires on the next microtask.
21
+ */
22
+ connectClient(): {
23
+ client: TransportAdapter;
24
+ peerId: string;
25
+ };
26
+ /** Called by a peer when it disconnects. */
27
+ _peerClosed(peerId: string): void;
28
+ close(): void;
29
+ }
30
+ /**
31
+ * Internal server-side adapter, one per connected peer. `send` goes to
32
+ * the client; `onMessage` fires when the client sends.
33
+ */
34
+ export declare class MemoryPeerTransport implements TransportAdapter {
35
+ readonly peerId: string;
36
+ private server;
37
+ private serverMessageHandler;
38
+ private serverCloseHandler;
39
+ private clientMessageHandler;
40
+ private clientCloseHandler;
41
+ private clientOpenHandler;
42
+ constructor(peerId: string, server: MemoryServerTransport);
43
+ send(data: Uint8Array): void;
44
+ onOpen(_handler: () => void): void;
45
+ onMessage(handler: (data: Uint8Array) => void): void;
46
+ onClose(handler: () => void): void;
47
+ close(): void;
48
+ /** Client-side adapter view: sends to the server, observes server messages. */
49
+ clientView(): TransportAdapter;
50
+ _openClient(): void;
51
+ }
@@ -0,0 +1 @@
1
+ export {};