Checkpoint Eibenreith ink architecture

This commit is contained in:
2026-05-24 09:09:41 +02:00
parent beac5a2be3
commit d42540f29d
35 changed files with 12015 additions and 54 deletions
+30 -1
View File
@@ -33,6 +33,8 @@ class GameLoopModule extends BaseModule {
this.clientResetGeneration = 0;
this.restoreGeneration = 0;
this.pendingHistoryRestoreCleanup = null;
this.gameOperationGeneration = 0;
this.autoSaveGeneration = 0;
// Bind methods using parent's bindMethods utility
this.bindMethods([
@@ -212,6 +214,8 @@ class GameLoopModule extends BaseModule {
async resumeAutosaveIfAvailable() {
if (this.resumeAttempted) return false;
this.resumeAttempted = true;
const operationGeneration = ++this.gameOperationGeneration;
const isCurrentOperation = () => operationGeneration === this.gameOperationGeneration;
const storyHistory = this.getModule('story-history');
const socketClient = this.getModule('socket-client');
@@ -220,11 +224,13 @@ class GameLoopModule extends BaseModule {
}
const browserSave = await storyHistory.loadSlot(this.autoSaveSlot);
if (!isCurrentOperation()) return false;
if (!browserSave?.inkState || browserSave.running === false) {
return false;
}
await this.resetClientPlaybackAndDisplay();
if (!isCurrentOperation()) return false;
this.currentChoices = [];
this.currentInputMode = 'none';
document.dispatchEvent(new CustomEvent('story:choices', { detail: [] }));
@@ -234,6 +240,7 @@ class GameLoopModule extends BaseModule {
}));
const response = await socketClient.resumeGame(browserSave.inkState);
if (!isCurrentOperation()) return false;
if (!response?.success) {
console.warn('GameLoop: autosave resume failed', response);
document.dispatchEvent(new CustomEvent('story:history-restoring', {
@@ -249,6 +256,7 @@ class GameLoopModule extends BaseModule {
this.gameState.canLoad = true;
this.updateUIState();
await this.restoreBrowserSave(browserSave, 'autosave-resume', { resetDisplay: false });
if (!isCurrentOperation()) return false;
this.restoreInputStateFromSave(browserSave, 'autosave-resume');
return true;
}
@@ -297,6 +305,8 @@ class GameLoopModule extends BaseModule {
async requestStartGame() {
const socketClient = this.getModule('socket-client');
if (!socketClient) return;
const operationGeneration = ++this.gameOperationGeneration;
const isCurrentOperation = () => operationGeneration === this.gameOperationGeneration;
this.gameState.started = true;
this.gameState.startedOnce = true;
@@ -304,9 +314,11 @@ class GameLoopModule extends BaseModule {
this.gameState.canSave = true;
this.updateUIState();
await this.resetClientPlaybackAndDisplay();
if (!isCurrentOperation()) return;
const storyHistory = this.getModule('story-history');
if (storyHistory && typeof storyHistory.startNewGame === 'function') {
await storyHistory.startNewGame();
if (!isCurrentOperation()) return;
if (typeof storyHistory.saveSlot === 'function') {
await storyHistory.saveSlot(this.autoSaveSlot, {
inkState: null,
@@ -317,6 +329,7 @@ class GameLoopModule extends BaseModule {
}
}
const response = await socketClient.newGame();
if (!isCurrentOperation()) return;
if (!response?.success) {
console.error('GameLoop: newGame failed', response);
this.gameState.started = false;
@@ -331,6 +344,7 @@ class GameLoopModule extends BaseModule {
this.gameState.canLoad = Boolean(response.canLoad);
this.updateUIState();
if (response.savedState && storyHistory && typeof storyHistory.saveSlot === 'function') {
if (!isCurrentOperation()) return;
await storyHistory.saveSlot(this.autoSaveSlot, {
inkState: response.savedState,
choices: [],
@@ -375,12 +389,16 @@ class GameLoopModule extends BaseModule {
async requestLoadGame() {
const socketClient = this.getModule('socket-client');
if (!socketClient) return;
const operationGeneration = ++this.gameOperationGeneration;
const isCurrentOperation = () => operationGeneration === this.gameOperationGeneration;
const storyHistory = this.getModule('story-history');
const browserSave = storyHistory && typeof storyHistory.loadSlot === 'function'
? await storyHistory.loadSlot(1)
: null;
if (!isCurrentOperation()) return;
const hasSave = browserSave ? { result: true } : await socketClient.hasSaveGame(1);
if (!isCurrentOperation()) return;
if (!hasSave?.result) {
this.gameState.canLoad = false;
this.updateUIState();
@@ -394,7 +412,9 @@ class GameLoopModule extends BaseModule {
this.gameState.canLoad = true;
this.updateUIState();
await this.restoreBrowserSave(browserSave, 'load-game', { resetDisplay: true });
if (!isCurrentOperation()) return;
const response = await socketClient.loadGame(1, browserSave?.inkState || null);
if (!isCurrentOperation()) return;
if (response?.success && browserSave && Array.isArray(browserSave.choices)) {
this.currentChoices = browserSave.choices;
this.currentInputMode = browserSave.inputMode || this.currentInputMode;
@@ -523,11 +543,16 @@ class GameLoopModule extends BaseModule {
return;
}
const autoSaveGeneration = this.autoSaveGeneration;
const gameId = storyHistory.currentGameId || null;
this.autoSaveInProgress = true;
try {
const response = this.gameState.started && typeof socketClient.exportGameState === 'function'
? await socketClient.exportGameState()
: null;
if (autoSaveGeneration !== this.autoSaveGeneration || storyHistory.currentGameId !== gameId) {
return;
}
if (!response?.success || !response.savedState) {
return;
}
@@ -535,6 +560,7 @@ class GameLoopModule extends BaseModule {
const audioManager = this.getModule('audio-manager');
await storyHistory.saveSlot(this.autoSaveSlot, {
gameId,
inkState: response.savedState,
latestRenderedBlockId: storyHistory.latestRenderedBlockId || 0,
renderedLineCount: storyHistory.renderedLineCount || 0,
@@ -545,9 +571,11 @@ class GameLoopModule extends BaseModule {
});
} finally {
this.autoSaveInProgress = false;
if (this.autoSaveQueued) {
if (autoSaveGeneration === this.autoSaveGeneration && this.autoSaveQueued) {
this.autoSaveQueued = false;
this.autoSaveCurrentSession();
} else {
this.autoSaveQueued = false;
}
}
}
@@ -568,6 +596,7 @@ class GameLoopModule extends BaseModule {
async resetClientPlaybackAndDisplay() {
this.clientResetGeneration += 1;
this.restoreGeneration += 1;
this.autoSaveGeneration += 1;
this.clearPendingHistoryRestore('client-reset');
const playbackCoordinator = this.getModule('playback-coordinator');