@al8b/time 0.1.12 → 0.1.14
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/dist/core/index.js +24 -11
- package/dist/core/index.js.map +1 -1
- package/dist/core/index.mjs +24 -11
- package/dist/core/index.mjs.map +1 -1
- package/dist/core/machine.js +24 -9
- package/dist/core/machine.js.map +1 -1
- package/dist/core/machine.mjs +24 -11
- package/dist/core/machine.mjs.map +1 -1
- package/dist/index.js +24 -11
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +24 -11
- package/dist/index.mjs.map +1 -1
- package/package.json +33 -35
package/dist/core/index.js
CHANGED
|
@@ -25,9 +25,6 @@ __export(core_exports, {
|
|
|
25
25
|
});
|
|
26
26
|
module.exports = __toCommonJS(core_exports);
|
|
27
27
|
|
|
28
|
-
// src/core/machine.ts
|
|
29
|
-
var import_diagnostics = require("@al8b/diagnostics");
|
|
30
|
-
|
|
31
28
|
// src/constants.ts
|
|
32
29
|
var DEFAULT_RECORD_BUFFER_FRAMES = 60 * 30;
|
|
33
30
|
var DEFAULT_LOOP_BUFFER_FRAMES = 60 * 4;
|
|
@@ -311,8 +308,12 @@ var TimeMachine = class {
|
|
|
311
308
|
}
|
|
312
309
|
this.sendStatus();
|
|
313
310
|
} catch (err) {
|
|
314
|
-
|
|
315
|
-
|
|
311
|
+
this.runtime?.listener?.reportError?.({
|
|
312
|
+
code: "E7082",
|
|
313
|
+
message: "Time machine step error",
|
|
314
|
+
data: {
|
|
315
|
+
error: String(err)
|
|
316
|
+
}
|
|
316
317
|
});
|
|
317
318
|
}
|
|
318
319
|
}
|
|
@@ -360,8 +361,12 @@ var TimeMachine = class {
|
|
|
360
361
|
this.sendStatus();
|
|
361
362
|
} catch (err) {
|
|
362
363
|
this.recording = false;
|
|
363
|
-
|
|
364
|
-
|
|
364
|
+
this.runtime?.listener?.reportError?.({
|
|
365
|
+
code: "E7083",
|
|
366
|
+
message: "Time machine recording error",
|
|
367
|
+
data: {
|
|
368
|
+
error: String(err)
|
|
369
|
+
}
|
|
365
370
|
});
|
|
366
371
|
}
|
|
367
372
|
}
|
|
@@ -376,8 +381,12 @@ var TimeMachine = class {
|
|
|
376
381
|
this.recording = false;
|
|
377
382
|
this.sendStatus();
|
|
378
383
|
} catch (err) {
|
|
379
|
-
|
|
380
|
-
|
|
384
|
+
this.runtime?.listener?.reportError?.({
|
|
385
|
+
code: "E7083",
|
|
386
|
+
message: "Time machine recording error",
|
|
387
|
+
data: {
|
|
388
|
+
error: String(err)
|
|
389
|
+
}
|
|
381
390
|
});
|
|
382
391
|
}
|
|
383
392
|
}
|
|
@@ -410,8 +419,12 @@ var TimeMachine = class {
|
|
|
410
419
|
*/
|
|
411
420
|
setReplayPosition(position) {
|
|
412
421
|
if (!isFinite(position) || position < 0) {
|
|
413
|
-
|
|
414
|
-
|
|
422
|
+
this.runtime?.listener?.reportError?.({
|
|
423
|
+
code: "E7081",
|
|
424
|
+
message: "Invalid replay position",
|
|
425
|
+
data: {
|
|
426
|
+
value: String(position)
|
|
427
|
+
}
|
|
415
428
|
});
|
|
416
429
|
return;
|
|
417
430
|
}
|
package/dist/core/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/core/index.ts","../../src/core/machine.ts","../../src/constants.ts","../../src/utils.ts","../../src/playback/player.ts","../../src/recording/recorder.ts"],"sourcesContent":["/**\n * Core module exports\n */\n\nexport type { TimeMachineRuntime } from \"./machine\";\nexport { TimeMachine } from \"./machine\";\n","/**\n * TimeMachine - Main time machine controller\n *\n * Responsibilities:\n * - Coordinate recording and playback\n * - Handle commands\n * - Manage state\n */\n\nimport { APIErrorCode, reportRuntimeError } from \"@al8b/diagnostics\";\nimport { DEFAULT_LOOP_BUFFER_FRAMES, DEFAULT_RECORD_BUFFER_FRAMES } from \"../constants\";\nimport { StatePlayer } from \"../playback\";\nimport { StateRecorder } from \"../recording\";\nimport type { TimeMachineMessage, TimeMachineStatus } from \"../types\";\n\nexport interface TimeMachineRuntime {\n\tvm?: {\n\t\tcontext?: {\n\t\t\tglobal?: any;\n\t\t};\n\t};\n\tupdateCall?: () => void;\n\tdrawCall?: () => void;\n}\n\nexport class TimeMachine {\n\tprivate runtime: TimeMachineRuntime;\n\tprivate recorder: StateRecorder;\n\tprivate player: StatePlayer;\n\tprivate recording = false;\n\tprivate replayPosition = 0;\n\tprivate statusCallback?: (status: TimeMachineStatus) => void;\n\n\tconstructor(runtime: TimeMachineRuntime) {\n\t\tthis.runtime = runtime;\n\t\tthis.recorder = new StateRecorder(DEFAULT_RECORD_BUFFER_FRAMES);\n\t\tthis.player = new StatePlayer(DEFAULT_LOOP_BUFFER_FRAMES);\n\n\t\t// Configure which objects should be excluded from state recording\n\t\tthis.setupExclusions();\n\t}\n\n\t/**\n\t * Setup objects to exclude from recording\n\t */\n\tprivate setupExclusions(): void {\n\t\tconst excluded: any[] = [];\n\n\t\tif (this.runtime.vm?.context?.global) {\n\t\t\tconst global = this.runtime.vm.context.global;\n\n\t\t\t// Exclude system APIs and non-serializable objects from recording\n\t\t\tif (global.random) excluded.push(global.random);\n\t\t\tif (global.screen) excluded.push(global.screen);\n\t\t\tif (global.audio) excluded.push(global.audio);\n\t\t\tif (global.keyboard) excluded.push(global.keyboard);\n\t\t\tif (global.mouse) excluded.push(global.mouse);\n\t\t\tif (global.touch) excluded.push(global.touch);\n\t\t\tif (global.gamepad) excluded.push(global.gamepad);\n\t\t\tif (global.system) excluded.push(global.system);\n\t\t\tif (global.storage) excluded.push(global.storage);\n\t\t\tif (global.host) excluded.push(global.host);\n\t\t\tif (global.session) excluded.push(global.session);\n\t\t\tif (global.memory) excluded.push(global.memory);\n\t\t}\n\n\t\tthis.recorder.setExcluded(excluded);\n\t}\n\n\t/**\n\t * Step function - call each frame\n\t */\n\tstep(): void {\n\t\tif (!this.recording) {\n\t\t\treturn;\n\t\t}\n\n\t\ttry {\n\t\t\t// Apply replay position changes and update loop if active\n\t\t\tif (this.replayPosition !== 0) {\n\t\t\t\tthis.recorder.trimTo(this.replayPosition);\n\t\t\t\tif (this.player.isLooping()) {\n\t\t\t\t\tthis.player.startLoop(this.recorder.getLength(), () => this.loopCallback());\n\t\t\t\t}\n\t\t\t\tthis.replayPosition = 0;\n\t\t\t}\n\n\t\t\t// Capture current global state snapshot for time travel\n\t\t\tif (this.runtime.vm?.context?.global) {\n\t\t\t\tthis.recorder.record(this.runtime.vm.context.global);\n\t\t\t}\n\n\t\t\tthis.sendStatus();\n\t\t} catch (err) {\n\t\t\treportRuntimeError((this.runtime as any)?.listener, APIErrorCode.E7082, { error: String(err) });\n\t\t}\n\t}\n\n\t/**\n\t * Handle incoming messages\n\t */\n\tmessageReceived(data: TimeMachineMessage): void {\n\t\tswitch (data.command) {\n\t\t\tcase \"start_recording\":\n\t\t\t\tthis.startRecording();\n\t\t\t\tbreak;\n\n\t\t\tcase \"stop_recording\":\n\t\t\t\tthis.stopRecording();\n\t\t\t\tbreak;\n\n\t\t\tcase \"step_backward\":\n\t\t\t\tthis.stepBackward();\n\t\t\t\tbreak;\n\n\t\t\tcase \"step_forward\":\n\t\t\t\tthis.stepForward();\n\t\t\t\tbreak;\n\n\t\t\tcase \"replay_position\":\n\t\t\t\tif (data.position != null) {\n\t\t\t\t\tthis.setReplayPosition(data.position);\n\t\t\t\t}\n\t\t\t\tbreak;\n\n\t\t\tcase \"start_looping\":\n\t\t\t\tthis.startLooping();\n\t\t\t\tbreak;\n\n\t\t\tcase \"stop_looping\":\n\t\t\t\tthis.stopLooping();\n\t\t\t\tbreak;\n\t\t}\n\t}\n\n\t/**\n\t * Start recording\n\t */\n\tprivate startRecording(): void {\n\t\tif (this.recording) {\n\t\t\treturn;\n\t\t}\n\n\t\ttry {\n\t\t\tthis.recording = true;\n\t\t\tthis.recorder.clear();\n\t\t\tthis.replayPosition = 0;\n\t\t\tthis.sendStatus();\n\t\t} catch (err) {\n\t\t\tthis.recording = false;\n\t\t\treportRuntimeError((this.runtime as any)?.listener, APIErrorCode.E7083, { error: String(err) });\n\t\t}\n\t}\n\n\t/**\n\t * Stop recording\n\t */\n\tprivate stopRecording(): void {\n\t\tif (!this.recording) {\n\t\t\treturn;\n\t\t}\n\n\t\ttry {\n\t\t\tthis.recording = false;\n\t\t\tthis.sendStatus();\n\t\t} catch (err) {\n\t\t\treportRuntimeError((this.runtime as any)?.listener, APIErrorCode.E7083, { error: String(err) });\n\t\t}\n\t}\n\n\t/**\n\t * Step backward in time\n\t */\n\tprivate stepBackward(): void {\n\t\tif (this.replayPosition + 1 >= this.recorder.getLength()) {\n\t\t\treturn;\n\t\t}\n\n\t\tthis.stopLooping();\n\t\tthis.replayPosition++;\n\t\tthis.replay();\n\t\tthis.sendStatus();\n\t}\n\n\t/**\n\t * Step forward in time\n\t */\n\tprivate stepForward(): void {\n\t\tif (this.replayPosition <= 1) {\n\t\t\treturn;\n\t\t}\n\n\t\tthis.stopLooping();\n\t\tthis.replayPosition--;\n\t\tthis.replay();\n\t\tthis.sendStatus();\n\t}\n\n\t/**\n\t * Set replay position\n\t */\n\tprivate setReplayPosition(position: number): void {\n\t\t// Validate time value is finite and non-negative\n\t\tif (!isFinite(position) || position < 0) {\n\t\t\treportRuntimeError((this.runtime as any)?.listener, APIErrorCode.E7081, { value: String(position) });\n\t\t\treturn;\n\t\t}\n\n\t\tconst pos = Math.round(position);\n\t\tthis.replayPosition = Math.max(2, Math.min(this.recorder.getLength() - 1, pos));\n\n\t\tif (this.player.isLooping()) {\n\t\t\tthis.player.startLoop(this.replayPosition, () => this.loopCallback());\n\t\t}\n\n\t\tthis.replay();\n\t\tthis.sendStatus();\n\t}\n\n\t/**\n\t * Start looping\n\t */\n\tprivate startLooping(): void {\n\t\tif (this.recorder.getLength() === 0) {\n\t\t\treturn;\n\t\t}\n\n\t\tthis.recording = false;\n\t\tconst position = Math.max(this.replayPosition, 1);\n\t\tthis.player.startLoop(position, () => this.loopCallback());\n\t}\n\n\t/**\n\t * Stop looping\n\t */\n\tprivate stopLooping(): void {\n\t\tif (!this.player.isLooping()) {\n\t\t\treturn;\n\t\t}\n\n\t\tthis.replayPosition = this.player.stopLoop();\n\t\tthis.sendStatus();\n\t}\n\n\t/**\n\t * Replay state at current position\n\t */\n\tprivate replay(skipDraw = false): void {\n\t\tif (this.replayPosition >= this.recorder.getLength()) {\n\t\t\treturn;\n\t\t}\n\n\t\tconst snapshot = this.recorder.getState(this.replayPosition);\n\t\tif (snapshot && this.runtime.vm?.context?.global) {\n\t\t\tthis.player.restoreState(this.runtime.vm.context.global, snapshot);\n\t\t}\n\n\t\tif (!skipDraw) {\n\t\t\tthis.runtime.drawCall?.();\n\t\t}\n\t}\n\n\t/**\n\t * Loop callback\n\t */\n\tprivate loopCallback(): void {\n\t\tconst position = this.player.updateLoop();\n\t\tif (position !== null) {\n\t\t\tthis.replayPosition = position;\n\t\t\tthis.replay(true);\n\t\t\tthis.runtime.updateCall?.();\n\t\t\tthis.runtime.drawCall?.();\n\t\t}\n\t\tthis.sendStatus();\n\t}\n\n\t/**\n\t * Send status update\n\t */\n\tprivate sendStatus(): void {\n\t\tif (this.statusCallback) {\n\t\t\tthis.statusCallback({\n\t\t\t\trecording: this.recording,\n\t\t\t\tlooping: this.player.isLooping(),\n\t\t\t\tposition: this.replayPosition,\n\t\t\t\tlength: this.recorder.getLength(),\n\t\t\t\tmax: this.recorder.getMaxLength(),\n\t\t\t});\n\t\t}\n\t}\n\n\t/**\n\t * Advance the loop playback by one tick.\n\t * Call once per real game frame (from the orchestrator's watch-step hook).\n\t * No-op when not looping.\n\t */\n\tloopStep(): void {\n\t\tif (this.player.isLooping()) {\n\t\t\tthis.player.executeCallback();\n\t\t}\n\t}\n\n\t/**\n\t * Set status callback\n\t */\n\tonStatus(callback: (status: TimeMachineStatus) => void): void {\n\t\tthis.statusCallback = callback;\n\t}\n\n\t/**\n\t * Check if recording\n\t */\n\tisRecording(): boolean {\n\t\treturn this.recording;\n\t}\n\n\t/**\n\t * Check if looping\n\t */\n\tisLooping(): boolean {\n\t\treturn this.player.isLooping();\n\t}\n}\n","/** Default recording buffer: 30 seconds at 60fps (1800 frames) */\nexport const DEFAULT_RECORD_BUFFER_FRAMES = 60 * 30;\n\n/** Default loop playback buffer: 4 seconds at 60fps (240 frames) */\nexport const DEFAULT_LOOP_BUFFER_FRAMES = 60 * 4;\n","/**\n * Deep copy a value, optionally skipping excluded references.\n *\n * @param value - The value to deep copy\n * @param excluded - Optional array of object references to skip (replaced with null)\n */\nexport function deepCopy(value: any, excluded?: any[]): any {\n\tif (value == null) {\n\t\treturn value;\n\t}\n\n\tif (excluded && excluded.includes(value)) {\n\t\treturn null;\n\t}\n\n\tif (typeof value === \"string\" || typeof value === \"number\" || typeof value === \"boolean\") {\n\t\treturn value;\n\t}\n\n\tif (Array.isArray(value)) {\n\t\tconst result: any[] = [];\n\t\tfor (let i = 0; i < value.length; i++) {\n\t\t\tresult[i] = deepCopy(value[i], excluded);\n\t\t}\n\t\treturn result;\n\t}\n\n\tif (typeof value === \"object\") {\n\t\tconst result: any = {};\n\t\tfor (const key in value) {\n\t\t\tif (Object.hasOwn(value, key)) {\n\t\t\t\tresult[key] = deepCopy(value[key], excluded);\n\t\t\t}\n\t\t}\n\t\treturn result;\n\t}\n\n\t// Non-serializable types: return null when filtering, passthrough otherwise\n\treturn excluded ? null : value;\n}\n","/**\n * StatePlayer - Handles replay and loop playback\n *\n * Responsibilities:\n * - Restore state snapshots\n * - Manage loop playback\n * - Control playback position\n */\nimport { DEFAULT_LOOP_BUFFER_FRAMES } from \"../constants\";\nimport { deepCopy } from \"../utils\";\n\nimport type { StateSnapshot } from \"../types\";\n\nexport class StatePlayer {\n\tprivate looping = false;\n\tprivate loopStart = 0;\n\tprivate loopIndex = 0;\n\tprivate loopLength: number;\n\tprivate loopCallback: (() => void) | null = null;\n\n\tconstructor(loopLength = DEFAULT_LOOP_BUFFER_FRAMES) {\n\t\tthis.loopLength = loopLength;\n\t}\n\n\t/**\n\t * Check if currently looping\n\t */\n\tisLooping(): boolean {\n\t\treturn this.looping;\n\t}\n\n\t/**\n\t * Start loop playback\n\t */\n\tstartLoop(position: number, callback: () => void): void {\n\t\tthis.looping = true;\n\t\tthis.loopStart = Math.max(position, 1);\n\t\tthis.loopIndex = 0;\n\t\tthis.loopCallback = callback;\n\t}\n\n\t/**\n\t * Stop loop playback\n\t */\n\tstopLoop(): number {\n\t\tthis.looping = false;\n\t\tthis.loopCallback = null;\n\t\treturn this.loopStart;\n\t}\n\n\t/**\n\t * Update loop (call each frame)\n\t * Returns position to replay, or null if not looping\n\t */\n\tupdateLoop(): number | null {\n\t\tif (!this.looping) {\n\t\t\treturn null;\n\t\t}\n\n\t\tif (this.loopIndex === 0) {\n\t\t\tthis.loopIndex++;\n\t\t\treturn this.loopStart;\n\t\t}\n\n\t\tthis.loopIndex++;\n\t\tif (this.loopIndex > this.loopLength) {\n\t\t\tthis.loopIndex = 0;\n\t\t}\n\n\t\treturn this.loopStart - this.loopIndex;\n\t}\n\n\t/**\n\t * Execute loop callback\n\t */\n\texecuteCallback(): void {\n\t\tif (this.loopCallback) {\n\t\t\tthis.loopCallback();\n\t\t}\n\t}\n\n\t/**\n\t * Restore state to target object\n\t */\n\trestoreState(target: any, snapshot: StateSnapshot): void {\n\t\tif (!snapshot || !target) {\n\t\t\treturn;\n\t\t}\n\n\t\t// Remove all existing properties except protected system APIs\n\t\tfor (const key in target) {\n\t\t\tif (Object.hasOwn(target, key) && !this.isProtectedKey(key)) {\n\t\t\t\tdelete target[key];\n\t\t\t}\n\t\t}\n\n\t\t// Apply snapshot properties to target object via deep copy\n\t\tfor (const key in snapshot) {\n\t\t\tif (Object.hasOwn(snapshot, key)) {\n\t\t\t\ttarget[key] = deepCopy(snapshot[key]);\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Check if key should be protected from restoration\n\t */\n\tprivate isProtectedKey(key: string): boolean {\n\t\t// Prevent system APIs and runtime objects from being overwritten during restore\n\t\tconst protected_keys = [\n\t\t\t\"screen\",\n\t\t\t\"audio\",\n\t\t\t\"keyboard\",\n\t\t\t\"mouse\",\n\t\t\t\"touch\",\n\t\t\t\"gamepad\",\n\t\t\t\"system\",\n\t\t\t\"storage\",\n\t\t\t\"sprites\",\n\t\t\t\"maps\",\n\t\t\t\"sounds\",\n\t\t\t\"music\",\n\t\t\t\"assets\",\n\t\t\t\"host\",\n\t\t\t\"session\",\n\t\t\t\"memory\",\n\t\t];\n\t\treturn protected_keys.includes(key);\n\t}\n\n}\n","/**\n * StateRecorder - Records game state history\n *\n * Responsibilities:\n * - Capture state snapshots\n * - Manage circular buffer\n * - Exclude non-serializable objects\n */\nimport { DEFAULT_RECORD_BUFFER_FRAMES } from \"../constants\";\nimport { deepCopy } from \"../utils\";\n\nimport type { StateSnapshot } from \"../types\";\n\nexport class StateRecorder {\n\tprivate history: StateSnapshot[] = [];\n\tprivate recordIndex = 0;\n\tprivate recordLength = 0;\n\tprivate maxLength: number;\n\tprivate excluded: any[] = [];\n\n\tconstructor(maxLength = DEFAULT_RECORD_BUFFER_FRAMES) {\n\t\tthis.maxLength = maxLength;\n\t}\n\n\t/**\n\t * Set objects to exclude from serialization\n\t */\n\tsetExcluded(excluded: any[]): void {\n\t\tthis.excluded = excluded;\n\t}\n\n\t/**\n\t * Record a state snapshot\n\t */\n\trecord(state: any): void {\n\t\tconst snapshot = this.makeStorableState(state);\n\t\tthis.history[this.recordIndex++] = snapshot;\n\t\tthis.recordLength = Math.min(this.recordLength + 1, this.maxLength);\n\n\t\tif (this.recordIndex >= this.maxLength) {\n\t\t\tthis.recordIndex = 0;\n\t\t}\n\t}\n\n\t/**\n\t * Get state at specific position (0 = most recent)\n\t */\n\tgetState(position: number): StateSnapshot | null {\n\t\tif (position >= this.recordLength) {\n\t\t\treturn null;\n\t\t}\n\n\t\tconst index = (this.recordIndex - position - 1 + this.maxLength) % this.maxLength;\n\t\treturn this.history[index];\n\t}\n\n\t/**\n\t * Get current record length\n\t */\n\tgetLength(): number {\n\t\treturn this.recordLength;\n\t}\n\n\t/**\n\t * Get maximum record length\n\t */\n\tgetMaxLength(): number {\n\t\treturn this.maxLength;\n\t}\n\n\t/**\n\t * Clear all recorded history\n\t */\n\tclear(): void {\n\t\tthis.history = [];\n\t\tthis.recordIndex = 0;\n\t\tthis.recordLength = 0;\n\t}\n\n\t/**\n\t * Trim history to specific position\n\t */\n\ttrimTo(position: number): void {\n\t\tif (position >= this.recordLength) {\n\t\t\treturn;\n\t\t}\n\n\t\tconst histo: StateSnapshot[] = [];\n\t\tconst start = this.recordLength;\n\t\tconst end = position + 1;\n\n\t\tfor (let i = start; i >= end; i--) {\n\t\t\tconst index = (this.recordIndex - i + this.maxLength) % this.maxLength;\n\t\t\thisto.push(this.history[index]);\n\t\t}\n\n\t\tthis.history = histo;\n\t\tthis.recordIndex = this.history.length;\n\t\tthis.recordLength = this.history.length;\n\t}\n\n\tprivate makeStorableState(value: any): any {\n\t\treturn deepCopy(value, this.excluded);\n\t}\n}\n"],"mappings":"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","names":["DEFAULT_RECORD_BUFFER_FRAMES","DEFAULT_LOOP_BUFFER_FRAMES","deepCopy","value","excluded","includes","Array","isArray","result","i","length","key","Object","hasOwn","StatePlayer","looping","loopStart","loopIndex","loopLength","loopCallback","DEFAULT_LOOP_BUFFER_FRAMES","isLooping","startLoop","position","callback","Math","max","stopLoop","updateLoop","executeCallback","restoreState","target","snapshot","key","Object","hasOwn","isProtectedKey","deepCopy","protected_keys","includes","StateRecorder","history","recordIndex","recordLength","maxLength","excluded","DEFAULT_RECORD_BUFFER_FRAMES","setExcluded","record","state","snapshot","makeStorableState","Math","min","getState","position","index","getLength","getMaxLength","clear","trimTo","histo","start","end","i","push","length","value","deepCopy","TimeMachine","runtime","recorder","player","recording","replayPosition","statusCallback","StateRecorder","DEFAULT_RECORD_BUFFER_FRAMES","StatePlayer","DEFAULT_LOOP_BUFFER_FRAMES","setupExclusions","excluded","vm","context","global","random","push","screen","audio","keyboard","mouse","touch","gamepad","system","storage","host","session","memory","setExcluded","step","trimTo","isLooping","startLoop","getLength","loopCallback","record","sendStatus","err","reportRuntimeError","listener","APIErrorCode","E7082","error","String","messageReceived","data","command","startRecording","stopRecording","stepBackward","stepForward","position","setReplayPosition","startLooping","stopLooping","clear","E7083","replay","isFinite","E7081","value","pos","Math","round","max","min","stopLoop","skipDraw","snapshot","getState","restoreState","drawCall","updateLoop","updateCall","looping","length","getMaxLength","loopStep","executeCallback","onStatus","callback","isRecording"]}
|
|
1
|
+
{"version":3,"sources":["../../src/core/index.ts","../../src/constants.ts","../../src/utils.ts","../../src/playback/player.ts","../../src/recording/recorder.ts","../../src/core/machine.ts"],"sourcesContent":["/**\n * Core module exports\n */\n\nexport type { TimeMachineRuntime } from \"./machine\";\nexport { TimeMachine } from \"./machine\";\n","/** Default recording buffer: 30 seconds at 60fps (1800 frames) */\nexport const DEFAULT_RECORD_BUFFER_FRAMES = 60 * 30;\n\n/** Default loop playback buffer: 4 seconds at 60fps (240 frames) */\nexport const DEFAULT_LOOP_BUFFER_FRAMES = 60 * 4;\n","/**\n * Deep copy a value, optionally skipping excluded references.\n *\n * @param value - The value to deep copy\n * @param excluded - Optional array of object references to skip (replaced with null)\n */\nexport function deepCopy(value: any, excluded?: any[]): any {\n\tif (value == null) {\n\t\treturn value;\n\t}\n\n\tif (excluded && excluded.includes(value)) {\n\t\treturn null;\n\t}\n\n\tif (typeof value === \"string\" || typeof value === \"number\" || typeof value === \"boolean\") {\n\t\treturn value;\n\t}\n\n\tif (Array.isArray(value)) {\n\t\tconst result: any[] = [];\n\t\tfor (let i = 0; i < value.length; i++) {\n\t\t\tresult[i] = deepCopy(value[i], excluded);\n\t\t}\n\t\treturn result;\n\t}\n\n\tif (typeof value === \"object\") {\n\t\tconst result: any = {};\n\t\tfor (const key in value) {\n\t\t\tif (Object.hasOwn(value, key)) {\n\t\t\t\tresult[key] = deepCopy(value[key], excluded);\n\t\t\t}\n\t\t}\n\t\treturn result;\n\t}\n\n\t// Non-serializable types: return null when filtering, passthrough otherwise\n\treturn excluded ? null : value;\n}\n","/**\n * StatePlayer - Handles replay and loop playback\n *\n * Responsibilities:\n * - Restore state snapshots\n * - Manage loop playback\n * - Control playback position\n */\nimport { DEFAULT_LOOP_BUFFER_FRAMES } from \"../constants\";\nimport { deepCopy } from \"../utils\";\n\nimport type { StateSnapshot } from \"../types\";\n\nexport class StatePlayer {\n\tprivate looping = false;\n\tprivate loopStart = 0;\n\tprivate loopIndex = 0;\n\tprivate loopLength: number;\n\tprivate loopCallback: (() => void) | null = null;\n\n\tconstructor(loopLength = DEFAULT_LOOP_BUFFER_FRAMES) {\n\t\tthis.loopLength = loopLength;\n\t}\n\n\t/**\n\t * Check if currently looping\n\t */\n\tisLooping(): boolean {\n\t\treturn this.looping;\n\t}\n\n\t/**\n\t * Start loop playback\n\t */\n\tstartLoop(position: number, callback: () => void): void {\n\t\tthis.looping = true;\n\t\tthis.loopStart = Math.max(position, 1);\n\t\tthis.loopIndex = 0;\n\t\tthis.loopCallback = callback;\n\t}\n\n\t/**\n\t * Stop loop playback\n\t */\n\tstopLoop(): number {\n\t\tthis.looping = false;\n\t\tthis.loopCallback = null;\n\t\treturn this.loopStart;\n\t}\n\n\t/**\n\t * Update loop (call each frame)\n\t * Returns position to replay, or null if not looping\n\t */\n\tupdateLoop(): number | null {\n\t\tif (!this.looping) {\n\t\t\treturn null;\n\t\t}\n\n\t\tif (this.loopIndex === 0) {\n\t\t\tthis.loopIndex++;\n\t\t\treturn this.loopStart;\n\t\t}\n\n\t\tthis.loopIndex++;\n\t\tif (this.loopIndex > this.loopLength) {\n\t\t\tthis.loopIndex = 0;\n\t\t}\n\n\t\treturn this.loopStart - this.loopIndex;\n\t}\n\n\t/**\n\t * Execute loop callback\n\t */\n\texecuteCallback(): void {\n\t\tif (this.loopCallback) {\n\t\t\tthis.loopCallback();\n\t\t}\n\t}\n\n\t/**\n\t * Restore state to target object\n\t */\n\trestoreState(target: any, snapshot: StateSnapshot): void {\n\t\tif (!snapshot || !target) {\n\t\t\treturn;\n\t\t}\n\n\t\t// Remove all existing properties except protected system APIs\n\t\tfor (const key in target) {\n\t\t\tif (Object.hasOwn(target, key) && !this.isProtectedKey(key)) {\n\t\t\t\tdelete target[key];\n\t\t\t}\n\t\t}\n\n\t\t// Apply snapshot properties to target object via deep copy\n\t\tfor (const key in snapshot) {\n\t\t\tif (Object.hasOwn(snapshot, key)) {\n\t\t\t\ttarget[key] = deepCopy(snapshot[key]);\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Check if key should be protected from restoration\n\t */\n\tprivate isProtectedKey(key: string): boolean {\n\t\t// Prevent system APIs and runtime objects from being overwritten during restore\n\t\tconst protected_keys = [\n\t\t\t\"screen\",\n\t\t\t\"audio\",\n\t\t\t\"keyboard\",\n\t\t\t\"mouse\",\n\t\t\t\"touch\",\n\t\t\t\"gamepad\",\n\t\t\t\"system\",\n\t\t\t\"storage\",\n\t\t\t\"sprites\",\n\t\t\t\"maps\",\n\t\t\t\"sounds\",\n\t\t\t\"music\",\n\t\t\t\"assets\",\n\t\t\t\"host\",\n\t\t\t\"session\",\n\t\t\t\"memory\",\n\t\t];\n\t\treturn protected_keys.includes(key);\n\t}\n\n}\n","/**\n * StateRecorder - Records game state history\n *\n * Responsibilities:\n * - Capture state snapshots\n * - Manage circular buffer\n * - Exclude non-serializable objects\n */\nimport { DEFAULT_RECORD_BUFFER_FRAMES } from \"../constants\";\nimport { deepCopy } from \"../utils\";\n\nimport type { StateSnapshot } from \"../types\";\n\nexport class StateRecorder {\n\tprivate history: StateSnapshot[] = [];\n\tprivate recordIndex = 0;\n\tprivate recordLength = 0;\n\tprivate maxLength: number;\n\tprivate excluded: any[] = [];\n\n\tconstructor(maxLength = DEFAULT_RECORD_BUFFER_FRAMES) {\n\t\tthis.maxLength = maxLength;\n\t}\n\n\t/**\n\t * Set objects to exclude from serialization\n\t */\n\tsetExcluded(excluded: any[]): void {\n\t\tthis.excluded = excluded;\n\t}\n\n\t/**\n\t * Record a state snapshot\n\t */\n\trecord(state: any): void {\n\t\tconst snapshot = this.makeStorableState(state);\n\t\tthis.history[this.recordIndex++] = snapshot;\n\t\tthis.recordLength = Math.min(this.recordLength + 1, this.maxLength);\n\n\t\tif (this.recordIndex >= this.maxLength) {\n\t\t\tthis.recordIndex = 0;\n\t\t}\n\t}\n\n\t/**\n\t * Get state at specific position (0 = most recent)\n\t */\n\tgetState(position: number): StateSnapshot | null {\n\t\tif (position >= this.recordLength) {\n\t\t\treturn null;\n\t\t}\n\n\t\tconst index = (this.recordIndex - position - 1 + this.maxLength) % this.maxLength;\n\t\treturn this.history[index];\n\t}\n\n\t/**\n\t * Get current record length\n\t */\n\tgetLength(): number {\n\t\treturn this.recordLength;\n\t}\n\n\t/**\n\t * Get maximum record length\n\t */\n\tgetMaxLength(): number {\n\t\treturn this.maxLength;\n\t}\n\n\t/**\n\t * Clear all recorded history\n\t */\n\tclear(): void {\n\t\tthis.history = [];\n\t\tthis.recordIndex = 0;\n\t\tthis.recordLength = 0;\n\t}\n\n\t/**\n\t * Trim history to specific position\n\t */\n\ttrimTo(position: number): void {\n\t\tif (position >= this.recordLength) {\n\t\t\treturn;\n\t\t}\n\n\t\tconst histo: StateSnapshot[] = [];\n\t\tconst start = this.recordLength;\n\t\tconst end = position + 1;\n\n\t\tfor (let i = start; i >= end; i--) {\n\t\t\tconst index = (this.recordIndex - i + this.maxLength) % this.maxLength;\n\t\t\thisto.push(this.history[index]);\n\t\t}\n\n\t\tthis.history = histo;\n\t\tthis.recordIndex = this.history.length;\n\t\tthis.recordLength = this.history.length;\n\t}\n\n\tprivate makeStorableState(value: any): any {\n\t\treturn deepCopy(value, this.excluded);\n\t}\n}\n","/**\n * TimeMachine - Main time machine controller\n *\n * Responsibilities:\n * - Coordinate recording and playback\n * - Handle commands\n * - Manage state\n */\n\nimport { DEFAULT_LOOP_BUFFER_FRAMES, DEFAULT_RECORD_BUFFER_FRAMES } from \"../constants\";\nimport { StatePlayer } from \"../playback\";\nimport { StateRecorder } from \"../recording\";\nimport type { TimeMachineMessage, TimeMachineStatus } from \"../types\";\n\nexport interface TimeMachineRuntime {\n\tvm?: {\n\t\tcontext?: {\n\t\t\tglobal?: any;\n\t\t};\n\t};\n\tupdateCall?: () => void;\n\tdrawCall?: () => void;\n}\n\nexport class TimeMachine {\n\tprivate runtime: TimeMachineRuntime;\n\tprivate recorder: StateRecorder;\n\tprivate player: StatePlayer;\n\tprivate recording = false;\n\tprivate replayPosition = 0;\n\tprivate statusCallback?: (status: TimeMachineStatus) => void;\n\n\tconstructor(runtime: TimeMachineRuntime) {\n\t\tthis.runtime = runtime;\n\t\tthis.recorder = new StateRecorder(DEFAULT_RECORD_BUFFER_FRAMES);\n\t\tthis.player = new StatePlayer(DEFAULT_LOOP_BUFFER_FRAMES);\n\n\t\t// Configure which objects should be excluded from state recording\n\t\tthis.setupExclusions();\n\t}\n\n\t/**\n\t * Setup objects to exclude from recording\n\t */\n\tprivate setupExclusions(): void {\n\t\tconst excluded: any[] = [];\n\n\t\tif (this.runtime.vm?.context?.global) {\n\t\t\tconst global = this.runtime.vm.context.global;\n\n\t\t\t// Exclude system APIs and non-serializable objects from recording\n\t\t\tif (global.random) excluded.push(global.random);\n\t\t\tif (global.screen) excluded.push(global.screen);\n\t\t\tif (global.audio) excluded.push(global.audio);\n\t\t\tif (global.keyboard) excluded.push(global.keyboard);\n\t\t\tif (global.mouse) excluded.push(global.mouse);\n\t\t\tif (global.touch) excluded.push(global.touch);\n\t\t\tif (global.gamepad) excluded.push(global.gamepad);\n\t\t\tif (global.system) excluded.push(global.system);\n\t\t\tif (global.storage) excluded.push(global.storage);\n\t\t\tif (global.host) excluded.push(global.host);\n\t\t\tif (global.session) excluded.push(global.session);\n\t\t\tif (global.memory) excluded.push(global.memory);\n\t\t}\n\n\t\tthis.recorder.setExcluded(excluded);\n\t}\n\n\t/**\n\t * Step function - call each frame\n\t */\n\tstep(): void {\n\t\tif (!this.recording) {\n\t\t\treturn;\n\t\t}\n\n\t\ttry {\n\t\t\t// Apply replay position changes and update loop if active\n\t\t\tif (this.replayPosition !== 0) {\n\t\t\t\tthis.recorder.trimTo(this.replayPosition);\n\t\t\t\tif (this.player.isLooping()) {\n\t\t\t\t\tthis.player.startLoop(this.recorder.getLength(), () => this.loopCallback());\n\t\t\t\t}\n\t\t\t\tthis.replayPosition = 0;\n\t\t\t}\n\n\t\t\t// Capture current global state snapshot for time travel\n\t\t\tif (this.runtime.vm?.context?.global) {\n\t\t\t\tthis.recorder.record(this.runtime.vm.context.global);\n\t\t\t}\n\n\t\t\tthis.sendStatus();\n\t\t} catch (err) {\n\t\t\t(this.runtime as any)?.listener?.reportError?.({ code: \"E7082\", message: \"Time machine step error\", data: { error: String(err) } });\n\t\t}\n\t}\n\n\t/**\n\t * Handle incoming messages\n\t */\n\tmessageReceived(data: TimeMachineMessage): void {\n\t\tswitch (data.command) {\n\t\t\tcase \"start_recording\":\n\t\t\t\tthis.startRecording();\n\t\t\t\tbreak;\n\n\t\t\tcase \"stop_recording\":\n\t\t\t\tthis.stopRecording();\n\t\t\t\tbreak;\n\n\t\t\tcase \"step_backward\":\n\t\t\t\tthis.stepBackward();\n\t\t\t\tbreak;\n\n\t\t\tcase \"step_forward\":\n\t\t\t\tthis.stepForward();\n\t\t\t\tbreak;\n\n\t\t\tcase \"replay_position\":\n\t\t\t\tif (data.position != null) {\n\t\t\t\t\tthis.setReplayPosition(data.position);\n\t\t\t\t}\n\t\t\t\tbreak;\n\n\t\t\tcase \"start_looping\":\n\t\t\t\tthis.startLooping();\n\t\t\t\tbreak;\n\n\t\t\tcase \"stop_looping\":\n\t\t\t\tthis.stopLooping();\n\t\t\t\tbreak;\n\t\t}\n\t}\n\n\t/**\n\t * Start recording\n\t */\n\tprivate startRecording(): void {\n\t\tif (this.recording) {\n\t\t\treturn;\n\t\t}\n\n\t\ttry {\n\t\t\tthis.recording = true;\n\t\t\tthis.recorder.clear();\n\t\t\tthis.replayPosition = 0;\n\t\t\tthis.sendStatus();\n\t\t} catch (err) {\n\t\t\tthis.recording = false;\n\t\t\t(this.runtime as any)?.listener?.reportError?.({ code: \"E7083\", message: \"Time machine recording error\", data: { error: String(err) } });\n\t\t}\n\t}\n\n\t/**\n\t * Stop recording\n\t */\n\tprivate stopRecording(): void {\n\t\tif (!this.recording) {\n\t\t\treturn;\n\t\t}\n\n\t\ttry {\n\t\t\tthis.recording = false;\n\t\t\tthis.sendStatus();\n\t\t} catch (err) {\n\t\t\t(this.runtime as any)?.listener?.reportError?.({ code: \"E7083\", message: \"Time machine recording error\", data: { error: String(err) } });\n\t\t}\n\t}\n\n\t/**\n\t * Step backward in time\n\t */\n\tprivate stepBackward(): void {\n\t\tif (this.replayPosition + 1 >= this.recorder.getLength()) {\n\t\t\treturn;\n\t\t}\n\n\t\tthis.stopLooping();\n\t\tthis.replayPosition++;\n\t\tthis.replay();\n\t\tthis.sendStatus();\n\t}\n\n\t/**\n\t * Step forward in time\n\t */\n\tprivate stepForward(): void {\n\t\tif (this.replayPosition <= 1) {\n\t\t\treturn;\n\t\t}\n\n\t\tthis.stopLooping();\n\t\tthis.replayPosition--;\n\t\tthis.replay();\n\t\tthis.sendStatus();\n\t}\n\n\t/**\n\t * Set replay position\n\t */\n\tprivate setReplayPosition(position: number): void {\n\t\t// Validate time value is finite and non-negative\n\t\tif (!isFinite(position) || position < 0) {\n\t\t\t(this.runtime as any)?.listener?.reportError?.({ code: \"E7081\", message: \"Invalid replay position\", data: { value: String(position) } });\n\t\t\treturn;\n\t\t}\n\n\t\tconst pos = Math.round(position);\n\t\tthis.replayPosition = Math.max(2, Math.min(this.recorder.getLength() - 1, pos));\n\n\t\tif (this.player.isLooping()) {\n\t\t\tthis.player.startLoop(this.replayPosition, () => this.loopCallback());\n\t\t}\n\n\t\tthis.replay();\n\t\tthis.sendStatus();\n\t}\n\n\t/**\n\t * Start looping\n\t */\n\tprivate startLooping(): void {\n\t\tif (this.recorder.getLength() === 0) {\n\t\t\treturn;\n\t\t}\n\n\t\tthis.recording = false;\n\t\tconst position = Math.max(this.replayPosition, 1);\n\t\tthis.player.startLoop(position, () => this.loopCallback());\n\t}\n\n\t/**\n\t * Stop looping\n\t */\n\tprivate stopLooping(): void {\n\t\tif (!this.player.isLooping()) {\n\t\t\treturn;\n\t\t}\n\n\t\tthis.replayPosition = this.player.stopLoop();\n\t\tthis.sendStatus();\n\t}\n\n\t/**\n\t * Replay state at current position\n\t */\n\tprivate replay(skipDraw = false): void {\n\t\tif (this.replayPosition >= this.recorder.getLength()) {\n\t\t\treturn;\n\t\t}\n\n\t\tconst snapshot = this.recorder.getState(this.replayPosition);\n\t\tif (snapshot && this.runtime.vm?.context?.global) {\n\t\t\tthis.player.restoreState(this.runtime.vm.context.global, snapshot);\n\t\t}\n\n\t\tif (!skipDraw) {\n\t\t\tthis.runtime.drawCall?.();\n\t\t}\n\t}\n\n\t/**\n\t * Loop callback\n\t */\n\tprivate loopCallback(): void {\n\t\tconst position = this.player.updateLoop();\n\t\tif (position !== null) {\n\t\t\tthis.replayPosition = position;\n\t\t\tthis.replay(true);\n\t\t\tthis.runtime.updateCall?.();\n\t\t\tthis.runtime.drawCall?.();\n\t\t}\n\t\tthis.sendStatus();\n\t}\n\n\t/**\n\t * Send status update\n\t */\n\tprivate sendStatus(): void {\n\t\tif (this.statusCallback) {\n\t\t\tthis.statusCallback({\n\t\t\t\trecording: this.recording,\n\t\t\t\tlooping: this.player.isLooping(),\n\t\t\t\tposition: this.replayPosition,\n\t\t\t\tlength: this.recorder.getLength(),\n\t\t\t\tmax: this.recorder.getMaxLength(),\n\t\t\t});\n\t\t}\n\t}\n\n\t/**\n\t * Advance the loop playback by one tick.\n\t * Call once per real game frame (from the orchestrator's watch-step hook).\n\t * No-op when not looping.\n\t */\n\tloopStep(): void {\n\t\tif (this.player.isLooping()) {\n\t\t\tthis.player.executeCallback();\n\t\t}\n\t}\n\n\t/**\n\t * Set status callback\n\t */\n\tonStatus(callback: (status: TimeMachineStatus) => void): void {\n\t\tthis.statusCallback = callback;\n\t}\n\n\t/**\n\t * Check if recording\n\t */\n\tisRecording(): boolean {\n\t\treturn this.recording;\n\t}\n\n\t/**\n\t * Check if looping\n\t */\n\tisLooping(): boolean {\n\t\treturn this.player.isLooping();\n\t}\n}\n"],"mappings":"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","names":["DEFAULT_RECORD_BUFFER_FRAMES","DEFAULT_LOOP_BUFFER_FRAMES","deepCopy","value","excluded","includes","Array","isArray","result","i","length","key","Object","hasOwn","StatePlayer","looping","loopStart","loopIndex","loopLength","loopCallback","DEFAULT_LOOP_BUFFER_FRAMES","isLooping","startLoop","position","callback","Math","max","stopLoop","updateLoop","executeCallback","restoreState","target","snapshot","key","Object","hasOwn","isProtectedKey","deepCopy","protected_keys","includes","StateRecorder","history","recordIndex","recordLength","maxLength","excluded","DEFAULT_RECORD_BUFFER_FRAMES","setExcluded","record","state","snapshot","makeStorableState","Math","min","getState","position","index","getLength","getMaxLength","clear","trimTo","histo","start","end","i","push","length","value","deepCopy","TimeMachine","runtime","recorder","player","recording","replayPosition","statusCallback","StateRecorder","DEFAULT_RECORD_BUFFER_FRAMES","StatePlayer","DEFAULT_LOOP_BUFFER_FRAMES","setupExclusions","excluded","vm","context","global","random","push","screen","audio","keyboard","mouse","touch","gamepad","system","storage","host","session","memory","setExcluded","step","trimTo","isLooping","startLoop","getLength","loopCallback","record","sendStatus","err","listener","reportError","code","message","data","error","String","messageReceived","command","startRecording","stopRecording","stepBackward","stepForward","position","setReplayPosition","startLooping","stopLooping","clear","replay","isFinite","value","pos","Math","round","max","min","stopLoop","skipDraw","snapshot","getState","restoreState","drawCall","updateLoop","updateCall","looping","length","getMaxLength","loopStep","executeCallback","onStatus","callback","isRecording"]}
|
package/dist/core/index.mjs
CHANGED
|
@@ -1,9 +1,6 @@
|
|
|
1
1
|
var __defProp = Object.defineProperty;
|
|
2
2
|
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
|
|
3
3
|
|
|
4
|
-
// src/core/machine.ts
|
|
5
|
-
import { APIErrorCode, reportRuntimeError } from "@al8b/diagnostics";
|
|
6
|
-
|
|
7
4
|
// src/constants.ts
|
|
8
5
|
var DEFAULT_RECORD_BUFFER_FRAMES = 60 * 30;
|
|
9
6
|
var DEFAULT_LOOP_BUFFER_FRAMES = 60 * 4;
|
|
@@ -287,8 +284,12 @@ var TimeMachine = class {
|
|
|
287
284
|
}
|
|
288
285
|
this.sendStatus();
|
|
289
286
|
} catch (err) {
|
|
290
|
-
|
|
291
|
-
|
|
287
|
+
this.runtime?.listener?.reportError?.({
|
|
288
|
+
code: "E7082",
|
|
289
|
+
message: "Time machine step error",
|
|
290
|
+
data: {
|
|
291
|
+
error: String(err)
|
|
292
|
+
}
|
|
292
293
|
});
|
|
293
294
|
}
|
|
294
295
|
}
|
|
@@ -336,8 +337,12 @@ var TimeMachine = class {
|
|
|
336
337
|
this.sendStatus();
|
|
337
338
|
} catch (err) {
|
|
338
339
|
this.recording = false;
|
|
339
|
-
|
|
340
|
-
|
|
340
|
+
this.runtime?.listener?.reportError?.({
|
|
341
|
+
code: "E7083",
|
|
342
|
+
message: "Time machine recording error",
|
|
343
|
+
data: {
|
|
344
|
+
error: String(err)
|
|
345
|
+
}
|
|
341
346
|
});
|
|
342
347
|
}
|
|
343
348
|
}
|
|
@@ -352,8 +357,12 @@ var TimeMachine = class {
|
|
|
352
357
|
this.recording = false;
|
|
353
358
|
this.sendStatus();
|
|
354
359
|
} catch (err) {
|
|
355
|
-
|
|
356
|
-
|
|
360
|
+
this.runtime?.listener?.reportError?.({
|
|
361
|
+
code: "E7083",
|
|
362
|
+
message: "Time machine recording error",
|
|
363
|
+
data: {
|
|
364
|
+
error: String(err)
|
|
365
|
+
}
|
|
357
366
|
});
|
|
358
367
|
}
|
|
359
368
|
}
|
|
@@ -386,8 +395,12 @@ var TimeMachine = class {
|
|
|
386
395
|
*/
|
|
387
396
|
setReplayPosition(position) {
|
|
388
397
|
if (!isFinite(position) || position < 0) {
|
|
389
|
-
|
|
390
|
-
|
|
398
|
+
this.runtime?.listener?.reportError?.({
|
|
399
|
+
code: "E7081",
|
|
400
|
+
message: "Invalid replay position",
|
|
401
|
+
data: {
|
|
402
|
+
value: String(position)
|
|
403
|
+
}
|
|
391
404
|
});
|
|
392
405
|
return;
|
|
393
406
|
}
|
package/dist/core/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/core/machine.ts","../../src/constants.ts","../../src/utils.ts","../../src/playback/player.ts","../../src/recording/recorder.ts"],"sourcesContent":["/**\n * TimeMachine - Main time machine controller\n *\n * Responsibilities:\n * - Coordinate recording and playback\n * - Handle commands\n * - Manage state\n */\n\nimport { APIErrorCode, reportRuntimeError } from \"@al8b/diagnostics\";\nimport { DEFAULT_LOOP_BUFFER_FRAMES, DEFAULT_RECORD_BUFFER_FRAMES } from \"../constants\";\nimport { StatePlayer } from \"../playback\";\nimport { StateRecorder } from \"../recording\";\nimport type { TimeMachineMessage, TimeMachineStatus } from \"../types\";\n\nexport interface TimeMachineRuntime {\n\tvm?: {\n\t\tcontext?: {\n\t\t\tglobal?: any;\n\t\t};\n\t};\n\tupdateCall?: () => void;\n\tdrawCall?: () => void;\n}\n\nexport class TimeMachine {\n\tprivate runtime: TimeMachineRuntime;\n\tprivate recorder: StateRecorder;\n\tprivate player: StatePlayer;\n\tprivate recording = false;\n\tprivate replayPosition = 0;\n\tprivate statusCallback?: (status: TimeMachineStatus) => void;\n\n\tconstructor(runtime: TimeMachineRuntime) {\n\t\tthis.runtime = runtime;\n\t\tthis.recorder = new StateRecorder(DEFAULT_RECORD_BUFFER_FRAMES);\n\t\tthis.player = new StatePlayer(DEFAULT_LOOP_BUFFER_FRAMES);\n\n\t\t// Configure which objects should be excluded from state recording\n\t\tthis.setupExclusions();\n\t}\n\n\t/**\n\t * Setup objects to exclude from recording\n\t */\n\tprivate setupExclusions(): void {\n\t\tconst excluded: any[] = [];\n\n\t\tif (this.runtime.vm?.context?.global) {\n\t\t\tconst global = this.runtime.vm.context.global;\n\n\t\t\t// Exclude system APIs and non-serializable objects from recording\n\t\t\tif (global.random) excluded.push(global.random);\n\t\t\tif (global.screen) excluded.push(global.screen);\n\t\t\tif (global.audio) excluded.push(global.audio);\n\t\t\tif (global.keyboard) excluded.push(global.keyboard);\n\t\t\tif (global.mouse) excluded.push(global.mouse);\n\t\t\tif (global.touch) excluded.push(global.touch);\n\t\t\tif (global.gamepad) excluded.push(global.gamepad);\n\t\t\tif (global.system) excluded.push(global.system);\n\t\t\tif (global.storage) excluded.push(global.storage);\n\t\t\tif (global.host) excluded.push(global.host);\n\t\t\tif (global.session) excluded.push(global.session);\n\t\t\tif (global.memory) excluded.push(global.memory);\n\t\t}\n\n\t\tthis.recorder.setExcluded(excluded);\n\t}\n\n\t/**\n\t * Step function - call each frame\n\t */\n\tstep(): void {\n\t\tif (!this.recording) {\n\t\t\treturn;\n\t\t}\n\n\t\ttry {\n\t\t\t// Apply replay position changes and update loop if active\n\t\t\tif (this.replayPosition !== 0) {\n\t\t\t\tthis.recorder.trimTo(this.replayPosition);\n\t\t\t\tif (this.player.isLooping()) {\n\t\t\t\t\tthis.player.startLoop(this.recorder.getLength(), () => this.loopCallback());\n\t\t\t\t}\n\t\t\t\tthis.replayPosition = 0;\n\t\t\t}\n\n\t\t\t// Capture current global state snapshot for time travel\n\t\t\tif (this.runtime.vm?.context?.global) {\n\t\t\t\tthis.recorder.record(this.runtime.vm.context.global);\n\t\t\t}\n\n\t\t\tthis.sendStatus();\n\t\t} catch (err) {\n\t\t\treportRuntimeError((this.runtime as any)?.listener, APIErrorCode.E7082, { error: String(err) });\n\t\t}\n\t}\n\n\t/**\n\t * Handle incoming messages\n\t */\n\tmessageReceived(data: TimeMachineMessage): void {\n\t\tswitch (data.command) {\n\t\t\tcase \"start_recording\":\n\t\t\t\tthis.startRecording();\n\t\t\t\tbreak;\n\n\t\t\tcase \"stop_recording\":\n\t\t\t\tthis.stopRecording();\n\t\t\t\tbreak;\n\n\t\t\tcase \"step_backward\":\n\t\t\t\tthis.stepBackward();\n\t\t\t\tbreak;\n\n\t\t\tcase \"step_forward\":\n\t\t\t\tthis.stepForward();\n\t\t\t\tbreak;\n\n\t\t\tcase \"replay_position\":\n\t\t\t\tif (data.position != null) {\n\t\t\t\t\tthis.setReplayPosition(data.position);\n\t\t\t\t}\n\t\t\t\tbreak;\n\n\t\t\tcase \"start_looping\":\n\t\t\t\tthis.startLooping();\n\t\t\t\tbreak;\n\n\t\t\tcase \"stop_looping\":\n\t\t\t\tthis.stopLooping();\n\t\t\t\tbreak;\n\t\t}\n\t}\n\n\t/**\n\t * Start recording\n\t */\n\tprivate startRecording(): void {\n\t\tif (this.recording) {\n\t\t\treturn;\n\t\t}\n\n\t\ttry {\n\t\t\tthis.recording = true;\n\t\t\tthis.recorder.clear();\n\t\t\tthis.replayPosition = 0;\n\t\t\tthis.sendStatus();\n\t\t} catch (err) {\n\t\t\tthis.recording = false;\n\t\t\treportRuntimeError((this.runtime as any)?.listener, APIErrorCode.E7083, { error: String(err) });\n\t\t}\n\t}\n\n\t/**\n\t * Stop recording\n\t */\n\tprivate stopRecording(): void {\n\t\tif (!this.recording) {\n\t\t\treturn;\n\t\t}\n\n\t\ttry {\n\t\t\tthis.recording = false;\n\t\t\tthis.sendStatus();\n\t\t} catch (err) {\n\t\t\treportRuntimeError((this.runtime as any)?.listener, APIErrorCode.E7083, { error: String(err) });\n\t\t}\n\t}\n\n\t/**\n\t * Step backward in time\n\t */\n\tprivate stepBackward(): void {\n\t\tif (this.replayPosition + 1 >= this.recorder.getLength()) {\n\t\t\treturn;\n\t\t}\n\n\t\tthis.stopLooping();\n\t\tthis.replayPosition++;\n\t\tthis.replay();\n\t\tthis.sendStatus();\n\t}\n\n\t/**\n\t * Step forward in time\n\t */\n\tprivate stepForward(): void {\n\t\tif (this.replayPosition <= 1) {\n\t\t\treturn;\n\t\t}\n\n\t\tthis.stopLooping();\n\t\tthis.replayPosition--;\n\t\tthis.replay();\n\t\tthis.sendStatus();\n\t}\n\n\t/**\n\t * Set replay position\n\t */\n\tprivate setReplayPosition(position: number): void {\n\t\t// Validate time value is finite and non-negative\n\t\tif (!isFinite(position) || position < 0) {\n\t\t\treportRuntimeError((this.runtime as any)?.listener, APIErrorCode.E7081, { value: String(position) });\n\t\t\treturn;\n\t\t}\n\n\t\tconst pos = Math.round(position);\n\t\tthis.replayPosition = Math.max(2, Math.min(this.recorder.getLength() - 1, pos));\n\n\t\tif (this.player.isLooping()) {\n\t\t\tthis.player.startLoop(this.replayPosition, () => this.loopCallback());\n\t\t}\n\n\t\tthis.replay();\n\t\tthis.sendStatus();\n\t}\n\n\t/**\n\t * Start looping\n\t */\n\tprivate startLooping(): void {\n\t\tif (this.recorder.getLength() === 0) {\n\t\t\treturn;\n\t\t}\n\n\t\tthis.recording = false;\n\t\tconst position = Math.max(this.replayPosition, 1);\n\t\tthis.player.startLoop(position, () => this.loopCallback());\n\t}\n\n\t/**\n\t * Stop looping\n\t */\n\tprivate stopLooping(): void {\n\t\tif (!this.player.isLooping()) {\n\t\t\treturn;\n\t\t}\n\n\t\tthis.replayPosition = this.player.stopLoop();\n\t\tthis.sendStatus();\n\t}\n\n\t/**\n\t * Replay state at current position\n\t */\n\tprivate replay(skipDraw = false): void {\n\t\tif (this.replayPosition >= this.recorder.getLength()) {\n\t\t\treturn;\n\t\t}\n\n\t\tconst snapshot = this.recorder.getState(this.replayPosition);\n\t\tif (snapshot && this.runtime.vm?.context?.global) {\n\t\t\tthis.player.restoreState(this.runtime.vm.context.global, snapshot);\n\t\t}\n\n\t\tif (!skipDraw) {\n\t\t\tthis.runtime.drawCall?.();\n\t\t}\n\t}\n\n\t/**\n\t * Loop callback\n\t */\n\tprivate loopCallback(): void {\n\t\tconst position = this.player.updateLoop();\n\t\tif (position !== null) {\n\t\t\tthis.replayPosition = position;\n\t\t\tthis.replay(true);\n\t\t\tthis.runtime.updateCall?.();\n\t\t\tthis.runtime.drawCall?.();\n\t\t}\n\t\tthis.sendStatus();\n\t}\n\n\t/**\n\t * Send status update\n\t */\n\tprivate sendStatus(): void {\n\t\tif (this.statusCallback) {\n\t\t\tthis.statusCallback({\n\t\t\t\trecording: this.recording,\n\t\t\t\tlooping: this.player.isLooping(),\n\t\t\t\tposition: this.replayPosition,\n\t\t\t\tlength: this.recorder.getLength(),\n\t\t\t\tmax: this.recorder.getMaxLength(),\n\t\t\t});\n\t\t}\n\t}\n\n\t/**\n\t * Advance the loop playback by one tick.\n\t * Call once per real game frame (from the orchestrator's watch-step hook).\n\t * No-op when not looping.\n\t */\n\tloopStep(): void {\n\t\tif (this.player.isLooping()) {\n\t\t\tthis.player.executeCallback();\n\t\t}\n\t}\n\n\t/**\n\t * Set status callback\n\t */\n\tonStatus(callback: (status: TimeMachineStatus) => void): void {\n\t\tthis.statusCallback = callback;\n\t}\n\n\t/**\n\t * Check if recording\n\t */\n\tisRecording(): boolean {\n\t\treturn this.recording;\n\t}\n\n\t/**\n\t * Check if looping\n\t */\n\tisLooping(): boolean {\n\t\treturn this.player.isLooping();\n\t}\n}\n","/** Default recording buffer: 30 seconds at 60fps (1800 frames) */\nexport const DEFAULT_RECORD_BUFFER_FRAMES = 60 * 30;\n\n/** Default loop playback buffer: 4 seconds at 60fps (240 frames) */\nexport const DEFAULT_LOOP_BUFFER_FRAMES = 60 * 4;\n","/**\n * Deep copy a value, optionally skipping excluded references.\n *\n * @param value - The value to deep copy\n * @param excluded - Optional array of object references to skip (replaced with null)\n */\nexport function deepCopy(value: any, excluded?: any[]): any {\n\tif (value == null) {\n\t\treturn value;\n\t}\n\n\tif (excluded && excluded.includes(value)) {\n\t\treturn null;\n\t}\n\n\tif (typeof value === \"string\" || typeof value === \"number\" || typeof value === \"boolean\") {\n\t\treturn value;\n\t}\n\n\tif (Array.isArray(value)) {\n\t\tconst result: any[] = [];\n\t\tfor (let i = 0; i < value.length; i++) {\n\t\t\tresult[i] = deepCopy(value[i], excluded);\n\t\t}\n\t\treturn result;\n\t}\n\n\tif (typeof value === \"object\") {\n\t\tconst result: any = {};\n\t\tfor (const key in value) {\n\t\t\tif (Object.hasOwn(value, key)) {\n\t\t\t\tresult[key] = deepCopy(value[key], excluded);\n\t\t\t}\n\t\t}\n\t\treturn result;\n\t}\n\n\t// Non-serializable types: return null when filtering, passthrough otherwise\n\treturn excluded ? null : value;\n}\n","/**\n * StatePlayer - Handles replay and loop playback\n *\n * Responsibilities:\n * - Restore state snapshots\n * - Manage loop playback\n * - Control playback position\n */\nimport { DEFAULT_LOOP_BUFFER_FRAMES } from \"../constants\";\nimport { deepCopy } from \"../utils\";\n\nimport type { StateSnapshot } from \"../types\";\n\nexport class StatePlayer {\n\tprivate looping = false;\n\tprivate loopStart = 0;\n\tprivate loopIndex = 0;\n\tprivate loopLength: number;\n\tprivate loopCallback: (() => void) | null = null;\n\n\tconstructor(loopLength = DEFAULT_LOOP_BUFFER_FRAMES) {\n\t\tthis.loopLength = loopLength;\n\t}\n\n\t/**\n\t * Check if currently looping\n\t */\n\tisLooping(): boolean {\n\t\treturn this.looping;\n\t}\n\n\t/**\n\t * Start loop playback\n\t */\n\tstartLoop(position: number, callback: () => void): void {\n\t\tthis.looping = true;\n\t\tthis.loopStart = Math.max(position, 1);\n\t\tthis.loopIndex = 0;\n\t\tthis.loopCallback = callback;\n\t}\n\n\t/**\n\t * Stop loop playback\n\t */\n\tstopLoop(): number {\n\t\tthis.looping = false;\n\t\tthis.loopCallback = null;\n\t\treturn this.loopStart;\n\t}\n\n\t/**\n\t * Update loop (call each frame)\n\t * Returns position to replay, or null if not looping\n\t */\n\tupdateLoop(): number | null {\n\t\tif (!this.looping) {\n\t\t\treturn null;\n\t\t}\n\n\t\tif (this.loopIndex === 0) {\n\t\t\tthis.loopIndex++;\n\t\t\treturn this.loopStart;\n\t\t}\n\n\t\tthis.loopIndex++;\n\t\tif (this.loopIndex > this.loopLength) {\n\t\t\tthis.loopIndex = 0;\n\t\t}\n\n\t\treturn this.loopStart - this.loopIndex;\n\t}\n\n\t/**\n\t * Execute loop callback\n\t */\n\texecuteCallback(): void {\n\t\tif (this.loopCallback) {\n\t\t\tthis.loopCallback();\n\t\t}\n\t}\n\n\t/**\n\t * Restore state to target object\n\t */\n\trestoreState(target: any, snapshot: StateSnapshot): void {\n\t\tif (!snapshot || !target) {\n\t\t\treturn;\n\t\t}\n\n\t\t// Remove all existing properties except protected system APIs\n\t\tfor (const key in target) {\n\t\t\tif (Object.hasOwn(target, key) && !this.isProtectedKey(key)) {\n\t\t\t\tdelete target[key];\n\t\t\t}\n\t\t}\n\n\t\t// Apply snapshot properties to target object via deep copy\n\t\tfor (const key in snapshot) {\n\t\t\tif (Object.hasOwn(snapshot, key)) {\n\t\t\t\ttarget[key] = deepCopy(snapshot[key]);\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Check if key should be protected from restoration\n\t */\n\tprivate isProtectedKey(key: string): boolean {\n\t\t// Prevent system APIs and runtime objects from being overwritten during restore\n\t\tconst protected_keys = [\n\t\t\t\"screen\",\n\t\t\t\"audio\",\n\t\t\t\"keyboard\",\n\t\t\t\"mouse\",\n\t\t\t\"touch\",\n\t\t\t\"gamepad\",\n\t\t\t\"system\",\n\t\t\t\"storage\",\n\t\t\t\"sprites\",\n\t\t\t\"maps\",\n\t\t\t\"sounds\",\n\t\t\t\"music\",\n\t\t\t\"assets\",\n\t\t\t\"host\",\n\t\t\t\"session\",\n\t\t\t\"memory\",\n\t\t];\n\t\treturn protected_keys.includes(key);\n\t}\n\n}\n","/**\n * StateRecorder - Records game state history\n *\n * Responsibilities:\n * - Capture state snapshots\n * - Manage circular buffer\n * - Exclude non-serializable objects\n */\nimport { DEFAULT_RECORD_BUFFER_FRAMES } from \"../constants\";\nimport { deepCopy } from \"../utils\";\n\nimport type { StateSnapshot } from \"../types\";\n\nexport class StateRecorder {\n\tprivate history: StateSnapshot[] = [];\n\tprivate recordIndex = 0;\n\tprivate recordLength = 0;\n\tprivate maxLength: number;\n\tprivate excluded: any[] = [];\n\n\tconstructor(maxLength = DEFAULT_RECORD_BUFFER_FRAMES) {\n\t\tthis.maxLength = maxLength;\n\t}\n\n\t/**\n\t * Set objects to exclude from serialization\n\t */\n\tsetExcluded(excluded: any[]): void {\n\t\tthis.excluded = excluded;\n\t}\n\n\t/**\n\t * Record a state snapshot\n\t */\n\trecord(state: any): void {\n\t\tconst snapshot = this.makeStorableState(state);\n\t\tthis.history[this.recordIndex++] = snapshot;\n\t\tthis.recordLength = Math.min(this.recordLength + 1, this.maxLength);\n\n\t\tif (this.recordIndex >= this.maxLength) {\n\t\t\tthis.recordIndex = 0;\n\t\t}\n\t}\n\n\t/**\n\t * Get state at specific position (0 = most recent)\n\t */\n\tgetState(position: number): StateSnapshot | null {\n\t\tif (position >= this.recordLength) {\n\t\t\treturn null;\n\t\t}\n\n\t\tconst index = (this.recordIndex - position - 1 + this.maxLength) % this.maxLength;\n\t\treturn this.history[index];\n\t}\n\n\t/**\n\t * Get current record length\n\t */\n\tgetLength(): number {\n\t\treturn this.recordLength;\n\t}\n\n\t/**\n\t * Get maximum record length\n\t */\n\tgetMaxLength(): number {\n\t\treturn this.maxLength;\n\t}\n\n\t/**\n\t * Clear all recorded history\n\t */\n\tclear(): void {\n\t\tthis.history = [];\n\t\tthis.recordIndex = 0;\n\t\tthis.recordLength = 0;\n\t}\n\n\t/**\n\t * Trim history to specific position\n\t */\n\ttrimTo(position: number): void {\n\t\tif (position >= this.recordLength) {\n\t\t\treturn;\n\t\t}\n\n\t\tconst histo: StateSnapshot[] = [];\n\t\tconst start = this.recordLength;\n\t\tconst end = position + 1;\n\n\t\tfor (let i = start; i >= end; i--) {\n\t\t\tconst index = (this.recordIndex - i + this.maxLength) % this.maxLength;\n\t\t\thisto.push(this.history[index]);\n\t\t}\n\n\t\tthis.history = histo;\n\t\tthis.recordIndex = this.history.length;\n\t\tthis.recordLength = this.history.length;\n\t}\n\n\tprivate makeStorableState(value: any): any {\n\t\treturn deepCopy(value, this.excluded);\n\t}\n}\n"],"mappings":"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","names":["APIErrorCode","reportRuntimeError","DEFAULT_RECORD_BUFFER_FRAMES","DEFAULT_LOOP_BUFFER_FRAMES","deepCopy","value","excluded","includes","Array","isArray","result","i","length","key","Object","hasOwn","StatePlayer","looping","loopStart","loopIndex","loopLength","loopCallback","DEFAULT_LOOP_BUFFER_FRAMES","isLooping","startLoop","position","callback","Math","max","stopLoop","updateLoop","executeCallback","restoreState","target","snapshot","key","Object","hasOwn","isProtectedKey","deepCopy","protected_keys","includes","StateRecorder","history","recordIndex","recordLength","maxLength","excluded","DEFAULT_RECORD_BUFFER_FRAMES","setExcluded","record","state","snapshot","makeStorableState","Math","min","getState","position","index","getLength","getMaxLength","clear","trimTo","histo","start","end","i","push","length","value","deepCopy","TimeMachine","runtime","recorder","player","recording","replayPosition","statusCallback","StateRecorder","DEFAULT_RECORD_BUFFER_FRAMES","StatePlayer","DEFAULT_LOOP_BUFFER_FRAMES","setupExclusions","excluded","vm","context","global","random","push","screen","audio","keyboard","mouse","touch","gamepad","system","storage","host","session","memory","setExcluded","step","trimTo","isLooping","startLoop","getLength","loopCallback","record","sendStatus","err","reportRuntimeError","listener","APIErrorCode","E7082","error","String","messageReceived","data","command","startRecording","stopRecording","stepBackward","stepForward","position","setReplayPosition","startLooping","stopLooping","clear","E7083","replay","isFinite","E7081","value","pos","Math","round","max","min","stopLoop","skipDraw","snapshot","getState","restoreState","drawCall","updateLoop","updateCall","looping","length","getMaxLength","loopStep","executeCallback","onStatus","callback","isRecording"]}
|
|
1
|
+
{"version":3,"sources":["../../src/constants.ts","../../src/utils.ts","../../src/playback/player.ts","../../src/recording/recorder.ts","../../src/core/machine.ts"],"sourcesContent":["/** Default recording buffer: 30 seconds at 60fps (1800 frames) */\nexport const DEFAULT_RECORD_BUFFER_FRAMES = 60 * 30;\n\n/** Default loop playback buffer: 4 seconds at 60fps (240 frames) */\nexport const DEFAULT_LOOP_BUFFER_FRAMES = 60 * 4;\n","/**\n * Deep copy a value, optionally skipping excluded references.\n *\n * @param value - The value to deep copy\n * @param excluded - Optional array of object references to skip (replaced with null)\n */\nexport function deepCopy(value: any, excluded?: any[]): any {\n\tif (value == null) {\n\t\treturn value;\n\t}\n\n\tif (excluded && excluded.includes(value)) {\n\t\treturn null;\n\t}\n\n\tif (typeof value === \"string\" || typeof value === \"number\" || typeof value === \"boolean\") {\n\t\treturn value;\n\t}\n\n\tif (Array.isArray(value)) {\n\t\tconst result: any[] = [];\n\t\tfor (let i = 0; i < value.length; i++) {\n\t\t\tresult[i] = deepCopy(value[i], excluded);\n\t\t}\n\t\treturn result;\n\t}\n\n\tif (typeof value === \"object\") {\n\t\tconst result: any = {};\n\t\tfor (const key in value) {\n\t\t\tif (Object.hasOwn(value, key)) {\n\t\t\t\tresult[key] = deepCopy(value[key], excluded);\n\t\t\t}\n\t\t}\n\t\treturn result;\n\t}\n\n\t// Non-serializable types: return null when filtering, passthrough otherwise\n\treturn excluded ? null : value;\n}\n","/**\n * StatePlayer - Handles replay and loop playback\n *\n * Responsibilities:\n * - Restore state snapshots\n * - Manage loop playback\n * - Control playback position\n */\nimport { DEFAULT_LOOP_BUFFER_FRAMES } from \"../constants\";\nimport { deepCopy } from \"../utils\";\n\nimport type { StateSnapshot } from \"../types\";\n\nexport class StatePlayer {\n\tprivate looping = false;\n\tprivate loopStart = 0;\n\tprivate loopIndex = 0;\n\tprivate loopLength: number;\n\tprivate loopCallback: (() => void) | null = null;\n\n\tconstructor(loopLength = DEFAULT_LOOP_BUFFER_FRAMES) {\n\t\tthis.loopLength = loopLength;\n\t}\n\n\t/**\n\t * Check if currently looping\n\t */\n\tisLooping(): boolean {\n\t\treturn this.looping;\n\t}\n\n\t/**\n\t * Start loop playback\n\t */\n\tstartLoop(position: number, callback: () => void): void {\n\t\tthis.looping = true;\n\t\tthis.loopStart = Math.max(position, 1);\n\t\tthis.loopIndex = 0;\n\t\tthis.loopCallback = callback;\n\t}\n\n\t/**\n\t * Stop loop playback\n\t */\n\tstopLoop(): number {\n\t\tthis.looping = false;\n\t\tthis.loopCallback = null;\n\t\treturn this.loopStart;\n\t}\n\n\t/**\n\t * Update loop (call each frame)\n\t * Returns position to replay, or null if not looping\n\t */\n\tupdateLoop(): number | null {\n\t\tif (!this.looping) {\n\t\t\treturn null;\n\t\t}\n\n\t\tif (this.loopIndex === 0) {\n\t\t\tthis.loopIndex++;\n\t\t\treturn this.loopStart;\n\t\t}\n\n\t\tthis.loopIndex++;\n\t\tif (this.loopIndex > this.loopLength) {\n\t\t\tthis.loopIndex = 0;\n\t\t}\n\n\t\treturn this.loopStart - this.loopIndex;\n\t}\n\n\t/**\n\t * Execute loop callback\n\t */\n\texecuteCallback(): void {\n\t\tif (this.loopCallback) {\n\t\t\tthis.loopCallback();\n\t\t}\n\t}\n\n\t/**\n\t * Restore state to target object\n\t */\n\trestoreState(target: any, snapshot: StateSnapshot): void {\n\t\tif (!snapshot || !target) {\n\t\t\treturn;\n\t\t}\n\n\t\t// Remove all existing properties except protected system APIs\n\t\tfor (const key in target) {\n\t\t\tif (Object.hasOwn(target, key) && !this.isProtectedKey(key)) {\n\t\t\t\tdelete target[key];\n\t\t\t}\n\t\t}\n\n\t\t// Apply snapshot properties to target object via deep copy\n\t\tfor (const key in snapshot) {\n\t\t\tif (Object.hasOwn(snapshot, key)) {\n\t\t\t\ttarget[key] = deepCopy(snapshot[key]);\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Check if key should be protected from restoration\n\t */\n\tprivate isProtectedKey(key: string): boolean {\n\t\t// Prevent system APIs and runtime objects from being overwritten during restore\n\t\tconst protected_keys = [\n\t\t\t\"screen\",\n\t\t\t\"audio\",\n\t\t\t\"keyboard\",\n\t\t\t\"mouse\",\n\t\t\t\"touch\",\n\t\t\t\"gamepad\",\n\t\t\t\"system\",\n\t\t\t\"storage\",\n\t\t\t\"sprites\",\n\t\t\t\"maps\",\n\t\t\t\"sounds\",\n\t\t\t\"music\",\n\t\t\t\"assets\",\n\t\t\t\"host\",\n\t\t\t\"session\",\n\t\t\t\"memory\",\n\t\t];\n\t\treturn protected_keys.includes(key);\n\t}\n\n}\n","/**\n * StateRecorder - Records game state history\n *\n * Responsibilities:\n * - Capture state snapshots\n * - Manage circular buffer\n * - Exclude non-serializable objects\n */\nimport { DEFAULT_RECORD_BUFFER_FRAMES } from \"../constants\";\nimport { deepCopy } from \"../utils\";\n\nimport type { StateSnapshot } from \"../types\";\n\nexport class StateRecorder {\n\tprivate history: StateSnapshot[] = [];\n\tprivate recordIndex = 0;\n\tprivate recordLength = 0;\n\tprivate maxLength: number;\n\tprivate excluded: any[] = [];\n\n\tconstructor(maxLength = DEFAULT_RECORD_BUFFER_FRAMES) {\n\t\tthis.maxLength = maxLength;\n\t}\n\n\t/**\n\t * Set objects to exclude from serialization\n\t */\n\tsetExcluded(excluded: any[]): void {\n\t\tthis.excluded = excluded;\n\t}\n\n\t/**\n\t * Record a state snapshot\n\t */\n\trecord(state: any): void {\n\t\tconst snapshot = this.makeStorableState(state);\n\t\tthis.history[this.recordIndex++] = snapshot;\n\t\tthis.recordLength = Math.min(this.recordLength + 1, this.maxLength);\n\n\t\tif (this.recordIndex >= this.maxLength) {\n\t\t\tthis.recordIndex = 0;\n\t\t}\n\t}\n\n\t/**\n\t * Get state at specific position (0 = most recent)\n\t */\n\tgetState(position: number): StateSnapshot | null {\n\t\tif (position >= this.recordLength) {\n\t\t\treturn null;\n\t\t}\n\n\t\tconst index = (this.recordIndex - position - 1 + this.maxLength) % this.maxLength;\n\t\treturn this.history[index];\n\t}\n\n\t/**\n\t * Get current record length\n\t */\n\tgetLength(): number {\n\t\treturn this.recordLength;\n\t}\n\n\t/**\n\t * Get maximum record length\n\t */\n\tgetMaxLength(): number {\n\t\treturn this.maxLength;\n\t}\n\n\t/**\n\t * Clear all recorded history\n\t */\n\tclear(): void {\n\t\tthis.history = [];\n\t\tthis.recordIndex = 0;\n\t\tthis.recordLength = 0;\n\t}\n\n\t/**\n\t * Trim history to specific position\n\t */\n\ttrimTo(position: number): void {\n\t\tif (position >= this.recordLength) {\n\t\t\treturn;\n\t\t}\n\n\t\tconst histo: StateSnapshot[] = [];\n\t\tconst start = this.recordLength;\n\t\tconst end = position + 1;\n\n\t\tfor (let i = start; i >= end; i--) {\n\t\t\tconst index = (this.recordIndex - i + this.maxLength) % this.maxLength;\n\t\t\thisto.push(this.history[index]);\n\t\t}\n\n\t\tthis.history = histo;\n\t\tthis.recordIndex = this.history.length;\n\t\tthis.recordLength = this.history.length;\n\t}\n\n\tprivate makeStorableState(value: any): any {\n\t\treturn deepCopy(value, this.excluded);\n\t}\n}\n","/**\n * TimeMachine - Main time machine controller\n *\n * Responsibilities:\n * - Coordinate recording and playback\n * - Handle commands\n * - Manage state\n */\n\nimport { DEFAULT_LOOP_BUFFER_FRAMES, DEFAULT_RECORD_BUFFER_FRAMES } from \"../constants\";\nimport { StatePlayer } from \"../playback\";\nimport { StateRecorder } from \"../recording\";\nimport type { TimeMachineMessage, TimeMachineStatus } from \"../types\";\n\nexport interface TimeMachineRuntime {\n\tvm?: {\n\t\tcontext?: {\n\t\t\tglobal?: any;\n\t\t};\n\t};\n\tupdateCall?: () => void;\n\tdrawCall?: () => void;\n}\n\nexport class TimeMachine {\n\tprivate runtime: TimeMachineRuntime;\n\tprivate recorder: StateRecorder;\n\tprivate player: StatePlayer;\n\tprivate recording = false;\n\tprivate replayPosition = 0;\n\tprivate statusCallback?: (status: TimeMachineStatus) => void;\n\n\tconstructor(runtime: TimeMachineRuntime) {\n\t\tthis.runtime = runtime;\n\t\tthis.recorder = new StateRecorder(DEFAULT_RECORD_BUFFER_FRAMES);\n\t\tthis.player = new StatePlayer(DEFAULT_LOOP_BUFFER_FRAMES);\n\n\t\t// Configure which objects should be excluded from state recording\n\t\tthis.setupExclusions();\n\t}\n\n\t/**\n\t * Setup objects to exclude from recording\n\t */\n\tprivate setupExclusions(): void {\n\t\tconst excluded: any[] = [];\n\n\t\tif (this.runtime.vm?.context?.global) {\n\t\t\tconst global = this.runtime.vm.context.global;\n\n\t\t\t// Exclude system APIs and non-serializable objects from recording\n\t\t\tif (global.random) excluded.push(global.random);\n\t\t\tif (global.screen) excluded.push(global.screen);\n\t\t\tif (global.audio) excluded.push(global.audio);\n\t\t\tif (global.keyboard) excluded.push(global.keyboard);\n\t\t\tif (global.mouse) excluded.push(global.mouse);\n\t\t\tif (global.touch) excluded.push(global.touch);\n\t\t\tif (global.gamepad) excluded.push(global.gamepad);\n\t\t\tif (global.system) excluded.push(global.system);\n\t\t\tif (global.storage) excluded.push(global.storage);\n\t\t\tif (global.host) excluded.push(global.host);\n\t\t\tif (global.session) excluded.push(global.session);\n\t\t\tif (global.memory) excluded.push(global.memory);\n\t\t}\n\n\t\tthis.recorder.setExcluded(excluded);\n\t}\n\n\t/**\n\t * Step function - call each frame\n\t */\n\tstep(): void {\n\t\tif (!this.recording) {\n\t\t\treturn;\n\t\t}\n\n\t\ttry {\n\t\t\t// Apply replay position changes and update loop if active\n\t\t\tif (this.replayPosition !== 0) {\n\t\t\t\tthis.recorder.trimTo(this.replayPosition);\n\t\t\t\tif (this.player.isLooping()) {\n\t\t\t\t\tthis.player.startLoop(this.recorder.getLength(), () => this.loopCallback());\n\t\t\t\t}\n\t\t\t\tthis.replayPosition = 0;\n\t\t\t}\n\n\t\t\t// Capture current global state snapshot for time travel\n\t\t\tif (this.runtime.vm?.context?.global) {\n\t\t\t\tthis.recorder.record(this.runtime.vm.context.global);\n\t\t\t}\n\n\t\t\tthis.sendStatus();\n\t\t} catch (err) {\n\t\t\t(this.runtime as any)?.listener?.reportError?.({ code: \"E7082\", message: \"Time machine step error\", data: { error: String(err) } });\n\t\t}\n\t}\n\n\t/**\n\t * Handle incoming messages\n\t */\n\tmessageReceived(data: TimeMachineMessage): void {\n\t\tswitch (data.command) {\n\t\t\tcase \"start_recording\":\n\t\t\t\tthis.startRecording();\n\t\t\t\tbreak;\n\n\t\t\tcase \"stop_recording\":\n\t\t\t\tthis.stopRecording();\n\t\t\t\tbreak;\n\n\t\t\tcase \"step_backward\":\n\t\t\t\tthis.stepBackward();\n\t\t\t\tbreak;\n\n\t\t\tcase \"step_forward\":\n\t\t\t\tthis.stepForward();\n\t\t\t\tbreak;\n\n\t\t\tcase \"replay_position\":\n\t\t\t\tif (data.position != null) {\n\t\t\t\t\tthis.setReplayPosition(data.position);\n\t\t\t\t}\n\t\t\t\tbreak;\n\n\t\t\tcase \"start_looping\":\n\t\t\t\tthis.startLooping();\n\t\t\t\tbreak;\n\n\t\t\tcase \"stop_looping\":\n\t\t\t\tthis.stopLooping();\n\t\t\t\tbreak;\n\t\t}\n\t}\n\n\t/**\n\t * Start recording\n\t */\n\tprivate startRecording(): void {\n\t\tif (this.recording) {\n\t\t\treturn;\n\t\t}\n\n\t\ttry {\n\t\t\tthis.recording = true;\n\t\t\tthis.recorder.clear();\n\t\t\tthis.replayPosition = 0;\n\t\t\tthis.sendStatus();\n\t\t} catch (err) {\n\t\t\tthis.recording = false;\n\t\t\t(this.runtime as any)?.listener?.reportError?.({ code: \"E7083\", message: \"Time machine recording error\", data: { error: String(err) } });\n\t\t}\n\t}\n\n\t/**\n\t * Stop recording\n\t */\n\tprivate stopRecording(): void {\n\t\tif (!this.recording) {\n\t\t\treturn;\n\t\t}\n\n\t\ttry {\n\t\t\tthis.recording = false;\n\t\t\tthis.sendStatus();\n\t\t} catch (err) {\n\t\t\t(this.runtime as any)?.listener?.reportError?.({ code: \"E7083\", message: \"Time machine recording error\", data: { error: String(err) } });\n\t\t}\n\t}\n\n\t/**\n\t * Step backward in time\n\t */\n\tprivate stepBackward(): void {\n\t\tif (this.replayPosition + 1 >= this.recorder.getLength()) {\n\t\t\treturn;\n\t\t}\n\n\t\tthis.stopLooping();\n\t\tthis.replayPosition++;\n\t\tthis.replay();\n\t\tthis.sendStatus();\n\t}\n\n\t/**\n\t * Step forward in time\n\t */\n\tprivate stepForward(): void {\n\t\tif (this.replayPosition <= 1) {\n\t\t\treturn;\n\t\t}\n\n\t\tthis.stopLooping();\n\t\tthis.replayPosition--;\n\t\tthis.replay();\n\t\tthis.sendStatus();\n\t}\n\n\t/**\n\t * Set replay position\n\t */\n\tprivate setReplayPosition(position: number): void {\n\t\t// Validate time value is finite and non-negative\n\t\tif (!isFinite(position) || position < 0) {\n\t\t\t(this.runtime as any)?.listener?.reportError?.({ code: \"E7081\", message: \"Invalid replay position\", data: { value: String(position) } });\n\t\t\treturn;\n\t\t}\n\n\t\tconst pos = Math.round(position);\n\t\tthis.replayPosition = Math.max(2, Math.min(this.recorder.getLength() - 1, pos));\n\n\t\tif (this.player.isLooping()) {\n\t\t\tthis.player.startLoop(this.replayPosition, () => this.loopCallback());\n\t\t}\n\n\t\tthis.replay();\n\t\tthis.sendStatus();\n\t}\n\n\t/**\n\t * Start looping\n\t */\n\tprivate startLooping(): void {\n\t\tif (this.recorder.getLength() === 0) {\n\t\t\treturn;\n\t\t}\n\n\t\tthis.recording = false;\n\t\tconst position = Math.max(this.replayPosition, 1);\n\t\tthis.player.startLoop(position, () => this.loopCallback());\n\t}\n\n\t/**\n\t * Stop looping\n\t */\n\tprivate stopLooping(): void {\n\t\tif (!this.player.isLooping()) {\n\t\t\treturn;\n\t\t}\n\n\t\tthis.replayPosition = this.player.stopLoop();\n\t\tthis.sendStatus();\n\t}\n\n\t/**\n\t * Replay state at current position\n\t */\n\tprivate replay(skipDraw = false): void {\n\t\tif (this.replayPosition >= this.recorder.getLength()) {\n\t\t\treturn;\n\t\t}\n\n\t\tconst snapshot = this.recorder.getState(this.replayPosition);\n\t\tif (snapshot && this.runtime.vm?.context?.global) {\n\t\t\tthis.player.restoreState(this.runtime.vm.context.global, snapshot);\n\t\t}\n\n\t\tif (!skipDraw) {\n\t\t\tthis.runtime.drawCall?.();\n\t\t}\n\t}\n\n\t/**\n\t * Loop callback\n\t */\n\tprivate loopCallback(): void {\n\t\tconst position = this.player.updateLoop();\n\t\tif (position !== null) {\n\t\t\tthis.replayPosition = position;\n\t\t\tthis.replay(true);\n\t\t\tthis.runtime.updateCall?.();\n\t\t\tthis.runtime.drawCall?.();\n\t\t}\n\t\tthis.sendStatus();\n\t}\n\n\t/**\n\t * Send status update\n\t */\n\tprivate sendStatus(): void {\n\t\tif (this.statusCallback) {\n\t\t\tthis.statusCallback({\n\t\t\t\trecording: this.recording,\n\t\t\t\tlooping: this.player.isLooping(),\n\t\t\t\tposition: this.replayPosition,\n\t\t\t\tlength: this.recorder.getLength(),\n\t\t\t\tmax: this.recorder.getMaxLength(),\n\t\t\t});\n\t\t}\n\t}\n\n\t/**\n\t * Advance the loop playback by one tick.\n\t * Call once per real game frame (from the orchestrator's watch-step hook).\n\t * No-op when not looping.\n\t */\n\tloopStep(): void {\n\t\tif (this.player.isLooping()) {\n\t\t\tthis.player.executeCallback();\n\t\t}\n\t}\n\n\t/**\n\t * Set status callback\n\t */\n\tonStatus(callback: (status: TimeMachineStatus) => void): void {\n\t\tthis.statusCallback = callback;\n\t}\n\n\t/**\n\t * Check if recording\n\t */\n\tisRecording(): boolean {\n\t\treturn this.recording;\n\t}\n\n\t/**\n\t * Check if looping\n\t */\n\tisLooping(): boolean {\n\t\treturn this.player.isLooping();\n\t}\n}\n"],"mappings":"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","names":["DEFAULT_RECORD_BUFFER_FRAMES","DEFAULT_LOOP_BUFFER_FRAMES","deepCopy","value","excluded","includes","Array","isArray","result","i","length","key","Object","hasOwn","StatePlayer","looping","loopStart","loopIndex","loopLength","loopCallback","DEFAULT_LOOP_BUFFER_FRAMES","isLooping","startLoop","position","callback","Math","max","stopLoop","updateLoop","executeCallback","restoreState","target","snapshot","key","Object","hasOwn","isProtectedKey","deepCopy","protected_keys","includes","StateRecorder","history","recordIndex","recordLength","maxLength","excluded","DEFAULT_RECORD_BUFFER_FRAMES","setExcluded","record","state","snapshot","makeStorableState","Math","min","getState","position","index","getLength","getMaxLength","clear","trimTo","histo","start","end","i","push","length","value","deepCopy","TimeMachine","runtime","recorder","player","recording","replayPosition","statusCallback","StateRecorder","DEFAULT_RECORD_BUFFER_FRAMES","StatePlayer","DEFAULT_LOOP_BUFFER_FRAMES","setupExclusions","excluded","vm","context","global","random","push","screen","audio","keyboard","mouse","touch","gamepad","system","storage","host","session","memory","setExcluded","step","trimTo","isLooping","startLoop","getLength","loopCallback","record","sendStatus","err","listener","reportError","code","message","data","error","String","messageReceived","command","startRecording","stopRecording","stepBackward","stepForward","position","setReplayPosition","startLooping","stopLooping","clear","replay","isFinite","value","pos","Math","round","max","min","stopLoop","skipDraw","snapshot","getState","restoreState","drawCall","updateLoop","updateCall","looping","length","getMaxLength","loopStep","executeCallback","onStatus","callback","isRecording"]}
|
package/dist/core/machine.js
CHANGED
|
@@ -24,7 +24,6 @@ __export(machine_exports, {
|
|
|
24
24
|
TimeMachine: () => TimeMachine
|
|
25
25
|
});
|
|
26
26
|
module.exports = __toCommonJS(machine_exports);
|
|
27
|
-
var import_diagnostics = require("@al8b/diagnostics");
|
|
28
27
|
|
|
29
28
|
// src/constants.ts
|
|
30
29
|
var DEFAULT_RECORD_BUFFER_FRAMES = 60 * 30;
|
|
@@ -309,8 +308,12 @@ var TimeMachine = class {
|
|
|
309
308
|
}
|
|
310
309
|
this.sendStatus();
|
|
311
310
|
} catch (err) {
|
|
312
|
-
|
|
313
|
-
|
|
311
|
+
this.runtime?.listener?.reportError?.({
|
|
312
|
+
code: "E7082",
|
|
313
|
+
message: "Time machine step error",
|
|
314
|
+
data: {
|
|
315
|
+
error: String(err)
|
|
316
|
+
}
|
|
314
317
|
});
|
|
315
318
|
}
|
|
316
319
|
}
|
|
@@ -358,8 +361,12 @@ var TimeMachine = class {
|
|
|
358
361
|
this.sendStatus();
|
|
359
362
|
} catch (err) {
|
|
360
363
|
this.recording = false;
|
|
361
|
-
|
|
362
|
-
|
|
364
|
+
this.runtime?.listener?.reportError?.({
|
|
365
|
+
code: "E7083",
|
|
366
|
+
message: "Time machine recording error",
|
|
367
|
+
data: {
|
|
368
|
+
error: String(err)
|
|
369
|
+
}
|
|
363
370
|
});
|
|
364
371
|
}
|
|
365
372
|
}
|
|
@@ -374,8 +381,12 @@ var TimeMachine = class {
|
|
|
374
381
|
this.recording = false;
|
|
375
382
|
this.sendStatus();
|
|
376
383
|
} catch (err) {
|
|
377
|
-
|
|
378
|
-
|
|
384
|
+
this.runtime?.listener?.reportError?.({
|
|
385
|
+
code: "E7083",
|
|
386
|
+
message: "Time machine recording error",
|
|
387
|
+
data: {
|
|
388
|
+
error: String(err)
|
|
389
|
+
}
|
|
379
390
|
});
|
|
380
391
|
}
|
|
381
392
|
}
|
|
@@ -408,8 +419,12 @@ var TimeMachine = class {
|
|
|
408
419
|
*/
|
|
409
420
|
setReplayPosition(position) {
|
|
410
421
|
if (!isFinite(position) || position < 0) {
|
|
411
|
-
|
|
412
|
-
|
|
422
|
+
this.runtime?.listener?.reportError?.({
|
|
423
|
+
code: "E7081",
|
|
424
|
+
message: "Invalid replay position",
|
|
425
|
+
data: {
|
|
426
|
+
value: String(position)
|
|
427
|
+
}
|
|
413
428
|
});
|
|
414
429
|
return;
|
|
415
430
|
}
|
package/dist/core/machine.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/core/machine.ts","../../src/constants.ts","../../src/utils.ts","../../src/playback/player.ts","../../src/recording/recorder.ts"],"sourcesContent":["/**\n * TimeMachine - Main time machine controller\n *\n * Responsibilities:\n * - Coordinate recording and playback\n * - Handle commands\n * - Manage state\n */\n\nimport { APIErrorCode, reportRuntimeError } from \"@al8b/diagnostics\";\nimport { DEFAULT_LOOP_BUFFER_FRAMES, DEFAULT_RECORD_BUFFER_FRAMES } from \"../constants\";\nimport { StatePlayer } from \"../playback\";\nimport { StateRecorder } from \"../recording\";\nimport type { TimeMachineMessage, TimeMachineStatus } from \"../types\";\n\nexport interface TimeMachineRuntime {\n\tvm?: {\n\t\tcontext?: {\n\t\t\tglobal?: any;\n\t\t};\n\t};\n\tupdateCall?: () => void;\n\tdrawCall?: () => void;\n}\n\nexport class TimeMachine {\n\tprivate runtime: TimeMachineRuntime;\n\tprivate recorder: StateRecorder;\n\tprivate player: StatePlayer;\n\tprivate recording = false;\n\tprivate replayPosition = 0;\n\tprivate statusCallback?: (status: TimeMachineStatus) => void;\n\n\tconstructor(runtime: TimeMachineRuntime) {\n\t\tthis.runtime = runtime;\n\t\tthis.recorder = new StateRecorder(DEFAULT_RECORD_BUFFER_FRAMES);\n\t\tthis.player = new StatePlayer(DEFAULT_LOOP_BUFFER_FRAMES);\n\n\t\t// Configure which objects should be excluded from state recording\n\t\tthis.setupExclusions();\n\t}\n\n\t/**\n\t * Setup objects to exclude from recording\n\t */\n\tprivate setupExclusions(): void {\n\t\tconst excluded: any[] = [];\n\n\t\tif (this.runtime.vm?.context?.global) {\n\t\t\tconst global = this.runtime.vm.context.global;\n\n\t\t\t// Exclude system APIs and non-serializable objects from recording\n\t\t\tif (global.random) excluded.push(global.random);\n\t\t\tif (global.screen) excluded.push(global.screen);\n\t\t\tif (global.audio) excluded.push(global.audio);\n\t\t\tif (global.keyboard) excluded.push(global.keyboard);\n\t\t\tif (global.mouse) excluded.push(global.mouse);\n\t\t\tif (global.touch) excluded.push(global.touch);\n\t\t\tif (global.gamepad) excluded.push(global.gamepad);\n\t\t\tif (global.system) excluded.push(global.system);\n\t\t\tif (global.storage) excluded.push(global.storage);\n\t\t\tif (global.host) excluded.push(global.host);\n\t\t\tif (global.session) excluded.push(global.session);\n\t\t\tif (global.memory) excluded.push(global.memory);\n\t\t}\n\n\t\tthis.recorder.setExcluded(excluded);\n\t}\n\n\t/**\n\t * Step function - call each frame\n\t */\n\tstep(): void {\n\t\tif (!this.recording) {\n\t\t\treturn;\n\t\t}\n\n\t\ttry {\n\t\t\t// Apply replay position changes and update loop if active\n\t\t\tif (this.replayPosition !== 0) {\n\t\t\t\tthis.recorder.trimTo(this.replayPosition);\n\t\t\t\tif (this.player.isLooping()) {\n\t\t\t\t\tthis.player.startLoop(this.recorder.getLength(), () => this.loopCallback());\n\t\t\t\t}\n\t\t\t\tthis.replayPosition = 0;\n\t\t\t}\n\n\t\t\t// Capture current global state snapshot for time travel\n\t\t\tif (this.runtime.vm?.context?.global) {\n\t\t\t\tthis.recorder.record(this.runtime.vm.context.global);\n\t\t\t}\n\n\t\t\tthis.sendStatus();\n\t\t} catch (err) {\n\t\t\treportRuntimeError((this.runtime as any)?.listener, APIErrorCode.E7082, { error: String(err) });\n\t\t}\n\t}\n\n\t/**\n\t * Handle incoming messages\n\t */\n\tmessageReceived(data: TimeMachineMessage): void {\n\t\tswitch (data.command) {\n\t\t\tcase \"start_recording\":\n\t\t\t\tthis.startRecording();\n\t\t\t\tbreak;\n\n\t\t\tcase \"stop_recording\":\n\t\t\t\tthis.stopRecording();\n\t\t\t\tbreak;\n\n\t\t\tcase \"step_backward\":\n\t\t\t\tthis.stepBackward();\n\t\t\t\tbreak;\n\n\t\t\tcase \"step_forward\":\n\t\t\t\tthis.stepForward();\n\t\t\t\tbreak;\n\n\t\t\tcase \"replay_position\":\n\t\t\t\tif (data.position != null) {\n\t\t\t\t\tthis.setReplayPosition(data.position);\n\t\t\t\t}\n\t\t\t\tbreak;\n\n\t\t\tcase \"start_looping\":\n\t\t\t\tthis.startLooping();\n\t\t\t\tbreak;\n\n\t\t\tcase \"stop_looping\":\n\t\t\t\tthis.stopLooping();\n\t\t\t\tbreak;\n\t\t}\n\t}\n\n\t/**\n\t * Start recording\n\t */\n\tprivate startRecording(): void {\n\t\tif (this.recording) {\n\t\t\treturn;\n\t\t}\n\n\t\ttry {\n\t\t\tthis.recording = true;\n\t\t\tthis.recorder.clear();\n\t\t\tthis.replayPosition = 0;\n\t\t\tthis.sendStatus();\n\t\t} catch (err) {\n\t\t\tthis.recording = false;\n\t\t\treportRuntimeError((this.runtime as any)?.listener, APIErrorCode.E7083, { error: String(err) });\n\t\t}\n\t}\n\n\t/**\n\t * Stop recording\n\t */\n\tprivate stopRecording(): void {\n\t\tif (!this.recording) {\n\t\t\treturn;\n\t\t}\n\n\t\ttry {\n\t\t\tthis.recording = false;\n\t\t\tthis.sendStatus();\n\t\t} catch (err) {\n\t\t\treportRuntimeError((this.runtime as any)?.listener, APIErrorCode.E7083, { error: String(err) });\n\t\t}\n\t}\n\n\t/**\n\t * Step backward in time\n\t */\n\tprivate stepBackward(): void {\n\t\tif (this.replayPosition + 1 >= this.recorder.getLength()) {\n\t\t\treturn;\n\t\t}\n\n\t\tthis.stopLooping();\n\t\tthis.replayPosition++;\n\t\tthis.replay();\n\t\tthis.sendStatus();\n\t}\n\n\t/**\n\t * Step forward in time\n\t */\n\tprivate stepForward(): void {\n\t\tif (this.replayPosition <= 1) {\n\t\t\treturn;\n\t\t}\n\n\t\tthis.stopLooping();\n\t\tthis.replayPosition--;\n\t\tthis.replay();\n\t\tthis.sendStatus();\n\t}\n\n\t/**\n\t * Set replay position\n\t */\n\tprivate setReplayPosition(position: number): void {\n\t\t// Validate time value is finite and non-negative\n\t\tif (!isFinite(position) || position < 0) {\n\t\t\treportRuntimeError((this.runtime as any)?.listener, APIErrorCode.E7081, { value: String(position) });\n\t\t\treturn;\n\t\t}\n\n\t\tconst pos = Math.round(position);\n\t\tthis.replayPosition = Math.max(2, Math.min(this.recorder.getLength() - 1, pos));\n\n\t\tif (this.player.isLooping()) {\n\t\t\tthis.player.startLoop(this.replayPosition, () => this.loopCallback());\n\t\t}\n\n\t\tthis.replay();\n\t\tthis.sendStatus();\n\t}\n\n\t/**\n\t * Start looping\n\t */\n\tprivate startLooping(): void {\n\t\tif (this.recorder.getLength() === 0) {\n\t\t\treturn;\n\t\t}\n\n\t\tthis.recording = false;\n\t\tconst position = Math.max(this.replayPosition, 1);\n\t\tthis.player.startLoop(position, () => this.loopCallback());\n\t}\n\n\t/**\n\t * Stop looping\n\t */\n\tprivate stopLooping(): void {\n\t\tif (!this.player.isLooping()) {\n\t\t\treturn;\n\t\t}\n\n\t\tthis.replayPosition = this.player.stopLoop();\n\t\tthis.sendStatus();\n\t}\n\n\t/**\n\t * Replay state at current position\n\t */\n\tprivate replay(skipDraw = false): void {\n\t\tif (this.replayPosition >= this.recorder.getLength()) {\n\t\t\treturn;\n\t\t}\n\n\t\tconst snapshot = this.recorder.getState(this.replayPosition);\n\t\tif (snapshot && this.runtime.vm?.context?.global) {\n\t\t\tthis.player.restoreState(this.runtime.vm.context.global, snapshot);\n\t\t}\n\n\t\tif (!skipDraw) {\n\t\t\tthis.runtime.drawCall?.();\n\t\t}\n\t}\n\n\t/**\n\t * Loop callback\n\t */\n\tprivate loopCallback(): void {\n\t\tconst position = this.player.updateLoop();\n\t\tif (position !== null) {\n\t\t\tthis.replayPosition = position;\n\t\t\tthis.replay(true);\n\t\t\tthis.runtime.updateCall?.();\n\t\t\tthis.runtime.drawCall?.();\n\t\t}\n\t\tthis.sendStatus();\n\t}\n\n\t/**\n\t * Send status update\n\t */\n\tprivate sendStatus(): void {\n\t\tif (this.statusCallback) {\n\t\t\tthis.statusCallback({\n\t\t\t\trecording: this.recording,\n\t\t\t\tlooping: this.player.isLooping(),\n\t\t\t\tposition: this.replayPosition,\n\t\t\t\tlength: this.recorder.getLength(),\n\t\t\t\tmax: this.recorder.getMaxLength(),\n\t\t\t});\n\t\t}\n\t}\n\n\t/**\n\t * Advance the loop playback by one tick.\n\t * Call once per real game frame (from the orchestrator's watch-step hook).\n\t * No-op when not looping.\n\t */\n\tloopStep(): void {\n\t\tif (this.player.isLooping()) {\n\t\t\tthis.player.executeCallback();\n\t\t}\n\t}\n\n\t/**\n\t * Set status callback\n\t */\n\tonStatus(callback: (status: TimeMachineStatus) => void): void {\n\t\tthis.statusCallback = callback;\n\t}\n\n\t/**\n\t * Check if recording\n\t */\n\tisRecording(): boolean {\n\t\treturn this.recording;\n\t}\n\n\t/**\n\t * Check if looping\n\t */\n\tisLooping(): boolean {\n\t\treturn this.player.isLooping();\n\t}\n}\n","/** Default recording buffer: 30 seconds at 60fps (1800 frames) */\nexport const DEFAULT_RECORD_BUFFER_FRAMES = 60 * 30;\n\n/** Default loop playback buffer: 4 seconds at 60fps (240 frames) */\nexport const DEFAULT_LOOP_BUFFER_FRAMES = 60 * 4;\n","/**\n * Deep copy a value, optionally skipping excluded references.\n *\n * @param value - The value to deep copy\n * @param excluded - Optional array of object references to skip (replaced with null)\n */\nexport function deepCopy(value: any, excluded?: any[]): any {\n\tif (value == null) {\n\t\treturn value;\n\t}\n\n\tif (excluded && excluded.includes(value)) {\n\t\treturn null;\n\t}\n\n\tif (typeof value === \"string\" || typeof value === \"number\" || typeof value === \"boolean\") {\n\t\treturn value;\n\t}\n\n\tif (Array.isArray(value)) {\n\t\tconst result: any[] = [];\n\t\tfor (let i = 0; i < value.length; i++) {\n\t\t\tresult[i] = deepCopy(value[i], excluded);\n\t\t}\n\t\treturn result;\n\t}\n\n\tif (typeof value === \"object\") {\n\t\tconst result: any = {};\n\t\tfor (const key in value) {\n\t\t\tif (Object.hasOwn(value, key)) {\n\t\t\t\tresult[key] = deepCopy(value[key], excluded);\n\t\t\t}\n\t\t}\n\t\treturn result;\n\t}\n\n\t// Non-serializable types: return null when filtering, passthrough otherwise\n\treturn excluded ? null : value;\n}\n","/**\n * StatePlayer - Handles replay and loop playback\n *\n * Responsibilities:\n * - Restore state snapshots\n * - Manage loop playback\n * - Control playback position\n */\nimport { DEFAULT_LOOP_BUFFER_FRAMES } from \"../constants\";\nimport { deepCopy } from \"../utils\";\n\nimport type { StateSnapshot } from \"../types\";\n\nexport class StatePlayer {\n\tprivate looping = false;\n\tprivate loopStart = 0;\n\tprivate loopIndex = 0;\n\tprivate loopLength: number;\n\tprivate loopCallback: (() => void) | null = null;\n\n\tconstructor(loopLength = DEFAULT_LOOP_BUFFER_FRAMES) {\n\t\tthis.loopLength = loopLength;\n\t}\n\n\t/**\n\t * Check if currently looping\n\t */\n\tisLooping(): boolean {\n\t\treturn this.looping;\n\t}\n\n\t/**\n\t * Start loop playback\n\t */\n\tstartLoop(position: number, callback: () => void): void {\n\t\tthis.looping = true;\n\t\tthis.loopStart = Math.max(position, 1);\n\t\tthis.loopIndex = 0;\n\t\tthis.loopCallback = callback;\n\t}\n\n\t/**\n\t * Stop loop playback\n\t */\n\tstopLoop(): number {\n\t\tthis.looping = false;\n\t\tthis.loopCallback = null;\n\t\treturn this.loopStart;\n\t}\n\n\t/**\n\t * Update loop (call each frame)\n\t * Returns position to replay, or null if not looping\n\t */\n\tupdateLoop(): number | null {\n\t\tif (!this.looping) {\n\t\t\treturn null;\n\t\t}\n\n\t\tif (this.loopIndex === 0) {\n\t\t\tthis.loopIndex++;\n\t\t\treturn this.loopStart;\n\t\t}\n\n\t\tthis.loopIndex++;\n\t\tif (this.loopIndex > this.loopLength) {\n\t\t\tthis.loopIndex = 0;\n\t\t}\n\n\t\treturn this.loopStart - this.loopIndex;\n\t}\n\n\t/**\n\t * Execute loop callback\n\t */\n\texecuteCallback(): void {\n\t\tif (this.loopCallback) {\n\t\t\tthis.loopCallback();\n\t\t}\n\t}\n\n\t/**\n\t * Restore state to target object\n\t */\n\trestoreState(target: any, snapshot: StateSnapshot): void {\n\t\tif (!snapshot || !target) {\n\t\t\treturn;\n\t\t}\n\n\t\t// Remove all existing properties except protected system APIs\n\t\tfor (const key in target) {\n\t\t\tif (Object.hasOwn(target, key) && !this.isProtectedKey(key)) {\n\t\t\t\tdelete target[key];\n\t\t\t}\n\t\t}\n\n\t\t// Apply snapshot properties to target object via deep copy\n\t\tfor (const key in snapshot) {\n\t\t\tif (Object.hasOwn(snapshot, key)) {\n\t\t\t\ttarget[key] = deepCopy(snapshot[key]);\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Check if key should be protected from restoration\n\t */\n\tprivate isProtectedKey(key: string): boolean {\n\t\t// Prevent system APIs and runtime objects from being overwritten during restore\n\t\tconst protected_keys = [\n\t\t\t\"screen\",\n\t\t\t\"audio\",\n\t\t\t\"keyboard\",\n\t\t\t\"mouse\",\n\t\t\t\"touch\",\n\t\t\t\"gamepad\",\n\t\t\t\"system\",\n\t\t\t\"storage\",\n\t\t\t\"sprites\",\n\t\t\t\"maps\",\n\t\t\t\"sounds\",\n\t\t\t\"music\",\n\t\t\t\"assets\",\n\t\t\t\"host\",\n\t\t\t\"session\",\n\t\t\t\"memory\",\n\t\t];\n\t\treturn protected_keys.includes(key);\n\t}\n\n}\n","/**\n * StateRecorder - Records game state history\n *\n * Responsibilities:\n * - Capture state snapshots\n * - Manage circular buffer\n * - Exclude non-serializable objects\n */\nimport { DEFAULT_RECORD_BUFFER_FRAMES } from \"../constants\";\nimport { deepCopy } from \"../utils\";\n\nimport type { StateSnapshot } from \"../types\";\n\nexport class StateRecorder {\n\tprivate history: StateSnapshot[] = [];\n\tprivate recordIndex = 0;\n\tprivate recordLength = 0;\n\tprivate maxLength: number;\n\tprivate excluded: any[] = [];\n\n\tconstructor(maxLength = DEFAULT_RECORD_BUFFER_FRAMES) {\n\t\tthis.maxLength = maxLength;\n\t}\n\n\t/**\n\t * Set objects to exclude from serialization\n\t */\n\tsetExcluded(excluded: any[]): void {\n\t\tthis.excluded = excluded;\n\t}\n\n\t/**\n\t * Record a state snapshot\n\t */\n\trecord(state: any): void {\n\t\tconst snapshot = this.makeStorableState(state);\n\t\tthis.history[this.recordIndex++] = snapshot;\n\t\tthis.recordLength = Math.min(this.recordLength + 1, this.maxLength);\n\n\t\tif (this.recordIndex >= this.maxLength) {\n\t\t\tthis.recordIndex = 0;\n\t\t}\n\t}\n\n\t/**\n\t * Get state at specific position (0 = most recent)\n\t */\n\tgetState(position: number): StateSnapshot | null {\n\t\tif (position >= this.recordLength) {\n\t\t\treturn null;\n\t\t}\n\n\t\tconst index = (this.recordIndex - position - 1 + this.maxLength) % this.maxLength;\n\t\treturn this.history[index];\n\t}\n\n\t/**\n\t * Get current record length\n\t */\n\tgetLength(): number {\n\t\treturn this.recordLength;\n\t}\n\n\t/**\n\t * Get maximum record length\n\t */\n\tgetMaxLength(): number {\n\t\treturn this.maxLength;\n\t}\n\n\t/**\n\t * Clear all recorded history\n\t */\n\tclear(): void {\n\t\tthis.history = [];\n\t\tthis.recordIndex = 0;\n\t\tthis.recordLength = 0;\n\t}\n\n\t/**\n\t * Trim history to specific position\n\t */\n\ttrimTo(position: number): void {\n\t\tif (position >= this.recordLength) {\n\t\t\treturn;\n\t\t}\n\n\t\tconst histo: StateSnapshot[] = [];\n\t\tconst start = this.recordLength;\n\t\tconst end = position + 1;\n\n\t\tfor (let i = start; i >= end; i--) {\n\t\t\tconst index = (this.recordIndex - i + this.maxLength) % this.maxLength;\n\t\t\thisto.push(this.history[index]);\n\t\t}\n\n\t\tthis.history = histo;\n\t\tthis.recordIndex = this.history.length;\n\t\tthis.recordLength = this.history.length;\n\t}\n\n\tprivate makeStorableState(value: any): any {\n\t\treturn deepCopy(value, this.excluded);\n\t}\n}\n"],"mappings":"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","names":["DEFAULT_RECORD_BUFFER_FRAMES","DEFAULT_LOOP_BUFFER_FRAMES","deepCopy","value","excluded","includes","Array","isArray","result","i","length","key","Object","hasOwn","StatePlayer","looping","loopStart","loopIndex","loopLength","loopCallback","DEFAULT_LOOP_BUFFER_FRAMES","isLooping","startLoop","position","callback","Math","max","stopLoop","updateLoop","executeCallback","restoreState","target","snapshot","key","Object","hasOwn","isProtectedKey","deepCopy","protected_keys","includes","StateRecorder","history","recordIndex","recordLength","maxLength","excluded","DEFAULT_RECORD_BUFFER_FRAMES","setExcluded","record","state","snapshot","makeStorableState","Math","min","getState","position","index","getLength","getMaxLength","clear","trimTo","histo","start","end","i","push","length","value","deepCopy","TimeMachine","runtime","recorder","player","recording","replayPosition","statusCallback","StateRecorder","DEFAULT_RECORD_BUFFER_FRAMES","StatePlayer","DEFAULT_LOOP_BUFFER_FRAMES","setupExclusions","excluded","vm","context","global","random","push","screen","audio","keyboard","mouse","touch","gamepad","system","storage","host","session","memory","setExcluded","step","trimTo","isLooping","startLoop","getLength","loopCallback","record","sendStatus","err","reportRuntimeError","listener","APIErrorCode","E7082","error","String","messageReceived","data","command","startRecording","stopRecording","stepBackward","stepForward","position","setReplayPosition","startLooping","stopLooping","clear","E7083","replay","isFinite","E7081","value","pos","Math","round","max","min","stopLoop","skipDraw","snapshot","getState","restoreState","drawCall","updateLoop","updateCall","looping","length","getMaxLength","loopStep","executeCallback","onStatus","callback","isRecording"]}
|
|
1
|
+
{"version":3,"sources":["../../src/core/machine.ts","../../src/constants.ts","../../src/utils.ts","../../src/playback/player.ts","../../src/recording/recorder.ts"],"sourcesContent":["/**\n * TimeMachine - Main time machine controller\n *\n * Responsibilities:\n * - Coordinate recording and playback\n * - Handle commands\n * - Manage state\n */\n\nimport { DEFAULT_LOOP_BUFFER_FRAMES, DEFAULT_RECORD_BUFFER_FRAMES } from \"../constants\";\nimport { StatePlayer } from \"../playback\";\nimport { StateRecorder } from \"../recording\";\nimport type { TimeMachineMessage, TimeMachineStatus } from \"../types\";\n\nexport interface TimeMachineRuntime {\n\tvm?: {\n\t\tcontext?: {\n\t\t\tglobal?: any;\n\t\t};\n\t};\n\tupdateCall?: () => void;\n\tdrawCall?: () => void;\n}\n\nexport class TimeMachine {\n\tprivate runtime: TimeMachineRuntime;\n\tprivate recorder: StateRecorder;\n\tprivate player: StatePlayer;\n\tprivate recording = false;\n\tprivate replayPosition = 0;\n\tprivate statusCallback?: (status: TimeMachineStatus) => void;\n\n\tconstructor(runtime: TimeMachineRuntime) {\n\t\tthis.runtime = runtime;\n\t\tthis.recorder = new StateRecorder(DEFAULT_RECORD_BUFFER_FRAMES);\n\t\tthis.player = new StatePlayer(DEFAULT_LOOP_BUFFER_FRAMES);\n\n\t\t// Configure which objects should be excluded from state recording\n\t\tthis.setupExclusions();\n\t}\n\n\t/**\n\t * Setup objects to exclude from recording\n\t */\n\tprivate setupExclusions(): void {\n\t\tconst excluded: any[] = [];\n\n\t\tif (this.runtime.vm?.context?.global) {\n\t\t\tconst global = this.runtime.vm.context.global;\n\n\t\t\t// Exclude system APIs and non-serializable objects from recording\n\t\t\tif (global.random) excluded.push(global.random);\n\t\t\tif (global.screen) excluded.push(global.screen);\n\t\t\tif (global.audio) excluded.push(global.audio);\n\t\t\tif (global.keyboard) excluded.push(global.keyboard);\n\t\t\tif (global.mouse) excluded.push(global.mouse);\n\t\t\tif (global.touch) excluded.push(global.touch);\n\t\t\tif (global.gamepad) excluded.push(global.gamepad);\n\t\t\tif (global.system) excluded.push(global.system);\n\t\t\tif (global.storage) excluded.push(global.storage);\n\t\t\tif (global.host) excluded.push(global.host);\n\t\t\tif (global.session) excluded.push(global.session);\n\t\t\tif (global.memory) excluded.push(global.memory);\n\t\t}\n\n\t\tthis.recorder.setExcluded(excluded);\n\t}\n\n\t/**\n\t * Step function - call each frame\n\t */\n\tstep(): void {\n\t\tif (!this.recording) {\n\t\t\treturn;\n\t\t}\n\n\t\ttry {\n\t\t\t// Apply replay position changes and update loop if active\n\t\t\tif (this.replayPosition !== 0) {\n\t\t\t\tthis.recorder.trimTo(this.replayPosition);\n\t\t\t\tif (this.player.isLooping()) {\n\t\t\t\t\tthis.player.startLoop(this.recorder.getLength(), () => this.loopCallback());\n\t\t\t\t}\n\t\t\t\tthis.replayPosition = 0;\n\t\t\t}\n\n\t\t\t// Capture current global state snapshot for time travel\n\t\t\tif (this.runtime.vm?.context?.global) {\n\t\t\t\tthis.recorder.record(this.runtime.vm.context.global);\n\t\t\t}\n\n\t\t\tthis.sendStatus();\n\t\t} catch (err) {\n\t\t\t(this.runtime as any)?.listener?.reportError?.({ code: \"E7082\", message: \"Time machine step error\", data: { error: String(err) } });\n\t\t}\n\t}\n\n\t/**\n\t * Handle incoming messages\n\t */\n\tmessageReceived(data: TimeMachineMessage): void {\n\t\tswitch (data.command) {\n\t\t\tcase \"start_recording\":\n\t\t\t\tthis.startRecording();\n\t\t\t\tbreak;\n\n\t\t\tcase \"stop_recording\":\n\t\t\t\tthis.stopRecording();\n\t\t\t\tbreak;\n\n\t\t\tcase \"step_backward\":\n\t\t\t\tthis.stepBackward();\n\t\t\t\tbreak;\n\n\t\t\tcase \"step_forward\":\n\t\t\t\tthis.stepForward();\n\t\t\t\tbreak;\n\n\t\t\tcase \"replay_position\":\n\t\t\t\tif (data.position != null) {\n\t\t\t\t\tthis.setReplayPosition(data.position);\n\t\t\t\t}\n\t\t\t\tbreak;\n\n\t\t\tcase \"start_looping\":\n\t\t\t\tthis.startLooping();\n\t\t\t\tbreak;\n\n\t\t\tcase \"stop_looping\":\n\t\t\t\tthis.stopLooping();\n\t\t\t\tbreak;\n\t\t}\n\t}\n\n\t/**\n\t * Start recording\n\t */\n\tprivate startRecording(): void {\n\t\tif (this.recording) {\n\t\t\treturn;\n\t\t}\n\n\t\ttry {\n\t\t\tthis.recording = true;\n\t\t\tthis.recorder.clear();\n\t\t\tthis.replayPosition = 0;\n\t\t\tthis.sendStatus();\n\t\t} catch (err) {\n\t\t\tthis.recording = false;\n\t\t\t(this.runtime as any)?.listener?.reportError?.({ code: \"E7083\", message: \"Time machine recording error\", data: { error: String(err) } });\n\t\t}\n\t}\n\n\t/**\n\t * Stop recording\n\t */\n\tprivate stopRecording(): void {\n\t\tif (!this.recording) {\n\t\t\treturn;\n\t\t}\n\n\t\ttry {\n\t\t\tthis.recording = false;\n\t\t\tthis.sendStatus();\n\t\t} catch (err) {\n\t\t\t(this.runtime as any)?.listener?.reportError?.({ code: \"E7083\", message: \"Time machine recording error\", data: { error: String(err) } });\n\t\t}\n\t}\n\n\t/**\n\t * Step backward in time\n\t */\n\tprivate stepBackward(): void {\n\t\tif (this.replayPosition + 1 >= this.recorder.getLength()) {\n\t\t\treturn;\n\t\t}\n\n\t\tthis.stopLooping();\n\t\tthis.replayPosition++;\n\t\tthis.replay();\n\t\tthis.sendStatus();\n\t}\n\n\t/**\n\t * Step forward in time\n\t */\n\tprivate stepForward(): void {\n\t\tif (this.replayPosition <= 1) {\n\t\t\treturn;\n\t\t}\n\n\t\tthis.stopLooping();\n\t\tthis.replayPosition--;\n\t\tthis.replay();\n\t\tthis.sendStatus();\n\t}\n\n\t/**\n\t * Set replay position\n\t */\n\tprivate setReplayPosition(position: number): void {\n\t\t// Validate time value is finite and non-negative\n\t\tif (!isFinite(position) || position < 0) {\n\t\t\t(this.runtime as any)?.listener?.reportError?.({ code: \"E7081\", message: \"Invalid replay position\", data: { value: String(position) } });\n\t\t\treturn;\n\t\t}\n\n\t\tconst pos = Math.round(position);\n\t\tthis.replayPosition = Math.max(2, Math.min(this.recorder.getLength() - 1, pos));\n\n\t\tif (this.player.isLooping()) {\n\t\t\tthis.player.startLoop(this.replayPosition, () => this.loopCallback());\n\t\t}\n\n\t\tthis.replay();\n\t\tthis.sendStatus();\n\t}\n\n\t/**\n\t * Start looping\n\t */\n\tprivate startLooping(): void {\n\t\tif (this.recorder.getLength() === 0) {\n\t\t\treturn;\n\t\t}\n\n\t\tthis.recording = false;\n\t\tconst position = Math.max(this.replayPosition, 1);\n\t\tthis.player.startLoop(position, () => this.loopCallback());\n\t}\n\n\t/**\n\t * Stop looping\n\t */\n\tprivate stopLooping(): void {\n\t\tif (!this.player.isLooping()) {\n\t\t\treturn;\n\t\t}\n\n\t\tthis.replayPosition = this.player.stopLoop();\n\t\tthis.sendStatus();\n\t}\n\n\t/**\n\t * Replay state at current position\n\t */\n\tprivate replay(skipDraw = false): void {\n\t\tif (this.replayPosition >= this.recorder.getLength()) {\n\t\t\treturn;\n\t\t}\n\n\t\tconst snapshot = this.recorder.getState(this.replayPosition);\n\t\tif (snapshot && this.runtime.vm?.context?.global) {\n\t\t\tthis.player.restoreState(this.runtime.vm.context.global, snapshot);\n\t\t}\n\n\t\tif (!skipDraw) {\n\t\t\tthis.runtime.drawCall?.();\n\t\t}\n\t}\n\n\t/**\n\t * Loop callback\n\t */\n\tprivate loopCallback(): void {\n\t\tconst position = this.player.updateLoop();\n\t\tif (position !== null) {\n\t\t\tthis.replayPosition = position;\n\t\t\tthis.replay(true);\n\t\t\tthis.runtime.updateCall?.();\n\t\t\tthis.runtime.drawCall?.();\n\t\t}\n\t\tthis.sendStatus();\n\t}\n\n\t/**\n\t * Send status update\n\t */\n\tprivate sendStatus(): void {\n\t\tif (this.statusCallback) {\n\t\t\tthis.statusCallback({\n\t\t\t\trecording: this.recording,\n\t\t\t\tlooping: this.player.isLooping(),\n\t\t\t\tposition: this.replayPosition,\n\t\t\t\tlength: this.recorder.getLength(),\n\t\t\t\tmax: this.recorder.getMaxLength(),\n\t\t\t});\n\t\t}\n\t}\n\n\t/**\n\t * Advance the loop playback by one tick.\n\t * Call once per real game frame (from the orchestrator's watch-step hook).\n\t * No-op when not looping.\n\t */\n\tloopStep(): void {\n\t\tif (this.player.isLooping()) {\n\t\t\tthis.player.executeCallback();\n\t\t}\n\t}\n\n\t/**\n\t * Set status callback\n\t */\n\tonStatus(callback: (status: TimeMachineStatus) => void): void {\n\t\tthis.statusCallback = callback;\n\t}\n\n\t/**\n\t * Check if recording\n\t */\n\tisRecording(): boolean {\n\t\treturn this.recording;\n\t}\n\n\t/**\n\t * Check if looping\n\t */\n\tisLooping(): boolean {\n\t\treturn this.player.isLooping();\n\t}\n}\n","/** Default recording buffer: 30 seconds at 60fps (1800 frames) */\nexport const DEFAULT_RECORD_BUFFER_FRAMES = 60 * 30;\n\n/** Default loop playback buffer: 4 seconds at 60fps (240 frames) */\nexport const DEFAULT_LOOP_BUFFER_FRAMES = 60 * 4;\n","/**\n * Deep copy a value, optionally skipping excluded references.\n *\n * @param value - The value to deep copy\n * @param excluded - Optional array of object references to skip (replaced with null)\n */\nexport function deepCopy(value: any, excluded?: any[]): any {\n\tif (value == null) {\n\t\treturn value;\n\t}\n\n\tif (excluded && excluded.includes(value)) {\n\t\treturn null;\n\t}\n\n\tif (typeof value === \"string\" || typeof value === \"number\" || typeof value === \"boolean\") {\n\t\treturn value;\n\t}\n\n\tif (Array.isArray(value)) {\n\t\tconst result: any[] = [];\n\t\tfor (let i = 0; i < value.length; i++) {\n\t\t\tresult[i] = deepCopy(value[i], excluded);\n\t\t}\n\t\treturn result;\n\t}\n\n\tif (typeof value === \"object\") {\n\t\tconst result: any = {};\n\t\tfor (const key in value) {\n\t\t\tif (Object.hasOwn(value, key)) {\n\t\t\t\tresult[key] = deepCopy(value[key], excluded);\n\t\t\t}\n\t\t}\n\t\treturn result;\n\t}\n\n\t// Non-serializable types: return null when filtering, passthrough otherwise\n\treturn excluded ? null : value;\n}\n","/**\n * StatePlayer - Handles replay and loop playback\n *\n * Responsibilities:\n * - Restore state snapshots\n * - Manage loop playback\n * - Control playback position\n */\nimport { DEFAULT_LOOP_BUFFER_FRAMES } from \"../constants\";\nimport { deepCopy } from \"../utils\";\n\nimport type { StateSnapshot } from \"../types\";\n\nexport class StatePlayer {\n\tprivate looping = false;\n\tprivate loopStart = 0;\n\tprivate loopIndex = 0;\n\tprivate loopLength: number;\n\tprivate loopCallback: (() => void) | null = null;\n\n\tconstructor(loopLength = DEFAULT_LOOP_BUFFER_FRAMES) {\n\t\tthis.loopLength = loopLength;\n\t}\n\n\t/**\n\t * Check if currently looping\n\t */\n\tisLooping(): boolean {\n\t\treturn this.looping;\n\t}\n\n\t/**\n\t * Start loop playback\n\t */\n\tstartLoop(position: number, callback: () => void): void {\n\t\tthis.looping = true;\n\t\tthis.loopStart = Math.max(position, 1);\n\t\tthis.loopIndex = 0;\n\t\tthis.loopCallback = callback;\n\t}\n\n\t/**\n\t * Stop loop playback\n\t */\n\tstopLoop(): number {\n\t\tthis.looping = false;\n\t\tthis.loopCallback = null;\n\t\treturn this.loopStart;\n\t}\n\n\t/**\n\t * Update loop (call each frame)\n\t * Returns position to replay, or null if not looping\n\t */\n\tupdateLoop(): number | null {\n\t\tif (!this.looping) {\n\t\t\treturn null;\n\t\t}\n\n\t\tif (this.loopIndex === 0) {\n\t\t\tthis.loopIndex++;\n\t\t\treturn this.loopStart;\n\t\t}\n\n\t\tthis.loopIndex++;\n\t\tif (this.loopIndex > this.loopLength) {\n\t\t\tthis.loopIndex = 0;\n\t\t}\n\n\t\treturn this.loopStart - this.loopIndex;\n\t}\n\n\t/**\n\t * Execute loop callback\n\t */\n\texecuteCallback(): void {\n\t\tif (this.loopCallback) {\n\t\t\tthis.loopCallback();\n\t\t}\n\t}\n\n\t/**\n\t * Restore state to target object\n\t */\n\trestoreState(target: any, snapshot: StateSnapshot): void {\n\t\tif (!snapshot || !target) {\n\t\t\treturn;\n\t\t}\n\n\t\t// Remove all existing properties except protected system APIs\n\t\tfor (const key in target) {\n\t\t\tif (Object.hasOwn(target, key) && !this.isProtectedKey(key)) {\n\t\t\t\tdelete target[key];\n\t\t\t}\n\t\t}\n\n\t\t// Apply snapshot properties to target object via deep copy\n\t\tfor (const key in snapshot) {\n\t\t\tif (Object.hasOwn(snapshot, key)) {\n\t\t\t\ttarget[key] = deepCopy(snapshot[key]);\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Check if key should be protected from restoration\n\t */\n\tprivate isProtectedKey(key: string): boolean {\n\t\t// Prevent system APIs and runtime objects from being overwritten during restore\n\t\tconst protected_keys = [\n\t\t\t\"screen\",\n\t\t\t\"audio\",\n\t\t\t\"keyboard\",\n\t\t\t\"mouse\",\n\t\t\t\"touch\",\n\t\t\t\"gamepad\",\n\t\t\t\"system\",\n\t\t\t\"storage\",\n\t\t\t\"sprites\",\n\t\t\t\"maps\",\n\t\t\t\"sounds\",\n\t\t\t\"music\",\n\t\t\t\"assets\",\n\t\t\t\"host\",\n\t\t\t\"session\",\n\t\t\t\"memory\",\n\t\t];\n\t\treturn protected_keys.includes(key);\n\t}\n\n}\n","/**\n * StateRecorder - Records game state history\n *\n * Responsibilities:\n * - Capture state snapshots\n * - Manage circular buffer\n * - Exclude non-serializable objects\n */\nimport { DEFAULT_RECORD_BUFFER_FRAMES } from \"../constants\";\nimport { deepCopy } from \"../utils\";\n\nimport type { StateSnapshot } from \"../types\";\n\nexport class StateRecorder {\n\tprivate history: StateSnapshot[] = [];\n\tprivate recordIndex = 0;\n\tprivate recordLength = 0;\n\tprivate maxLength: number;\n\tprivate excluded: any[] = [];\n\n\tconstructor(maxLength = DEFAULT_RECORD_BUFFER_FRAMES) {\n\t\tthis.maxLength = maxLength;\n\t}\n\n\t/**\n\t * Set objects to exclude from serialization\n\t */\n\tsetExcluded(excluded: any[]): void {\n\t\tthis.excluded = excluded;\n\t}\n\n\t/**\n\t * Record a state snapshot\n\t */\n\trecord(state: any): void {\n\t\tconst snapshot = this.makeStorableState(state);\n\t\tthis.history[this.recordIndex++] = snapshot;\n\t\tthis.recordLength = Math.min(this.recordLength + 1, this.maxLength);\n\n\t\tif (this.recordIndex >= this.maxLength) {\n\t\t\tthis.recordIndex = 0;\n\t\t}\n\t}\n\n\t/**\n\t * Get state at specific position (0 = most recent)\n\t */\n\tgetState(position: number): StateSnapshot | null {\n\t\tif (position >= this.recordLength) {\n\t\t\treturn null;\n\t\t}\n\n\t\tconst index = (this.recordIndex - position - 1 + this.maxLength) % this.maxLength;\n\t\treturn this.history[index];\n\t}\n\n\t/**\n\t * Get current record length\n\t */\n\tgetLength(): number {\n\t\treturn this.recordLength;\n\t}\n\n\t/**\n\t * Get maximum record length\n\t */\n\tgetMaxLength(): number {\n\t\treturn this.maxLength;\n\t}\n\n\t/**\n\t * Clear all recorded history\n\t */\n\tclear(): void {\n\t\tthis.history = [];\n\t\tthis.recordIndex = 0;\n\t\tthis.recordLength = 0;\n\t}\n\n\t/**\n\t * Trim history to specific position\n\t */\n\ttrimTo(position: number): void {\n\t\tif (position >= this.recordLength) {\n\t\t\treturn;\n\t\t}\n\n\t\tconst histo: StateSnapshot[] = [];\n\t\tconst start = this.recordLength;\n\t\tconst end = position + 1;\n\n\t\tfor (let i = start; i >= end; i--) {\n\t\t\tconst index = (this.recordIndex - i + this.maxLength) % this.maxLength;\n\t\t\thisto.push(this.history[index]);\n\t\t}\n\n\t\tthis.history = histo;\n\t\tthis.recordIndex = this.history.length;\n\t\tthis.recordLength = this.history.length;\n\t}\n\n\tprivate makeStorableState(value: any): any {\n\t\treturn deepCopy(value, this.excluded);\n\t}\n}\n"],"mappings":"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","names":["DEFAULT_RECORD_BUFFER_FRAMES","DEFAULT_LOOP_BUFFER_FRAMES","deepCopy","value","excluded","includes","Array","isArray","result","i","length","key","Object","hasOwn","StatePlayer","looping","loopStart","loopIndex","loopLength","loopCallback","DEFAULT_LOOP_BUFFER_FRAMES","isLooping","startLoop","position","callback","Math","max","stopLoop","updateLoop","executeCallback","restoreState","target","snapshot","key","Object","hasOwn","isProtectedKey","deepCopy","protected_keys","includes","StateRecorder","history","recordIndex","recordLength","maxLength","excluded","DEFAULT_RECORD_BUFFER_FRAMES","setExcluded","record","state","snapshot","makeStorableState","Math","min","getState","position","index","getLength","getMaxLength","clear","trimTo","histo","start","end","i","push","length","value","deepCopy","TimeMachine","runtime","recorder","player","recording","replayPosition","statusCallback","StateRecorder","DEFAULT_RECORD_BUFFER_FRAMES","StatePlayer","DEFAULT_LOOP_BUFFER_FRAMES","setupExclusions","excluded","vm","context","global","random","push","screen","audio","keyboard","mouse","touch","gamepad","system","storage","host","session","memory","setExcluded","step","trimTo","isLooping","startLoop","getLength","loopCallback","record","sendStatus","err","listener","reportError","code","message","data","error","String","messageReceived","command","startRecording","stopRecording","stepBackward","stepForward","position","setReplayPosition","startLooping","stopLooping","clear","replay","isFinite","value","pos","Math","round","max","min","stopLoop","skipDraw","snapshot","getState","restoreState","drawCall","updateLoop","updateCall","looping","length","getMaxLength","loopStep","executeCallback","onStatus","callback","isRecording"]}
|