prts-terrarchive 0.2.0

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 (287) hide show
  1. package/GAME_ASSETS.md +26 -0
  2. package/LICENSE +21 -0
  3. package/README.en.md +274 -0
  4. package/README.md +423 -0
  5. package/THIRD_PARTY_NOTICES.md +61 -0
  6. package/bin/bake-rhine-details.mjs +34 -0
  7. package/bin/install.js +273 -0
  8. package/bin/pack-map-assets.mjs +110 -0
  9. package/bin/preview-rhine.mjs +71 -0
  10. package/bin/rhine-detail-baker.ts +84 -0
  11. package/contracts/cloud_tools_v1.schema.json +243 -0
  12. package/contracts/corpus_i18n_v1.schema.json +179 -0
  13. package/contracts/corpus_tools_v1.schema.json +1801 -0
  14. package/cordis.patch.yml +32 -0
  15. package/docs/endfield-official-i18n.md +103 -0
  16. package/lib/client.js +3912 -0
  17. package/lib/endfield-map/map.js.br +0 -0
  18. package/lib/endfield-map/map.js.gz +0 -0
  19. package/lib/endfield-map/resources/map-00b0d0744a1b4404.json.br +0 -0
  20. package/lib/endfield-map/resources/map-00b0d0744a1b4404.json.gz +0 -0
  21. package/lib/endfield-map/resources/map-01616f534aaaeca1.png +0 -0
  22. package/lib/endfield-map/resources/map-1562e0dff4be3d23.png +0 -0
  23. package/lib/endfield-map/resources/map-160fe040f080f6a3.png +0 -0
  24. package/lib/endfield-map/resources/map-17b9d8be25dc2b39.json.br +0 -0
  25. package/lib/endfield-map/resources/map-17b9d8be25dc2b39.json.gz +0 -0
  26. package/lib/endfield-map/resources/map-1c4245c4e8b80c43.json.br +0 -0
  27. package/lib/endfield-map/resources/map-1c4245c4e8b80c43.json.gz +0 -0
  28. package/lib/endfield-map/resources/map-1d30baaaef3e29cc.png +0 -0
  29. package/lib/endfield-map/resources/map-21a61386af79cd12.json.br +0 -0
  30. package/lib/endfield-map/resources/map-21a61386af79cd12.json.gz +0 -0
  31. package/lib/endfield-map/resources/map-24b920b8dab49b44.json.br +0 -0
  32. package/lib/endfield-map/resources/map-24b920b8dab49b44.json.gz +0 -0
  33. package/lib/endfield-map/resources/map-26caf8d71247abb7.png +0 -0
  34. package/lib/endfield-map/resources/map-2729942c89545e73.json.br +0 -0
  35. package/lib/endfield-map/resources/map-2729942c89545e73.json.gz +0 -0
  36. package/lib/endfield-map/resources/map-29063a9802f67014.json.br +0 -0
  37. package/lib/endfield-map/resources/map-29063a9802f67014.json.gz +0 -0
  38. package/lib/endfield-map/resources/map-2f35d09a3ab60a63.json.br +0 -0
  39. package/lib/endfield-map/resources/map-2f35d09a3ab60a63.json.gz +0 -0
  40. package/lib/endfield-map/resources/map-2f98a873da81425e.json.br +0 -0
  41. package/lib/endfield-map/resources/map-2f98a873da81425e.json.gz +0 -0
  42. package/lib/endfield-map/resources/map-3ab978ae44190f80.json.br +0 -0
  43. package/lib/endfield-map/resources/map-3ab978ae44190f80.json.gz +0 -0
  44. package/lib/endfield-map/resources/map-3b62c80d14208902.json.br +0 -0
  45. package/lib/endfield-map/resources/map-3b62c80d14208902.json.gz +0 -0
  46. package/lib/endfield-map/resources/map-3d486e887ae121ac.png +0 -0
  47. package/lib/endfield-map/resources/map-418dc4865608fce5.png +0 -0
  48. package/lib/endfield-map/resources/map-4366b84607335f76.json.br +0 -0
  49. package/lib/endfield-map/resources/map-4366b84607335f76.json.gz +0 -0
  50. package/lib/endfield-map/resources/map-4371259d3ab7aac7.png +0 -0
  51. package/lib/endfield-map/resources/map-48dcc0fd55e13f7f.png +0 -0
  52. package/lib/endfield-map/resources/map-4aabda0e41197570.json.br +0 -0
  53. package/lib/endfield-map/resources/map-4aabda0e41197570.json.gz +0 -0
  54. package/lib/endfield-map/resources/map-505f5687b287043c.json.br +0 -0
  55. package/lib/endfield-map/resources/map-505f5687b287043c.json.gz +0 -0
  56. package/lib/endfield-map/resources/map-5177e9d1d46f47e2.png +0 -0
  57. package/lib/endfield-map/resources/map-51baeb59b7f8c28d.png +0 -0
  58. package/lib/endfield-map/resources/map-549561b46e621874.png +0 -0
  59. package/lib/endfield-map/resources/map-56c6a0bef799c8dc.json.br +0 -0
  60. package/lib/endfield-map/resources/map-56c6a0bef799c8dc.json.gz +0 -0
  61. package/lib/endfield-map/resources/map-5a6963909423b674.png +0 -0
  62. package/lib/endfield-map/resources/map-5a9c533c0552d7b7.png +0 -0
  63. package/lib/endfield-map/resources/map-5d20ae9ffa9a02b0.png +0 -0
  64. package/lib/endfield-map/resources/map-626c8847393f60bf.png +0 -0
  65. package/lib/endfield-map/resources/map-63d43cbec3bed3b8.png +0 -0
  66. package/lib/endfield-map/resources/map-661b6ec56e722574.png +0 -0
  67. package/lib/endfield-map/resources/map-6ddd384eca190828.png +0 -0
  68. package/lib/endfield-map/resources/map-6fc1aa9144f8156c.json.br +0 -0
  69. package/lib/endfield-map/resources/map-6fc1aa9144f8156c.json.gz +0 -0
  70. package/lib/endfield-map/resources/map-738ebaa002c33cfc.png +0 -0
  71. package/lib/endfield-map/resources/map-758c13be6085ec07.json.br +0 -0
  72. package/lib/endfield-map/resources/map-758c13be6085ec07.json.gz +0 -0
  73. package/lib/endfield-map/resources/map-78929f26d6844509.png +0 -0
  74. package/lib/endfield-map/resources/map-7f09c142950a0f74.png +0 -0
  75. package/lib/endfield-map/resources/map-8385e11f39e1a51c.json.br +0 -0
  76. package/lib/endfield-map/resources/map-8385e11f39e1a51c.json.gz +0 -0
  77. package/lib/endfield-map/resources/map-881343926a568f27.json.br +0 -0
  78. package/lib/endfield-map/resources/map-881343926a568f27.json.gz +0 -0
  79. package/lib/endfield-map/resources/map-8d1ade3f01d952b0.png +0 -0
  80. package/lib/endfield-map/resources/map-8fef3495bb7ed60f.png +0 -0
  81. package/lib/endfield-map/resources/map-907a7ecf8a0ce352.png +0 -0
  82. package/lib/endfield-map/resources/map-9113d5951c4db903.json.br +0 -0
  83. package/lib/endfield-map/resources/map-9113d5951c4db903.json.gz +0 -0
  84. package/lib/endfield-map/resources/map-94ad6634436224e3.json.br +0 -0
  85. package/lib/endfield-map/resources/map-94ad6634436224e3.json.gz +0 -0
  86. package/lib/endfield-map/resources/map-99722e61da5f3e53.png +0 -0
  87. package/lib/endfield-map/resources/map-9be9596cc059abe3.png +0 -0
  88. package/lib/endfield-map/resources/map-a4db9a4763e47fbf.png +0 -0
  89. package/lib/endfield-map/resources/map-a4e8ef07159997e7.json.br +0 -0
  90. package/lib/endfield-map/resources/map-a4e8ef07159997e7.json.gz +0 -0
  91. package/lib/endfield-map/resources/map-ab35d06875ee418f.png +0 -0
  92. package/lib/endfield-map/resources/map-ac51bb3e7623cd8b.png +0 -0
  93. package/lib/endfield-map/resources/map-ac830a66296bc83a.png +0 -0
  94. package/lib/endfield-map/resources/map-b3fd04209718e55e.json.br +0 -0
  95. package/lib/endfield-map/resources/map-b3fd04209718e55e.json.gz +0 -0
  96. package/lib/endfield-map/resources/map-b8164f1af7c5d895.png +0 -0
  97. package/lib/endfield-map/resources/map-b8cde93b965384d8.json.br +0 -0
  98. package/lib/endfield-map/resources/map-b8cde93b965384d8.json.gz +0 -0
  99. package/lib/endfield-map/resources/map-b8e48511502e0e8d.json.br +0 -0
  100. package/lib/endfield-map/resources/map-b8e48511502e0e8d.json.gz +0 -0
  101. package/lib/endfield-map/resources/map-bfc58277d5ba649d.png +0 -0
  102. package/lib/endfield-map/resources/map-c013f82048c6891a.png +0 -0
  103. package/lib/endfield-map/resources/map-c49c947841ae4ab5.json.br +0 -0
  104. package/lib/endfield-map/resources/map-c49c947841ae4ab5.json.gz +0 -0
  105. package/lib/endfield-map/resources/map-c4d1b1c6d6706c81.json.br +0 -0
  106. package/lib/endfield-map/resources/map-c4d1b1c6d6706c81.json.gz +0 -0
  107. package/lib/endfield-map/resources/map-c607f8b4c8ad8967.png +0 -0
  108. package/lib/endfield-map/resources/map-c6f20276b8781326.json.br +0 -0
  109. package/lib/endfield-map/resources/map-c6f20276b8781326.json.gz +0 -0
  110. package/lib/endfield-map/resources/map-d51adf5952af1e8e.png +0 -0
  111. package/lib/endfield-map/resources/map-d695eface22f8b74.json.br +0 -0
  112. package/lib/endfield-map/resources/map-d695eface22f8b74.json.gz +0 -0
  113. package/lib/endfield-map/resources/map-db11d394456a7465.png +0 -0
  114. package/lib/endfield-map/resources/map-dcc4d218581e3dc6.png +0 -0
  115. package/lib/endfield-map/resources/map-ddd43dbb8ab8a4e3.json.br +0 -0
  116. package/lib/endfield-map/resources/map-ddd43dbb8ab8a4e3.json.gz +0 -0
  117. package/lib/endfield-map/resources/map-defe5db7dabf748a.png +0 -0
  118. package/lib/endfield-map/resources/map-e1aeb833e22520aa.json.br +0 -0
  119. package/lib/endfield-map/resources/map-e1aeb833e22520aa.json.gz +0 -0
  120. package/lib/endfield-map/resources/map-e25ee9af6e493500.png +0 -0
  121. package/lib/endfield-map/resources/map-e3824082cd6f747d.json.br +0 -0
  122. package/lib/endfield-map/resources/map-e3824082cd6f747d.json.gz +0 -0
  123. package/lib/endfield-map/resources/map-e694a91e630b1bd6.png +0 -0
  124. package/lib/endfield-map/resources/map-eaa93af79d12a87d.png +0 -0
  125. package/lib/endfield-map/resources/map-ec9575ee65d7766f.json.br +0 -0
  126. package/lib/endfield-map/resources/map-ec9575ee65d7766f.json.gz +0 -0
  127. package/lib/endfield-map/resources/map-ede421a329dc78a4.png +0 -0
  128. package/lib/endfield-map/resources/map-eff3bce8f6888fc5.json.br +0 -0
  129. package/lib/endfield-map/resources/map-eff3bce8f6888fc5.json.gz +0 -0
  130. package/lib/endfield-map/resources/map-fb5f4a59a181452c.png +0 -0
  131. package/lib/endfield-map/resources/map-fdeaa17eeba90d8e.json.br +0 -0
  132. package/lib/endfield-map/resources/map-fdeaa17eeba90d8e.json.gz +0 -0
  133. package/lib/rhine/assets/archive-assembly.glb +0 -0
  134. package/lib/rhine/assets/archive-cassette.glb +0 -0
  135. package/lib/rhine/fonts/MiSans-Bold.woff2 +0 -0
  136. package/lib/rhine/fonts/MiSans-Demibold.woff2 +0 -0
  137. package/lib/rhine/fonts/MiSans-Light.woff2 +0 -0
  138. package/lib/rhine/fonts/MiSans-Regular.woff2 +0 -0
  139. package/lib/rhine/fonts/MiSans-license.pdf +0 -0
  140. package/lib/rhine/fonts/NOTICE.txt +6 -0
  141. package/lib/rhine/licenses/RhineLabUI-MIT.txt +21 -0
  142. package/lib/rhine/licenses/entities-BSD-2-Clause.txt +11 -0
  143. package/lib/rhine/licenses/linkify-it-MIT.txt +22 -0
  144. package/lib/rhine/licenses/markdown-it-MIT.txt +22 -0
  145. package/lib/rhine/licenses/mdurl-MIT.txt +45 -0
  146. package/lib/rhine/licenses/punycode.js-MIT.txt +20 -0
  147. package/lib/rhine/licenses/rolling-number-MIT.txt +21 -0
  148. package/lib/rhine/licenses/three-MIT.txt +21 -0
  149. package/lib/rhine/licenses/uc.micro-MIT.txt +20 -0
  150. package/lib/rhine/rhine.css +1 -0
  151. package/lib/rhine/rhine.js +5106 -0
  152. package/lib/skins/common.css +31 -0
  153. package/lib/skins/endfield-aic.css +141 -0
  154. package/lib/skins/prts-agent.css +136 -0
  155. package/lib/skins/rhine-lab.css +70 -0
  156. package/package.json +112 -0
  157. package/presets/definition.js +26 -0
  158. package/presets/prts/agent.cordis.yml +25 -0
  159. package/presets/prts/preset.yml +3 -0
  160. package/presets/register.js +86 -0
  161. package/resources/entity-routing.json +21 -0
  162. package/skills/prts-investigation/SKILL.md +121 -0
  163. package/skills/prts-retrieval/SKILL.md +35 -0
  164. package/skills/prts-retrieval/references/cloud-tools.md +13 -0
  165. package/skills/prts-retrieval/references/diagnostics.md +15 -0
  166. package/skills/prts-retrieval/references/local-tools.md +24 -0
  167. package/skills/prts-retrieval/references/module-arknights.md +23 -0
  168. package/skills/prts-retrieval/references/module-dual.md +15 -0
  169. package/skills/prts-retrieval/references/module-endfield.md +21 -0
  170. package/skills/prts-retrieval/references/retrieval-process.md +44 -0
  171. package/skills/prts-retrieval/references/retrieval-recipes-dual.md +34 -0
  172. package/skills/prts-retrieval/references/retrieval-recipes-endfield.md +37 -0
  173. package/skills/prts-retrieval/references/retrieval-recipes.md +39 -0
  174. package/skills/prts-retrieval/references/tools-runtime.md +48 -0
  175. package/skills/prts-retrieval/references/wiki-schema.md +38 -0
  176. package/src/archive-presentation.js +287 -0
  177. package/src/cloud-projection.js +124 -0
  178. package/src/cloud.js +498 -0
  179. package/src/entity-recognizer.js +311 -0
  180. package/src/entity-routing.js +82 -0
  181. package/src/evidence-state.js +332 -0
  182. package/src/i18n.js +365 -0
  183. package/src/index.js +1261 -0
  184. package/src/installer.js +1362 -0
  185. package/src/investigation-review.js +87 -0
  186. package/src/investigation-store.js +579 -0
  187. package/src/investigation-tools.js +113 -0
  188. package/src/localization-format.js +23 -0
  189. package/src/manual-search.js +103 -0
  190. package/src/read.js +1097 -0
  191. package/src/release-compatibility.js +73 -0
  192. package/src/release-watch.js +40 -0
  193. package/src/search-projection.js +139 -0
  194. package/src/search.js +1522 -0
  195. package/src/skill.js +98 -0
  196. package/src/source-map.js +348 -0
  197. package/src/state.js +314 -0
  198. package/src/store.js +1569 -0
  199. package/src/timeline.js +386 -0
  200. package/src/ui.js +1353 -0
  201. package/src/wiki.js +111 -0
  202. package/tsconfig.rhine.json +15 -0
  203. package/ui/rhine/archive-navigation-limit.ts +31 -0
  204. package/ui/rhine/archive-rack.ts +83 -0
  205. package/ui/rhine/assets/evidence-observatory.webp +0 -0
  206. package/ui/rhine/board-plane-transform.ts +24 -0
  207. package/ui/rhine/catalogue.ts +72 -0
  208. package/ui/rhine/deferred-preparation.ts +146 -0
  209. package/ui/rhine/evidence-board-controls.css +22 -0
  210. package/ui/rhine/evidence-board-history.ts +73 -0
  211. package/ui/rhine/evidence-board-model.ts +189 -0
  212. package/ui/rhine/evidence-board-panel.ts +762 -0
  213. package/ui/rhine/evidence-board-scene.ts +548 -0
  214. package/ui/rhine/evidence-board.css +191 -0
  215. package/ui/rhine/evidence-card-art.ts +470 -0
  216. package/ui/rhine/evidence-card-style.ts +17 -0
  217. package/ui/rhine/evidence-inbox-scene.ts +43 -0
  218. package/ui/rhine/evidence-inbox.css +29 -0
  219. package/ui/rhine/evidence-inbox.ts +104 -0
  220. package/ui/rhine/index.ts +12 -0
  221. package/ui/rhine/interaction.css +20 -0
  222. package/ui/rhine/investigation-board-plane.ts +5 -0
  223. package/ui/rhine/investigation-board.css +34 -0
  224. package/ui/rhine/investigation-board.ts +348 -0
  225. package/ui/rhine/investigation-edits.ts +46 -0
  226. package/ui/rhine/investigation-layout.ts +29 -0
  227. package/ui/rhine/label-prefetch.ts +34 -0
  228. package/ui/rhine/looping-title.ts +40 -0
  229. package/ui/rhine/manual-search.css +41 -0
  230. package/ui/rhine/manual-search.ts +89 -0
  231. package/ui/rhine/original/appearance.ts +180 -0
  232. package/ui/rhine/original/archive-label.ts +84 -0
  233. package/ui/rhine/original/archive-lighting.ts +39 -0
  234. package/ui/rhine/original/archive-loop.ts +82 -0
  235. package/ui/rhine/original/archive-refill.ts +64 -0
  236. package/ui/rhine/original/array-detail-data.ts +2 -0
  237. package/ui/rhine/original/array-detail.ts +41 -0
  238. package/ui/rhine/original/array-interior.ts +411 -0
  239. package/ui/rhine/original/array-occlusion.ts +166 -0
  240. package/ui/rhine/original/array-shell.ts +38 -0
  241. package/ui/rhine/original/array-visibility.ts +309 -0
  242. package/ui/rhine/original/asset-url.ts +4 -0
  243. package/ui/rhine/original/brand.ts +12 -0
  244. package/ui/rhine/original/cassette-lod.ts +81 -0
  245. package/ui/rhine/original/data.ts +21 -0
  246. package/ui/rhine/original/decryption.ts +185 -0
  247. package/ui/rhine/original/detail-signature.ts +18 -0
  248. package/ui/rhine/original/glass-reveal.ts +45 -0
  249. package/ui/rhine/original/internal-optics.ts +70 -0
  250. package/ui/rhine/original/label-renderer.ts +119 -0
  251. package/ui/rhine/original/label-textures.ts +117 -0
  252. package/ui/rhine/original/model-viewer.ts +658 -0
  253. package/ui/rhine/original/motion.ts +116 -0
  254. package/ui/rhine/original/opaque-backfaces.ts +64 -0
  255. package/ui/rhine/original/program-preparation.ts +111 -0
  256. package/ui/rhine/original/quality-renderer.ts +97 -0
  257. package/ui/rhine/original/quality-settings.ts +111 -0
  258. package/ui/rhine/original/render-quality.ts +163 -0
  259. package/ui/rhine/original/scene.ts +2051 -0
  260. package/ui/rhine/original/shelf-interior.ts +142 -0
  261. package/ui/rhine/original/ui-transitions.ts +111 -0
  262. package/ui/rhine/original/viewer-camera.ts +92 -0
  263. package/ui/rhine/original/viewport-layout.ts +41 -0
  264. package/ui/rhine/original-ui.css +2334 -0
  265. package/ui/rhine/readability.css +46 -0
  266. package/ui/rhine/reading-object.ts +96 -0
  267. package/ui/rhine/reading-origin.ts +28 -0
  268. package/ui/rhine/reading-room.css +55 -0
  269. package/ui/rhine/report-markdown.ts +98 -0
  270. package/ui/rhine/scene-edge-navigation.ts +84 -0
  271. package/ui/rhine/scene.ts +1726 -0
  272. package/ui/rhine/shelf-layout.ts +38 -0
  273. package/ui/rhine/snapshot-update-gate.ts +22 -0
  274. package/ui/rhine/source-reading.ts +15 -0
  275. package/ui/rhine/source-selection.css +164 -0
  276. package/ui/rhine/temporary-performance-observers.ts +163 -0
  277. package/ui/rhine/temporary-performance-panel.ts +124 -0
  278. package/ui/rhine/temporary-performance.css +19 -0
  279. package/ui/rhine/temporary-performance.ts +552 -0
  280. package/ui/rhine/temporary-render-diagnostics.ts +105 -0
  281. package/ui/rhine/tool-activity.ts +69 -0
  282. package/ui/rhine/types.ts +219 -0
  283. package/ui/rhine/visible-scene-hit.ts +13 -0
  284. package/ui/rhine/workbench.css +818 -0
  285. package/ui/rhine/workbench.ts +2245 -0
  286. package/ui/rhine/workspace-travel.ts +32 -0
  287. package/vite.rhine.config.ts +19 -0
@@ -0,0 +1,189 @@
1
+ export type EvidenceTemplate = 'note' | 'question' | 'source' | 'tag';
2
+ export type EvidenceBoardTool = { mode: 'select' }
3
+ | { mode: 'place'; template: EvidenceTemplate }
4
+ | { mode: 'connect'; fromId?: string };
5
+
6
+ export type EvidenceCard = {
7
+ id: string;
8
+ title: string;
9
+ body: string;
10
+ stage: 0 | 1 | 2;
11
+ kind: 'note' | 'source' | 'question';
12
+ clueKind?: 'excerpt' | 'finding' | 'time' | 'relation' | 'question' | 'contrast' | 'report';
13
+ summary?: string;
14
+ contentRevision?: number;
15
+ layoutRevision?: number;
16
+ variant?: number;
17
+ evidenceLabel?: string;
18
+ sourceLabel?: string;
19
+ relationKinds?: Record<string, string>;
20
+ sourceTitle?: string;
21
+ sourceId?: string;
22
+ position?: { x: number; y: number };
23
+ rotation?: number;
24
+ scale?: number;
25
+ links?: string[];
26
+ visual?: 'observatory' | 'schematic' | 'signal';
27
+ presentation?: 'compact' | 'tag';
28
+ };
29
+
30
+ export const EVIDENCE_STAGES = ['线索收集', '交叉核验', '阶段结论'] as const;
31
+ export const EVIDENCE_CARD_CAPACITY = 12;
32
+ export const EVIDENCE_TITLE_LIMIT = 120;
33
+ export const EVIDENCE_BODY_LIMIT = 4000;
34
+ export const EVIDENCE_STORAGE_LIMIT = 100000;
35
+ export const EVIDENCE_WRITING_BOUNDS = { left: -7.55, right: 7.55, bottom: -3.82, top: 3.9 } as const;
36
+ export const EVIDENCE_SCALE_MIN = 0.5;
37
+ export const EVIDENCE_SCALE_MAX = 2;
38
+
39
+ /** Do not coerce browser storage or form values into scene dimensions. */
40
+ export function normalizeEvidenceScale(value: unknown): number {
41
+ return typeof value === 'number' && Number.isFinite(value)
42
+ ? Math.max(EVIDENCE_SCALE_MIN, Math.min(EVIDENCE_SCALE_MAX, value)) : 1;
43
+ }
44
+
45
+ export function evidenceStorageKey(sessionId: string) {
46
+ return `prts-rhine-evidence-board:v1:${encodeURIComponent(sessionId || 'default')}`;
47
+ }
48
+
49
+ export function createEvidenceId() {
50
+ return `card:${globalThis.crypto?.randomUUID?.() || `${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}`}`;
51
+ }
52
+
53
+ /** Creating a template never stores a card; placement is confirmed by the caller. */
54
+ export function createEvidenceTemplate(template: EvidenceTemplate): EvidenceCard {
55
+ const titles = { note: '新的观察', question: '待解的问题', source: '资料摘录', tag: '关键词' };
56
+ return { id: createEvidenceId(), title: titles[template], body: '', stage: 0,
57
+ kind: template === 'tag' ? 'note' : template, rotation: 0,
58
+ ...(template === 'tag' ? { presentation: 'tag' as const }
59
+ : template === 'source' ? {} : { presentation: 'compact' as const }) };
60
+ }
61
+
62
+ const POSITIONS = [
63
+ [0, 2.5, -2], [-5.35, 2.15, 7], [5.25, 2.25, -6], [-2.8, -0.1, -4],
64
+ [2.6, -0.35, 5], [-5.1, -2.2, -5], [-0.05, -2.2, 3], [5.1, -2.2, -4],
65
+ [-2.7, 2.95, 2], [2.85, 2.95, -3], [-6, -0.35, 3], [6, -0.35, -2],
66
+ ];
67
+
68
+ function basePaperSize(card: EvidenceCard) {
69
+ if (card.clueKind) {
70
+ const sizes = { report: [3.85, 2.75], excerpt: [2.55, 1.62], finding: [2.50, 1.86],
71
+ time: [2.60, 1.22], relation: [2.55, 1.58], question: [2.22, 1.70], contrast: [2.55, 1.88] };
72
+ const [width, height] = sizes[card.clueKind]; return { width, height };
73
+ }
74
+
75
+ const [width, height] = card.presentation === 'tag' ? [3, 0.7]
76
+ : card.presentation === 'compact' ? card.kind === 'question' ? [3.25, 1.55] : [3.35, 1.7]
77
+ : card.visual === 'observatory' ? [4.6, 3.25]
78
+ : card.visual === 'schematic' ? [5.35, 3.65] : card.visual === 'signal' ? [4.2, 1.95]
79
+ : card.kind === 'source' ? [3.4, 3.8] : card.kind === 'question' ? [3.8, 1.7] : [3.5, 2.25];
80
+ return { width: width * 0.8, height: height * 0.8 };
81
+ }
82
+
83
+ /** The rotated sheet must fit on the writing surface even at maximum size. */
84
+ export function evidenceCardScale(card: EvidenceCard, index = 0): number {
85
+ const { width, height } = basePaperSize(card);
86
+ const rotation = card.rotation ?? POSITIONS[index % POSITIONS.length][2];
87
+ const angle = rotation * Math.PI / 180;
88
+ const cosine = Math.abs(Math.cos(angle)), sine = Math.abs(Math.sin(angle));
89
+ const availableWidth = EVIDENCE_WRITING_BOUNDS.right - EVIDENCE_WRITING_BOUNDS.left;
90
+ const availableHeight = EVIDENCE_WRITING_BOUNDS.top - EVIDENCE_WRITING_BOUNDS.bottom;
91
+ return Math.min(normalizeEvidenceScale(card.scale),
92
+ availableWidth / (width * cosine + height * sine),
93
+ availableHeight / (width * sine + height * cosine));
94
+ }
95
+
96
+ export function evidenceCardLayout(card: EvidenceCard, index: number) {
97
+ const [x, y, rotation] = POSITIONS[index % POSITIONS.length];
98
+ const { width, height } = basePaperSize(card), scale = evidenceCardScale(card, index);
99
+ const layout = { x: card.position?.x ?? x, y: card.position?.y ?? y,
100
+ rotation: card.rotation ?? rotation, width: width * scale, height: height * scale, scale };
101
+ return { ...layout, ...clampPaperPosition(layout, layout.x, layout.y) };
102
+ }
103
+
104
+ function clampPaperPosition(layout: { rotation: number; width: number; height: number }, x: number, y: number) {
105
+ const angle = layout.rotation * Math.PI / 180;
106
+ const halfWidth = (Math.abs(Math.cos(angle)) * layout.width + Math.abs(Math.sin(angle)) * layout.height) / 2;
107
+ const halfHeight = (Math.abs(Math.sin(angle)) * layout.width + Math.abs(Math.cos(angle)) * layout.height) / 2;
108
+ return { x: Math.max(EVIDENCE_WRITING_BOUNDS.left + halfWidth, Math.min(EVIDENCE_WRITING_BOUNDS.right - halfWidth, x)),
109
+ y: Math.max(EVIDENCE_WRITING_BOUNDS.bottom + halfHeight, Math.min(EVIDENCE_WRITING_BOUNDS.top - halfHeight, y)) };
110
+ }
111
+
112
+ /** Keep all four paper corners inside the usable writing surface. */
113
+ export function clampEvidencePosition(card: EvidenceCard, index: number, x: number, y: number) {
114
+ return clampPaperPosition(evidenceCardLayout(card, index), x, y);
115
+ }
116
+
117
+ /** Browser storage is untrusted; accept only bounded cards that fit the board. */
118
+ export function parseEvidenceCards(value: unknown): EvidenceCard[] {
119
+ if (!Array.isArray(value)) return [];
120
+ const cards: EvidenceCard[] = [], ids = new Set<string>();
121
+ for (const raw of value.slice(0, 64)) {
122
+ if (!raw || typeof raw !== 'object' || Array.isArray(raw)) continue;
123
+ const card = raw as Record<string, unknown>;
124
+ if (typeof card.id !== 'string' || !card.id || card.id.length > 128 || ids.has(card.id)
125
+ || typeof card.title !== 'string' || !card.title.trim() || typeof card.body !== 'string'
126
+ || card.stage !== 0 && card.stage !== 1 && card.stage !== 2
127
+ || card.kind !== 'note' && card.kind !== 'source' && card.kind !== 'question') continue;
128
+ cards.push({ id: card.id, title: card.title.trim().slice(0, EVIDENCE_TITLE_LIMIT),
129
+ body: card.body.slice(0, EVIDENCE_BODY_LIMIT), stage: card.stage, kind: card.kind,
130
+ ...(card.visual === 'observatory' || card.visual === 'schematic' || card.visual === 'signal'
131
+ ? { visual: card.visual as EvidenceCard['visual'] } : {}),
132
+ ...(card.presentation === 'compact' || card.presentation === 'tag'
133
+ ? { presentation: card.presentation } : {}),
134
+ ...(typeof card.sourceTitle === 'string' && card.sourceTitle.trim()
135
+ ? { sourceTitle: card.sourceTitle.trim().slice(0, 512) } : {}),
136
+ ...(typeof card.sourceId === 'string' && card.sourceId
137
+ ? { sourceId: card.sourceId.slice(0, 1024) } : {}),
138
+ ...(typeof card.rotation === 'number' && Number.isFinite(card.rotation)
139
+ ? { rotation: Math.max(-15, Math.min(15, card.rotation)) } : {}),
140
+ ...(typeof card.scale === 'number' && Number.isFinite(card.scale)
141
+ ? { scale: normalizeEvidenceScale(card.scale) } : {}),
142
+ ...(Array.isArray(card.links) ? { links: [...new Set(card.links.slice(0, 64).filter((id): id is string =>
143
+ typeof id === 'string' && id.length > 0 && id.length <= 128 && id !== card.id))] } : {}),
144
+ });
145
+ const position = card.position as { x?: unknown; y?: unknown } | undefined;
146
+ if (position && typeof position.x === 'number' && Number.isFinite(position.x)
147
+ && typeof position.y === 'number' && Number.isFinite(position.y)) {
148
+ cards[cards.length - 1].position = clampEvidencePosition(cards[cards.length - 1], cards.length - 1, position.x, position.y);
149
+ }
150
+ ids.add(card.id);
151
+ if (cards.length === EVIDENCE_CARD_CAPACITY) break;
152
+ }
153
+ return cards.map(card => card.links ? { ...card, links: card.links.filter(id => ids.has(id)).slice(0, EVIDENCE_CARD_CAPACITY - 1) } : card);
154
+ }
155
+
156
+ export function isPreviewCard(card: EvidenceCard) {
157
+ return card.id.startsWith('preview:');
158
+ }
159
+
160
+ /** Reviewed story paraphrases for the scene preview, not Agent research records.
161
+ * Corpus revision: e501261a50770f281a1cab5ed5a57d97c0d36ce73f7da768b27f592516117d33.
162
+ * Sources: CW-10 before L70–89 / L94–118; CW-ST-4 L81–92 / L109–110 / L136–159;
163
+ * CW-8 after L129–148; CW-7 before L158–167 and after L53. */
164
+ export function createPreviewCards(): EvidenceCard[] {
165
+ return [
166
+ { id: 'preview:question', title: '星荚之外有什么', stage: 0, kind: 'question',
167
+ body: '克丽斯腾不愿止步于天空。阻隔层之外,泰拉又身处何方?',
168
+ sourceTitle: '《孤星》CW-10 行动前 · 研究提问', position: { x: -0.35, y: 2.65 }, rotation: -3,
169
+ links: ['preview:collect', 'preview:connect'] },
170
+ { id: 'preview:collect', title: '克丽斯腾的群星', stage: 0, kind: 'source', visual: 'observatory',
171
+ body: '万星园越过星荚。克丽斯腾留下观测记录,在群星之间准备进入休眠。',
172
+ sourceTitle: '《孤星》CW-ST-4 · 剧情改写', position: { x: -4.7, y: 1 }, rotation: 4,
173
+ links: ['preview:another'] },
174
+ { id: 'preview:verify', title: '保存者最后的选择', stage: 1, kind: 'source',
175
+ body: '保存者亲手关闭旧计划,将最后的能源交给克丽斯腾。\n他选择让年轻的文明迈出下一步。',
176
+ sourceTitle: '《孤星》CW-8 行动后 · 剧情改写', position: { x: 4.65, y: 0.95 }, rotation: -3,
177
+ links: ['preview:connect', 'preview:conclude'] },
178
+ { id: 'preview:connect', title: '万星园的真正航向', stage: 0, kind: 'source', visual: 'schematic',
179
+ body: '弧光一号是对军方的幌子。万星园的目标,在星荚之外。',
180
+ sourceTitle: '《孤星》CW-10 行动前 · 剧情改写', position: { x: -0.05, y: -0.25 }, rotation: -2.2,
181
+ links: ['preview:another', 'preview:conclude'] },
182
+ { id: 'preview:another', title: '无法停止的蓄能', stage: 1, kind: 'note', visual: 'signal',
183
+ body: '能量井读数持续升高,军方无法关闭。聚焦发生器已抵达上空。',
184
+ sourceTitle: '《孤星》CW-7 行动前后 · 剧情改写', position: { x: -4.1, y: -2.05 }, rotation: -5 },
185
+ { id: 'preview:conclude', title: '科学应当看向谁', stage: 2, kind: 'question',
186
+ body: '赫默发起《特里蒙科学伦理联合宣言》。飞向天空,也要回应留在大地上的人。',
187
+ sourceTitle: '《孤星》CW-ST-4 · 剧情改写', position: { x: 4.05, y: -2.15 }, rotation: 5 },
188
+ ];
189
+ }