@oh-story/dsh 0.1.9 → 0.1.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +12 -12
- package/lib/client.js +204 -72
- package/lib/client.js.map +3 -3
- package/lib/drama/manifest.json +84 -74
- package/lib/drama/skills/short-drama/SKILL.md +2 -2
- package/lib/drama/skills/short-drama/scripts/creator_markdown_check.py +14 -0
- package/lib/drama/skills/short-drama-assets/references/character-and-look.md +10 -0
- package/lib/drama/skills/short-drama-assets/references/continuity-lock.md +6 -9
- package/lib/drama/skills/short-drama-assets/references/voice-direction.md +28 -0
- package/lib/drama/skills/short-drama-edit/SKILL.md +27 -29
- package/lib/drama/skills/short-drama-edit/assets//345/211/252/350/276/221/345/215/225.md +2 -2
- package/lib/drama/skills/short-drama-edit/references/cut-craft.md +24 -67
- package/lib/drama/skills/short-drama-edit/references/delivery-verify.md +27 -56
- package/lib/drama/skills/short-drama-edit/references/generated-footage.md +37 -79
- package/lib/drama/skills/short-drama-edit/references/sound-and-subtitles.md +39 -92
- package/lib/drama/skills/short-drama-edit/references/stage-contract.md +12 -16
- package/lib/drama/skills/short-drama-edit/scripts/edit_tool.py +126 -11
- package/lib/drama/skills/short-drama-edit/scripts/selftest.py +64 -0
- package/lib/drama/skills/short-drama-image-prompts/SKILL.md +2 -0
- package/lib/drama/skills/short-drama-image-prompts/references/common-recipe.md +11 -0
- package/lib/drama/skills/short-drama-produce/SKILL.md +5 -1
- package/lib/drama/skills/short-drama-produce/references/adapter-contract.md +14 -20
- package/lib/drama/skills/short-drama-produce/references/providers/minimax-h3-video.md +8 -26
- package/lib/drama/skills/short-drama-produce/references/providers/minimax-speech.md +41 -0
- package/lib/drama/skills/short-drama-produce/scripts/provider_adapters.py +142 -1
- package/lib/drama/skills/short-drama-review/SKILL.md +2 -3
- package/lib/drama/skills/short-drama-review/references/production-quality-gates.md +6 -19
- package/lib/drama/skills/short-drama-review/references/rubric-visual-motion.md +9 -35
- package/lib/drama/skills/short-drama-storyboard/SKILL.md +10 -6
- package/lib/drama/skills/short-drama-storyboard/references/lighting-craft.md +39 -0
- package/lib/drama/skills/short-drama-storyboard/references/shot-craft.md +58 -21
- package/lib/drama/skills/short-drama-storyboard/references/stage-contract.md +1 -1
- package/lib/drama/skills/short-drama-video-prompts/SKILL.md +10 -9
- package/lib/drama/skills/short-drama-video-prompts/references/camera-audio-continuity.md +17 -0
- package/lib/drama/skills/short-drama-video-prompts/references/minimax-h3.md +12 -38
- package/lib/drama/skills/short-drama-video-prompts/references/motion-recipe.md +4 -7
- package/lib/drama/skills/short-drama-video-prompts/references/performance-action-timing.md +8 -22
- package/lib/drama/skills/short-drama-video-prompts/references/production-prompt-grammar.md +6 -19
- package/lib/drama/skills/short-drama-video-prompts/references/seedance-2.0.md +2 -3
- package/lib/drama/skills/short-drama-video-prompts/references/seedance-2.5.md +34 -9
- package/lib/drama/skills/short-drama-video-prompts/references/stage-contract.md +2 -2
- package/lib/drama/skills/short-drama-write/SKILL.md +2 -4
- package/lib/index.js +289 -91
- package/lib/index.js.map +4 -4
- package/lib/novel-to-game/README.md +299 -59
- package/lib/novel-to-game/README_ZH.md +299 -56
- package/lib/novel-to-game/VERSION +1 -1
- package/lib/novel-to-game/examples/jin-ping-mei/build/BUILD_BRIEF.md +3 -1
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/RUN.md +11 -3
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/css/new-guofeng.css +397 -8
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/css/style.css +0 -31
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/data.js +71 -73
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/engine.js +78 -89
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/main.js +37 -14
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/text.js +5 -5
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/package.json +2 -1
- package/lib/novel-to-game/examples/jin-ping-mei/qa/README.md +11 -16
- package/lib/novel-to-game/examples/jin-ping-mei/qa/verification.json +11 -11
- package/lib/novel-to-game/examples/jin-ping-mei/qa/verify_playable_model.mjs +23 -0
- package/lib/novel-to-game/manifest.json +77 -87
- package/lib/novel-to-game/skills/game-art-direction/SKILL.md +7 -12
- package/lib/novel-to-game/skills/game-art-direction/references/art-direction-method.md +27 -106
- package/lib/novel-to-game/skills/game-build/SKILL.md +13 -14
- package/lib/novel-to-game/skills/game-build/references/blender-asset-workflow.md +24 -0
- package/lib/novel-to-game/skills/game-build/references/build-brief-contract.md +45 -82
- package/lib/novel-to-game/skills/game-build/references/generative-media-pipeline.md +14 -45
- package/lib/novel-to-game/skills/game-build/references/playable-model-contract.md +30 -65
- package/lib/novel-to-game/skills/game-build/references/tts-production-contract.md +13 -27
- package/lib/novel-to-game/skills/game-concept/SKILL.md +13 -15
- package/lib/novel-to-game/skills/game-concept/references/concept-method.md +28 -75
- package/lib/novel-to-game/skills/game-qa/SKILL.md +6 -7
- package/lib/novel-to-game/skills/game-qa/references/test-design-method.md +9 -5
- package/lib/novel-to-game/skills/game-world-design/SKILL.md +12 -16
- package/lib/novel-to-game/skills/game-world-design/references/narrative-design-method.md +23 -72
- package/lib/novel-to-game/skills/game-world-design/references/numeric-design-method.md +14 -48
- package/lib/novel-to-game/skills/game-world-design/references/world-design-method.md +36 -103
- package/lib/novel-to-game/skills/novel-game-analyze/SKILL.md +4 -10
- package/lib/novel-to-game/skills/novel-game-analyze/references/gameability-protocol.md +4 -4
- package/lib/novel-to-game/skills/novel-to-game/SKILL.md +9 -21
- package/lib/novel-to-game/skills/novel-to-game/references/intake-method.md +5 -4
- package/lib/novel-to-game/skills/novel-to-game/references/pipeline-contract.md +5 -8
- package/lib/oh-story/manifest.json +230 -185
- package/lib/oh-story/roles/chapter-extractor.md +177 -237
- package/lib/oh-story/roles/consistency-checker.md +4 -4
- package/lib/oh-story/roles/narrative-writer.md +24 -25
- package/lib/oh-story/roles/story-architect.md +5 -5
- package/lib/oh-story/roles/story-explorer.md +11 -11
- package/lib/oh-story/skills/story/SKILL.md +13 -7
- package/lib/oh-story/skills/story/VERSION +1 -1
- package/lib/oh-story/skills/story/references/author-memory.md +54 -24
- package/lib/oh-story/skills/story/scripts/author_memory_commit.py +934 -170
- package/lib/oh-story/skills/story-cover/SKILL.md +10 -1
- package/lib/oh-story/skills/story-deslop/SKILL.md +40 -53
- package/lib/oh-story/skills/story-deslop/references/anti-ai-writing.md +11 -10
- package/lib/oh-story/skills/story-deslop/references/author-memory.md +54 -24
- package/lib/oh-story/skills/story-deslop/references/deslop-gates.md +1 -1
- package/lib/oh-story/skills/story-deslop/scripts/author_memory_commit.py +934 -170
- package/lib/oh-story/skills/story-import/SKILL.md +77 -81
- package/lib/oh-story/skills/story-import/references/character-state-reverse.md +1 -1
- package/lib/oh-story/skills/story-import/references/state-tracking.md +4 -69
- package/lib/oh-story/skills/story-import/references/structure-mapping-long.md +10 -8
- package/lib/oh-story/skills/story-import/references/tracking-transaction.md +8 -6
- package/lib/oh-story/skills/story-import/scripts/check-outline-contract.js +34 -16
- package/lib/oh-story/skills/story-import/scripts/tracking_commit.py +210 -2
- package/lib/oh-story/skills/story-import/scripts/wordcount_core.py +3 -0
- package/lib/oh-story/skills/story-long-analyze/SKILL.md +91 -258
- package/lib/oh-story/skills/story-long-analyze/references/author-facing.md +245 -0
- package/lib/oh-story/skills/story-long-analyze/references/deconstruction-notes.md +2 -2
- package/lib/oh-story/skills/story-long-analyze/references/inspiration-library.md +105 -0
- package/lib/oh-story/skills/story-long-analyze/references/material-decomposition.md +113 -105
- package/lib/oh-story/skills/story-long-analyze/references/output-templates.md +169 -125
- package/lib/oh-story/skills/story-long-analyze/references/pipeline-ops.md +172 -64
- package/lib/oh-story/skills/story-long-analyze/references/semantic-acceptance-fixtures.md +84 -0
- package/lib/oh-story/skills/story-long-analyze/references/style-profile-generator.md +42 -132
- package/lib/oh-story/skills/story-long-analyze/references/style-profile-protocol.md +5 -5
- package/lib/oh-story/skills/story-long-analyze/scripts/build_chapter_index.py +466 -0
- package/lib/oh-story/skills/story-long-analyze/scripts/inspect_existing_assets.py +702 -0
- package/lib/oh-story/skills/story-long-analyze/scripts/inspiration_index.py +864 -0
- package/lib/oh-story/skills/story-long-analyze/scripts/manage_analysis_run.py +1106 -0
- package/lib/oh-story/skills/story-long-analyze/scripts/render_relation_chart.py +425 -0
- package/lib/oh-story/skills/story-long-scan/SKILL.md +4 -1
- package/lib/oh-story/skills/story-long-write/SKILL.md +38 -29
- package/lib/oh-story/skills/story-long-write/references/anti-ai-writing.md +11 -10
- package/lib/oh-story/skills/story-long-write/references/artifact-protocols.md +25 -20
- package/lib/oh-story/skills/story-long-write/references/author-memory.md +54 -24
- package/lib/oh-story/skills/story-long-write/references/benchmark-recall.md +3 -3
- package/lib/oh-story/skills/story-long-write/references/cross-book-recall.md +26 -0
- package/lib/oh-story/skills/story-long-write/references/dialogue-mastery.md +4 -0
- package/lib/oh-story/skills/story-long-write/references/emotional-methods.md +2 -2
- package/lib/oh-story/skills/story-long-write/references/long-chapter-quality.md +4 -13
- package/lib/oh-story/skills/story-long-write/references/outline-structure-theory.md +1 -1
- package/lib/oh-story/skills/story-long-write/references/project-files.md +9 -7
- package/lib/oh-story/skills/story-long-write/references/reader-contract-and-progression.md +12 -4
- package/lib/oh-story/skills/story-long-write/references/reference-index.md +1 -2
- package/lib/oh-story/skills/story-long-write/references/tracking-transaction.md +8 -6
- package/lib/oh-story/skills/story-long-write/references/workflow-chapter.md +86 -115
- package/lib/oh-story/skills/story-long-write/references/workflow-daily.md +55 -120
- package/lib/oh-story/skills/story-long-write/references/workflow-revision.md +15 -11
- package/lib/oh-story/skills/story-long-write/references/workflow-setup.md +66 -68
- package/lib/oh-story/skills/story-long-write/references/writer-prompt-fallback.md +3 -3
- package/lib/oh-story/skills/story-long-write/references/writing-craft.md +4 -12
- package/lib/oh-story/skills/story-long-write/scripts/author_memory_commit.py +934 -170
- package/lib/oh-story/skills/story-long-write/scripts/build_writer_prompt.py +139 -18
- package/lib/oh-story/skills/story-long-write/scripts/check-outline-contract.js +34 -16
- package/lib/oh-story/skills/story-long-write/scripts/outline_view.py +5 -3
- package/lib/oh-story/skills/story-long-write/scripts/storyctl.py +58 -0
- package/lib/oh-story/skills/story-long-write/scripts/tracking_commit.py +210 -2
- package/lib/oh-story/skills/story-long-write/scripts/wordcount_core.py +3 -0
- package/lib/oh-story/skills/story-review/SKILL.md +68 -85
- package/lib/oh-story/skills/story-review/references/anti-ai-writing.md +11 -10
- package/lib/oh-story/skills/story-review/references/author-memory.md +54 -24
- package/lib/oh-story/skills/story-review/references/dialogue-mastery.md +4 -0
- package/lib/oh-story/skills/story-review/references/review-quality.md +4 -10
- package/lib/oh-story/skills/story-review/references/tracking-transaction.md +8 -6
- package/lib/oh-story/skills/story-review/scripts/author_memory_commit.py +934 -170
- package/lib/oh-story/skills/story-review/scripts/tracking_commit.py +210 -2
- package/lib/oh-story/skills/story-review/scripts/wordcount_core.py +3 -0
- package/lib/oh-story/skills/story-setup/SKILL.md +50 -42
- package/lib/oh-story/skills/story-setup/references/agent-references/anti-ai-writing.md +11 -10
- package/lib/oh-story/skills/story-setup/references/agent-references/deslop-gates.md +1 -1
- package/lib/oh-story/skills/story-setup/references/agent-references/dialogue-mastery.md +4 -0
- package/lib/oh-story/skills/story-setup/references/agent-references/long-emotional-methods.md +2 -2
- package/lib/oh-story/skills/story-setup/references/agent-references/long-quality.md +1 -1
- package/lib/oh-story/skills/story-setup/references/agent-references/writing-craft.md +4 -12
- package/lib/oh-story/skills/story-setup/references/diagnostics.md +1 -1
- package/lib/oh-story/skills/story-short-analyze/SKILL.md +22 -3
- package/lib/oh-story/skills/story-short-analyze/references/material-decomposition.md +5 -1
- package/lib/oh-story/skills/story-short-scan/SKILL.md +5 -1
- package/lib/oh-story/skills/story-short-write/SKILL.md +6 -147
- package/lib/oh-story/skills/story-short-write/references/author-memory.md +54 -24
- package/lib/oh-story/skills/story-short-write/references/dialogue-mastery.md +4 -0
- package/lib/oh-story/skills/story-short-write/references/reference-index.md +2 -0
- package/lib/oh-story/skills/story-short-write/references/workflow-design.md +3 -2
- package/lib/oh-story/skills/story-short-write/references/workflow-draft.md +126 -0
- package/lib/oh-story/skills/story-short-write/references/workflow-revision.md +32 -0
- package/lib/oh-story/skills/story-short-write/scripts/author_memory_commit.py +934 -170
- package/lib/types/client/drama-production-view.d.ts.map +1 -1
- package/lib/types/client/file-activity.d.ts +19 -1
- package/lib/types/client/file-activity.d.ts.map +1 -1
- package/lib/types/client/index.d.ts.map +1 -1
- package/lib/types/client/production-prompts.d.ts +7 -0
- package/lib/types/client/production-prompts.d.ts.map +1 -1
- package/lib/types/client/production-runtime.d.ts +16 -0
- package/lib/types/client/production-runtime.d.ts.map +1 -1
- package/lib/types/drama-adapters.d.ts +5 -3
- package/lib/types/drama-adapters.d.ts.map +1 -1
- package/lib/types/native-hooks.d.ts +52 -0
- package/lib/types/native-hooks.d.ts.map +1 -1
- package/lib/types/role-identity.d.ts +17 -0
- package/lib/types/role-identity.d.ts.map +1 -0
- package/lib/types/role-tool.d.ts.map +1 -1
- package/lib/types/skill-provider.d.ts.map +1 -1
- package/package.json +45 -45
- package/lib/novel-to-game/skills/game-world-design/references/dialogue-design-method.md +0 -58
- package/lib/novel-to-game/skills/game-world-design/references/game-writing-craft.md +0 -53
- package/lib/novel-to-game/skills/novel-to-game/references/intake-benchmark-reference.md +0 -38
- package/lib/oh-story/skills/story-long-write/references/state-tracking.md +0 -108
|
@@ -346,9 +346,17 @@ function renderAgeGate() {
|
|
|
346
346
|
function renderTitle() {
|
|
347
347
|
const saved = loadSave();
|
|
348
348
|
const hasSave = !!saved;
|
|
349
|
+
const titleCast = [
|
|
350
|
+
['月娘', '定账'],
|
|
351
|
+
['金莲', '识谎'],
|
|
352
|
+
['瓶儿', '私钥'],
|
|
353
|
+
['玉楼', '名帖'],
|
|
354
|
+
['雪娥', '灶权'],
|
|
355
|
+
];
|
|
349
356
|
app.innerHTML = `
|
|
350
357
|
<main class="title-screen">
|
|
351
358
|
<div class="title-art" style="background-image:url('${urlFor('cover')}')" role="img" aria-label="月娘、金莲、瓶儿、玉楼与雪娥各据一处,五道视线都落在你身上"></div>
|
|
359
|
+
<aside class="title-folio" aria-hidden="true"><b>风月总账</b><span>二十日 · 四幕</span></aside>
|
|
352
360
|
<section class="title-copy">
|
|
353
361
|
<p class="eyebrow">成人后宫关系游戏 · 二十日四幕</p>
|
|
354
362
|
<h1>${TEXT.title}</h1>
|
|
@@ -362,6 +370,9 @@ function renderTitle() {
|
|
|
362
370
|
<p class="rating-line">${TEXT.rating}</p>
|
|
363
371
|
<p class="save-note">${TEXT.saveNote}</p>
|
|
364
372
|
</section>
|
|
373
|
+
<ol class="title-cast" aria-label="五院与各自握有的权力">
|
|
374
|
+
${titleCast.map(([name, role], index) => `<li><i>${index + 1}</i><span>${name}</span><b>${role}</b></li>`).join('')}
|
|
375
|
+
</ol>
|
|
365
376
|
</main>`;
|
|
366
377
|
if (newGameConfirmOpen && hasSave) appendNewGameConfirm(saved);
|
|
367
378
|
if (galleryOpen) appendGallery();
|
|
@@ -903,8 +914,9 @@ function renderOpening() {
|
|
|
903
914
|
return `
|
|
904
915
|
<div class="opening-scene visual-stage" style="--scene-bg:url('${urlFor('cover')}')">
|
|
905
916
|
<div class="decision-panel opening-panel">
|
|
906
|
-
|
|
907
|
-
|
|
917
|
+
<div class="opening-case" aria-hidden="true"><span>初日</span><b>一</b><i>正堂·灯下</i></div>
|
|
918
|
+
${phaseHeader('正堂 · 五更尽', '账上空了五十两', '窗纸还没有亮。月娘把两本账摊在灯下,其中一页有一行新墨。金莲坐在桌另一头,指尖还按着一只没人喝的酒杯。')}
|
|
919
|
+
<p class="speaker-line"><b>月娘</b>“你先看账。”<br/><b>金莲</b>“也先听听人怎么说。”</p>
|
|
908
920
|
<div class="choice-grid">${OPENING_CHOICES.map((choice) => choiceButton(choice, 'opening')).join('')}</div>
|
|
909
921
|
</div>
|
|
910
922
|
</div>`;
|
|
@@ -1262,7 +1274,8 @@ function renderDay() {
|
|
|
1262
1274
|
return `
|
|
1263
1275
|
<div class="hub-stage visual-stage" style="--scene-bg:url('${urlFor('compound')}')">
|
|
1264
1276
|
<div class="decision-panel day-panel">
|
|
1265
|
-
|
|
1277
|
+
<div class="day-scene-mark" aria-hidden="true"><span>DAY</span><b>${String(state.day).padStart(2, '0')}</b><i>${escapeHtml(def.act)}</i></div>
|
|
1278
|
+
${phaseHeader(`第 ${state.day} 日 · 院中`, def.name, `${def.pressure} ${def.tell}`)}
|
|
1266
1279
|
${contextSection}
|
|
1267
1280
|
${renderEvidenceBoard()}
|
|
1268
1281
|
<div class="day-actions">${dayOptions.map((choice) => choiceButton({ ...choice, hint: `${choice.actor ? `${HEROINES[choice.actor].short} · ` : ''}${choice.hint}`, meta: '' }, 'day-action')).join('')}</div>
|
|
@@ -1289,7 +1302,7 @@ function renderDayAftermath() {
|
|
|
1289
1302
|
return `<figure class="${row.delta > 0 ? 'accepts' : 'resists'}"><img src="${urlFor(heroine.portrait)}" alt="${heroine.name}"/><figcaption><span>${escapeHtml(row.tone)}</span><b>${heroine.name}</b><p>${escapeHtml(row.text)}</p></figcaption></figure>`;
|
|
1290
1303
|
}).join('')}</div>`
|
|
1291
1304
|
: '';
|
|
1292
|
-
const consequence = beat.resolved === null ? '' : `<div class="day-consequence ${beat.resolved ? 'resolved' : 'missed'}"><span>${beat.resolved ? '
|
|
1305
|
+
const consequence = beat.resolved === null ? '' : `<div class="day-consequence ${beat.resolved ? 'resolved' : 'missed'}"><span>${beat.resolved ? '这件事暂时稳住了' : '还有一处没有堵住'}</span><b>${beat.resolved ? '人、物和原话都留在眼前,明日还能顺着查下去。' : '眼前的好处拿到了,门外那只手却已经换了一条路。'}</b></div>`;
|
|
1293
1306
|
return `
|
|
1294
1307
|
<div class="day-aftermath-stage visual-stage dialogue-${story.actor} beat-${beat.id}" data-day-aftermath="${beat.id}" style="--scene-bg:url('${urlFor(actor.close || actor.portrait)}')">
|
|
1295
1308
|
<div class="day-aftermath-lead" aria-hidden="true" style="background-image:url('${urlFor(actor.close || actor.portrait)}')"></div>
|
|
@@ -1642,9 +1655,9 @@ function renderSharedNight() {
|
|
|
1642
1655
|
return `
|
|
1643
1656
|
<div class="shared-stage visual-stage" style="--scene-bg:url('${urlFor(backdrop)}')">
|
|
1644
1657
|
<div class="decision-panel shared-panel">
|
|
1645
|
-
${phaseHeader('第二十夜 · 五人都没走', '
|
|
1658
|
+
${phaseHeader('第二十夜 · 五人都没走', '茶还热着,五把钥匙各在主人手里', TEXT.sharedNightLead)}
|
|
1646
1659
|
<div class="accord-seals shared-seals">${rows.map((row) => `<span class="accord-seal ${row.complete ? 'complete' : ''}" data-accord="${row.key}"><i>${row.glyph}</i>${row.label}</span>`).join('')}</div>
|
|
1647
|
-
<p class="shared-proof">${narrativeProgress(Math.min(E.jointActionCount(state), E.JOINT_ACTION_TARGET), E.JOINT_ACTION_TARGET, ['
|
|
1660
|
+
<p class="shared-proof">${narrativeProgress(Math.min(E.jointActionCount(state), E.JOINT_ACTION_TARGET), E.JOINT_ACTION_TARGET, ['她们还没有一起做过事', '已有人并肩做完一桩事', '五组搭档都真正共过事'])} · ${narrativeProgress(state.resolvedPressures.length, E.PRESSURE_TARGET, ['门外的事还在逼近', '她们已一起撑过几回', '今夜终于有余地谈自己'])}</p>
|
|
1648
1661
|
<div class="choice-grid">${E.sharedNightOptions(state).map((choice) => choiceButton(choice, 'shared-night')).join('')}</div>
|
|
1649
1662
|
</div>
|
|
1650
1663
|
</div>`;
|
|
@@ -1657,7 +1670,7 @@ function renderSharedAfterglow() {
|
|
|
1657
1670
|
<div class="afterglow-stage visual-stage" data-shared-beat="${beat.id}" style="--scene-bg:url('${urlFor('cg/group/inner_court_accord')}')">
|
|
1658
1671
|
<div class="decision-panel afterglow-panel">
|
|
1659
1672
|
${phaseHeader(beat.kicker, beat.title, beat.body)}
|
|
1660
|
-
<p class="shared-proof"
|
|
1673
|
+
<p class="shared-proof">灯还亮着 · ${narrativeStep(state.sharedAfterglowChoices.length + 1, SHARED_AFTERGLOW_BEATS.length)} · 这一步要等五个人都开口</p>
|
|
1661
1674
|
<div class="choice-grid">${E.sharedAfterglowOptions(state).map((choice) => choiceButton(choice, 'shared-afterglow')).join('')}</div>
|
|
1662
1675
|
</div>
|
|
1663
1676
|
</div>`;
|
|
@@ -1771,9 +1784,10 @@ function renderVisit() {
|
|
|
1771
1784
|
<div class="dialogue-stage visual-stage dialogue-${id}">
|
|
1772
1785
|
<div class="close-cg" style="background-image:url('${urlFor(h.close)}')" role="img" aria-label="${h.name}近景"></div>
|
|
1773
1786
|
<div class="decision-panel dialogue-panel">
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
1787
|
+
<div class="character-seal" aria-hidden="true"><i>${h.glyph}</i><span>${h.house}</span></div>
|
|
1788
|
+
${phaseHeader(`第 ${state.day} 夜 · ${h.house}`, h.name, h.voice)}
|
|
1789
|
+
<p class="want-line"><b>她今夜要你回答</b>${h.want}<br/><span>${h.gives}</span><br/><small>${stance.covenant === stance.private ? (stance.covenant ? '你们既谈家中的事,也留过只在门内的话。' : '这是你们第一次认真谈今后怎样相处。') : stance.covenant > stance.private ? '她记得你更常在人前接住她的事。' : '她记得你更常把话留在门内。'}</small></p>
|
|
1790
|
+
${branch ? `<aside class="route-branch-memory branch-${branch.lane}" data-route-branch="${branch.lane}:${branch.step}"><span>${escapeHtml(branch.label)} · ${branch.lane === 'covenant' ? '她记得你在人前怎样做' : '她记得你在门内怎样说'}</span><b>今夜从这件旧事往下谈</b><p>${escapeHtml(branch.body)}</p></aside>` : ''}
|
|
1777
1791
|
${favorReckoningMemory(id)}
|
|
1778
1792
|
${routeReckoningMemory(id)}
|
|
1779
1793
|
${intimacyMemory(id)}
|
|
@@ -1848,7 +1862,7 @@ function renderRouteAftermath() {
|
|
|
1848
1862
|
${phaseHeader(event.milestone ? '再次进门的关键章' : `路线小章 · ${narrativeStep(event.act, 4)}`, event.title, event.body)}
|
|
1849
1863
|
<p class="aftermath-echo"><span>上一拍</span><b>${escapeHtml(event.sourceLabel)}</b><small>${escapeHtml(event.sourceText)}</small></p>
|
|
1850
1864
|
${routeContinuityCard(event)}
|
|
1851
|
-
<p class="aftermath-question"
|
|
1865
|
+
<p class="aftermath-question">另一处院门已经听见了。你是把话拿到人前,让她们当面说,还是只护住今夜这扇门?</p>
|
|
1852
1866
|
<div class="choice-stack">${E.routeAftermathOptions(state).map((choice) => choiceButton(choice, 'route-aftermath')).join('')}</div>
|
|
1853
1867
|
</div>
|
|
1854
1868
|
</div>`;
|
|
@@ -1886,7 +1900,7 @@ function renderNight() {
|
|
|
1886
1900
|
${phaseHeader(`第 ${state.day} 夜 · 夜谈${narrativeProgress(conversation.chapter, 4, ['刚刚起话', '正在接续', '已成条款'])} · 回答后章${narrativeStep(result.beat + 1, result.count)}`, result.current.title, result.current.body)}
|
|
1887
1901
|
${intimacyMemory(id)}
|
|
1888
1902
|
${result.beat === 0 && conversation.memoryEcho ? `<aside class="night-talk-memory"><span>上一章没有消失</span><p>${escapeHtml(conversation.memoryEcho)}</p></aside>` : ''}
|
|
1889
|
-
<aside class="night-talk-stake" data-night-stake="${result.mode}"><span
|
|
1903
|
+
<aside class="night-talk-stake" data-night-stake="${result.mode}"><span>天亮以后会留下</span><b>${escapeHtml(result.stake.label)} · ${escapeHtml(narrativeChoiceMeta(result.stake.resourceText))}</b><p>${escapeHtml(result.stake.text)}</p></aside>
|
|
1890
1904
|
${result.beat > 0 ? `<p class="night-result-origin"><span>你刚才回答</span><b>${escapeHtml(result.label)}</b></p>` : ''}
|
|
1891
1905
|
${previousBeats.length ? `<ol class="night-result-transcript" aria-label="这次回答已经发生的后果">${previousBeats.map((beat, index) => `<li><span>${narrativeBeatMark(index)}</span><div><b>${escapeHtml(beat.title)}</b><p>${escapeHtml(beat.body)}</p></div></li>`).join('')}</ol>` : ''}
|
|
1892
1906
|
${result.beat + 1 === result.count ? `<div class="night-talk-continuity continuity-${result.continuity.kind}">
|
|
@@ -1924,7 +1938,8 @@ function renderNight() {
|
|
|
1924
1938
|
<div class="dialogue-stage visual-stage night dialogue-${id}">
|
|
1925
1939
|
<div class="close-cg closer" style="background-image:url('${urlFor(h.close)}')" role="img" aria-label="${h.name}夜间近景"></div>
|
|
1926
1940
|
<div class="decision-panel dialogue-panel">
|
|
1927
|
-
|
|
1941
|
+
<div class="character-seal" aria-hidden="true"><i>${h.glyph}</i><span>${h.house}</span></div>
|
|
1942
|
+
${phaseHeader(`第 ${state.day} 夜 · 灯下`, h.name, TEXT.nightLead)}
|
|
1928
1943
|
${intimacyMemory(id)}
|
|
1929
1944
|
<div class="choice-stack">${choices.map((choice) => choiceButton(choice, 'night')).join('')}</div>
|
|
1930
1945
|
<p class="consent-note">她往前一步,才有下一步。她若停下,门便停在这里。</p>
|
|
@@ -2289,6 +2304,13 @@ function renderPersonalAfterglowAftermath() {
|
|
|
2289
2304
|
function renderEnding() {
|
|
2290
2305
|
const end = state.ending;
|
|
2291
2306
|
const top = HEROINE_IDS.slice().sort((a, b) => state.relations[b].qing - state.relations[a].qing)[0];
|
|
2307
|
+
const endingLead = ({
|
|
2308
|
+
balanced: '天亮时,五盏灯还在同一张桌边。月娘合上总账,金莲把最后一句假话撤掉,瓶儿、玉楼与雪娥各自收回钥匙、名帖和工簿。她们愿意留下,不等于把自己交给一本总账。',
|
|
2309
|
+
alliance: '天亮时,留在桌边的人只答应了自己亲手做过的那一部分。没有坐进来的院门仍各自收着钥匙;今夜的亲近,不替任何人补一个没说出口的“愿意”。',
|
|
2310
|
+
exclusive: `天亮时,${HEROINES[top].name}的门还开着。其余四院没有因为你做了选择就自动原谅;她们把各自的原话、钥匙和去处收回,留下的人也不替你遮掉这份代价。`,
|
|
2311
|
+
intrigue: '天亮时,门外的账暂时压住了,门里却没有因此重新热起来。你用银子、名字和口供换来一口气;谁被暴露,谁收回了手,仍然一笔笔留在桌上。',
|
|
2312
|
+
unstable: '天亮时,五处院门依次灭了灯。月娘封账,金莲收走你说过的话,瓶儿扣回钥匙,玉楼把名帖收进匣里,雪娥合上工簿。门外的债还在,这一回没有人再替你把空话接下去。',
|
|
2313
|
+
})[end.id] ?? end.text;
|
|
2292
2314
|
const relationshipSummary = end.id === 'balanced'
|
|
2293
2315
|
? '<span>五院关系 <b>都还在桌上</b></span>'
|
|
2294
2316
|
: end.id === 'alliance'
|
|
@@ -2329,7 +2351,8 @@ function renderEnding() {
|
|
|
2329
2351
|
<p class="eyebrow">第 20 日 · 风月总账</p>
|
|
2330
2352
|
<h1 tabindex="-1">${end.title}</h1>
|
|
2331
2353
|
<p class="ending-tag">${end.tag}${end.heroineName ? ` · ${end.heroineName}` : ''}${end.reckoningResult ? ` · ${end.reckoningResult}` : ''}${end.routeResult ? ` · ${end.routeResult}` : ''}</p>
|
|
2332
|
-
<p>${
|
|
2354
|
+
<p>${endingLead}</p>
|
|
2355
|
+
<details class="ending-old-account"><summary>翻看这二十日的来路</summary><p>${end.text}</p></details>
|
|
2333
2356
|
<div class="ending-ledger">
|
|
2334
2357
|
${relationshipSummary}
|
|
2335
2358
|
<span>结局总账 <b>${loadEndingArchive().length ? '已有旧卷' : '首卷落定'}</b></span>
|
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
export const TEXT = Object.freeze({
|
|
4
4
|
title: '金瓶梅·风月总账',
|
|
5
|
-
subtitle: '
|
|
6
|
-
identity: '
|
|
5
|
+
subtitle: '二十日,五扇门。你说过的话,她们都记得。',
|
|
6
|
+
identity: '你是西门庆。门外的债已经追到家门,门里有五个人等你把银子、谎话和旧承诺一件件说清。你可以偏爱,但不能叫她们替你撒谎。',
|
|
7
7
|
rating: '18+ · 仅限成年人 · 含成人关系、轻度裸露与亲密内容',
|
|
8
8
|
ageTitle: '十八岁以下,止步',
|
|
9
9
|
ageBody: '本作只供成年人,含成人关系、肩背与腿部等轻度裸露、亲密场景与成熟主题。所有可进入亲密路线的人物均为成年虚构角色。',
|
|
@@ -15,10 +15,10 @@ export const TEXT = Object.freeze({
|
|
|
15
15
|
restart: '从头再来',
|
|
16
16
|
muteOn: '开声',
|
|
17
17
|
muteOff: '静音',
|
|
18
|
-
dayLead: '
|
|
19
|
-
chooseVisit: '
|
|
18
|
+
dayLead: '院里的事已摆到眼前。你先动哪一只手?',
|
|
19
|
+
chooseVisit: '暮鼓响过,五处院门仍留着灯。今夜你去见谁?',
|
|
20
20
|
sharedNightLead: '五个人都没走。账还没凉,酒已经热了——今夜是各回各院,还是在每个人都独立点头之后让五盏灯留在一处,就等你开口。',
|
|
21
|
-
nightLead: '
|
|
21
|
+
nightLead: '茶还热着,她的手在门闩上停了一会儿。接下来怎样,听她的。',
|
|
22
22
|
morningLead: '天刚透进窗纸,昨夜没说尽的话已经到了门外。',
|
|
23
23
|
banquetLead: '五只杯子都等着。这一次若把功劳、财权或证词分偏了,夜里就有一扇门先落闩。',
|
|
24
24
|
galleryEmpty: '册子还是空的。等有人肯把门留给你,再来看。',
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
"description": "《金瓶梅·风月总账》20日男性第一人称成人后宫关系模拟:五条深线、五院共约、联院协力与公开问责",
|
|
6
6
|
"scripts": {
|
|
7
7
|
"start": "python3 -m http.server 5173",
|
|
8
|
-
"verify:model": "node ../../qa/verify_playable_model.mjs"
|
|
8
|
+
"verify:model": "node ../../qa/verify_playable_model.mjs",
|
|
9
|
+
"verify:visual": "python3 test/verify_visual.py --write-evidence"
|
|
9
10
|
}
|
|
10
11
|
}
|
|
@@ -1,24 +1,19 @@
|
|
|
1
1
|
# QA 证据说明
|
|
2
2
|
|
|
3
|
-
本 example
|
|
3
|
+
本 example 的当前权威 QA 命令是:
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
- 390×844、768×1024、1024×768、1280×800、1920×1080、2048×973 六个视口都走到结局;主游玩面板实际溢出为 0,浏览器错误数组为空。
|
|
10
|
-
- 年龄确认前只请求封面与宅院两张启动图,共 653,950 bytes;成人内容资源请求为 0。
|
|
11
|
-
- 临时请求拦截分别阻断一张安全关键图和一张持久场景册成人图,两条路径都进入 `#asset-error`,没有继续显示残页。
|
|
5
|
+
```bash
|
|
6
|
+
cd examples/jin-ping-mei/build/app
|
|
7
|
+
python3 test/verify_visual.py --write-evidence
|
|
8
|
+
```
|
|
12
9
|
|
|
13
|
-
|
|
10
|
+
命令开始时先把 `verification.json` 原子写为未运行的失败态,完整路径成功后才原子更新
|
|
11
|
+
`verification.json` 与严格 schema-v1 `evidence/run.json`。浏览器由脚本自启临时本地服务;全部推进使用
|
|
12
|
+
Playwright `locator.click` 且不使用 `force`,DOM 只负责读取下一项可见可行控件的 selector。
|
|
14
13
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
失败。仓内 `final-screen.webp` 保留表现层复核前的终局基线;复核后的桌面角色抉择、桌面终局和
|
|
19
|
-
390×844 终局分别见 `evidence/visual-review-dialogue-after.jpg`、
|
|
20
|
-
`evidence/visual-review-ending-after.jpg` 与 `evidence/visual-review-ending-mobile-after.jpg`。权威正常速度
|
|
21
|
-
完整路径仍明确属于上面的 2026-08-22 候选。
|
|
14
|
+
当前路径覆盖年龄门、五院标题、五档标题/首日布局、首日真实夜访与选择后章,再以每屏第一项可行
|
|
15
|
+
主动作快速到达一个第二十日失稳结局并换 seed 重开。只保留标题、首日、夜访和结局四张代表图;该路径不
|
|
16
|
+
穷举其他结局,不复测全键盘或五档完整二十日矩阵,也不证明主观吸引力。
|
|
22
17
|
|
|
23
18
|
仓内另提供一条不依赖浏览器的可执行改编合同:
|
|
24
19
|
|
|
@@ -2,14 +2,14 @@
|
|
|
2
2
|
"schemaVersion": 3,
|
|
3
3
|
"status": "PASS",
|
|
4
4
|
"verify": {
|
|
5
|
-
"command": "
|
|
5
|
+
"command": "cd examples/jin-ping-mei/build/app && python3 test/verify_visual.py --write-evidence",
|
|
6
6
|
"exitCode": 0
|
|
7
7
|
},
|
|
8
8
|
"completeRun": {
|
|
9
|
-
"id": "jin-ping-mei-
|
|
9
|
+
"id": "jin-ping-mei-visual-reconstruction-2026-09-05",
|
|
10
10
|
"cleanContext": true,
|
|
11
11
|
"terminal": "day-20-unstable-ending",
|
|
12
|
-
"restart": "day-1-opening",
|
|
12
|
+
"restart": "day-1-opening-new-seed",
|
|
13
13
|
"evidence": "qa/evidence/run.json"
|
|
14
14
|
},
|
|
15
15
|
"checks": {
|
|
@@ -22,20 +22,20 @@
|
|
|
22
22
|
},
|
|
23
23
|
"limitations": [
|
|
24
24
|
{
|
|
25
|
-
"scope": "
|
|
26
|
-
"reason": "
|
|
25
|
+
"scope": "路径覆盖",
|
|
26
|
+
"reason": "快速路径在每屏选择第一项可行主动作,只到达一个失稳结局;没有穷举其他选项或结局。"
|
|
27
27
|
},
|
|
28
28
|
{
|
|
29
|
-
"scope": "
|
|
30
|
-
"reason": "
|
|
29
|
+
"scope": "输入覆盖",
|
|
30
|
+
"reason": "本次使用 Playwright locator.click 且不使用 force;没有复测 2026-08 的全键盘正常速度路径。"
|
|
31
31
|
},
|
|
32
32
|
{
|
|
33
|
-
"scope": "
|
|
34
|
-
"reason": "
|
|
33
|
+
"scope": "响应式覆盖",
|
|
34
|
+
"reason": "五档视口覆盖标题与首日,390×844 和 1280×800 覆盖夜访,1280×800 覆盖本次结局;未声明五档完整二十日矩阵。"
|
|
35
35
|
},
|
|
36
36
|
{
|
|
37
|
-
"scope": "
|
|
38
|
-
"reason": "
|
|
37
|
+
"scope": "体验判断",
|
|
38
|
+
"reason": "自动化只证明当前候选可启动、渲染、输入、走到结果并重开,不判断主观吸引力、长期平衡或其他浏览器。"
|
|
39
39
|
}
|
|
40
40
|
]
|
|
41
41
|
}
|
|
@@ -11,6 +11,28 @@ const engine = await import(resolve(here, '../build/app/js/engine.js'));
|
|
|
11
11
|
|
|
12
12
|
const digest = (value) => createHash('sha256').update(JSON.stringify(value)).digest('hex');
|
|
13
13
|
const assertText = (value, label) => assert.equal(typeof value === 'string' && value.length > 0, true, label);
|
|
14
|
+
const ROUTE_RULES_DIGEST = '3b3c6bd6dc565ff05825d16738ef3a76ad87bd1392d7d149794dc55ff8c005fa';
|
|
15
|
+
|
|
16
|
+
function routeRulesProjection() {
|
|
17
|
+
const route = Object.fromEntries(Object.entries(data.ROUTE_CHOICES).map(([heroine, steps]) => [
|
|
18
|
+
heroine,
|
|
19
|
+
steps.map((choices) => choices.map((choice) => ({
|
|
20
|
+
id: choice.id,
|
|
21
|
+
effects: choice.effects,
|
|
22
|
+
condition: choice.condition ?? null,
|
|
23
|
+
}))),
|
|
24
|
+
]));
|
|
25
|
+
const accord = Object.fromEntries(Object.entries(data.ACCORD_CHOICES).map(([heroine, choice]) => [
|
|
26
|
+
heroine,
|
|
27
|
+
{ id: choice.id, effects: choice.effects, condition: choice.condition ?? null },
|
|
28
|
+
]));
|
|
29
|
+
const shared = data.SHARED_NIGHT_CHOICES.map((choice) => ({
|
|
30
|
+
id: choice.id,
|
|
31
|
+
effects: choice.effects ?? null,
|
|
32
|
+
condition: choice.condition ?? null,
|
|
33
|
+
}));
|
|
34
|
+
return { route, accord, shared };
|
|
35
|
+
}
|
|
14
36
|
|
|
15
37
|
function finishOpening(state) {
|
|
16
38
|
let beats = 0;
|
|
@@ -119,6 +141,7 @@ for (const [index, path] of Object.values(contract.validationPaths).entries()) {
|
|
|
119
141
|
assert.deepEqual(Object.keys(data.DAY_ACTIONS), contract.stateSurface.dayActions);
|
|
120
142
|
assert.deepEqual(data.HEROINE_IDS, contract.stateSurface.relationshipActors);
|
|
121
143
|
assert.deepEqual(data.OPENING_CHOICES.map(({ id }) => id), contract.openingActions.map(({ id }) => id));
|
|
144
|
+
assert.equal(digest(routeRulesProjection()), ROUTE_RULES_DIGEST, '文案改写不得改动五条路线、五院约或共享终夜的 id/condition/effects');
|
|
122
145
|
|
|
123
146
|
const firstRuns = contract.openingActions.map(runOpening);
|
|
124
147
|
const replayRuns = contract.openingActions.map(runOpening);
|