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
+123 -9
View File
@@ -15,6 +15,9 @@ class ChoiceDisplayModule extends BaseModule {
this.choices = [];
this.inputMode = 'none';
this.processState = document.documentElement.dataset.processState || 'loading';
this.currentTurnId = 0;
this.autoTurnCounter = 0;
this.lastAutoTurn = new Map();
this.template = {
cells: {
default: {
@@ -39,8 +42,15 @@ class ChoiceDisplayModule extends BaseModule {
'shuffleChoices',
'randomInt',
'assignLetters',
'selectAutoChoice',
'isAutoChoiceReady',
'getAutoChoiceConfig',
'getAutoChoiceKey',
'getAutoChoiceDelay',
'markAutoChoiceTriggered',
'selectChoice',
'getTagValue',
'getTag',
'getTemplateCell',
'renderChoiceText'
]);
@@ -60,10 +70,23 @@ class ChoiceDisplayModule extends BaseModule {
this.addEventListener(document, 'story:process-state', (event) => {
this.handleProcessState(event.detail?.state || 'ready');
});
this.addEventListener(document, 'story:turn-start', () => {
this.addEventListener(document, 'story:turn-start', (event) => {
const turnId = Number(event.detail?.turnId);
if (turnId === 1) {
this.currentTurnId = 0;
this.autoTurnCounter = 0;
this.lastAutoTurn.clear();
}
this.processState = 'waiting-generating';
this.render();
});
this.addEventListener(document, 'story:turn-complete', (event) => {
const turnId = Number(event.detail?.turnId);
if (Number.isInteger(turnId) && turnId !== this.currentTurnId) {
this.currentTurnId = turnId;
this.autoTurnCounter += 1;
}
});
this.addEventListener(document, 'story:history-restoring', (event) => {
document.documentElement.dataset.historyRestoring = event.detail?.active ? 'true' : 'false';
this.render();
@@ -130,7 +153,7 @@ class ChoiceDisplayModule extends BaseModule {
}
const letter = event.key.toUpperCase();
const choice = this.choices.find((item) => item.letter === letter);
const choice = this.choices.find((item) => !item.auto && item.letter === letter);
if (!choice) {
return;
}
@@ -151,11 +174,20 @@ class ChoiceDisplayModule extends BaseModule {
sourceOrder: order,
optional: this.hasTag(tags, 'optional'),
letter: '',
templateCell: this.getTemplateCell({ ...choice, tags, category })
templateCell: this.getTemplateCell({ ...choice, tags, category }),
auto: this.hasTag(tags, 'auto')
};
});
return this.assignLetters(this.orderChoicesForPresentation(normalized));
const autoChoices = normalized
.filter((choice) => choice.auto)
.sort((a, b) => a.sourceOrder - b.sourceOrder);
const visibleChoices = normalized.filter((choice) => !choice.auto);
return [
...autoChoices,
...this.assignLetters(this.orderChoicesForPresentation(visibleChoices))
];
}
orderChoicesForPresentation(choices) {
@@ -254,11 +286,17 @@ class ChoiceDisplayModule extends BaseModule {
}
getTagValue(tags, key) {
const normalizedKey = String(key).toLowerCase();
const tag = tags.find((item) => String(item?.key || '').toLowerCase() === normalizedKey);
const tag = this.getTag(tags, key);
return tag?.value;
}
getTag(tags, key) {
const normalizedKey = String(key).toLowerCase();
return Array.isArray(tags)
? tags.find((item) => String(item?.key || '').toLowerCase() === normalizedKey)
: undefined;
}
hasTag(tags, key) {
const normalizedKey = String(key).toLowerCase();
return Array.isArray(tags) && tags.some((item) => String(item?.key || '').toLowerCase() === normalizedKey);
@@ -276,8 +314,15 @@ class ChoiceDisplayModule extends BaseModule {
this.inputMode === 'choice' &&
this.choices.length > 0 &&
this.processState === 'ready';
this.container.hidden = !readyForChoices;
this.container.dataset.choiceReady = readyForChoices ? 'true' : 'false';
if (readyForChoices && this.selectAutoChoice()) {
return;
}
const visibleChoices = this.choices.filter((choice) => !choice.auto);
const hasVisibleChoices = visibleChoices.length > 0;
this.container.hidden = !readyForChoices || !hasVisibleChoices;
this.container.dataset.choiceReady = readyForChoices && hasVisibleChoices ? 'true' : 'false';
if (this.container.hidden) {
return;
}
@@ -285,7 +330,7 @@ class ChoiceDisplayModule extends BaseModule {
const list = document.createElement('ol');
list.className = 'choice-list choice-template-default';
this.choices.forEach((choice) => {
visibleChoices.forEach((choice) => {
const item = document.createElement('li');
item.className = 'choice-list-item';
item.classList.toggle('choice-optional', Boolean(choice.optional));
@@ -307,6 +352,75 @@ class ChoiceDisplayModule extends BaseModule {
this.container.appendChild(list);
}
selectAutoChoice() {
const autoChoice = this.choices.find((choice) => choice.auto && this.isAutoChoiceReady(choice));
if (!autoChoice) {
return false;
}
this.markAutoChoiceTriggered(autoChoice);
this.selectChoice(autoChoice.index);
return true;
}
isAutoChoiceReady(choice) {
const config = this.getAutoChoiceConfig(choice);
if (!config) {
return false;
}
if (config.delay <= 0) {
return true;
}
const lastTurn = this.lastAutoTurn.get(config.key);
if (!Number.isFinite(lastTurn)) {
return true;
}
return (this.autoTurnCounter - lastTurn) >= config.delay;
}
getAutoChoiceConfig(choice) {
const autoTag = this.getTag(choice.tags, 'auto');
if (!autoTag) {
return null;
}
return {
key: this.getAutoChoiceKey(autoTag),
delay: this.getAutoChoiceDelay(autoTag)
};
}
getAutoChoiceKey(autoTag) {
const value = String(autoTag?.value || '').trim();
return value || '__global';
}
getAutoChoiceDelay(autoTag) {
const candidates = [autoTag?.param, autoTag?.value];
for (const candidate of candidates) {
const text = String(candidate || '').trim();
if (!text) continue;
const namedMatch = text.match(/(?:turns?|delay|cooldown)\s*=\s*(\d+)/i);
const plainMatch = text.match(/^\d+$/);
const value = namedMatch ? Number(namedMatch[1]) : plainMatch ? Number(text) : NaN;
if (Number.isFinite(value)) {
return Math.max(0, Math.floor(value));
}
}
return 0;
}
markAutoChoiceTriggered(choice) {
const config = this.getAutoChoiceConfig(choice);
if (!config) {
return;
}
this.lastAutoTurn.set(config.key, this.autoTurnCounter);
}
async selectChoice(index) {
if (!this.socketClient) {
this.socketClient = this.getModule('socket-client');