@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.
Files changed (197) hide show
  1. package/README.md +12 -12
  2. package/lib/client.js +204 -72
  3. package/lib/client.js.map +3 -3
  4. package/lib/drama/manifest.json +84 -74
  5. package/lib/drama/skills/short-drama/SKILL.md +2 -2
  6. package/lib/drama/skills/short-drama/scripts/creator_markdown_check.py +14 -0
  7. package/lib/drama/skills/short-drama-assets/references/character-and-look.md +10 -0
  8. package/lib/drama/skills/short-drama-assets/references/continuity-lock.md +6 -9
  9. package/lib/drama/skills/short-drama-assets/references/voice-direction.md +28 -0
  10. package/lib/drama/skills/short-drama-edit/SKILL.md +27 -29
  11. package/lib/drama/skills/short-drama-edit/assets//345/211/252/350/276/221/345/215/225.md +2 -2
  12. package/lib/drama/skills/short-drama-edit/references/cut-craft.md +24 -67
  13. package/lib/drama/skills/short-drama-edit/references/delivery-verify.md +27 -56
  14. package/lib/drama/skills/short-drama-edit/references/generated-footage.md +37 -79
  15. package/lib/drama/skills/short-drama-edit/references/sound-and-subtitles.md +39 -92
  16. package/lib/drama/skills/short-drama-edit/references/stage-contract.md +12 -16
  17. package/lib/drama/skills/short-drama-edit/scripts/edit_tool.py +126 -11
  18. package/lib/drama/skills/short-drama-edit/scripts/selftest.py +64 -0
  19. package/lib/drama/skills/short-drama-image-prompts/SKILL.md +2 -0
  20. package/lib/drama/skills/short-drama-image-prompts/references/common-recipe.md +11 -0
  21. package/lib/drama/skills/short-drama-produce/SKILL.md +5 -1
  22. package/lib/drama/skills/short-drama-produce/references/adapter-contract.md +14 -20
  23. package/lib/drama/skills/short-drama-produce/references/providers/minimax-h3-video.md +8 -26
  24. package/lib/drama/skills/short-drama-produce/references/providers/minimax-speech.md +41 -0
  25. package/lib/drama/skills/short-drama-produce/scripts/provider_adapters.py +142 -1
  26. package/lib/drama/skills/short-drama-review/SKILL.md +2 -3
  27. package/lib/drama/skills/short-drama-review/references/production-quality-gates.md +6 -19
  28. package/lib/drama/skills/short-drama-review/references/rubric-visual-motion.md +9 -35
  29. package/lib/drama/skills/short-drama-storyboard/SKILL.md +10 -6
  30. package/lib/drama/skills/short-drama-storyboard/references/lighting-craft.md +39 -0
  31. package/lib/drama/skills/short-drama-storyboard/references/shot-craft.md +58 -21
  32. package/lib/drama/skills/short-drama-storyboard/references/stage-contract.md +1 -1
  33. package/lib/drama/skills/short-drama-video-prompts/SKILL.md +10 -9
  34. package/lib/drama/skills/short-drama-video-prompts/references/camera-audio-continuity.md +17 -0
  35. package/lib/drama/skills/short-drama-video-prompts/references/minimax-h3.md +12 -38
  36. package/lib/drama/skills/short-drama-video-prompts/references/motion-recipe.md +4 -7
  37. package/lib/drama/skills/short-drama-video-prompts/references/performance-action-timing.md +8 -22
  38. package/lib/drama/skills/short-drama-video-prompts/references/production-prompt-grammar.md +6 -19
  39. package/lib/drama/skills/short-drama-video-prompts/references/seedance-2.0.md +2 -3
  40. package/lib/drama/skills/short-drama-video-prompts/references/seedance-2.5.md +34 -9
  41. package/lib/drama/skills/short-drama-video-prompts/references/stage-contract.md +2 -2
  42. package/lib/drama/skills/short-drama-write/SKILL.md +2 -4
  43. package/lib/index.js +289 -91
  44. package/lib/index.js.map +4 -4
  45. package/lib/novel-to-game/README.md +299 -59
  46. package/lib/novel-to-game/README_ZH.md +299 -56
  47. package/lib/novel-to-game/VERSION +1 -1
  48. package/lib/novel-to-game/examples/jin-ping-mei/build/BUILD_BRIEF.md +3 -1
  49. package/lib/novel-to-game/examples/jin-ping-mei/build/app/RUN.md +11 -3
  50. package/lib/novel-to-game/examples/jin-ping-mei/build/app/css/new-guofeng.css +397 -8
  51. package/lib/novel-to-game/examples/jin-ping-mei/build/app/css/style.css +0 -31
  52. package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/data.js +71 -73
  53. package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/engine.js +78 -89
  54. package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/main.js +37 -14
  55. package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/text.js +5 -5
  56. package/lib/novel-to-game/examples/jin-ping-mei/build/app/package.json +2 -1
  57. package/lib/novel-to-game/examples/jin-ping-mei/qa/README.md +11 -16
  58. package/lib/novel-to-game/examples/jin-ping-mei/qa/verification.json +11 -11
  59. package/lib/novel-to-game/examples/jin-ping-mei/qa/verify_playable_model.mjs +23 -0
  60. package/lib/novel-to-game/manifest.json +77 -87
  61. package/lib/novel-to-game/skills/game-art-direction/SKILL.md +7 -12
  62. package/lib/novel-to-game/skills/game-art-direction/references/art-direction-method.md +27 -106
  63. package/lib/novel-to-game/skills/game-build/SKILL.md +13 -14
  64. package/lib/novel-to-game/skills/game-build/references/blender-asset-workflow.md +24 -0
  65. package/lib/novel-to-game/skills/game-build/references/build-brief-contract.md +45 -82
  66. package/lib/novel-to-game/skills/game-build/references/generative-media-pipeline.md +14 -45
  67. package/lib/novel-to-game/skills/game-build/references/playable-model-contract.md +30 -65
  68. package/lib/novel-to-game/skills/game-build/references/tts-production-contract.md +13 -27
  69. package/lib/novel-to-game/skills/game-concept/SKILL.md +13 -15
  70. package/lib/novel-to-game/skills/game-concept/references/concept-method.md +28 -75
  71. package/lib/novel-to-game/skills/game-qa/SKILL.md +6 -7
  72. package/lib/novel-to-game/skills/game-qa/references/test-design-method.md +9 -5
  73. package/lib/novel-to-game/skills/game-world-design/SKILL.md +12 -16
  74. package/lib/novel-to-game/skills/game-world-design/references/narrative-design-method.md +23 -72
  75. package/lib/novel-to-game/skills/game-world-design/references/numeric-design-method.md +14 -48
  76. package/lib/novel-to-game/skills/game-world-design/references/world-design-method.md +36 -103
  77. package/lib/novel-to-game/skills/novel-game-analyze/SKILL.md +4 -10
  78. package/lib/novel-to-game/skills/novel-game-analyze/references/gameability-protocol.md +4 -4
  79. package/lib/novel-to-game/skills/novel-to-game/SKILL.md +9 -21
  80. package/lib/novel-to-game/skills/novel-to-game/references/intake-method.md +5 -4
  81. package/lib/novel-to-game/skills/novel-to-game/references/pipeline-contract.md +5 -8
  82. package/lib/oh-story/manifest.json +230 -185
  83. package/lib/oh-story/roles/chapter-extractor.md +177 -237
  84. package/lib/oh-story/roles/consistency-checker.md +4 -4
  85. package/lib/oh-story/roles/narrative-writer.md +24 -25
  86. package/lib/oh-story/roles/story-architect.md +5 -5
  87. package/lib/oh-story/roles/story-explorer.md +11 -11
  88. package/lib/oh-story/skills/story/SKILL.md +13 -7
  89. package/lib/oh-story/skills/story/VERSION +1 -1
  90. package/lib/oh-story/skills/story/references/author-memory.md +54 -24
  91. package/lib/oh-story/skills/story/scripts/author_memory_commit.py +934 -170
  92. package/lib/oh-story/skills/story-cover/SKILL.md +10 -1
  93. package/lib/oh-story/skills/story-deslop/SKILL.md +40 -53
  94. package/lib/oh-story/skills/story-deslop/references/anti-ai-writing.md +11 -10
  95. package/lib/oh-story/skills/story-deslop/references/author-memory.md +54 -24
  96. package/lib/oh-story/skills/story-deslop/references/deslop-gates.md +1 -1
  97. package/lib/oh-story/skills/story-deslop/scripts/author_memory_commit.py +934 -170
  98. package/lib/oh-story/skills/story-import/SKILL.md +77 -81
  99. package/lib/oh-story/skills/story-import/references/character-state-reverse.md +1 -1
  100. package/lib/oh-story/skills/story-import/references/state-tracking.md +4 -69
  101. package/lib/oh-story/skills/story-import/references/structure-mapping-long.md +10 -8
  102. package/lib/oh-story/skills/story-import/references/tracking-transaction.md +8 -6
  103. package/lib/oh-story/skills/story-import/scripts/check-outline-contract.js +34 -16
  104. package/lib/oh-story/skills/story-import/scripts/tracking_commit.py +210 -2
  105. package/lib/oh-story/skills/story-import/scripts/wordcount_core.py +3 -0
  106. package/lib/oh-story/skills/story-long-analyze/SKILL.md +91 -258
  107. package/lib/oh-story/skills/story-long-analyze/references/author-facing.md +245 -0
  108. package/lib/oh-story/skills/story-long-analyze/references/deconstruction-notes.md +2 -2
  109. package/lib/oh-story/skills/story-long-analyze/references/inspiration-library.md +105 -0
  110. package/lib/oh-story/skills/story-long-analyze/references/material-decomposition.md +113 -105
  111. package/lib/oh-story/skills/story-long-analyze/references/output-templates.md +169 -125
  112. package/lib/oh-story/skills/story-long-analyze/references/pipeline-ops.md +172 -64
  113. package/lib/oh-story/skills/story-long-analyze/references/semantic-acceptance-fixtures.md +84 -0
  114. package/lib/oh-story/skills/story-long-analyze/references/style-profile-generator.md +42 -132
  115. package/lib/oh-story/skills/story-long-analyze/references/style-profile-protocol.md +5 -5
  116. package/lib/oh-story/skills/story-long-analyze/scripts/build_chapter_index.py +466 -0
  117. package/lib/oh-story/skills/story-long-analyze/scripts/inspect_existing_assets.py +702 -0
  118. package/lib/oh-story/skills/story-long-analyze/scripts/inspiration_index.py +864 -0
  119. package/lib/oh-story/skills/story-long-analyze/scripts/manage_analysis_run.py +1106 -0
  120. package/lib/oh-story/skills/story-long-analyze/scripts/render_relation_chart.py +425 -0
  121. package/lib/oh-story/skills/story-long-scan/SKILL.md +4 -1
  122. package/lib/oh-story/skills/story-long-write/SKILL.md +38 -29
  123. package/lib/oh-story/skills/story-long-write/references/anti-ai-writing.md +11 -10
  124. package/lib/oh-story/skills/story-long-write/references/artifact-protocols.md +25 -20
  125. package/lib/oh-story/skills/story-long-write/references/author-memory.md +54 -24
  126. package/lib/oh-story/skills/story-long-write/references/benchmark-recall.md +3 -3
  127. package/lib/oh-story/skills/story-long-write/references/cross-book-recall.md +26 -0
  128. package/lib/oh-story/skills/story-long-write/references/dialogue-mastery.md +4 -0
  129. package/lib/oh-story/skills/story-long-write/references/emotional-methods.md +2 -2
  130. package/lib/oh-story/skills/story-long-write/references/long-chapter-quality.md +4 -13
  131. package/lib/oh-story/skills/story-long-write/references/outline-structure-theory.md +1 -1
  132. package/lib/oh-story/skills/story-long-write/references/project-files.md +9 -7
  133. package/lib/oh-story/skills/story-long-write/references/reader-contract-and-progression.md +12 -4
  134. package/lib/oh-story/skills/story-long-write/references/reference-index.md +1 -2
  135. package/lib/oh-story/skills/story-long-write/references/tracking-transaction.md +8 -6
  136. package/lib/oh-story/skills/story-long-write/references/workflow-chapter.md +86 -115
  137. package/lib/oh-story/skills/story-long-write/references/workflow-daily.md +55 -120
  138. package/lib/oh-story/skills/story-long-write/references/workflow-revision.md +15 -11
  139. package/lib/oh-story/skills/story-long-write/references/workflow-setup.md +66 -68
  140. package/lib/oh-story/skills/story-long-write/references/writer-prompt-fallback.md +3 -3
  141. package/lib/oh-story/skills/story-long-write/references/writing-craft.md +4 -12
  142. package/lib/oh-story/skills/story-long-write/scripts/author_memory_commit.py +934 -170
  143. package/lib/oh-story/skills/story-long-write/scripts/build_writer_prompt.py +139 -18
  144. package/lib/oh-story/skills/story-long-write/scripts/check-outline-contract.js +34 -16
  145. package/lib/oh-story/skills/story-long-write/scripts/outline_view.py +5 -3
  146. package/lib/oh-story/skills/story-long-write/scripts/storyctl.py +58 -0
  147. package/lib/oh-story/skills/story-long-write/scripts/tracking_commit.py +210 -2
  148. package/lib/oh-story/skills/story-long-write/scripts/wordcount_core.py +3 -0
  149. package/lib/oh-story/skills/story-review/SKILL.md +68 -85
  150. package/lib/oh-story/skills/story-review/references/anti-ai-writing.md +11 -10
  151. package/lib/oh-story/skills/story-review/references/author-memory.md +54 -24
  152. package/lib/oh-story/skills/story-review/references/dialogue-mastery.md +4 -0
  153. package/lib/oh-story/skills/story-review/references/review-quality.md +4 -10
  154. package/lib/oh-story/skills/story-review/references/tracking-transaction.md +8 -6
  155. package/lib/oh-story/skills/story-review/scripts/author_memory_commit.py +934 -170
  156. package/lib/oh-story/skills/story-review/scripts/tracking_commit.py +210 -2
  157. package/lib/oh-story/skills/story-review/scripts/wordcount_core.py +3 -0
  158. package/lib/oh-story/skills/story-setup/SKILL.md +50 -42
  159. package/lib/oh-story/skills/story-setup/references/agent-references/anti-ai-writing.md +11 -10
  160. package/lib/oh-story/skills/story-setup/references/agent-references/deslop-gates.md +1 -1
  161. package/lib/oh-story/skills/story-setup/references/agent-references/dialogue-mastery.md +4 -0
  162. package/lib/oh-story/skills/story-setup/references/agent-references/long-emotional-methods.md +2 -2
  163. package/lib/oh-story/skills/story-setup/references/agent-references/long-quality.md +1 -1
  164. package/lib/oh-story/skills/story-setup/references/agent-references/writing-craft.md +4 -12
  165. package/lib/oh-story/skills/story-setup/references/diagnostics.md +1 -1
  166. package/lib/oh-story/skills/story-short-analyze/SKILL.md +22 -3
  167. package/lib/oh-story/skills/story-short-analyze/references/material-decomposition.md +5 -1
  168. package/lib/oh-story/skills/story-short-scan/SKILL.md +5 -1
  169. package/lib/oh-story/skills/story-short-write/SKILL.md +6 -147
  170. package/lib/oh-story/skills/story-short-write/references/author-memory.md +54 -24
  171. package/lib/oh-story/skills/story-short-write/references/dialogue-mastery.md +4 -0
  172. package/lib/oh-story/skills/story-short-write/references/reference-index.md +2 -0
  173. package/lib/oh-story/skills/story-short-write/references/workflow-design.md +3 -2
  174. package/lib/oh-story/skills/story-short-write/references/workflow-draft.md +126 -0
  175. package/lib/oh-story/skills/story-short-write/references/workflow-revision.md +32 -0
  176. package/lib/oh-story/skills/story-short-write/scripts/author_memory_commit.py +934 -170
  177. package/lib/types/client/drama-production-view.d.ts.map +1 -1
  178. package/lib/types/client/file-activity.d.ts +19 -1
  179. package/lib/types/client/file-activity.d.ts.map +1 -1
  180. package/lib/types/client/index.d.ts.map +1 -1
  181. package/lib/types/client/production-prompts.d.ts +7 -0
  182. package/lib/types/client/production-prompts.d.ts.map +1 -1
  183. package/lib/types/client/production-runtime.d.ts +16 -0
  184. package/lib/types/client/production-runtime.d.ts.map +1 -1
  185. package/lib/types/drama-adapters.d.ts +5 -3
  186. package/lib/types/drama-adapters.d.ts.map +1 -1
  187. package/lib/types/native-hooks.d.ts +52 -0
  188. package/lib/types/native-hooks.d.ts.map +1 -1
  189. package/lib/types/role-identity.d.ts +17 -0
  190. package/lib/types/role-identity.d.ts.map +1 -0
  191. package/lib/types/role-tool.d.ts.map +1 -1
  192. package/lib/types/skill-provider.d.ts.map +1 -1
  193. package/package.json +45 -45
  194. package/lib/novel-to-game/skills/game-world-design/references/dialogue-design-method.md +0 -58
  195. package/lib/novel-to-game/skills/game-world-design/references/game-writing-craft.md +0 -53
  196. package/lib/novel-to-game/skills/novel-to-game/references/intake-benchmark-reference.md +0 -38
  197. 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
- ${phaseHeader('第一日 · 正堂', '五十两银子不见了', '月娘守着账簿,金莲把酒送到你手边。瓶儿的钥匙、玉楼的名帖和雪娥的米账,都会在今后二十日进入这本总账。')}
907
- <p class="speaker-line">月娘:“真账留下。” 金莲:“人也留下,先喝我这杯。”</p>
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
- ${phaseHeader('白日', def.name, `${def.pressure} ${def.tell}`)}
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 ? '危局已收' : '危局漏口'}</span><b>${beat.resolved ? '这一步给后宫关系网留下一条能继续追的线。' : '得到的眼前收益仍在,但真正的对手已经换了下一手。'}</b></div>`;
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('第二十夜 · 五人都没走', '总账还在桌上,五处院门的钥匙都在各自手里', TEXT.sharedNightLead)}
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, ['联院差事尚未成形', '已有搭档真正共事', '五组搭档都已共事'])} · ${narrativeProgress(state.resolvedPressures.length, E.PRESSURE_TARGET, ['危局仍压着宅门', '多桩危局已经收住', '宅门经得住最后一问'])}</p>
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">夜还在往下走 · ${narrativeStep(state.sharedAfterglowChoices.length + 1, SHARED_AFTERGLOW_BEATS.length)} · 每句话都会有人接</p>
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
- ${phaseHeader(`${h.house} · ${h.shape}`, h.name, h.voice)}
1775
- <p class="want-line">${h.want}<br/><span>${h.gives}</span><br/><small>这条线已记:${stance.covenant === stance.private ? (stance.covenant ? '共同承担与私下情分仍在拉扯' : '两种相处都还没有定形') : stance.covenant > stance.private ? '共同承担渐成主线' : '私下情分渐成主线'}</small></p>
1776
- ${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>` : ''}
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">这件事已经越过一扇院门。你现在决定它怎样进入另外一个人的生活。</p>
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>这章实际改变</span><b>${escapeHtml(result.stake.label)} · ${escapeHtml(narrativeChoiceMeta(result.stake.resourceText))}</b><p>${escapeHtml(result.stake.text)}</p></aside>
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
- ${phaseHeader('夜深了', h.name, TEXT.nightLead)}
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>${end.text}</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
- 2026-08-22 的最终候选完成了以下验证:
6
-
7
- - 逻辑账本基线 `d3f4087` 为 320/320;基线后唯一的引擎差异是把隐藏的关系数值摘要改成定性文案,当前候选已用定向断言复核。
8
- - Chromium 145 在正常速度下,以全键盘 Enter 激活 551 个可见控件,经过 552 个唯一状态到达第二十日结局,并成功重开到第一日。
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
- 压缩结果见 `verification.json` 与 `evidence/run.json`。这些证据不证明主观趣味、长期平衡、其他浏览器或所有结局。
10
+ 命令开始时先把 `verification.json` 原子写为未运行的失败态,完整路径成功后才原子更新
11
+ `verification.json` 与严格 schema-v1 `evidence/run.json`。浏览器由脚本自启临时本地服务;全部推进使用
12
+ Playwright `locator.click` 且不使用 `force`,DOM 只负责读取下一项可见可行控件的 selector。
14
13
 
15
- 2026-08-25 的表现层重塑没有改规则、状态、存档或完成条件,因此没有把旧的正常速度完整路径伪装成
16
- 一次新跑。当前构建另做了 566 次可见控件的快速视觉烟测,重新到达第二十日结局;41 张运行图全部
17
- 解码,1280×800、1600×800 与 390×844 的年龄门/首屏和桌面关键流程无水平溢出、控制台错误或请求
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": "临时外部验证器:d3 账本基线 + 当前引擎定性摘要 + 关键资源失败拦截 + Chromium 当前候选全键盘路径 + 六视口布局路径",
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-release-candidate-2026-08-22",
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": "320/320 来自 d3f4087 基线;其后唯一引擎差异是将隐藏的数值关系摘要改为定性文案,当前候选已做定向断言。"
25
+ "scope": "路径覆盖",
26
+ "reason": "快速路径在每屏选择第一项可行主动作,只到达一个失稳结局;没有穷举其他选项或结局。"
27
27
  },
28
28
  {
29
- "scope": "浏览器与路径",
30
- "reason": "当前候选在本机 Chromium 完成正常速度键盘主路径和六个目标视口;未穷举其他浏览器与所有结局。"
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": "2026-08-25 表现层重塑",
38
- "reason": "当前视觉候选完成三视口关键流程与快速结局烟测;没有把它表述为一次新的正常速度全路径权威验证,原完整路径证据仍属于 2026-08-22 候选。"
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);