@lagless/core 0.0.46 → 0.0.50
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.
- package/README.md +1 -1
- package/dist/lib/ecs-simulation.d.ts +3 -1
- package/dist/lib/ecs-simulation.d.ts.map +1 -1
- package/dist/lib/ecs-simulation.js +7 -0
- package/dist/lib/ecs-simulation.js.map +1 -1
- package/dist/lib/hash-verification/create-hash-reporter.d.ts +21 -8
- package/dist/lib/hash-verification/create-hash-reporter.d.ts.map +1 -1
- package/dist/lib/hash-verification/create-hash-reporter.js +34 -12
- package/dist/lib/hash-verification/create-hash-reporter.js.map +1 -1
- package/dist/lib/hash-verification/divergence.signal.d.ts +5 -10
- package/dist/lib/hash-verification/divergence.signal.d.ts.map +1 -1
- package/dist/lib/hash-verification/divergence.signal.js +3 -3
- package/dist/lib/hash-verification/divergence.signal.js.map +1 -1
- package/dist/lib/hash-verification/index.d.ts +2 -3
- package/dist/lib/hash-verification/index.d.ts.map +1 -1
- package/dist/lib/hash-verification/index.js +0 -2
- package/dist/lib/hash-verification/index.js.map +1 -1
- package/dist/lib/input/rpc-history.d.ts +2 -0
- package/dist/lib/input/rpc-history.d.ts.map +1 -1
- package/dist/lib/input/rpc-history.js +9 -0
- package/dist/lib/input/rpc-history.js.map +1 -1
- package/package.json +4 -4
- package/dist/lib/hash-verification/abstract-hash-verification.system.d.ts +0 -32
- package/dist/lib/hash-verification/abstract-hash-verification.system.d.ts.map +0 -1
- package/dist/lib/hash-verification/abstract-hash-verification.system.js +0 -54
- package/dist/lib/hash-verification/abstract-hash-verification.system.js.map +0 -1
package/README.md
CHANGED
|
@@ -121,7 +121,7 @@ class ECSConfig {
|
|
|
121
121
|
readonly maxInputDelayTick: number; // Max delay (default: 8)
|
|
122
122
|
readonly fps: number; // Target FPS (default: 60)
|
|
123
123
|
readonly frameLength: number; // Frame duration in ms (1000/fps)
|
|
124
|
-
readonly snapshotRate: number; // Save snapshot every N ticks (default:
|
|
124
|
+
readonly snapshotRate: number; // Save snapshot every N ticks (default: 5)
|
|
125
125
|
readonly snapshotHistorySize: number; // Max snapshots stored (default: 100)
|
|
126
126
|
readonly maxNudgePerFrame: number; // Max time correction per frame (default: frameLength/4)
|
|
127
127
|
|
|
@@ -12,7 +12,7 @@ export declare class ECSSimulation {
|
|
|
12
12
|
private readonly _signalsRegistry;
|
|
13
13
|
private readonly _frameLength;
|
|
14
14
|
private readonly _snapshotRate;
|
|
15
|
-
protected
|
|
15
|
+
protected _initialSnapshot: ArrayBuffer;
|
|
16
16
|
private readonly _systems;
|
|
17
17
|
private readonly _onTickHandlers;
|
|
18
18
|
private readonly _onRollbackHandlers;
|
|
@@ -32,9 +32,11 @@ export declare class ECSSimulation {
|
|
|
32
32
|
initSignals(signals: import('./signals/signal.js').Signal[]): void;
|
|
33
33
|
disposeSignals(): void;
|
|
34
34
|
throwIfSystemsNotRegistered(): void;
|
|
35
|
+
get registeredSystems(): ReadonlyArray<IECSSystem>;
|
|
35
36
|
get frameLength(): number;
|
|
36
37
|
get inputProvider(): AbstractInputProvider;
|
|
37
38
|
enableHashTracking(interval: number): void;
|
|
39
|
+
disableHashTracking(): void;
|
|
38
40
|
getHashAtTick(tick: number): number | undefined;
|
|
39
41
|
start(): void;
|
|
40
42
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ecs-simulation.d.ts","sourceRoot":"","sources":["../../src/lib/ecs-simulation.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,GAAG,EAAE,MAAM,gBAAgB,CAAC;AACrC,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAC5C,OAAO,EAAE,eAAe,EAAE,eAAe,EAAgB,MAAM,eAAe,CAAC;AAC/E,OAAO,EAAE,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AACvD,OAAO,EAAE,qBAAqB,EAAE,MAAM,kBAAkB,CAAC;AAKzD,qBAAa,aAAa;IA0BtB,SAAS,CAAC,QAAQ,CAAC,UAAU,EAAE,SAAS;IACxC,OAAO,CAAC,QAAQ,CAAC,QAAQ;IACzB,OAAO,CAAC,QAAQ,CAAC,cAAc;IA3BjC,SAAgB,GAAG,EAAE,GAAG,CAAC;IACzB,SAAgB,KAAK,EAAE,eAAe,CAAC;IACvC,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAkB;IACnD,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAS;IACtC,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAS;IACvC,SAAS,CAAC,
|
|
1
|
+
{"version":3,"file":"ecs-simulation.d.ts","sourceRoot":"","sources":["../../src/lib/ecs-simulation.ts"],"names":[],"mappings":"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"}
|
|
@@ -48,6 +48,9 @@ export class ECSSimulation {
|
|
|
48
48
|
throw new Error('No systems registered.');
|
|
49
49
|
}
|
|
50
50
|
}
|
|
51
|
+
get registeredSystems() {
|
|
52
|
+
return this._systems;
|
|
53
|
+
}
|
|
51
54
|
get frameLength() {
|
|
52
55
|
return this._frameLength;
|
|
53
56
|
}
|
|
@@ -57,6 +60,10 @@ export class ECSSimulation {
|
|
|
57
60
|
enableHashTracking(interval) {
|
|
58
61
|
this._hashTrackingInterval = interval;
|
|
59
62
|
}
|
|
63
|
+
disableHashTracking() {
|
|
64
|
+
this._hashTrackingInterval = 0;
|
|
65
|
+
this._hashHistory.clear();
|
|
66
|
+
}
|
|
60
67
|
getHashAtTick(tick) {
|
|
61
68
|
return this._hashHistory.get(tick);
|
|
62
69
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/lib/ecs-simulation.ts"],"sourcesContent":["import { MathOps } from '@lagless/math';\nimport { Mem } from './mem/index.js';\nimport { ECSConfig } from './ecs-config.js';\nimport { SimulationClock, SnapshotHistory, createLogger } from '@lagless/misc';\nimport { ECSDeps, IECSSystem } from './types/index.js';\nimport { AbstractInputProvider } from './input/index.js';\nimport { SignalsRegistry } from './signals/signals.registry.js';\n\nconst log = createLogger('ECSSimulation');\n\nexport class ECSSimulation {\n public readonly mem: Mem;\n public readonly clock: SimulationClock;\n private readonly _signalsRegistry: SignalsRegistry;\n private readonly _frameLength: number;\n private readonly _snapshotRate: number;\n protected readonly _initialSnapshot!: ArrayBuffer;\n private readonly _systems = new Array<IECSSystem>();\n private readonly _onTickHandlers = new Set<(tick: number) => void>();\n private readonly _onRollbackHandlers = new Set<(tick: number) => void>();\n private readonly _onStateTransferHandlers = new Set<(tick: number) => void>();\n\n private _interpolationFactor = 0;\n protected _snapshotHistory: SnapshotHistory<ArrayBuffer>;\n private _hashTrackingInterval = 0;\n private _hashHistory = new Map<number, number>();\n\n public get tick(): number {\n return this.mem.tickManager.tick;\n }\n\n public get interpolationFactor(): number {\n return this._interpolationFactor;\n }\n\n constructor(\n protected readonly _ECSConfig: ECSConfig,\n private readonly _ECSDeps: ECSDeps,\n private readonly _inputProvider: AbstractInputProvider,\n ) {\n this.mem = new Mem(this._ECSConfig, this._ECSDeps);\n this._frameLength = this._ECSConfig.frameLength;\n this._snapshotRate = this._ECSConfig.snapshotRate;\n this._snapshotHistory = new SnapshotHistory<ArrayBuffer>(this._ECSConfig.snapshotHistorySize);\n this._initialSnapshot = this.mem.exportSnapshot();\n this.clock = new SimulationClock(_ECSConfig.frameLength, _ECSConfig.maxNudgePerFrame);\n this._signalsRegistry = new SignalsRegistry();\n }\n\n public addTickHandler(handler: (tick: number) => void): () => void {\n this._onTickHandlers.add(handler);\n return () => {\n this._onTickHandlers.delete(handler);\n };\n }\n\n public removeTickHandler(handler: (tick: number) => void): void {\n this._onTickHandlers.delete(handler);\n }\n\n public addRollbackHandler(handler: (tick: number) => void): () => void {\n this._onRollbackHandlers.add(handler);\n return () => { this._onRollbackHandlers.delete(handler); };\n }\n\n public addStateTransferHandler(handler: (tick: number) => void): () => void {\n this._onStateTransferHandlers.add(handler);\n return () => { this._onStateTransferHandlers.delete(handler); };\n }\n\n public registerSystems(systems: IECSSystem[]): void {\n if (this._systems.length !== 0) throw new Error('Systems already registered');\n\n for (const system of systems) {\n this._systems.push(system);\n }\n }\n\n public initSignals(signals: import('./signals/signal.js').Signal[]): void {\n this._signalsRegistry.init(signals);\n }\n\n public disposeSignals(): void {\n this._signalsRegistry.dispose();\n }\n\n public throwIfSystemsNotRegistered(): void {\n if (this._systems.length === 0) {\n throw new Error('No systems registered.');\n }\n }\n\n public get frameLength(): number {\n return this._frameLength;\n }\n\n public get inputProvider(): AbstractInputProvider {\n return this._inputProvider;\n }\n\n public enableHashTracking(interval: number): void {\n this._hashTrackingInterval = interval;\n }\n\n public getHashAtTick(tick: number): number | undefined {\n return this._hashHistory.get(tick);\n }\n\n public start(): void {\n this.clock.start();\n }\n\n /**\n * Apply external state received from another client (late-join / reconnect).\n * Replaces the entire simulation state, sets the tick, and resets history.\n */\n public applyExternalState(state: ArrayBuffer, tick: number): void {\n log.info(`Applying external state at tick ${tick} (${state.byteLength} bytes)`);\n\n // Apply the snapshot to memory\n this.mem.applySnapshot(state);\n\n // Set tick (snapshot may have been taken at a different tick than requested)\n this.mem.tickManager.setTick(tick);\n\n // Reset snapshot history — old snapshots are from a different timeline\n this._snapshotHistory = new SnapshotHistory<ArrayBuffer>(this._ECSConfig.snapshotHistorySize);\n\n // Save the received state as the new baseline snapshot\n this.saveSnapshot(tick);\n\n // Adjust clock to match the new tick\n this.clock.setAccumulatedTime(tick * this._frameLength);\n\n // Reset signals — old predictions are invalid\n this._signalsRegistry.dispose();\n\n // Clear hash history — old hashes are from a different timeline\n this._hashHistory.clear();\n }\n\n /**\n * Export simulation state for network state transfer (late-join / reconnect).\n * Override in subclasses to include additional state (e.g. physics world).\n */\n public exportStateForTransfer(): ArrayBuffer {\n return this.mem.exportSnapshot();\n }\n\n /**\n * Apply state received from network state transfer (late-join / reconnect).\n * Override in subclasses to restore additional state (e.g. physics world).\n */\n public applyStateFromTransfer(blob: ArrayBuffer, tick: number): void {\n this.applyExternalState(blob, tick);\n this.notifyStateTransferHandlers(tick);\n }\n\n protected notifyStateTransferHandlers(tick: number): void {\n for (const handler of this._onStateTransferHandlers) handler(tick);\n }\n\n public update(dt: number) {\n this.clock.update(dt);\n\n const targetTick = Math.floor(this.clock.accumulatedTime / this._frameLength);\n\n this.checkAndRollback(this.mem.tickManager.tick);\n this.simulationTicks(this.mem.tickManager.tick, targetTick);\n\n this._inputProvider.update();\n\n const simTick = this.mem.tickManager.tick;\n const tickTime = simTick * this._frameLength;\n const leftover = this.clock.accumulatedTime - tickTime;\n\n this._interpolationFactor = MathOps.clamp01(leftover / this._frameLength);\n }\n\n private checkAndRollback(currentTick: number) {\n const rollbackTick = this._inputProvider.getInvalidateRollbackTick();\n\n if (rollbackTick === undefined || rollbackTick > currentTick) return;\n\n this.rollback(rollbackTick);\n\n for (const handler of this._onRollbackHandlers) handler(this.tick);\n }\n\n private simulationTicks(currentTick: number, toTick: number): void {\n if (toTick - currentTick > 1) {\n log.warn(`Simulation ticks: ${currentTick} -> ${toTick} (simulate ${toTick - currentTick})`);\n }\n\n while (currentTick < toTick) {\n this.mem.tickManager.setTick(++currentTick);\n this.simulate(currentTick);\n\n if (this._hashTrackingInterval > 0 && currentTick % this._hashTrackingInterval === 0) {\n this._hashHistory.set(currentTick, this.mem.getHash());\n }\n\n this._signalsRegistry.onTick(this._inputProvider.verifiedTick);\n this.storeSnapshotIfNeeded(currentTick);\n for (const handler of this._onTickHandlers) handler(currentTick);\n }\n\n // Prune old hash entries\n if (this._hashHistory.size > 0) {\n const pruneBelow = this._inputProvider.verifiedTick - 600;\n if (pruneBelow > 0) {\n for (const tick of this._hashHistory.keys()) {\n if (tick < pruneBelow) this._hashHistory.delete(tick);\n }\n }\n }\n }\n\n protected rollback(tick: number): void {\n this._signalsRegistry.onBeforeRollback(tick);\n let snapshot: ArrayBuffer;\n\n try {\n snapshot = this._snapshotHistory.getNearest(tick);\n log.warn(`Rollback to tick ${tick} succeeded`);\n } catch {\n snapshot = this._initialSnapshot;\n log.warn(`Rollback to tick ${tick} failed, using initial snapshot`);\n }\n\n this.mem.applySnapshot(snapshot);\n this._snapshotHistory.rollback(this.mem.tickManager.tick);\n }\n\n protected simulate(tick: number): void {\n for (let i = 0; i < this._systems.length; i++) {\n this._systems[i].update(tick);\n }\n }\n\n private storeSnapshotIfNeeded(tick: number): void {\n if (this._snapshotRate === 0) return;\n\n if (tick % this._snapshotRate === 0) {\n this.saveSnapshot(tick);\n }\n\n if (tick % 200 === 0) {\n log.debug(`Mem Hash at tick ${tick}: ${this.mem.getHash()}`);\n }\n }\n\n protected saveSnapshot(tick: number): void {\n this._snapshotHistory.set(tick, this.mem.exportSnapshot());\n }\n}\n"],"names":["MathOps","Mem","SimulationClock","SnapshotHistory","createLogger","SignalsRegistry","log","ECSSimulation","tick","mem","tickManager","interpolationFactor","_interpolationFactor","addTickHandler","handler","_onTickHandlers","add","delete","removeTickHandler","addRollbackHandler","_onRollbackHandlers","addStateTransferHandler","_onStateTransferHandlers","registerSystems","systems","_systems","length","Error","system","push","initSignals","signals","_signalsRegistry","init","disposeSignals","dispose","throwIfSystemsNotRegistered","frameLength","_frameLength","inputProvider","_inputProvider","enableHashTracking","interval","_hashTrackingInterval","getHashAtTick","_hashHistory","get","start","clock","applyExternalState","state","info","byteLength","applySnapshot","setTick","_snapshotHistory","_ECSConfig","snapshotHistorySize","saveSnapshot","setAccumulatedTime","clear","exportStateForTransfer","exportSnapshot","applyStateFromTransfer","blob","notifyStateTransferHandlers","update","dt","targetTick","Math","floor","accumulatedTime","checkAndRollback","simulationTicks","simTick","tickTime","leftover","clamp01","currentTick","rollbackTick","getInvalidateRollbackTick","undefined","rollback","toTick","warn","simulate","set","getHash","onTick","verifiedTick","storeSnapshotIfNeeded","size","pruneBelow","keys","onBeforeRollback","snapshot","getNearest","_initialSnapshot","i","_snapshotRate","debug","constructor","_ECSDeps","Array","Set","Map","snapshotRate","maxNudgePerFrame"],"rangeMappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;","mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../src/lib/ecs-simulation.ts"],"sourcesContent":["import { MathOps } from '@lagless/math';\nimport { Mem } from './mem/index.js';\nimport { ECSConfig } from './ecs-config.js';\nimport { SimulationClock, SnapshotHistory, createLogger } from '@lagless/misc';\nimport { ECSDeps, IECSSystem } from './types/index.js';\nimport { AbstractInputProvider } from './input/index.js';\nimport { SignalsRegistry } from './signals/signals.registry.js';\n\nconst log = createLogger('ECSSimulation');\n\nexport class ECSSimulation {\n public readonly mem: Mem;\n public readonly clock: SimulationClock;\n private readonly _signalsRegistry: SignalsRegistry;\n private readonly _frameLength: number;\n private readonly _snapshotRate: number;\n protected _initialSnapshot!: ArrayBuffer;\n private readonly _systems = new Array<IECSSystem>();\n private readonly _onTickHandlers = new Set<(tick: number) => void>();\n private readonly _onRollbackHandlers = new Set<(tick: number) => void>();\n private readonly _onStateTransferHandlers = new Set<(tick: number) => void>();\n\n private _interpolationFactor = 0;\n protected _snapshotHistory: SnapshotHistory<ArrayBuffer>;\n private _hashTrackingInterval = 0;\n private _hashHistory = new Map<number, number>();\n\n public get tick(): number {\n return this.mem.tickManager.tick;\n }\n\n public get interpolationFactor(): number {\n return this._interpolationFactor;\n }\n\n constructor(\n protected readonly _ECSConfig: ECSConfig,\n private readonly _ECSDeps: ECSDeps,\n private readonly _inputProvider: AbstractInputProvider,\n ) {\n this.mem = new Mem(this._ECSConfig, this._ECSDeps);\n this._frameLength = this._ECSConfig.frameLength;\n this._snapshotRate = this._ECSConfig.snapshotRate;\n this._snapshotHistory = new SnapshotHistory<ArrayBuffer>(this._ECSConfig.snapshotHistorySize);\n this._initialSnapshot = this.mem.exportSnapshot();\n this.clock = new SimulationClock(_ECSConfig.frameLength, _ECSConfig.maxNudgePerFrame);\n this._signalsRegistry = new SignalsRegistry();\n }\n\n public addTickHandler(handler: (tick: number) => void): () => void {\n this._onTickHandlers.add(handler);\n return () => {\n this._onTickHandlers.delete(handler);\n };\n }\n\n public removeTickHandler(handler: (tick: number) => void): void {\n this._onTickHandlers.delete(handler);\n }\n\n public addRollbackHandler(handler: (tick: number) => void): () => void {\n this._onRollbackHandlers.add(handler);\n return () => { this._onRollbackHandlers.delete(handler); };\n }\n\n public addStateTransferHandler(handler: (tick: number) => void): () => void {\n this._onStateTransferHandlers.add(handler);\n return () => { this._onStateTransferHandlers.delete(handler); };\n }\n\n public registerSystems(systems: IECSSystem[]): void {\n if (this._systems.length !== 0) throw new Error('Systems already registered');\n\n for (const system of systems) {\n this._systems.push(system);\n }\n }\n\n public initSignals(signals: import('./signals/signal.js').Signal[]): void {\n this._signalsRegistry.init(signals);\n }\n\n public disposeSignals(): void {\n this._signalsRegistry.dispose();\n }\n\n public throwIfSystemsNotRegistered(): void {\n if (this._systems.length === 0) {\n throw new Error('No systems registered.');\n }\n }\n\n public get registeredSystems(): ReadonlyArray<IECSSystem> {\n return this._systems;\n }\n\n public get frameLength(): number {\n return this._frameLength;\n }\n\n public get inputProvider(): AbstractInputProvider {\n return this._inputProvider;\n }\n\n public enableHashTracking(interval: number): void {\n this._hashTrackingInterval = interval;\n }\n\n public disableHashTracking(): void {\n this._hashTrackingInterval = 0;\n this._hashHistory.clear();\n }\n\n public getHashAtTick(tick: number): number | undefined {\n return this._hashHistory.get(tick);\n }\n\n public start(): void {\n this.clock.start();\n }\n\n /**\n * Apply external state received from another client (late-join / reconnect).\n * Replaces the entire simulation state, sets the tick, and resets history.\n */\n public applyExternalState(state: ArrayBuffer, tick: number): void {\n log.info(`Applying external state at tick ${tick} (${state.byteLength} bytes)`);\n\n // Apply the snapshot to memory\n this.mem.applySnapshot(state);\n\n // Set tick (snapshot may have been taken at a different tick than requested)\n this.mem.tickManager.setTick(tick);\n\n // Reset snapshot history — old snapshots are from a different timeline\n this._snapshotHistory = new SnapshotHistory<ArrayBuffer>(this._ECSConfig.snapshotHistorySize);\n\n // Save the received state as the new baseline snapshot\n this.saveSnapshot(tick);\n\n // Adjust clock to match the new tick\n this.clock.setAccumulatedTime(tick * this._frameLength);\n\n // Reset signals — old predictions are invalid\n this._signalsRegistry.dispose();\n\n // Clear hash history — old hashes are from a different timeline\n this._hashHistory.clear();\n }\n\n /**\n * Export simulation state for network state transfer (late-join / reconnect).\n * Override in subclasses to include additional state (e.g. physics world).\n */\n public exportStateForTransfer(): ArrayBuffer {\n return this.mem.exportSnapshot();\n }\n\n /**\n * Apply state received from network state transfer (late-join / reconnect).\n * Override in subclasses to restore additional state (e.g. physics world).\n */\n public applyStateFromTransfer(blob: ArrayBuffer, tick: number): void {\n this.applyExternalState(blob, tick);\n this.notifyStateTransferHandlers(tick);\n }\n\n protected notifyStateTransferHandlers(tick: number): void {\n for (const handler of this._onStateTransferHandlers) handler(tick);\n }\n\n public update(dt: number) {\n this.clock.update(dt);\n\n const targetTick = Math.floor(this.clock.accumulatedTime / this._frameLength);\n\n this.checkAndRollback(this.mem.tickManager.tick);\n this.simulationTicks(this.mem.tickManager.tick, targetTick);\n\n this._inputProvider.update();\n\n const simTick = this.mem.tickManager.tick;\n const tickTime = simTick * this._frameLength;\n const leftover = this.clock.accumulatedTime - tickTime;\n\n this._interpolationFactor = MathOps.clamp01(leftover / this._frameLength);\n }\n\n private checkAndRollback(currentTick: number) {\n const rollbackTick = this._inputProvider.getInvalidateRollbackTick();\n\n if (rollbackTick === undefined || rollbackTick > currentTick) return;\n\n this.rollback(rollbackTick);\n\n for (const handler of this._onRollbackHandlers) handler(this.tick);\n }\n\n private simulationTicks(currentTick: number, toTick: number): void {\n if (toTick - currentTick > 1) {\n log.warn(`Simulation ticks: ${currentTick} -> ${toTick} (simulate ${toTick - currentTick})`);\n }\n\n while (currentTick < toTick) {\n this.mem.tickManager.setTick(++currentTick);\n this.simulate(currentTick);\n\n if (this._hashTrackingInterval > 0 && currentTick % this._hashTrackingInterval === 0) {\n this._hashHistory.set(currentTick, this.mem.getHash());\n }\n\n this._signalsRegistry.onTick(this._inputProvider.verifiedTick);\n this.storeSnapshotIfNeeded(currentTick);\n for (const handler of this._onTickHandlers) handler(currentTick);\n }\n\n // Prune old hash entries\n if (this._hashHistory.size > 0) {\n const pruneBelow = this._inputProvider.verifiedTick - 600;\n if (pruneBelow > 0) {\n for (const tick of this._hashHistory.keys()) {\n if (tick < pruneBelow) this._hashHistory.delete(tick);\n }\n }\n }\n }\n\n protected rollback(tick: number): void {\n this._signalsRegistry.onBeforeRollback(tick);\n let snapshot: ArrayBuffer;\n\n try {\n snapshot = this._snapshotHistory.getNearest(tick);\n log.warn(`Rollback to tick ${tick} succeeded`);\n } catch {\n snapshot = this._initialSnapshot;\n log.warn(`Rollback to tick ${tick} failed, using initial snapshot`);\n }\n\n this.mem.applySnapshot(snapshot);\n this._snapshotHistory.rollback(this.mem.tickManager.tick);\n }\n\n protected simulate(tick: number): void {\n for (let i = 0; i < this._systems.length; i++) {\n this._systems[i].update(tick);\n }\n }\n\n private storeSnapshotIfNeeded(tick: number): void {\n if (this._snapshotRate === 0) return;\n\n if (tick % this._snapshotRate === 0) {\n this.saveSnapshot(tick);\n }\n\n if (tick % 200 === 0) {\n log.debug(`Mem Hash at tick ${tick}: ${this.mem.getHash()}`);\n }\n }\n\n protected saveSnapshot(tick: number): void {\n this._snapshotHistory.set(tick, this.mem.exportSnapshot());\n }\n}\n"],"names":["MathOps","Mem","SimulationClock","SnapshotHistory","createLogger","SignalsRegistry","log","ECSSimulation","tick","mem","tickManager","interpolationFactor","_interpolationFactor","addTickHandler","handler","_onTickHandlers","add","delete","removeTickHandler","addRollbackHandler","_onRollbackHandlers","addStateTransferHandler","_onStateTransferHandlers","registerSystems","systems","_systems","length","Error","system","push","initSignals","signals","_signalsRegistry","init","disposeSignals","dispose","throwIfSystemsNotRegistered","registeredSystems","frameLength","_frameLength","inputProvider","_inputProvider","enableHashTracking","interval","_hashTrackingInterval","disableHashTracking","_hashHistory","clear","getHashAtTick","get","start","clock","applyExternalState","state","info","byteLength","applySnapshot","setTick","_snapshotHistory","_ECSConfig","snapshotHistorySize","saveSnapshot","setAccumulatedTime","exportStateForTransfer","exportSnapshot","applyStateFromTransfer","blob","notifyStateTransferHandlers","update","dt","targetTick","Math","floor","accumulatedTime","checkAndRollback","simulationTicks","simTick","tickTime","leftover","clamp01","currentTick","rollbackTick","getInvalidateRollbackTick","undefined","rollback","toTick","warn","simulate","set","getHash","onTick","verifiedTick","storeSnapshotIfNeeded","size","pruneBelow","keys","onBeforeRollback","snapshot","getNearest","_initialSnapshot","i","_snapshotRate","debug","constructor","_ECSDeps","Array","Set","Map","snapshotRate","maxNudgePerFrame"],"rangeMappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;","mappings":"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"}
|
|
@@ -1,16 +1,29 @@
|
|
|
1
1
|
import type { ECSRunner } from '../ecs-runner.js';
|
|
2
|
-
import type { IAbstractInputConstructor, InputData } from '../types/index.js';
|
|
3
|
-
type AddRPCFn = <TInputCtor extends IAbstractInputConstructor>(InputCtor: TInputCtor, data: InputData<InstanceType<TInputCtor>>) => void;
|
|
4
2
|
export interface HashReporterConfig {
|
|
5
3
|
reportInterval: number;
|
|
6
|
-
|
|
4
|
+
send: (data: {
|
|
5
|
+
hash: number;
|
|
6
|
+
atTick: number;
|
|
7
|
+
}) => void;
|
|
8
|
+
}
|
|
9
|
+
export interface HashMismatchData {
|
|
10
|
+
slotA: number;
|
|
11
|
+
slotB: number;
|
|
12
|
+
hashA: number;
|
|
13
|
+
hashB: number;
|
|
14
|
+
atTick: number;
|
|
15
|
+
}
|
|
16
|
+
export interface HashReporter {
|
|
17
|
+
dispose(): void;
|
|
18
|
+
subscribeDivergence(fn: (data: HashMismatchData) => void): () => void;
|
|
19
|
+
reportMismatch(data: HashMismatchData): void;
|
|
7
20
|
}
|
|
8
21
|
/**
|
|
9
|
-
* Creates a hash reporter
|
|
22
|
+
* Creates a hash reporter that sends simulation state hashes
|
|
23
|
+
* via a dedicated protocol channel (not through RPCHistory).
|
|
10
24
|
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
25
|
+
* Hash reports are sent for verified ticks only, ensuring
|
|
26
|
+
* they reflect finalized simulation state.
|
|
13
27
|
*/
|
|
14
|
-
export declare function createHashReporter(runner: ECSRunner, config: HashReporterConfig):
|
|
15
|
-
export {};
|
|
28
|
+
export declare function createHashReporter(runner: ECSRunner, config: HashReporterConfig): HashReporter;
|
|
16
29
|
//# sourceMappingURL=create-hash-reporter.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"create-hash-reporter.d.ts","sourceRoot":"","sources":["../../../src/lib/hash-verification/create-hash-reporter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"create-hash-reporter.d.ts","sourceRoot":"","sources":["../../../src/lib/hash-verification/create-hash-reporter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAElD,MAAM,WAAW,kBAAkB;IACjC,cAAc,EAAE,MAAM,CAAC;IACvB,IAAI,EAAE,CAAC,IAAI,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,KAAK,IAAI,CAAC;CACxD;AAED,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,YAAY;IAC3B,OAAO,IAAI,IAAI,CAAC;IAChB,mBAAmB,CAAC,EAAE,EAAE,CAAC,IAAI,EAAE,gBAAgB,KAAK,IAAI,GAAG,MAAM,IAAI,CAAC;IACtE,cAAc,CAAC,IAAI,EAAE,gBAAgB,GAAG,IAAI,CAAC;CAC9C;AAED;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,kBAAkB,GAAG,YAAY,CAyC9F"}
|
|
@@ -1,21 +1,43 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Creates a hash reporter
|
|
2
|
+
* Creates a hash reporter that sends simulation state hashes
|
|
3
|
+
* via a dedicated protocol channel (not through RPCHistory).
|
|
3
4
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
5
|
+
* Hash reports are sent for verified ticks only, ensuring
|
|
6
|
+
* they reflect finalized simulation state.
|
|
6
7
|
*/ export function createHashReporter(runner, config) {
|
|
7
8
|
let lastReportedTick = -1;
|
|
8
|
-
|
|
9
|
+
let lastReportedHash = 0;
|
|
10
|
+
const subscribers = [];
|
|
11
|
+
const disposeTickHandler = runner.Simulation.addTickHandler(()=>{
|
|
9
12
|
const verifiedTick = runner.Simulation.inputProvider.verifiedTick;
|
|
10
13
|
const latestReportTick = Math.floor(verifiedTick / config.reportInterval) * config.reportInterval;
|
|
11
|
-
if (latestReportTick
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
14
|
+
if (latestReportTick <= 0) return;
|
|
15
|
+
const hash = runner.Simulation.getHashAtTick(latestReportTick);
|
|
16
|
+
if (hash === undefined) return;
|
|
17
|
+
if (latestReportTick > lastReportedTick || latestReportTick === lastReportedTick && hash !== lastReportedHash) {
|
|
18
|
+
lastReportedTick = latestReportTick;
|
|
19
|
+
lastReportedHash = hash;
|
|
20
|
+
config.send({
|
|
21
|
+
hash,
|
|
22
|
+
atTick: latestReportTick
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
});
|
|
26
|
+
return {
|
|
27
|
+
dispose () {
|
|
28
|
+
disposeTickHandler();
|
|
29
|
+
subscribers.length = 0;
|
|
30
|
+
},
|
|
31
|
+
subscribeDivergence (fn) {
|
|
32
|
+
subscribers.push(fn);
|
|
33
|
+
return ()=>{
|
|
34
|
+
const idx = subscribers.indexOf(fn);
|
|
35
|
+
if (idx >= 0) subscribers.splice(idx, 1);
|
|
36
|
+
};
|
|
37
|
+
},
|
|
38
|
+
reportMismatch (data) {
|
|
39
|
+
for (const fn of subscribers){
|
|
40
|
+
fn(data);
|
|
19
41
|
}
|
|
20
42
|
}
|
|
21
43
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/lib/hash-verification/create-hash-reporter.ts"],"sourcesContent":["import type { ECSRunner } from '../ecs-runner.js';\
|
|
1
|
+
{"version":3,"sources":["../../../src/lib/hash-verification/create-hash-reporter.ts"],"sourcesContent":["import type { ECSRunner } from '../ecs-runner.js';\n\nexport interface HashReporterConfig {\n reportInterval: number;\n send: (data: { hash: number; atTick: number }) => void;\n}\n\nexport interface HashMismatchData {\n slotA: number;\n slotB: number;\n hashA: number;\n hashB: number;\n atTick: number;\n}\n\nexport interface HashReporter {\n dispose(): void;\n subscribeDivergence(fn: (data: HashMismatchData) => void): () => void;\n reportMismatch(data: HashMismatchData): void;\n}\n\n/**\n * Creates a hash reporter that sends simulation state hashes\n * via a dedicated protocol channel (not through RPCHistory).\n *\n * Hash reports are sent for verified ticks only, ensuring\n * they reflect finalized simulation state.\n */\nexport function createHashReporter(runner: ECSRunner, config: HashReporterConfig): HashReporter {\n let lastReportedTick = -1;\n let lastReportedHash = 0;\n const subscribers: Array<(data: HashMismatchData) => void> = [];\n\n const disposeTickHandler = runner.Simulation.addTickHandler(() => {\n const verifiedTick = runner.Simulation.inputProvider.verifiedTick;\n const latestReportTick = Math.floor(verifiedTick / config.reportInterval) * config.reportInterval;\n if (latestReportTick <= 0) return;\n\n const hash = runner.Simulation.getHashAtTick(latestReportTick);\n if (hash === undefined) return;\n\n if (latestReportTick > lastReportedTick ||\n (latestReportTick === lastReportedTick && hash !== lastReportedHash)) {\n lastReportedTick = latestReportTick;\n lastReportedHash = hash;\n config.send({ hash, atTick: latestReportTick });\n }\n });\n\n return {\n dispose() {\n disposeTickHandler();\n subscribers.length = 0;\n },\n\n subscribeDivergence(fn: (data: HashMismatchData) => void): () => void {\n subscribers.push(fn);\n return () => {\n const idx = subscribers.indexOf(fn);\n if (idx >= 0) subscribers.splice(idx, 1);\n };\n },\n\n reportMismatch(data: HashMismatchData): void {\n for (const fn of subscribers) {\n fn(data);\n }\n },\n };\n}\n"],"names":["createHashReporter","runner","config","lastReportedTick","lastReportedHash","subscribers","disposeTickHandler","Simulation","addTickHandler","verifiedTick","inputProvider","latestReportTick","Math","floor","reportInterval","hash","getHashAtTick","undefined","send","atTick","dispose","length","subscribeDivergence","fn","push","idx","indexOf","splice","reportMismatch","data"],"rangeMappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;","mappings":"AAqBA;;;;;;CAMC,GACD,OAAO,SAASA,mBAAmBC,MAAiB,EAAEC,MAA0B;IAC9E,IAAIC,mBAAmB,CAAC;IACxB,IAAIC,mBAAmB;IACvB,MAAMC,cAAuD,EAAE;IAE/D,MAAMC,qBAAqBL,OAAOM,UAAU,CAACC,cAAc,CAAC;QAC1D,MAAMC,eAAeR,OAAOM,UAAU,CAACG,aAAa,CAACD,YAAY;QACjE,MAAME,mBAAmBC,KAAKC,KAAK,CAACJ,eAAeP,OAAOY,cAAc,IAAIZ,OAAOY,cAAc;QACjG,IAAIH,oBAAoB,GAAG;QAE3B,MAAMI,OAAOd,OAAOM,UAAU,CAACS,aAAa,CAACL;QAC7C,IAAII,SAASE,WAAW;QAExB,IAAIN,mBAAmBR,oBAClBQ,qBAAqBR,oBAAoBY,SAASX,kBAAmB;YACxED,mBAAmBQ;YACnBP,mBAAmBW;YACnBb,OAAOgB,IAAI,CAAC;gBAAEH;gBAAMI,QAAQR;YAAiB;QAC/C;IACF;IAEA,OAAO;QACLS;YACEd;YACAD,YAAYgB,MAAM,GAAG;QACvB;QAEAC,qBAAoBC,EAAoC;YACtDlB,YAAYmB,IAAI,CAACD;YACjB,OAAO;gBACL,MAAME,MAAMpB,YAAYqB,OAAO,CAACH;gBAChC,IAAIE,OAAO,GAAGpB,YAAYsB,MAAM,CAACF,KAAK;YACxC;QACF;QAEAG,gBAAeC,IAAsB;YACnC,KAAK,MAAMN,MAAMlB,YAAa;gBAC5BkB,GAAGM;YACL;QACF;IACF;AACF"}
|
|
@@ -1,11 +1,6 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
hashB: number;
|
|
7
|
-
atTick: number;
|
|
8
|
-
}
|
|
9
|
-
export declare class DivergenceSignal extends Signal<DivergenceData> {
|
|
10
|
-
}
|
|
1
|
+
import type { HashMismatchData } from './create-hash-reporter.js';
|
|
2
|
+
/**
|
|
3
|
+
* @deprecated Use `HashMismatchData` from `create-hash-reporter` instead.
|
|
4
|
+
*/
|
|
5
|
+
export type DivergenceData = HashMismatchData;
|
|
11
6
|
//# sourceMappingURL=divergence.signal.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"divergence.signal.d.ts","sourceRoot":"","sources":["../../../src/lib/hash-verification/divergence.signal.ts"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"divergence.signal.d.ts","sourceRoot":"","sources":["../../../src/lib/hash-verification/divergence.signal.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,2BAA2B,CAAC;AAElE;;GAEG;AACH,MAAM,MAAM,cAAc,GAAG,gBAAgB,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/lib/hash-verification/divergence.signal.ts"],"sourcesContent":["import {
|
|
1
|
+
{"version":3,"sources":["../../../src/lib/hash-verification/divergence.signal.ts"],"sourcesContent":["import type { HashMismatchData } from './create-hash-reporter.js';\n\n/**\n * @deprecated Use `HashMismatchData` from `create-hash-reporter` instead.\n */\nexport type DivergenceData = HashMismatchData;\n"],"names":[],"rangeMappings":";;","mappings":"AAEA;;CAEC,GACD,WAA8C"}
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
export {
|
|
2
|
-
export {
|
|
3
|
-
export { createHashReporter, type HashReporterConfig } from './create-hash-reporter.js';
|
|
1
|
+
export { type DivergenceData } from './divergence.signal.js';
|
|
2
|
+
export { createHashReporter, type HashReporterConfig, type HashMismatchData, type HashReporter } from './create-hash-reporter.js';
|
|
4
3
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/lib/hash-verification/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/lib/hash-verification/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,cAAc,EAAE,MAAM,wBAAwB,CAAC;AAC7D,OAAO,EAAE,kBAAkB,EAAE,KAAK,kBAAkB,EAAE,KAAK,gBAAgB,EAAE,KAAK,YAAY,EAAE,MAAM,2BAA2B,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/lib/hash-verification/index.ts"],"sourcesContent":["export {
|
|
1
|
+
{"version":3,"sources":["../../../src/lib/hash-verification/index.ts"],"sourcesContent":["export { type DivergenceData } from './divergence.signal.js';\nexport { createHashReporter, type HashReporterConfig, type HashMismatchData, type HashReporter } from './create-hash-reporter.js';\n"],"names":["createHashReporter"],"rangeMappings":"","mappings":"AACA,SAASA,kBAAkB,QAA2E,4BAA4B"}
|
|
@@ -15,6 +15,8 @@ export declare class RPCHistory {
|
|
|
15
15
|
clear(): void;
|
|
16
16
|
get size(): number;
|
|
17
17
|
get totalRPCCount(): number;
|
|
18
|
+
getRPCsAtTick(tick: number): ReadonlyArray<RPC>;
|
|
19
|
+
getRPCCountAtTick(tick: number): number;
|
|
18
20
|
debugExportAsJSON(): string;
|
|
19
21
|
/**
|
|
20
22
|
* Exports entire history to a compact binary format.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rpc-history.d.ts","sourceRoot":"","sources":["../../../src/lib/input/rpc-history.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAE,MAAM,UAAU,CAAC;AAC/B,OAAO,EAAE,yBAAyB,EAAE,MAAM,mBAAmB,CAAC;AAC9D,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAiDpD,qBAAa,UAAU;IACrB,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAiC;WAM5C,gBAAgB,CAAC,IAAI,EAAE,aAAa,CAAC,GAAG,CAAC,EAAE,eAAe,EAAE,MAAM,GAAG,GAAG,EAAE;IAQjF,MAAM,CAAC,GAAG,EAAE,GAAG,GAAG,IAAI;IAStB,QAAQ,CAAC,IAAI,EAAE,aAAa,CAAC,GAAG,CAAC,GAAG,IAAI;IA6B/C;;;OAGG;IACI,eAAe,CAAC,UAAU,SAAS,yBAAyB,EACjE,IAAI,EAAE,MAAM,EACZ,SAAS,EAAE,UAAU,GACpB,aAAa,CAAC,GAAG,CAAC,YAAY,CAAC,UAAU,CAAC,CAAC,CAAC;IAaxC,wBAAwB,CAAC,UAAU,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,IAAI;IAmB7E,KAAK,IAAI,IAAI;IAIpB,IAAW,IAAI,IAAI,MAAM,CAExB;IAED,IAAW,aAAa,IAAI,MAAM,CAMjC;
|
|
1
|
+
{"version":3,"file":"rpc-history.d.ts","sourceRoot":"","sources":["../../../src/lib/input/rpc-history.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAE,MAAM,UAAU,CAAC;AAC/B,OAAO,EAAE,yBAAyB,EAAE,MAAM,mBAAmB,CAAC;AAC9D,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAiDpD,qBAAa,UAAU;IACrB,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAiC;WAM5C,gBAAgB,CAAC,IAAI,EAAE,aAAa,CAAC,GAAG,CAAC,EAAE,eAAe,EAAE,MAAM,GAAG,GAAG,EAAE;IAQjF,MAAM,CAAC,GAAG,EAAE,GAAG,GAAG,IAAI;IAStB,QAAQ,CAAC,IAAI,EAAE,aAAa,CAAC,GAAG,CAAC,GAAG,IAAI;IA6B/C;;;OAGG;IACI,eAAe,CAAC,UAAU,SAAS,yBAAyB,EACjE,IAAI,EAAE,MAAM,EACZ,SAAS,EAAE,UAAU,GACpB,aAAa,CAAC,GAAG,CAAC,YAAY,CAAC,UAAU,CAAC,CAAC,CAAC;IAaxC,wBAAwB,CAAC,UAAU,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,IAAI;IAmB7E,KAAK,IAAI,IAAI;IAIpB,IAAW,IAAI,IAAI,MAAM,CAExB;IAED,IAAW,aAAa,IAAI,MAAM,CAMjC;IAEM,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,aAAa,CAAC,GAAG,CAAC;IAI/C,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM;IAQvC,iBAAiB,IAAI,MAAM;IAsBlC;;;;OAIG;IACI,MAAM,CAAC,QAAQ,EAAE,aAAa,GAAG,WAAW;IAgBnD;;;;OAIG;IACI,MAAM,CAAC,QAAQ,EAAE,aAAa,EAAE,MAAM,EAAE,WAAW,GAAG,IAAI;IAgCjE,OAAO,CAAC,iBAAiB;IA6BzB,OAAO,CAAC,mBAAmB;IAa3B,OAAO,CAAC,WAAW;IAYnB,OAAO,CAAC,gBAAgB;IAwCxB,OAAO,CAAC,UAAU;IAMlB,OAAO,CAAC,aAAa;IAmDrB,6EAA6E;IAC7E,OAAO,CAAC,YAAY;CAOrB"}
|
|
@@ -115,6 +115,15 @@ export class RPCHistory {
|
|
|
115
115
|
}
|
|
116
116
|
return count;
|
|
117
117
|
}
|
|
118
|
+
getRPCsAtTick(tick) {
|
|
119
|
+
var _this__history_get;
|
|
120
|
+
return (_this__history_get = this._history.get(tick)) != null ? _this__history_get : [];
|
|
121
|
+
}
|
|
122
|
+
getRPCCountAtTick(tick) {
|
|
123
|
+
var _this__history_get;
|
|
124
|
+
var _this__history_get_length;
|
|
125
|
+
return (_this__history_get_length = (_this__history_get = this._history.get(tick)) == null ? void 0 : _this__history_get.length) != null ? _this__history_get_length : 0;
|
|
126
|
+
}
|
|
118
127
|
// ─────────────────────────────────────────────────────────────────────────
|
|
119
128
|
// Serialization
|
|
120
129
|
// ─────────────────────────────────────────────────────────────────────────
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/lib/input/rpc-history.ts"],"sourcesContent":["import { RPC } from './rpc.js';\nimport { IAbstractInputConstructor } from '../types/index.js';\nimport { InputRegistry } from './input-registry.js';\nimport { InputBinarySchema, FieldType, fieldTypeSizeBytes, LE } from '@lagless/binary';\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Constants\n// ─────────────────────────────────────────────────────────────────────────────\n\nconst RPC_HISTORY_FORMAT_VERSION = 1;\n\n/**\n * Binary format layout:\n *\n * [Header]\n * version: Uint8 (format version for forward compatibility)\n * tickCount: Uint32 (number of unique ticks)\n *\n * [Per Tick Block] × tickCount\n * tick: Uint32 (tick number)\n * rpcCount: Uint16 (number of RPCs for this tick)\n *\n * [Per RPC Entry] × rpcCount\n * seq: Uint32 (sequence number)\n * playerSlot: Uint8 (player slot)\n * dataLength: Uint16 (byte length of packed input data)\n * data: Uint8[] (InputBinarySchema packed data including inputId & ordinal)\n */\n\nconst HEADER_SIZE =\n fieldTypeSizeBytes[FieldType.Uint8] + // version\n fieldTypeSizeBytes[FieldType.Uint32]; // tickCount\n\nconst TICK_HEADER_SIZE =\n fieldTypeSizeBytes[FieldType.Uint32] + // tick\n fieldTypeSizeBytes[FieldType.Uint16]; // rpcCount\n\nconst RPC_HEADER_SIZE =\n fieldTypeSizeBytes[FieldType.Uint32] + // seq\n fieldTypeSizeBytes[FieldType.Uint8] + // playerSlot\n fieldTypeSizeBytes[FieldType.Uint16]; // dataLength\n\nconst compareRPCs = (a: RPC, b: RPC): number =>\n a.meta.playerSlot - b.meta.playerSlot\n || a.meta.ordinal - b.meta.ordinal\n || a.meta.seq - b.meta.seq;\n\n// ─────────────────────────────────────────────────────────────────────────────\n// RPCHistory\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport class RPCHistory {\n private readonly _history: Map<number, RPC[]> = new Map();\n\n // ─────────────────────────────────────────────────────────────────────────\n // Static utilities\n // ─────────────────────────────────────────────────────────────────────────\n\n public static excludeLocalRPCs(rpcs: ReadonlyArray<RPC>, localPlayerSlot: number): RPC[] {\n return rpcs.filter((rpc) => rpc.meta.playerSlot !== localPlayerSlot);\n }\n\n // ─────────────────────────────────────────────────────────────────────────\n // Public read/write methods\n // ─────────────────────────────────────────────────────────────────────────\n\n public addRPC(rpc: RPC): void {\n let tickRPCs = this._history.get(rpc.meta.tick);\n if (!tickRPCs) {\n tickRPCs = [];\n this._history.set(rpc.meta.tick, tickRPCs);\n }\n this.insertSorted(tickRPCs, rpc);\n }\n\n public addBatch(rpcs: ReadonlyArray<RPC>): void {\n if (rpcs.length === 0) return;\n\n // Group incoming RPCs by tick to minimize Map lookups and sorts\n const perTick = new Map<number, RPC[]>();\n\n for (const rpc of rpcs) {\n let group = perTick.get(rpc.meta.tick);\n if (!group) {\n group = [];\n perTick.set(rpc.meta.tick, group);\n }\n group.push(rpc);\n }\n\n // Merge per-tick groups into history and sort once per tick\n for (const [tick, newRPCs] of perTick) {\n const existing = this._history.get(tick);\n\n if (!existing) {\n newRPCs.sort(compareRPCs);\n this._history.set(tick, newRPCs);\n } else {\n existing.push(...newRPCs);\n existing.sort(compareRPCs);\n }\n }\n }\n\n /**\n * Returns a new array of RPCs matching the given input type at the specified tick.\n * Each call allocates a fresh array — safe to store and use across multiple calls.\n */\n public collectTickRPCs<TInputCtor extends IAbstractInputConstructor>(\n tick: number,\n InputCtor: TInputCtor\n ): ReadonlyArray<RPC<InstanceType<TInputCtor>>> {\n const rpcs = this._history.get(tick);\n if (!rpcs) return [];\n\n const result: RPC<InstanceType<TInputCtor>>[] = [];\n for (const rpc of rpcs) {\n if (rpc.inputId === InputCtor.id) {\n result.push(rpc as RPC<InstanceType<TInputCtor>>);\n }\n }\n return result;\n }\n\n public removePlayerInputsAtTick(playerSlot: number, tick: number, seq: number): void {\n const tickRPCs = this._history.get(tick);\n if (!tickRPCs || tickRPCs.length === 0) return;\n\n let writeIndex = 0;\n for (let readIndex = 0; readIndex < tickRPCs.length; readIndex++) {\n const rpc = tickRPCs[readIndex];\n if (!(rpc.meta.playerSlot === playerSlot && rpc.meta.seq === seq)) {\n tickRPCs[writeIndex++] = rpc;\n }\n }\n\n tickRPCs.length = writeIndex;\n\n if (writeIndex === 0) {\n this._history.delete(tick);\n }\n }\n\n public clear(): void {\n this._history.clear();\n }\n\n public get size(): number {\n return this._history.size;\n }\n\n public get totalRPCCount(): number {\n let count = 0;\n for (const rpcs of this._history.values()) {\n count += rpcs.length;\n }\n return count;\n }\n\n // ─────────────────────────────────────────────────────────────────────────\n // Serialization\n // ─────────────────────────────────────────────────────────────────────────\n\n public debugExportAsJSON(): string {\n const exportObj: Record<number, Array<{\n inputId: number;\n seq: number;\n playerSlot: number;\n ordinal: number;\n data: unknown;\n }>> = {};\n\n for (const [tick, rpcs] of this._history) {\n exportObj[tick] = rpcs.map((rpc) => ({\n inputId: rpc.inputId,\n seq: rpc.meta.seq,\n playerSlot: rpc.meta.playerSlot,\n ordinal: rpc.meta.ordinal,\n data: rpc.data,\n }));\n }\n\n return JSON.stringify(exportObj, null, 2);\n }\n\n /**\n * Exports entire history to a compact binary format.\n * @param registry - InputRegistry for serializing input data\n * @returns ArrayBuffer containing serialized history\n */\n public export(registry: InputRegistry): ArrayBuffer {\n // First pass: pack all RPC data and calculate total size\n const tickEntries = this.prepareExportData(registry);\n const totalSize = this.calculateExportSize(tickEntries);\n\n // Second pass: write to buffer\n const buffer = new ArrayBuffer(totalSize);\n const view = new DataView(buffer);\n const uint8View = new Uint8Array(buffer);\n\n const offset = this.writeHeader(view, tickEntries.length);\n this.writeTickEntries(view, uint8View, offset, tickEntries);\n\n return buffer;\n }\n\n /**\n * Imports history from binary format, replacing current content.\n * @param registry - InputRegistry for deserializing input data\n * @param buffer - ArrayBuffer containing serialized history\n */\n public import(registry: InputRegistry, buffer: ArrayBuffer): void {\n const view = new DataView(buffer);\n let offset = 0;\n\n // Validate and read header\n const { version, tickCount } = this.readHeader(view, offset);\n offset += HEADER_SIZE;\n\n if (version !== RPC_HISTORY_FORMAT_VERSION) {\n throw new Error(\n `Unsupported RPCHistory format version: ${version} (expected ${RPC_HISTORY_FORMAT_VERSION})`\n );\n }\n\n // Clear existing data and read all ticks\n this._history.clear();\n\n for (let i = 0; i < tickCount; i++) {\n const result = this.readTickEntry(view, buffer, offset, registry);\n offset = result.nextOffset;\n\n if (result.rpcs.length > 0) {\n result.rpcs.sort(compareRPCs);\n this._history.set(result.tick, result.rpcs);\n }\n }\n }\n\n // ─────────────────────────────────────────────────────────────────────────\n // Private: Export helpers\n // ─────────────────────────────────────────────────────────────────────────\n\n private prepareExportData(registry: InputRegistry): ExportTickEntry[] {\n const entries: ExportTickEntry[] = [];\n\n for (const [tick, rpcs] of this._history) {\n const rpcEntries: ExportRPCEntry[] = [];\n\n for (const rpc of rpcs) {\n const packedData = InputBinarySchema.packBatch(registry, [{\n inputId: rpc.inputId,\n ordinal: rpc.meta.ordinal,\n values: rpc.data,\n }]);\n\n rpcEntries.push({\n seq: rpc.meta.seq,\n playerSlot: rpc.meta.playerSlot,\n packedData,\n });\n }\n\n entries.push({ tick, rpcs: rpcEntries });\n }\n\n // Sort by tick for deterministic output\n entries.sort((a, b) => a.tick - b.tick);\n\n return entries;\n }\n\n private calculateExportSize(tickEntries: ExportTickEntry[]): number {\n let size = HEADER_SIZE;\n\n for (const entry of tickEntries) {\n size += TICK_HEADER_SIZE;\n for (const rpc of entry.rpcs) {\n size += RPC_HEADER_SIZE + rpc.packedData.byteLength;\n }\n }\n\n return size;\n }\n\n private writeHeader(view: DataView, tickCount: number): number {\n let offset = 0;\n\n view.setUint8(offset, RPC_HISTORY_FORMAT_VERSION);\n offset += fieldTypeSizeBytes[FieldType.Uint8];\n\n view.setUint32(offset, tickCount, LE);\n offset += fieldTypeSizeBytes[FieldType.Uint32];\n\n return offset;\n }\n\n private writeTickEntries(\n view: DataView,\n uint8View: Uint8Array,\n startOffset: number,\n tickEntries: ExportTickEntry[]\n ): number {\n let offset = startOffset;\n\n for (const entry of tickEntries) {\n // Write tick header\n view.setUint32(offset, entry.tick, LE);\n offset += fieldTypeSizeBytes[FieldType.Uint32];\n\n view.setUint16(offset, entry.rpcs.length, LE);\n offset += fieldTypeSizeBytes[FieldType.Uint16];\n\n // Write RPC entries\n for (const rpc of entry.rpcs) {\n view.setUint32(offset, rpc.seq, LE);\n offset += fieldTypeSizeBytes[FieldType.Uint32];\n\n view.setUint8(offset, rpc.playerSlot);\n offset += fieldTypeSizeBytes[FieldType.Uint8];\n\n view.setUint16(offset, rpc.packedData.byteLength, LE);\n offset += fieldTypeSizeBytes[FieldType.Uint16];\n\n // Copy packed data\n uint8View.set(new Uint8Array(rpc.packedData), offset);\n offset += rpc.packedData.byteLength;\n }\n }\n\n return offset;\n }\n\n // ─────────────────────────────────────────────────────────────────────────\n // Private: Import helpers\n // ─────────────────────────────────────────────────────────────────────────\n\n private readHeader(view: DataView, offset: number): { version: number; tickCount: number } {\n const version = view.getUint8(offset);\n const tickCount = view.getUint32(offset + fieldTypeSizeBytes[FieldType.Uint8], LE);\n return { version, tickCount };\n }\n\n private readTickEntry(\n view: DataView,\n buffer: ArrayBuffer,\n startOffset: number,\n registry: InputRegistry\n ): { tick: number; rpcs: RPC[]; nextOffset: number } {\n let offset = startOffset;\n\n // Read tick header\n const tick = view.getUint32(offset, LE);\n offset += fieldTypeSizeBytes[FieldType.Uint32];\n\n const rpcCount = view.getUint16(offset, LE);\n offset += fieldTypeSizeBytes[FieldType.Uint16];\n\n const rpcs: RPC[] = [];\n\n // Read RPC entries\n for (let j = 0; j < rpcCount; j++) {\n const seq = view.getUint32(offset, LE);\n offset += fieldTypeSizeBytes[FieldType.Uint32];\n\n const playerSlot = view.getUint8(offset);\n offset += fieldTypeSizeBytes[FieldType.Uint8];\n\n const dataLength = view.getUint16(offset, LE);\n offset += fieldTypeSizeBytes[FieldType.Uint16];\n\n // Extract and unpack input data\n const dataSlice = buffer.slice(offset, offset + dataLength);\n offset += dataLength;\n\n const unpacked = InputBinarySchema.unpackBatch(registry, dataSlice);\n\n if (unpacked.length !== 1) {\n throw new Error(\n `Invalid RPC data: expected 1 input, got ${unpacked.length} at tick ${tick}`\n );\n }\n\n const { inputId, ordinal, values } = unpacked[0];\n rpcs.push(new RPC(inputId, { tick, seq, ordinal, playerSlot }, values));\n }\n\n return { tick, rpcs, nextOffset: offset };\n }\n\n // ─────────────────────────────────────────────────────────────────────────\n // Private: Sorting\n // ─────────────────────────────────────────────────────────────────────────\n\n /** Insert rpc into arr maintaining sorted order by (playerSlot, ordinal). */\n private insertSorted(arr: RPC[], rpc: RPC): void {\n let i = arr.length;\n while (i > 0 && compareRPCs(arr[i - 1], rpc) > 0) {\n i--;\n }\n arr.splice(i, 0, rpc);\n }\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Internal types\n// ─────────────────────────────────────────────────────────────────────────────\n\ninterface ExportRPCEntry {\n readonly seq: number;\n readonly playerSlot: number;\n readonly packedData: ArrayBuffer;\n}\n\ninterface ExportTickEntry {\n readonly tick: number;\n readonly rpcs: ExportRPCEntry[];\n}\n"],"names":["RPC","InputBinarySchema","FieldType","fieldTypeSizeBytes","LE","RPC_HISTORY_FORMAT_VERSION","HEADER_SIZE","Uint8","Uint32","TICK_HEADER_SIZE","Uint16","RPC_HEADER_SIZE","compareRPCs","a","b","meta","playerSlot","ordinal","seq","RPCHistory","excludeLocalRPCs","rpcs","localPlayerSlot","filter","rpc","addRPC","tickRPCs","_history","get","tick","set","insertSorted","addBatch","length","perTick","Map","group","push","newRPCs","existing","sort","collectTickRPCs","InputCtor","result","inputId","id","removePlayerInputsAtTick","writeIndex","readIndex","delete","clear","size","totalRPCCount","count","values","debugExportAsJSON","exportObj","map","data","JSON","stringify","export","registry","tickEntries","prepareExportData","totalSize","calculateExportSize","buffer","ArrayBuffer","view","DataView","uint8View","Uint8Array","offset","writeHeader","writeTickEntries","import","version","tickCount","readHeader","Error","i","readTickEntry","nextOffset","entries","rpcEntries","packedData","packBatch","entry","byteLength","setUint8","setUint32","startOffset","setUint16","getUint8","getUint32","rpcCount","getUint16","j","dataLength","dataSlice","slice","unpacked","unpackBatch","arr","splice"],"rangeMappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;","mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../src/lib/input/rpc-history.ts"],"sourcesContent":["import { RPC } from './rpc.js';\nimport { IAbstractInputConstructor } from '../types/index.js';\nimport { InputRegistry } from './input-registry.js';\nimport { InputBinarySchema, FieldType, fieldTypeSizeBytes, LE } from '@lagless/binary';\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Constants\n// ─────────────────────────────────────────────────────────────────────────────\n\nconst RPC_HISTORY_FORMAT_VERSION = 1;\n\n/**\n * Binary format layout:\n *\n * [Header]\n * version: Uint8 (format version for forward compatibility)\n * tickCount: Uint32 (number of unique ticks)\n *\n * [Per Tick Block] × tickCount\n * tick: Uint32 (tick number)\n * rpcCount: Uint16 (number of RPCs for this tick)\n *\n * [Per RPC Entry] × rpcCount\n * seq: Uint32 (sequence number)\n * playerSlot: Uint8 (player slot)\n * dataLength: Uint16 (byte length of packed input data)\n * data: Uint8[] (InputBinarySchema packed data including inputId & ordinal)\n */\n\nconst HEADER_SIZE =\n fieldTypeSizeBytes[FieldType.Uint8] + // version\n fieldTypeSizeBytes[FieldType.Uint32]; // tickCount\n\nconst TICK_HEADER_SIZE =\n fieldTypeSizeBytes[FieldType.Uint32] + // tick\n fieldTypeSizeBytes[FieldType.Uint16]; // rpcCount\n\nconst RPC_HEADER_SIZE =\n fieldTypeSizeBytes[FieldType.Uint32] + // seq\n fieldTypeSizeBytes[FieldType.Uint8] + // playerSlot\n fieldTypeSizeBytes[FieldType.Uint16]; // dataLength\n\nconst compareRPCs = (a: RPC, b: RPC): number =>\n a.meta.playerSlot - b.meta.playerSlot\n || a.meta.ordinal - b.meta.ordinal\n || a.meta.seq - b.meta.seq;\n\n// ─────────────────────────────────────────────────────────────────────────────\n// RPCHistory\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport class RPCHistory {\n private readonly _history: Map<number, RPC[]> = new Map();\n\n // ─────────────────────────────────────────────────────────────────────────\n // Static utilities\n // ─────────────────────────────────────────────────────────────────────────\n\n public static excludeLocalRPCs(rpcs: ReadonlyArray<RPC>, localPlayerSlot: number): RPC[] {\n return rpcs.filter((rpc) => rpc.meta.playerSlot !== localPlayerSlot);\n }\n\n // ─────────────────────────────────────────────────────────────────────────\n // Public read/write methods\n // ─────────────────────────────────────────────────────────────────────────\n\n public addRPC(rpc: RPC): void {\n let tickRPCs = this._history.get(rpc.meta.tick);\n if (!tickRPCs) {\n tickRPCs = [];\n this._history.set(rpc.meta.tick, tickRPCs);\n }\n this.insertSorted(tickRPCs, rpc);\n }\n\n public addBatch(rpcs: ReadonlyArray<RPC>): void {\n if (rpcs.length === 0) return;\n\n // Group incoming RPCs by tick to minimize Map lookups and sorts\n const perTick = new Map<number, RPC[]>();\n\n for (const rpc of rpcs) {\n let group = perTick.get(rpc.meta.tick);\n if (!group) {\n group = [];\n perTick.set(rpc.meta.tick, group);\n }\n group.push(rpc);\n }\n\n // Merge per-tick groups into history and sort once per tick\n for (const [tick, newRPCs] of perTick) {\n const existing = this._history.get(tick);\n\n if (!existing) {\n newRPCs.sort(compareRPCs);\n this._history.set(tick, newRPCs);\n } else {\n existing.push(...newRPCs);\n existing.sort(compareRPCs);\n }\n }\n }\n\n /**\n * Returns a new array of RPCs matching the given input type at the specified tick.\n * Each call allocates a fresh array — safe to store and use across multiple calls.\n */\n public collectTickRPCs<TInputCtor extends IAbstractInputConstructor>(\n tick: number,\n InputCtor: TInputCtor\n ): ReadonlyArray<RPC<InstanceType<TInputCtor>>> {\n const rpcs = this._history.get(tick);\n if (!rpcs) return [];\n\n const result: RPC<InstanceType<TInputCtor>>[] = [];\n for (const rpc of rpcs) {\n if (rpc.inputId === InputCtor.id) {\n result.push(rpc as RPC<InstanceType<TInputCtor>>);\n }\n }\n return result;\n }\n\n public removePlayerInputsAtTick(playerSlot: number, tick: number, seq: number): void {\n const tickRPCs = this._history.get(tick);\n if (!tickRPCs || tickRPCs.length === 0) return;\n\n let writeIndex = 0;\n for (let readIndex = 0; readIndex < tickRPCs.length; readIndex++) {\n const rpc = tickRPCs[readIndex];\n if (!(rpc.meta.playerSlot === playerSlot && rpc.meta.seq === seq)) {\n tickRPCs[writeIndex++] = rpc;\n }\n }\n\n tickRPCs.length = writeIndex;\n\n if (writeIndex === 0) {\n this._history.delete(tick);\n }\n }\n\n public clear(): void {\n this._history.clear();\n }\n\n public get size(): number {\n return this._history.size;\n }\n\n public get totalRPCCount(): number {\n let count = 0;\n for (const rpcs of this._history.values()) {\n count += rpcs.length;\n }\n return count;\n }\n\n public getRPCsAtTick(tick: number): ReadonlyArray<RPC> {\n return this._history.get(tick) ?? [];\n }\n\n public getRPCCountAtTick(tick: number): number {\n return this._history.get(tick)?.length ?? 0;\n }\n\n // ─────────────────────────────────────────────────────────────────────────\n // Serialization\n // ─────────────────────────────────────────────────────────────────────────\n\n public debugExportAsJSON(): string {\n const exportObj: Record<number, Array<{\n inputId: number;\n seq: number;\n playerSlot: number;\n ordinal: number;\n data: unknown;\n }>> = {};\n\n for (const [tick, rpcs] of this._history) {\n exportObj[tick] = rpcs.map((rpc) => ({\n inputId: rpc.inputId,\n seq: rpc.meta.seq,\n playerSlot: rpc.meta.playerSlot,\n ordinal: rpc.meta.ordinal,\n data: rpc.data,\n }));\n }\n\n return JSON.stringify(exportObj, null, 2);\n }\n\n /**\n * Exports entire history to a compact binary format.\n * @param registry - InputRegistry for serializing input data\n * @returns ArrayBuffer containing serialized history\n */\n public export(registry: InputRegistry): ArrayBuffer {\n // First pass: pack all RPC data and calculate total size\n const tickEntries = this.prepareExportData(registry);\n const totalSize = this.calculateExportSize(tickEntries);\n\n // Second pass: write to buffer\n const buffer = new ArrayBuffer(totalSize);\n const view = new DataView(buffer);\n const uint8View = new Uint8Array(buffer);\n\n const offset = this.writeHeader(view, tickEntries.length);\n this.writeTickEntries(view, uint8View, offset, tickEntries);\n\n return buffer;\n }\n\n /**\n * Imports history from binary format, replacing current content.\n * @param registry - InputRegistry for deserializing input data\n * @param buffer - ArrayBuffer containing serialized history\n */\n public import(registry: InputRegistry, buffer: ArrayBuffer): void {\n const view = new DataView(buffer);\n let offset = 0;\n\n // Validate and read header\n const { version, tickCount } = this.readHeader(view, offset);\n offset += HEADER_SIZE;\n\n if (version !== RPC_HISTORY_FORMAT_VERSION) {\n throw new Error(\n `Unsupported RPCHistory format version: ${version} (expected ${RPC_HISTORY_FORMAT_VERSION})`\n );\n }\n\n // Clear existing data and read all ticks\n this._history.clear();\n\n for (let i = 0; i < tickCount; i++) {\n const result = this.readTickEntry(view, buffer, offset, registry);\n offset = result.nextOffset;\n\n if (result.rpcs.length > 0) {\n result.rpcs.sort(compareRPCs);\n this._history.set(result.tick, result.rpcs);\n }\n }\n }\n\n // ─────────────────────────────────────────────────────────────────────────\n // Private: Export helpers\n // ─────────────────────────────────────────────────────────────────────────\n\n private prepareExportData(registry: InputRegistry): ExportTickEntry[] {\n const entries: ExportTickEntry[] = [];\n\n for (const [tick, rpcs] of this._history) {\n const rpcEntries: ExportRPCEntry[] = [];\n\n for (const rpc of rpcs) {\n const packedData = InputBinarySchema.packBatch(registry, [{\n inputId: rpc.inputId,\n ordinal: rpc.meta.ordinal,\n values: rpc.data,\n }]);\n\n rpcEntries.push({\n seq: rpc.meta.seq,\n playerSlot: rpc.meta.playerSlot,\n packedData,\n });\n }\n\n entries.push({ tick, rpcs: rpcEntries });\n }\n\n // Sort by tick for deterministic output\n entries.sort((a, b) => a.tick - b.tick);\n\n return entries;\n }\n\n private calculateExportSize(tickEntries: ExportTickEntry[]): number {\n let size = HEADER_SIZE;\n\n for (const entry of tickEntries) {\n size += TICK_HEADER_SIZE;\n for (const rpc of entry.rpcs) {\n size += RPC_HEADER_SIZE + rpc.packedData.byteLength;\n }\n }\n\n return size;\n }\n\n private writeHeader(view: DataView, tickCount: number): number {\n let offset = 0;\n\n view.setUint8(offset, RPC_HISTORY_FORMAT_VERSION);\n offset += fieldTypeSizeBytes[FieldType.Uint8];\n\n view.setUint32(offset, tickCount, LE);\n offset += fieldTypeSizeBytes[FieldType.Uint32];\n\n return offset;\n }\n\n private writeTickEntries(\n view: DataView,\n uint8View: Uint8Array,\n startOffset: number,\n tickEntries: ExportTickEntry[]\n ): number {\n let offset = startOffset;\n\n for (const entry of tickEntries) {\n // Write tick header\n view.setUint32(offset, entry.tick, LE);\n offset += fieldTypeSizeBytes[FieldType.Uint32];\n\n view.setUint16(offset, entry.rpcs.length, LE);\n offset += fieldTypeSizeBytes[FieldType.Uint16];\n\n // Write RPC entries\n for (const rpc of entry.rpcs) {\n view.setUint32(offset, rpc.seq, LE);\n offset += fieldTypeSizeBytes[FieldType.Uint32];\n\n view.setUint8(offset, rpc.playerSlot);\n offset += fieldTypeSizeBytes[FieldType.Uint8];\n\n view.setUint16(offset, rpc.packedData.byteLength, LE);\n offset += fieldTypeSizeBytes[FieldType.Uint16];\n\n // Copy packed data\n uint8View.set(new Uint8Array(rpc.packedData), offset);\n offset += rpc.packedData.byteLength;\n }\n }\n\n return offset;\n }\n\n // ─────────────────────────────────────────────────────────────────────────\n // Private: Import helpers\n // ─────────────────────────────────────────────────────────────────────────\n\n private readHeader(view: DataView, offset: number): { version: number; tickCount: number } {\n const version = view.getUint8(offset);\n const tickCount = view.getUint32(offset + fieldTypeSizeBytes[FieldType.Uint8], LE);\n return { version, tickCount };\n }\n\n private readTickEntry(\n view: DataView,\n buffer: ArrayBuffer,\n startOffset: number,\n registry: InputRegistry\n ): { tick: number; rpcs: RPC[]; nextOffset: number } {\n let offset = startOffset;\n\n // Read tick header\n const tick = view.getUint32(offset, LE);\n offset += fieldTypeSizeBytes[FieldType.Uint32];\n\n const rpcCount = view.getUint16(offset, LE);\n offset += fieldTypeSizeBytes[FieldType.Uint16];\n\n const rpcs: RPC[] = [];\n\n // Read RPC entries\n for (let j = 0; j < rpcCount; j++) {\n const seq = view.getUint32(offset, LE);\n offset += fieldTypeSizeBytes[FieldType.Uint32];\n\n const playerSlot = view.getUint8(offset);\n offset += fieldTypeSizeBytes[FieldType.Uint8];\n\n const dataLength = view.getUint16(offset, LE);\n offset += fieldTypeSizeBytes[FieldType.Uint16];\n\n // Extract and unpack input data\n const dataSlice = buffer.slice(offset, offset + dataLength);\n offset += dataLength;\n\n const unpacked = InputBinarySchema.unpackBatch(registry, dataSlice);\n\n if (unpacked.length !== 1) {\n throw new Error(\n `Invalid RPC data: expected 1 input, got ${unpacked.length} at tick ${tick}`\n );\n }\n\n const { inputId, ordinal, values } = unpacked[0];\n rpcs.push(new RPC(inputId, { tick, seq, ordinal, playerSlot }, values));\n }\n\n return { tick, rpcs, nextOffset: offset };\n }\n\n // ─────────────────────────────────────────────────────────────────────────\n // Private: Sorting\n // ─────────────────────────────────────────────────────────────────────────\n\n /** Insert rpc into arr maintaining sorted order by (playerSlot, ordinal). */\n private insertSorted(arr: RPC[], rpc: RPC): void {\n let i = arr.length;\n while (i > 0 && compareRPCs(arr[i - 1], rpc) > 0) {\n i--;\n }\n arr.splice(i, 0, rpc);\n }\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Internal types\n// ─────────────────────────────────────────────────────────────────────────────\n\ninterface ExportRPCEntry {\n readonly seq: number;\n readonly playerSlot: number;\n readonly packedData: ArrayBuffer;\n}\n\ninterface ExportTickEntry {\n readonly tick: number;\n readonly rpcs: ExportRPCEntry[];\n}\n"],"names":["RPC","InputBinarySchema","FieldType","fieldTypeSizeBytes","LE","RPC_HISTORY_FORMAT_VERSION","HEADER_SIZE","Uint8","Uint32","TICK_HEADER_SIZE","Uint16","RPC_HEADER_SIZE","compareRPCs","a","b","meta","playerSlot","ordinal","seq","RPCHistory","excludeLocalRPCs","rpcs","localPlayerSlot","filter","rpc","addRPC","tickRPCs","_history","get","tick","set","insertSorted","addBatch","length","perTick","Map","group","push","newRPCs","existing","sort","collectTickRPCs","InputCtor","result","inputId","id","removePlayerInputsAtTick","writeIndex","readIndex","delete","clear","size","totalRPCCount","count","values","getRPCsAtTick","getRPCCountAtTick","debugExportAsJSON","exportObj","map","data","JSON","stringify","export","registry","tickEntries","prepareExportData","totalSize","calculateExportSize","buffer","ArrayBuffer","view","DataView","uint8View","Uint8Array","offset","writeHeader","writeTickEntries","import","version","tickCount","readHeader","Error","i","readTickEntry","nextOffset","entries","rpcEntries","packedData","packBatch","entry","byteLength","setUint8","setUint32","startOffset","setUint16","getUint8","getUint32","rpcCount","getUint16","j","dataLength","dataSlice","slice","unpacked","unpackBatch","arr","splice"],"rangeMappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;","mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lagless/core",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.50",
|
|
4
4
|
"license": "CC-BY-NC-4.0",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -39,9 +39,9 @@
|
|
|
39
39
|
},
|
|
40
40
|
"dependencies": {
|
|
41
41
|
"@swc/helpers": "~0.5.11",
|
|
42
|
-
"@lagless/binary": "0.0.
|
|
43
|
-
"@lagless/
|
|
44
|
-
"@lagless/
|
|
42
|
+
"@lagless/binary": "0.0.50",
|
|
43
|
+
"@lagless/misc": "0.0.50",
|
|
44
|
+
"@lagless/math": "0.0.50"
|
|
45
45
|
},
|
|
46
46
|
"devDependencies": {
|
|
47
47
|
"unplugin-swc": "^1.5.7"
|
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
import type { IECSSystem, IAbstractInputConstructor, IPlayerResourceConstructor } from '../types/index.js';
|
|
2
|
-
import type { ECSConfig } from '../ecs-config.js';
|
|
3
|
-
import type { AbstractInputProvider } from '../input/abstract-input-provider.js';
|
|
4
|
-
import type { PlayerResources } from '../mem/managers/player-resources-manager.js';
|
|
5
|
-
import type { DivergenceSignal } from './divergence.signal.js';
|
|
6
|
-
/**
|
|
7
|
-
* Abstract base class for ECS hash verification systems.
|
|
8
|
-
*
|
|
9
|
-
* Subclass this in your game simulation and provide the concrete
|
|
10
|
-
* `_reportHashRpc` and `_playerResourceClass` from your codegen.
|
|
11
|
-
*
|
|
12
|
-
* The subclass must use `@ECSSystem()` decorator and declare constructor
|
|
13
|
-
* params for DI injection matching the base constructor signature.
|
|
14
|
-
*/
|
|
15
|
-
export declare abstract class AbstractHashVerificationSystem implements IECSSystem {
|
|
16
|
-
protected readonly _ECSConfig: ECSConfig;
|
|
17
|
-
protected readonly _InputProvider: AbstractInputProvider;
|
|
18
|
-
protected readonly _PlayerResources: PlayerResources;
|
|
19
|
-
protected readonly _DivergenceSignal: DivergenceSignal;
|
|
20
|
-
constructor(_ECSConfig: ECSConfig, _InputProvider: AbstractInputProvider, _PlayerResources: PlayerResources, _DivergenceSignal: DivergenceSignal);
|
|
21
|
-
/**
|
|
22
|
-
* The codegen-generated ReportHash input constructor.
|
|
23
|
-
* Must have `{ readonly id: number }` and conform to `IAbstractInputConstructor`.
|
|
24
|
-
*/
|
|
25
|
-
protected abstract readonly _reportHashRpc: IAbstractInputConstructor;
|
|
26
|
-
/**
|
|
27
|
-
* The codegen-generated PlayerResource class constructor.
|
|
28
|
-
*/
|
|
29
|
-
protected abstract readonly _playerResourceClass: IPlayerResourceConstructor;
|
|
30
|
-
update(tick: number): void;
|
|
31
|
-
}
|
|
32
|
-
//# sourceMappingURL=abstract-hash-verification.system.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"abstract-hash-verification.system.d.ts","sourceRoot":"","sources":["../../../src/lib/hash-verification/abstract-hash-verification.system.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,yBAAyB,EAAE,0BAA0B,EAAE,MAAM,mBAAmB,CAAC;AAC3G,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAClD,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,qCAAqC,CAAC;AACjF,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,6CAA6C,CAAC;AACnF,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAY/D;;;;;;;;GAQG;AACH,8BAAsB,8BAA+B,YAAW,UAAU;IAEtE,SAAS,CAAC,QAAQ,CAAC,UAAU,EAAE,SAAS;IACxC,SAAS,CAAC,QAAQ,CAAC,cAAc,EAAE,qBAAqB;IACxD,SAAS,CAAC,QAAQ,CAAC,gBAAgB,EAAE,eAAe;IACpD,SAAS,CAAC,QAAQ,CAAC,iBAAiB,EAAE,gBAAgB;gBAHnC,UAAU,EAAE,SAAS,EACrB,cAAc,EAAE,qBAAqB,EACrC,gBAAgB,EAAE,eAAe,EACjC,iBAAiB,EAAE,gBAAgB;IAGxD;;;OAGG;IACH,SAAS,CAAC,QAAQ,CAAC,QAAQ,CAAC,cAAc,EAAE,yBAAyB,CAAC;IAEtE;;OAEG;IACH,SAAS,CAAC,QAAQ,CAAC,QAAQ,CAAC,oBAAoB,EAAE,0BAA0B,CAAC;IAEtE,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;CA2ClC"}
|
|
@@ -1,54 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Abstract base class for ECS hash verification systems.
|
|
3
|
-
*
|
|
4
|
-
* Subclass this in your game simulation and provide the concrete
|
|
5
|
-
* `_reportHashRpc` and `_playerResourceClass` from your codegen.
|
|
6
|
-
*
|
|
7
|
-
* The subclass must use `@ECSSystem()` decorator and declare constructor
|
|
8
|
-
* params for DI injection matching the base constructor signature.
|
|
9
|
-
*/ export class AbstractHashVerificationSystem {
|
|
10
|
-
update(tick) {
|
|
11
|
-
const rpcs = this._InputProvider.collectTickRPCs(tick, this._reportHashRpc);
|
|
12
|
-
for (const rpc of rpcs){
|
|
13
|
-
const playerResource = this._PlayerResources.get(this._playerResourceClass, rpc.meta.playerSlot);
|
|
14
|
-
const safe = playerResource.safe;
|
|
15
|
-
safe.lastReportedHash = rpc.data.hash;
|
|
16
|
-
safe.lastReportedHashTick = rpc.data.atTick;
|
|
17
|
-
}
|
|
18
|
-
// Only compare hashes for ticks that are verified (server-confirmed, no
|
|
19
|
-
// future rollback possible). This replaces the old heuristic delay.
|
|
20
|
-
const verifiedTick = this._InputProvider.verifiedTick;
|
|
21
|
-
const maxPlayers = this._ECSConfig.maxPlayers;
|
|
22
|
-
for(let a = 0; a < maxPlayers; a++){
|
|
23
|
-
const pa = this._PlayerResources.get(this._playerResourceClass, a);
|
|
24
|
-
const safeA = pa.safe;
|
|
25
|
-
if (safeA.connected === 0 || safeA.lastReportedHashTick === 0) continue;
|
|
26
|
-
if (safeA.lastReportedHashTick > verifiedTick) continue;
|
|
27
|
-
for(let b = a + 1; b < maxPlayers; b++){
|
|
28
|
-
const pb = this._PlayerResources.get(this._playerResourceClass, b);
|
|
29
|
-
const safeB = pb.safe;
|
|
30
|
-
if (safeB.connected === 0 || safeB.lastReportedHashTick === 0) continue;
|
|
31
|
-
if (safeB.lastReportedHashTick > verifiedTick) continue;
|
|
32
|
-
if (safeA.lastReportedHashTick === safeB.lastReportedHashTick && safeA.lastReportedHash !== safeB.lastReportedHash) {
|
|
33
|
-
safeA.hashMismatchCount++;
|
|
34
|
-
safeB.hashMismatchCount++;
|
|
35
|
-
this._DivergenceSignal.emit(tick, {
|
|
36
|
-
slotA: a,
|
|
37
|
-
slotB: b,
|
|
38
|
-
hashA: safeA.lastReportedHash,
|
|
39
|
-
hashB: safeB.lastReportedHash,
|
|
40
|
-
atTick: safeA.lastReportedHashTick
|
|
41
|
-
});
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
constructor(_ECSConfig, _InputProvider, _PlayerResources, _DivergenceSignal){
|
|
47
|
-
this._ECSConfig = _ECSConfig;
|
|
48
|
-
this._InputProvider = _InputProvider;
|
|
49
|
-
this._PlayerResources = _PlayerResources;
|
|
50
|
-
this._DivergenceSignal = _DivergenceSignal;
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
//# sourceMappingURL=abstract-hash-verification.system.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/lib/hash-verification/abstract-hash-verification.system.ts"],"sourcesContent":["import type { IECSSystem, IAbstractInputConstructor, IPlayerResourceConstructor } from '../types/index.js';\nimport type { ECSConfig } from '../ecs-config.js';\nimport type { AbstractInputProvider } from '../input/abstract-input-provider.js';\nimport type { PlayerResources } from '../mem/managers/player-resources-manager.js';\nimport type { DivergenceSignal } from './divergence.signal.js';\n\n/**\n * Minimum fields that a PlayerResource must expose for hash verification.\n */\ninterface HashPlayerResourceProxy {\n connected: number;\n lastReportedHash: number;\n lastReportedHashTick: number;\n hashMismatchCount: number;\n}\n\n/**\n * Abstract base class for ECS hash verification systems.\n *\n * Subclass this in your game simulation and provide the concrete\n * `_reportHashRpc` and `_playerResourceClass` from your codegen.\n *\n * The subclass must use `@ECSSystem()` decorator and declare constructor\n * params for DI injection matching the base constructor signature.\n */\nexport abstract class AbstractHashVerificationSystem implements IECSSystem {\n constructor(\n protected readonly _ECSConfig: ECSConfig,\n protected readonly _InputProvider: AbstractInputProvider,\n protected readonly _PlayerResources: PlayerResources,\n protected readonly _DivergenceSignal: DivergenceSignal,\n ) {}\n\n /**\n * The codegen-generated ReportHash input constructor.\n * Must have `{ readonly id: number }` and conform to `IAbstractInputConstructor`.\n */\n protected abstract readonly _reportHashRpc: IAbstractInputConstructor;\n\n /**\n * The codegen-generated PlayerResource class constructor.\n */\n protected abstract readonly _playerResourceClass: IPlayerResourceConstructor;\n\n public update(tick: number): void {\n const rpcs = this._InputProvider.collectTickRPCs(tick, this._reportHashRpc) as unknown as Array<{ meta: { playerSlot: number }, data: { hash: number, atTick: number } }>;\n\n for (const rpc of rpcs) {\n const playerResource = this._PlayerResources.get(this._playerResourceClass, rpc.meta.playerSlot) as unknown as { safe: HashPlayerResourceProxy };\n const safe = playerResource.safe as HashPlayerResourceProxy;\n safe.lastReportedHash = rpc.data.hash;\n safe.lastReportedHashTick = rpc.data.atTick;\n }\n\n // Only compare hashes for ticks that are verified (server-confirmed, no\n // future rollback possible). This replaces the old heuristic delay.\n const verifiedTick = this._InputProvider.verifiedTick;\n\n const maxPlayers = this._ECSConfig.maxPlayers;\n for (let a = 0; a < maxPlayers; a++) {\n const pa = this._PlayerResources.get(this._playerResourceClass, a) as unknown as { safe: HashPlayerResourceProxy };\n const safeA = pa.safe as HashPlayerResourceProxy;\n if (safeA.connected === 0 || safeA.lastReportedHashTick === 0) continue;\n if (safeA.lastReportedHashTick > verifiedTick) continue;\n\n for (let b = a + 1; b < maxPlayers; b++) {\n const pb = this._PlayerResources.get(this._playerResourceClass, b) as unknown as { safe: HashPlayerResourceProxy };\n const safeB = pb.safe as HashPlayerResourceProxy;\n if (safeB.connected === 0 || safeB.lastReportedHashTick === 0) continue;\n if (safeB.lastReportedHashTick > verifiedTick) continue;\n\n if (safeA.lastReportedHashTick === safeB.lastReportedHashTick &&\n safeA.lastReportedHash !== safeB.lastReportedHash) {\n safeA.hashMismatchCount++;\n safeB.hashMismatchCount++;\n\n this._DivergenceSignal.emit(tick, {\n slotA: a,\n slotB: b,\n hashA: safeA.lastReportedHash,\n hashB: safeB.lastReportedHash,\n atTick: safeA.lastReportedHashTick,\n });\n }\n }\n }\n }\n}\n"],"names":["AbstractHashVerificationSystem","update","tick","rpcs","_InputProvider","collectTickRPCs","_reportHashRpc","rpc","playerResource","_PlayerResources","get","_playerResourceClass","meta","playerSlot","safe","lastReportedHash","data","hash","lastReportedHashTick","atTick","verifiedTick","maxPlayers","_ECSConfig","a","pa","safeA","connected","b","pb","safeB","hashMismatchCount","_DivergenceSignal","emit","slotA","slotB","hashA","hashB","constructor"],"rangeMappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;","mappings":"AAgBA;;;;;;;;CAQC,GACD,OAAO,MAAeA;IAmBbC,OAAOC,IAAY,EAAQ;QAChC,MAAMC,OAAO,IAAI,CAACC,cAAc,CAACC,eAAe,CAACH,MAAM,IAAI,CAACI,cAAc;QAE1E,KAAK,MAAMC,OAAOJ,KAAM;YACtB,MAAMK,iBAAiB,IAAI,CAACC,gBAAgB,CAACC,GAAG,CAAC,IAAI,CAACC,oBAAoB,EAAEJ,IAAIK,IAAI,CAACC,UAAU;YAC/F,MAAMC,OAAON,eAAeM,IAAI;YAChCA,KAAKC,gBAAgB,GAAGR,IAAIS,IAAI,CAACC,IAAI;YACrCH,KAAKI,oBAAoB,GAAGX,IAAIS,IAAI,CAACG,MAAM;QAC7C;QAEA,wEAAwE;QACxE,oEAAoE;QACpE,MAAMC,eAAe,IAAI,CAAChB,cAAc,CAACgB,YAAY;QAErD,MAAMC,aAAa,IAAI,CAACC,UAAU,CAACD,UAAU;QAC7C,IAAK,IAAIE,IAAI,GAAGA,IAAIF,YAAYE,IAAK;YACnC,MAAMC,KAAK,IAAI,CAACf,gBAAgB,CAACC,GAAG,CAAC,IAAI,CAACC,oBAAoB,EAAEY;YAChE,MAAME,QAAQD,GAAGV,IAAI;YACrB,IAAIW,MAAMC,SAAS,KAAK,KAAKD,MAAMP,oBAAoB,KAAK,GAAG;YAC/D,IAAIO,MAAMP,oBAAoB,GAAGE,cAAc;YAE/C,IAAK,IAAIO,IAAIJ,IAAI,GAAGI,IAAIN,YAAYM,IAAK;gBACvC,MAAMC,KAAK,IAAI,CAACnB,gBAAgB,CAACC,GAAG,CAAC,IAAI,CAACC,oBAAoB,EAAEgB;gBAChE,MAAME,QAAQD,GAAGd,IAAI;gBACrB,IAAIe,MAAMH,SAAS,KAAK,KAAKG,MAAMX,oBAAoB,KAAK,GAAG;gBAC/D,IAAIW,MAAMX,oBAAoB,GAAGE,cAAc;gBAE/C,IAAIK,MAAMP,oBAAoB,KAAKW,MAAMX,oBAAoB,IAC3DO,MAAMV,gBAAgB,KAAKc,MAAMd,gBAAgB,EAAE;oBACnDU,MAAMK,iBAAiB;oBACvBD,MAAMC,iBAAiB;oBAEvB,IAAI,CAACC,iBAAiB,CAACC,IAAI,CAAC9B,MAAM;wBAChC+B,OAAOV;wBACPW,OAAOP;wBACPQ,OAAOV,MAAMV,gBAAgB;wBAC7BqB,OAAOP,MAAMd,gBAAgB;wBAC7BI,QAAQM,MAAMP,oBAAoB;oBACpC;gBACF;YACF;QACF;IACF;IA5DAmB,YACE,AAAmBf,UAAqB,EACxC,AAAmBlB,cAAqC,EACxD,AAAmBK,gBAAiC,EACpD,AAAmBsB,iBAAmC,CACtD;aAJmBT,aAAAA;aACAlB,iBAAAA;aACAK,mBAAAA;aACAsB,oBAAAA;IAClB;AAwDL"}
|