prts-terrarchive 0.2.2 → 0.2.3

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 (40) hide show
  1. package/README.en.md +52 -18
  2. package/README.md +49 -16
  3. package/bin/build-mainline-catalog.py +195 -0
  4. package/contracts/corpus_i18n_v1.schema.json +1 -3
  5. package/contracts/corpus_tools_v1.schema.json +30 -1
  6. package/docs/endfield-mainline-navigation.md +45 -0
  7. package/lib/client.js +154 -27
  8. package/lib/skins/common.css +12 -2
  9. package/lib/skins/endfield-aic.css +1 -1
  10. package/lib/skins/prts-agent.css +9 -7
  11. package/package.json +7 -3
  12. package/resources/endfield-mainline.json +4632 -0
  13. package/skills/prts-investigation/SKILL.md +1 -1
  14. package/skills/prts-investigation/references/board-guide.md +1 -1
  15. package/skills/prts-retrieval/SKILL.md +2 -0
  16. package/skills/prts-retrieval/references/cloud-tools.md +3 -1
  17. package/skills/prts-retrieval/references/local-tools.md +3 -3
  18. package/skills/prts-retrieval/references/module-arknights.md +1 -1
  19. package/skills/prts-retrieval/references/module-dual.md +1 -1
  20. package/skills/prts-retrieval/references/module-endfield.md +3 -0
  21. package/skills/prts-retrieval/references/tools-runtime.md +22 -10
  22. package/src/cloud-projection.js +23 -2
  23. package/src/cloud.js +24 -0
  24. package/src/entity-recognizer.js +26 -5
  25. package/src/entity-routing.js +18 -4
  26. package/src/evidence-state.js +4 -2
  27. package/src/i18n.js +70 -15
  28. package/src/index.js +304 -98
  29. package/src/installer.js +86 -14
  30. package/src/investigation-tools.js +7 -4
  31. package/src/navigation.js +342 -0
  32. package/src/read-args.js +86 -0
  33. package/src/read.js +66 -15
  34. package/src/search-projection.js +154 -19
  35. package/src/search.js +177 -21
  36. package/src/store.js +127 -3
  37. package/src/timeline.js +86 -2
  38. package/src/tool-args.js +53 -0
  39. package/src/ui.js +158 -34
  40. package/src/web-guidance.js +14 -2
package/README.en.md CHANGED
@@ -55,8 +55,9 @@ change as service capacity evolves.
55
55
 
56
56
  ## Install
57
57
 
58
- Web requires Node.js >= 22.19 and DSH >= 0.1.2-alpha.2. The current compatibility
59
- target is DSH 0.2.0-rc.1. An official Electron Desktop installation supplies
58
+ Web requires Node.js >= 22.19 and DSH >= 0.2.0-rc.1 (declared as `engines.dsh`).
59
+ The package has been audited point by point against the DSH 0.2.1-rc.2 source.
60
+ An official Electron Desktop installation supplies
60
61
  its own runtime. Corpus disk usage is shown in Settings before download.
61
62
 
62
63
  ### Official Electron Desktop (recommended)
@@ -64,13 +65,13 @@ its own runtime. Corpus disk usage is shown in Settings before download.
64
65
  Recent DSH Desktop builds let you install from the UI without a terminal:
65
66
 
66
67
  1. Open **Plugins** in the sidebar, then click **+ Add plugin**.
67
- 2. Paste the npm package name into **Package name or address**. Enter only the name, **not an `npm install` or `dsh plugin add` command**:
68
+ 2. Paste the versioned npm package name into **Package name or address**. Enter only the name, **not an `npm install` or `dsh plugin add` command**:
68
69
 
69
70
  ```text
70
- prts-terrarchive
71
+ prts-terrarchive@0.2.3
71
72
  ```
72
73
 
73
- Without a version, this installs npm's `latest`. To pin this release, enter `prts-terrarchive@0.2.2`.
74
+ Specify a version. With only `prts-terrarchive`, the preview may show npm's `latest` while the installer selects an older version; see the explanation below.
74
75
  3. Choose the official npm registry or the mainland China mirror under **Registry**, then click **Install**.
75
76
  4. Click **Enable now** after installation. If you close the dialog instead, turn on the plugin in the installed list.
76
77
  5. Open **Settings → Plugins → PRTS Corpus** to download the corpus and choose a skin, then start a new session in **PRTS mode**.
@@ -79,7 +80,7 @@ The same dialog accepts these alternatives. Choose one; you do not need to insta
79
80
 
80
81
  | Source | Text to enter | Use |
81
82
  | --- | --- | --- |
82
- | npm (recommended) | `prts-terrarchive` | Install a published release |
83
+ | npm (recommended) | `prts-terrarchive@0.2.3` | Install the specified release |
83
84
  | GitHub | `https://github.com/HTian-qwq/prts-terrarchive.git` | Install repository source; requires access to GitHub |
84
85
  | Local directory | `D:\ds\prts-terrarchive` | Install a complete downloaded checkout; replace this with its actual absolute path |
85
86
 
@@ -91,6 +92,14 @@ does not require a separate `js-yaml` / `zod` installation or `npm ci`.
91
92
  If GitHub is unreachable, enter the npm package name and choose a mirror instead. If a mirror
92
93
  does not yet have a newly published version, try the official npm registry.
93
94
 
95
+ **Installation preview and installed version differ:** DSH builds using pnpm 11 default to
96
+ preferring versions published at least 24 hours ago, while the preview reads registry metadata.
97
+ For example, shortly after 0.2.3 is published, entering only the package name can preview 0.2.3
98
+ but install 0.2.1. This difference comes from the installer's version selection; see the
99
+ [pnpm release notes](https://pnpm.io/blog/releases/11.0). Follow the upgrade steps below to
100
+ uninstall the old version, install `prts-terrarchive@0.2.3`, then check the version in plugin
101
+ details. You do not need to clear corpus or session data.
102
+
94
103
  **Upgrades:** the current Desktop installation UI does not provide automatic updates.
95
104
  For npm or GitHub installs, uninstall from the plugin details, then add and enable the plugin
96
105
  again; change any pinned version to your target release. For a local checkout, run `git pull`
@@ -134,7 +143,7 @@ Install the bundle directly from npm; no additional installer command
134
143
  or npm lifecycle hook is needed:
135
144
 
136
145
  ```bash
137
- dsh plugin --profile web add prts-terrarchive@0.2.2
146
+ dsh plugin --profile web add prts-terrarchive@0.2.3
138
147
  ```
139
148
 
140
149
  Restart `dsh web`. On DSH 0.1.7 and later the plugin registers PRTS mode
@@ -163,8 +172,11 @@ Web and official Desktop share the default `$DSH_HOME` product data while
163
172
  installing their plugins separately. The plugin package includes its UI,
164
173
  map models, textures, skills, and presets. It does **not** bundle corpus data,
165
174
  Node/DSH runtimes, or user data. Open Settings → Plugins → PRTS 语料 and download
166
- the corpus explicitly before using local retrieval. Switching skins does not
167
- start a separate model or texture download.
175
+ the corpus explicitly before using local retrieval. Downloads follow `enabledGames`:
176
+ Endfield needs its two packs; Arknights needs its five packs. Enabling another game
177
+ and downloading again adds the missing packs and reuses verified files. Disabling
178
+ a game does not delete its installed data. Switching skins does not start a
179
+ separate model or texture download.
168
180
 
169
181
  PRTS Portable distributions include the plugin, presets, and the complete corpus,
170
182
  so no separate corpus download is needed after extraction. Build the official
@@ -234,10 +246,11 @@ download.
234
246
  `{n}` repetitions; no grouping, alternation or variable quantifiers) on NFKC-normalized text, with resource-type /
235
247
  character / story / activity / entity / speaker / Wiki-field filters;
236
248
  keep the original conditions and pass `page.next_after` back unchanged as
237
- `after` to continue, because the readable anchor includes the full
238
- `data_version` and becomes invalid after a corpus release switch
239
- - `corpus_read` — read Arknights stages by `stage_code` (adding `story_part`
240
- only when needed), operator records by character/name/segment, and character
249
+ `after` to continue; the anchor carries a short `data_version` prefix and
250
+ becomes invalid after a corpus release switch. `speakers` expands registered
251
+ aliases; hits keep original punctuation and mark the match with `⟦⟧`
252
+ - `corpus_read` — read Arknights stages by `stage_code` (`story_part` is
253
+ optional; ambiguous codes default to the pre-battle part with a note), operator records by character/name/segment, and character
241
254
  materials by category; it can stream a whole Arknights activity or Endfield
242
255
  mission collection with readable, version-bound continuation positions;
243
256
  it can also read a tagged Wiki field or page through a whole document; story
@@ -247,8 +260,11 @@ download.
247
260
  when a character locator needs same-name disambiguation.
248
261
  Use exactly one primary locator per call. If search returns `document_uid`, it
249
262
  replaces `title`; use `{document_uid, line}` or
250
- `{document_uid, mode:"document"}`, never both locators. `max_lines` and
251
- `max_chars` only cap output and do not select a read mode
263
+ `{document_uid, mode:"document"}`, never both locators. A bare `title` reads
264
+ the document from line 1; titles tolerate missing separators and Wiki
265
+ sequence suffixes, and unknown titles list the closest candidates.
266
+ `activity_name` and `collection_name` reroute to the other game when the
267
+ name only exists there
252
268
  - `timeline_search` — query the Terra calendar timeline; source markers can
253
269
  be resolved back to full provenance
254
270
  - `cloud_search` / `cloud_inspect` — optional PRTS.cloud hybrid retrieval
@@ -260,7 +276,19 @@ download.
260
276
  Repeated and partially reused reads retain the normal page limits and
261
277
  continuation. Partial reads include `coverage` identifying previously visible
262
278
  lines and newly returned lines; `page.has_more` still determines whether the
263
- document has more content. Cloud answers remain available when local corpus
279
+ document has more content. Reuse trusts the host's "currently visible surface":
280
+ if earlier output was compacted or collapsed out of context, that page can look
281
+ present while being unreadable — pass `reuse: false` to force a full resend
282
+ instead of tuning `max_lines`.
283
+
284
+ To continue a document read, pass `start_line` (not `line`): `line` means the
285
+ centre line of an `around` window (three lines each way by default) and is
286
+ rejected with `mode: "document"`. Every line hit in `corpus_search` also gets a
287
+ handle such as `doc_xxxxxxxxxxxxxxxx#L12`; feed it back with
288
+ `corpus_read({"citation":"doc_xxxxxxxxxxxxxxxx#L12"})` to return to those exact
289
+ lines without looking up a `document_uid` first. Zero-hit searches report the
290
+ covered scope (`scope`: documents and lines), and results carry a per-game hit
291
+ distribution (`by_game`) so cross-game homonyms can be narrowed with `games`. Cloud answers remain available when local corpus
264
292
  mapping fails, with a warning to install or repair the corpus before verifying
265
293
  the original text.
266
294
 
@@ -282,8 +310,14 @@ for field semantics and query recipes.
282
310
  DSH 0.1.2-alpha.1 and 0.1.2-alpha.2 have completed historical real-host tests
283
311
  with the web profile. Alpha.1 was built from the official tag and passed
284
312
  installation, preset resolution, host startup, settings-route, and
285
- client-bundle checks. The current plugin compatibility target is DSH
286
- 0.2.0-rc.1. The shared Connection Fetch transport supports Web and the official
313
+ client-bundle checks. The declared minimum runtime is DSH 0.2.0-rc.1
314
+ (`engines.dsh`), and every contract this plugin relies on — tool registration,
315
+ systemPrompt contexts, Connection Fetch routes, skills, presets and the client
316
+ module protocol — was checked against the 0.2.1-rc.2 source. Since 0.2.1 exact
317
+ Fetch routes admit only the owner principal, so a desktop carrier must declare
318
+ that principal; PTC sub-dispatches also persist `presentationMeta`, but the
319
+ client never reads it, so evidence cards still come from the plugin's receipt.
320
+ The shared Connection Fetch transport supports Web and the official
287
321
  Electron source implementation; Web also retains HTTP routes. Declarative
288
322
  registration and legacy file seeding pass this project's unit tests. Tests against
289
323
  0.1.7-rc.2 (`477b4f420553`) also exercise real Cordis, Tools, SystemPrompt,
package/README.md CHANGED
@@ -71,7 +71,7 @@ npm run preview:rhine
71
71
 
72
72
  ## 环境要求
73
73
 
74
- - Web:Node.js **≥ 22.19**,DSH 运行时 **≥ 0.1.2-alpha.2**;当前适配目标为 **0.2.0-rc.1**
74
+ - Web:Node.js **≥ 22.19**,DSH 运行时 **≥ 0.2.0-rc.1**(包内 `engines.dsh` 声明);已按 **0.2.1-rc.2** 源码逐项核对
75
75
  - 官方 Electron Desktop:使用与插件兼容的 DSH 桌面版本;桌面自带运行时
76
76
  - 磁盘空间:语料大小以设置页与 release 清单为准,下载前不会自动占用完整语料空间
77
77
 
@@ -82,13 +82,13 @@ npm run preview:rhine
82
82
  在新版 DSH 桌面端,无需打开终端:
83
83
 
84
84
  1. 打开左侧 **插件**,点击右上角 **+ 添加插件**。
85
- 2. 在「包名或地址」输入框中粘贴下面的 npm 包名;**只填包名,不要填 `npm install` 或 `dsh plugin add` 命令**:
85
+ 2. 在「包名或地址」输入框中粘贴下面带版本号的 npm 包名;**只填包名,不要填 `npm install` 或 `dsh plugin add` 命令**:
86
86
 
87
87
  ```text
88
- prts-terrarchive
88
+ prts-terrarchive@0.2.3
89
89
  ```
90
90
 
91
- 不带版本号时安装 npm 的 `latest`;需要固定本次发布版本时,填 `prts-terrarchive@0.2.2`。
91
+ 建议指定版本。只填 `prts-terrarchive` 时,安装预览可能显示 npm 的 `latest`,实际安装却选择较旧的版本,原因见下方说明。
92
92
  3. 在 **安装源** 中按网络情况选择「npm 官方源」或「中国大陆镜像源」,点击 **安装**。
93
93
  4. 安装完成后点击 **立即启用**;如果直接关闭了对话框,请在已安装列表中打开插件开关。
94
94
  5. 打开 **设置 → 插件 → PRTS 语料** 下载资料并选择皮肤,然后新建会话,选择 **PRTS 模式**。
@@ -97,7 +97,7 @@ npm run preview:rhine
97
97
 
98
98
  | 来源 | 输入框中填写的内容 | 适用情况 |
99
99
  | --- | --- | --- |
100
- | npm(推荐) | `prts-terrarchive` | 安装已发布版本 |
100
+ | npm(推荐) | `prts-terrarchive@0.2.3` | 安装指定发布版本 |
101
101
  | GitHub | `https://github.com/HTian-qwq/prts-terrarchive.git` | 安装仓库源码,需要本机能访问 GitHub |
102
102
  | 本地目录 | `D:\ds\prts-terrarchive` | 安装已下载的完整插件目录;替换为本机实际绝对路径 |
103
103
 
@@ -105,6 +105,8 @@ npm run preview:rhine
105
105
 
106
106
  **安装源只影响 npm 包及依赖下载,不能代理 GitHub 仓库或压缩包直链。** 如果 GitHub 无法连接,可以改填 npm 包名并选择镜像源。新发布版本在镜像中尚未可用时,可切换「npm 官方源」重试。
107
107
 
108
+ **安装预览与已安装版本不同:** 使用 pnpm 11 的 DSH 默认优先安装发布满 24 小时的版本,而安装预览读取的是注册表版本。例如,0.2.3 刚发布时,只填包名可能预览 0.2.3、实际安装 0.2.2。这是安装器的版本选择差异。[pnpm 官方说明](https://pnpm.io/blog/releases/11.0)介绍了这项默认设置。请按下面的升级流程卸载旧版,再填 `prts-terrarchive@0.2.3` 安装,完成后在插件详情中核对版本;无需清除语料或会话数据。
109
+
108
110
  **升级插件:** 当前桌面安装界面不提供自动更新。需要升级 npm 或 GitHub 安装的插件时,在插件详情中卸载,再通过「添加插件」重新安装并启用;指定版本号时记得换成目标版本。本地源码安装先在原目录执行 `git pull`,再完全退出并重启桌面端。插件更新与语料更新分别进行,资料版本在「PRTS 语料」中管理。
109
111
 
110
112
  旧版桌面如只支持 npm 包名,请使用 `prts-terrarchive` 并按其界面提示启用。Electron 的 `desktop` profile 由应用管理,不要运行 `node bin/install.js desktop` 或 `dsh plugin --profile desktop`。
@@ -134,7 +136,7 @@ node bin/install.js web /path/to/prts-terrarchive
134
136
  可直接从 npm 安装插件 bundle,无需额外运行安装脚本:
135
137
 
136
138
  ```bash
137
- dsh plugin --profile web add prts-terrarchive@0.2.2
139
+ dsh plugin --profile web add prts-terrarchive@0.2.3
138
140
  ```
139
141
 
140
142
  重启 `dsh web` 后,DSH `0.1.7` 起由插件注册「PRTS 模式」,禁用或卸载插件时
@@ -179,8 +181,7 @@ Web 本地安装需要可用的 `dsh.cmd`。安装器经 cmd.exe 调用它,插
179
181
 
180
182
  1. **重启** `dsh web` 或所用桌面应用;
181
183
  2. **设置 → 插件 →「PRTS 语料」**:选择皮肤(Harness 默认 / PRTS Agent / Endfield AIC / 莱茵生命资料馆);皮肤模型、贴图与字体已随插件包安装;
182
- 3. **版本管理**:Portable 已附带完整语料;其他安装方式下载双游戏资料(优先 ModelScope 镜像;大小以设置页与当前 release
183
- 清单为准)。未安装资料时
184
+ 3. **版本管理**:Portable 已附带完整语料;其他安装方式按设置中的启用游戏下载安装(优先 ModelScope 镜像)。只启用终末地时下载终末地官方资料和审校知识两包;只启用明日方舟时下载方舟的五包。之后启用另一款游戏,再点击下载或更新会补齐缺少的包,已校验文件会复用;关闭游戏不会删除已安装资料。未安装资料时
184
185
  PRTS 模式仍可进入;调用本地工具会提醒前往本设置页安装;
185
186
  4. **新建会话,顶部模式下拉选「PRTS 模式」**;
186
187
  5. 直接用自然语言提问,或让模型调用下列工具。
@@ -233,7 +234,7 @@ corpus_i18n({title: "<检索结果的完整标题>", line: 1, languages: ["EN"]}
233
234
  | `match_mode` | `literal`(默认,连续字面匹配)/ `regex`(线性安全子集:字面量、`^`/`$`、`.`、字符类、转义和固定次数 `{n}`;不支持分组、分支及可变量词) |
234
235
  | `character_names` / `story_names` / `activity_names` | 角色 / 篇章 / 活动展示名过滤 |
235
236
  | `entity_names` | 只返回出现指定实体的行(自动展开别名) |
236
- | `speakers` | 结构化说话人过滤,只匹配亲口台词 |
237
+ | `speakers` | 结构化说话人过滤,只匹配亲口台词(自动展开已登记别名,如 银灰→恩希欧迪斯) |
237
238
  | `wiki_sections` | Wiki 标签字段过滤(相关活动、剧情总结、角色剧情概括等 16 种) |
238
239
  | `context_terms` | 要求命中附近(±3 行)同时出现的语境词,≤8 个 |
239
240
  | `after` | 下一页的版本绑定可读锚点 `{ data_version, resource_type, title, position }`;与原搜索条件一起原样提交 |
@@ -257,26 +258,35 @@ corpus_i18n({title: "<检索结果的完整标题>", line: 1, languages: ["EN"]}
257
258
 
258
259
  ### corpus_read — 原文阅读
259
260
 
260
- 优先使用游戏内关卡代号、密录名、角色名、活动名或任务名;无法稳定定位时再使用搜索返回的完整标题。
261
+ 优先使用游戏内关卡代号、密录名、角色名、活动名或任务名;无法稳定定位时再使用搜索返回的标题;标题可省略分隔符与 Wiki 序号后缀,找不到时会列出最接近的完整标题。
261
262
  不使用内部 `document_id`、ref 或路径。搜索结果只在同名歧义时给出公开稳定的 `document_uid`;它替代 `title`,不得同时提交两者。同名多合集不会自动合并。
262
263
 
263
264
  | 用法 | 参数 |
264
265
  | --- | --- |
265
- | 明日方舟关卡 | `stage_code`;同代号多篇时加 `story_part`:`before` / `after` / `story`;加 `line` 可定点读上下文 |
266
+ | 明日方舟关卡 | `stage_code`;同代号多篇时可加 `story_part`:`before` / `after` / `story`(省略时默认行动前并提示其他篇);加 `line` 可定点读上下文 |
266
267
  | 干员密录 | `character_name` + `record_name`;多段密录加 `segment` |
267
268
  | 角色官方资料 | `character_name` + `material`,类别为 `profile/module/voice/skin/recruitment/potential` |
268
269
  | 双模块同名角色消歧 | 在角色定位参数之外加 `game: "arknights"` 或 `game: "endfield"`;无同名歧义时不填 |
269
270
  | 整活动连续阅读 | `activity_name` + `mode: "activity"` |
270
271
  | 终末地任务连续阅读 | `collection_name` + `mode: "collection"`,可选 `content_types` |
271
- | 其他资料定点上下文 | `title` + `line` |
272
+ | 其他资料定点上下文 | `title` + `line`(`line` 是中心行,默认前后各 3 行;不用于全文续读) |
273
+ | 终末地角色档案 | `character_name` + `material: "profile"`(与明日方舟同一套;如 `{"character_name":"管理员","material":"profile"}`) |
272
274
  | 读 Wiki 字段 | `title` + `section`(精确读取标签字段,不含标签行) |
273
- | 分页读其他全文 | `title` + `mode: "document"`(`max_lines` 默认 100、上限 500;`max_chars` 默认 12000、上限 100000) |
275
+ | 分页读其他全文 | `title` + `mode: "document"`,要从中间开始时加 `start_line`(`max_lines` 默认 100、上限 500;`max_chars` 默认 12000、上限 100000) |
274
276
  | 同名单篇 | `document_uid` + `line`,或 `document_uid` + `mode: "document"`;不再提交 `title` |
275
- | 续读 | 原样提交 `page.continuation`;单篇使用 `line`,活动/任务使用版本绑定的 `position`;旧 `cursor` 仅兼容历史会话 |
277
+ | 续读 | 复制结果末尾的续读调用;单篇续页给 `start_line`,活动/任务给 `position`,`data_version` 为短版本号且可省略;旧 `cursor` 仅兼容历史会话 |
278
+
279
+ `mode: "document"` 不接受 `line`(除 `line: 1` 这类无害占位):两者语义不同,混用会让续读停在同一段并误显示为「复用上文」。`start_line` 只用于全文连续读取。
276
280
 
277
281
  每次读取只选一种主定位方式。`max_lines/max_chars` 只限制输出量,不能代替 `line`、`section` 或 `mode`。
278
282
 
279
283
  重复读取与部分补读保持正常的预算和续页位置。部分补读会用 `coverage` 标明本页复用的上文行及新返回行;全文是否读完仍以 `page.has_more` 为准。
284
+ 复用只看宿主给出的「当前可见 surface」:长会话里上文若已被压缩或折叠,那一页会既看不到行、又被判定为可见。此时传 `reuse: false` 强制重发本页全文,不要改 `max_lines` 绕着试。
285
+
286
+ 每条命中还会带「句柄」:`doc_xxxxxxxxxxxxxxxx#L12`(区间为 `#L12-30`)。它同时是篇章定位和行号,直接把句柄塞回读取即可,不必先查 `document_uid`:
287
+ `corpus_read({"citation":"doc_xxxxxxxxxxxxxxxx#L12"})`。`citation` 与 `title`/`document_uid`/`line` 互斥,格式错误会直接报 `INVALID_REQUEST`。
288
+
289
+ 零命中会给出检索覆盖范围(`scope`:本地篇数/行数),例如「已检查完整检索范围(本地 34007 篇 / 1133511 行),没有找到」——「没有」因此可以当作否定证据引用,但范围仍以当前安装的资料包为准。
280
290
 
281
291
  引用格式统一为「《篇章名》第 N 行」。剧情文档只返回请求的原文;剧情总结与活动
282
292
  时间线必须通过 `timeline_search` 或 Wiki 字段显式检索,不自动夹带。
@@ -349,6 +359,25 @@ Wiki 资料不是无差别文本池:工具会区分规范角色页、活动/
349
359
  重新定位到当前版本后再调用即可。旧任务不会回写新版本的正文或倒排缓存,缓存复用
350
360
  及跨篇章读取也受同一版本检查约束。
351
361
 
362
+ ### 实体别名与冲突
363
+
364
+ 识别用户问题里的实体时,一个别名字面只能归属一个实体。当前数据里别名有两个来源:
365
+
366
+ - **明日方舟**:`references/char_alias.txt`(每行 `规范名;别名;别名…`,源自
367
+ `arknights_lore_wiki/data/char_alias.txt`,本意是生成剧情时定位有别名的人物)+ 实体资料里的
368
+ `aliases` 字段;
369
+ - **终末地**:只有实体资料里的 `aliases` 字段。
370
+
371
+ 冲突按以下顺序收敛,绝不把同一个字面交给两个实体:
372
+
373
+ 1. 字面等于某实体自身规范名时归该实体(「海嗣」归海嗣,不再当作某个人的别名);
374
+ 2. 否则若 `char_alias.txt` 明确声明了归属,按表裁决(「预言家」归博士);
375
+ 3. 两处都没有裁决的冲突(共用称号、同一人物两条实体记录、被误写成别名的类别词)一律不归属任何实体,
376
+ 并在本轮上下文里列出候选规范名,提示直呼实体名或分开检索。
377
+
378
+ 代码只负责"不许双重归属",不猜谁才是对的。要真正消除第 3 类,需要修数据:合并重复实体记录,
379
+ 或从实体 JSON 的 `aliases` 里删掉误写的词。
380
+
352
381
  **可配置项**(`$DSH_HOME/prts-corpus.json`,设置页可视化编辑):
353
382
 
354
383
  | 键 | 默认 | 说明 |
@@ -358,7 +387,7 @@ Wiki 资料不是无差别文本池:工具会区分规范角色页、活动/
358
387
  | `cloudBaseUrl` | `https://prts.chat` | 云端地址(https,或仅环回主机的 http) |
359
388
  | `cloudToken` | 空 | 静态 Bearer token;留空使用匿名 PoW 会话 |
360
389
  | `cloudGame` | `all` | `all`(一次检索两款游戏)/ `arknights` / `endfield` |
361
- | `enabledGames` | `["arknights","endfield"]` | 本地与云端工具允许检索的模块;单模块可只保留其中一项,新会话会装配对应 Skill |
390
+ | `enabledGames` | `["arknights","endfield"]` | 本地与云端工具允许检索的模块,同时决定新下载所需资料包;单模块可只保留其中一项,新会话会装配对应 Skill |
362
391
  | `cloudUserId` | 空 | 匿名会话的稳定用户标识 |
363
392
  | `cloudTimeoutMs` | `90000` | 云端请求超时(1000–600000) |
364
393
  | `cloudMaxResponseBytes` | 32 MiB | 云端响应上限(1 KiB–64 MiB) |
@@ -440,7 +469,11 @@ presets/ PRTS 模式声明及旧版用户预设初始化
440
469
 
441
470
  DSH `0.1.2-alpha.1` 与 `0.1.2-alpha.2`(web profile)已完成历史真机验证;其中
442
471
  `alpha.1` 通过官方 tag 构建并完成安装、预设解析、宿主启动、设置路由和客户端 bundle
443
- 加载检查。当前插件适配目标为 DSH `0.2.0-rc.1`,共享 Connection Fetch 通道支持
472
+ 加载检查。当前声明的最低运行时为 DSH `0.2.0-rc.1`(`engines.dsh`),并已对照
473
+ `0.2.1-rc.2` 源码逐个核对工具注册、systemPrompt 上下文、连接层 Fetch 路由、skill、
474
+ preset 与客户端模块协议:0.2.1 起精确 Fetch 路由只放行 owner 主体,桌面载体需显式
475
+ 声明该主体;PTC 子调用也会持久化 `presentationMeta`,但客户端不读取它,因此证据卡
476
+ 仍由插件的 receipt 提供。共享 Connection Fetch 通道支持
444
477
  Web 与官方 Electron 源码实现;Web 同时保留 HTTP 路由。新版声明式预设与
445
478
  旧版文件预设均通过本项目单元测试。针对 `0.1.7-rc.2`(`477b4f420553`),另以真实
446
479
  Cordis、Tools、SystemPrompt、Storage Domain 与官方网页工具验证作用域、工具回执、
@@ -0,0 +1,195 @@
1
+ #!/usr/bin/env python3
2
+ """Build evidence-labelled Endfield navigation; standard library only.
3
+ Community explicit lists supply display order, original assets supply classification.
4
+ Same-name joins stay unresolved; ID suffixes never supply mission order.
5
+ """
6
+ from __future__ import annotations
7
+ import argparse
8
+ from collections import defaultdict
9
+ import json
10
+ from pathlib import Path
11
+ import re
12
+
13
+ GAME_VERSION = '1.6.3_11010254-4'
14
+ COMMUNITY = 'https://endfield.wiki.gg/wiki/Mission/Main?oldid=63259'
15
+ INK = 'https://endfield.wiki.gg/wiki/Sanctuary_of_Ink?oldid=65256'
16
+ # Verbatim ordered mission titles from the community page's explicit <ol> lists.
17
+ PROCESSES = [
18
+ (1,0,'N/A',['e0'],'Process: Cold Start|Process: Awakening'),
19
+ (1,1,'The Broken Lands',['e1'],'Break the Siege|Valley Reboot|Rally and Unite|Westward into the Vale|Secure Sanctuary|Building an Outpost|Through the Haze|Fort Showdown'),
20
+ (1,2,'The Turbid Heavens',['e1'],'Paving the Way|Work Preparation|Maintenance Progress'),
21
+ (1,3,'Path of Ascension',['e2'],'The Mission Continues|Cross the Science Park|Park Way Party|Walk in the Park|Mystery in the Building|Down the Hill|Shattered Veins|Lodespring Crossing|Cradle of the Ages|Eve of Battle'),
22
+ (1,4,'Smoking Embers',['e3','e4'],'Entering the Endgame|Counterstrike Brewing|Scaldings of Utjug|Frontline Rush|Undying Cinders|Temporary Peace|Mysterious Signal|Conference of the Four|Work Preparation'),
23
+ (2,1,'Lost in the Marsh',['e5'],'Arrival at Wuling|Stumbling Into Qingbo Stockade|Destination Wuling'),
24
+ (2,2,'The Way of Water',['e6'],'A Glance of Astonishment|Ready the Easterlies|Of Grace and Gentle Might|The Surging Tide|Where Danger Sleeps'),
25
+ (2,3,'The Long Feud',['e7','c27'],"Unexpected Attack|Deep in the Mire|The Flow Splits Course|Surging From a Withered Spring|A River Restless in Still Winds|Another Wave Comes Crashing In|Ripples Spread Throughout the Flow|Purity Reveals the Stubborn Stone|'Twas One River That Gave Us Drink"),
26
+ (2,4,'Tales of Old',['e8'],'Like Fire and Ice|A Lethal Invitation|Phantom Wisps|Awaiting Thine Coming|Free Will'),
27
+ (2,5,'Wrothful Tide',['e9'],'Thus Rise the Wave and Wind|Tremors of the Crashing Tide|Where Friendship Was Made'),
28
+ (2,6,'Ponderous Load',['e10'],'A Breach Starts with a Crack|Patience Makes Greatness|Forged at the Very Beginning|Kindred Souls Who Share a Vision'),
29
+ (2,7,'Ruins in the Miasma',['e11'],'Bravely Breach the Hostile Winds|Thus Begins the Homeward March|A Past Shared by the Swordmancers|Same Old Faces in a Place Transformed|Warily Returns the Wandering Vigile|Finally Breaks the Hanging Clouds|"Homecoming"|Stubbornly Lingers the Setting Sun|Freedom of Expansion'),
30
+ (2,8,'Paint with Fresh Ink',['e12'],None),
31
+ ]
32
+ VERSIONS = [('1.0','零号委托'),('1.1','新潮起,故渊离'),('1.2','春晓时'),('1.3','寻遗散记'),('1.4','向渊行'),('1.5','雪凇幽梦'),('1.6','丹青渡')]
33
+
34
+
35
+ def load(path: Path):
36
+ return json.loads(path.read_text(encoding='utf-8'))
37
+
38
+
39
+ def normalized(text: str) -> str:
40
+ return re.sub(r'\s+', ' ', re.sub(r'<[^>]*>', '', text).replace('’', "'")).strip()
41
+
42
+
43
+ def build(game_root: Path, update_root: Path) -> dict:
44
+ table = game_root / 'tables' / GAME_VERSION / 'Table'
45
+ runtime = game_root / 'json' / GAME_VERSION / 'Data/Json/MissionRuntimeAsset'
46
+ index_path = update_root / 'wiki_generation/export/task_index.json'
47
+ tasks = load(index_path)
48
+ text = load(table / 'TextTable.json')
49
+ languages = {lang: load(table / f'I18nTextTable_{lang}.json') for lang in ('CN','EN')}
50
+
51
+ def localized(key, lang):
52
+ return normalized(languages[lang].get(str(text.get(key, {}).get('id')), ''))
53
+
54
+ def cn_for_en(english):
55
+ matches = {normalized(languages['CN'].get(k,'')) for k,v in languages['EN'].items() if normalized(v) == normalized(english)}
56
+ matches.discard('')
57
+ return next(iter(matches)) if len(matches) == 1 else ''
58
+
59
+ metadata = {}
60
+ by_name = defaultdict(list)
61
+ # Sort only for stable serialization, never as chronological evidence.
62
+ for path in sorted(runtime.glob('*.json')):
63
+ raw = load(path)
64
+ mid = raw.get('missionId')
65
+ if not mid or path.stem != mid:
66
+ continue
67
+ key = raw.get('missionName',{}).get('key','')
68
+ exported = tasks.get(mid,{})
69
+ row = {'id':mid, 'name':localized(key,'CN') or exported.get('name',''),
70
+ 'english_name':localized(key,'EN'), 'mission_type':raw.get('missionType'),
71
+ 'chapter_bitmask':raw.get('missionChapterBitmask'),
72
+ 'char_id':raw.get('charId',''), 'sort_id':raw.get('sortId'),
73
+ 'source':str(path), 'name_key':key,
74
+ 'export_type':exported.get('type'), 'export_series':exported.get('series'),
75
+ 'export_level':exported.get('level'),
76
+ 'has_local_story':bool(exported.get('fragments')) and exported.get('level') != 'skip',
77
+ 'char_linked':bool(raw.get('charId'))}
78
+ metadata[mid] = row
79
+ # Assets can reuse a parent's localization key. That proves a name
80
+ # alias, not a second independently named mission in the display list.
81
+ row['name_alias_of'] = key[:-5] if key.endswith('_name') and key != mid + '_name' else None
82
+ if row['english_name'] and row['name_alias_of'] is None:
83
+ by_name[normalized(row['english_name'])].append(mid)
84
+
85
+ chapters = []
86
+ for ordinal,(chapter,process,english,series,names) in enumerate(PROCESSES,1):
87
+ missions,unresolved = [],[]
88
+ if names is not None:
89
+ for position,name in enumerate(names.split('|'),1):
90
+ candidates = by_name.get(normalized(name),[])
91
+ if len(candidates) != 1:
92
+ unresolved.append({'order':position,'english_name':name,'candidate_ids':candidates,
93
+ 'reason':'ambiguous_name' if candidates else 'name_not_matched'})
94
+ continue
95
+ mid = candidates[0]
96
+ missions.append({'id':mid,'name':metadata[mid]['name'],'english_name':name,
97
+ 'order':position,'order_source':COMMUNITY,
98
+ 'has_local_story':metadata[mid]['has_local_story']})
99
+ else:
100
+ # Local membership only: no latest-process mission chronology claimed.
101
+ for mid,row in metadata.items():
102
+ if row['export_series'] in series and row['has_local_story'] and row['mission_type'] == 0:
103
+ missions.append({'id':mid,'name':row['name'],'english_name':row['english_name'],
104
+ 'order':None,'order_source':None,'has_local_story':True})
105
+ summaries = list(dict.fromkeys(t['series_name'] for sid in series for t in tasks.values()
106
+ if t.get('series') == sid and t.get('series_name')))
107
+ title_cn = cn_for_en(english) if english != 'N/A' else ''
108
+ summary_documents = [name + ' · 剧情总结' for name in summaries]
109
+ chapter_metadata = [metadata[m['id']] for m in missions]
110
+ chapters.append({'id':f'chapter{chapter}-process{process}','title':('序章' if process == 0 else title_cn or english),
111
+ 'aliases':list(dict.fromkeys([english] + summaries + ([title_cn] if title_cn else []))),
112
+ 'title_status':'display_alias' if process == 0 else 'localized_community_process_title',
113
+ 'title_en':english,'chapter_number':chapter,
114
+ 'chapter_title':localized(f'chapter{chapter}_name','CN'),
115
+ 'process_number':process,'order':ordinal,
116
+ 'collection_names':list(dict.fromkeys(m['name'] for m in missions if m['has_local_story'] and m['name'])),
117
+ 'summary_title':summary_documents[0] if summary_documents else '', 'summary_titles':summary_documents,
118
+ 'summary_series':series,
119
+ 'mission_metadata':chapter_metadata,
120
+ 'classification':{'basis':'MissionRuntimeAsset.missionType',
121
+ 'game_mission_types':sorted({r['mission_type'] for r in chapter_metadata if r['mission_type'] is not None}),
122
+ 'export_types':sorted({r['export_type'] for r in chapter_metadata if r['export_type']}),
123
+ 'char_linked_mission_ids':[r['id'] for r in chapter_metadata if r['char_linked']]},
124
+ 'collection_order':'community_explicit_order' if names is not None else 'unknown',
125
+ 'summary_coverage_note':'Exported series summaries: one series may cover several processes; one process may use several series.',
126
+ 'version':'1.6' if (chapter,process)==(2,8) else None,
127
+ 'missions':missions,'unresolved_missions':unresolved,
128
+ 'mission_order_status':'community_explicit_order' if names is not None else 'unavailable_membership_only',
129
+ 'classification_source':'MissionRuntimeAsset.missionType; export_type/series are separate ID-pattern grouping metadata',
130
+ 'evidence':[{'kind':'community','url':COMMUNITY if names is not None else INK,
131
+ 'claim':'chapter/process and explicit mission list order' if names is not None else 'Chapter II Process VIII; local mission membership only, no mission order claimed'}]})
132
+ versions = []
133
+ official_bounds = {
134
+ '1.1': ('https://endfield.gryphline.com/en-us/news/8907', 3),
135
+ '1.2': ('https://endfield.gryphline.com/en-us/news/0751', 6),
136
+ '1.4': ('https://endfield.gryphline.com/en-us/news/5200', 7),
137
+ }
138
+ introduced_processes = {'1.1':[3], '1.2':[4,5,6], '1.4':[7], '1.6':[8]}
139
+ for version,title in VERSIONS:
140
+ evidence = [{'kind':'user_confirmed','claim':'version number and Chinese title'}]
141
+ status = 'unknown'
142
+ if version in official_bounds:
143
+ url, boundary = official_bounds[version]
144
+ evidence.append({'kind':'official','url':url,
145
+ 'claim':f'Main Mission available up to Chapter II Process {boundary}; announcement is a cumulative endpoint, not an explicit list of newly introduced processes.'})
146
+ status = 'official_supported'
147
+ if version == '1.2':
148
+ status = 'derived_between_official_boundaries'
149
+ evidence.append({'kind':'derivation','urls':[official_bounds['1.1'][0],url],
150
+ 'claim':'Previous confirmed endpoint III, current endpoint VI: IV-VI are the intervening main-story units; version titles/numbers follow user confirmation.'})
151
+ elif version == '1.6':
152
+ status = 'community_supported'
153
+ evidence.append({'kind':'community','url':INK,'claim':'version 1.6, Chinese 丹青渡, Main Mission up to Chapter II Process VIII'})
154
+ versions.append({'version':version,'title':title,
155
+ 'chapter_ids':[f'chapter2-process{n}' for n in introduced_processes.get(version,[])],
156
+ 'chapter_ids_semantics':'new main-story navigation units, not cumulative availability',
157
+ 'mapping_status':status,'evidence':evidence})
158
+ by_id = {chapter['id']:chapter for chapter in chapters}
159
+ for version in versions:
160
+ for cid in version['chapter_ids']:
161
+ by_id[cid]['version'] = version['version']
162
+ by_id[cid]['version_mapping_status'] = version['mapping_status']
163
+ by_id[cid]['version_evidence'] = version['evidence']
164
+ relevant = {m['id'] for c in chapters for m in c['missions']}
165
+ relevant.update(mid for mid,row in metadata.items() if row['export_series'] in {f'e{i}' for i in range(13)} | {'c27'})
166
+ return {'schema_version':1,'source_game_version':GAME_VERSION,'versions':versions,'chapters':chapters,
167
+ 'mission_metadata':[metadata[mid] for mid in sorted(relevant)],
168
+ 'ordering_note':'Display order and pre-1.6 mission order cite community explicit tables. Latest mission order is unknown. Serialization, ID suffixes, sortId=0 and fragment order do not establish chronology.',
169
+ 'classification_note':'Original mission_type is independent of export_type/series. c27 has mission_type=0 and char_id=chr_0027_tangtang despite export_type=c. chapter_bitmask is not a chapter ID.',
170
+ 'sources':{'task_index':str(index_path),'mission_runtime_directory':str(runtime),
171
+ 'text_table':str(table/'TextTable.json'),'i18n_cn':str(table/'I18nTextTable_CN.json'),
172
+ 'i18n_en':str(table/'I18nTextTable_EN.json'),
173
+ 'chapter_bridge':str(table/'ChapterMissionChapterTable.json')},
174
+ 'gaps':['No runtime chapter/process membership table located in this snapshot.',
175
+ 'Version-to-process mapping remains unconfirmed for 1.0/1.3/1.5; unknown does not mean no new main story. Official notices give cumulative endpoints, while chapter_ids records new navigation units; 1.2 IV-VI is derived from adjacent confirmed endpoints.',
176
+ 'Work Preparation and Freedom of Expansion preserve same-name ambiguity.',
177
+ 'No cross-mission ordering evidence for e12; five story members have null order.']}
178
+
179
+
180
+ def main():
181
+ parser = argparse.ArgumentParser(description=__doc__)
182
+ parser.add_argument('--game-root',type=Path,default=Path('/home/cloudstack/sda/endfieldgamedata'))
183
+ parser.add_argument('--update-root',type=Path,default=Path('/home/cloudstack/ai-data/prts.chat/var/data_updates/endfield-1.6.3-11010254-4'))
184
+ parser.add_argument('--output',type=Path,default=Path(__file__).resolve().parents[1]/'resources/endfield-mainline.json')
185
+ args = parser.parse_args()
186
+ result = build(args.game_root,args.update_root)
187
+ args.output.parent.mkdir(parents=True,exist_ok=True)
188
+ args.output.write_text(json.dumps(result,ensure_ascii=False,indent=2)+'\n',encoding='utf-8')
189
+ print(json.dumps({'output':str(args.output),'processes':len(result['chapters']),
190
+ 'verified_missions':sum(len(c['missions']) for c in result['chapters']),
191
+ 'unresolved':sum(len(c['unresolved_missions']) for c in result['chapters'])},ensure_ascii=False))
192
+
193
+
194
+ if __name__ == '__main__':
195
+ main()
@@ -73,14 +73,12 @@
73
73
  },
74
74
  "data_version": {
75
75
  "type": "string",
76
- "description": "已有结果的资料版本"
76
+ "description": "已有结果的资料版本(可省略;续页给出的短版本号即可)"
77
77
  },
78
78
  "after": {
79
79
  "type": "object",
80
80
  "additionalProperties": false,
81
81
  "required": [
82
- "data_version",
83
- "request_hash",
84
82
  "position",
85
83
  "text_offset"
86
84
  ],
@@ -67,9 +67,10 @@
67
67
  "minLength": 1
68
68
  },
69
69
  "position": {
70
+ "description": "不透明续页位置,绑定当前安装范围;请原样复制,补装或切换资料范围后重新搜索。",
70
71
  "type": "integer",
71
72
  "minimum": 0,
72
- "maximum": 10000000
73
+ "maximum": 9007199254740991
73
74
  }
74
75
  }
75
76
  },
@@ -750,6 +751,34 @@
750
751
  "type": "string"
751
752
  }
752
753
  },
754
+ "by_game": {
755
+ "type": "object",
756
+ "additionalProperties": false,
757
+ "properties": {
758
+ "arknights": {
759
+ "type": "integer"
760
+ },
761
+ "endfield": {
762
+ "type": "integer"
763
+ }
764
+ }
765
+ },
766
+ "scope": {
767
+ "type": "object",
768
+ "additionalProperties": false,
769
+ "required": [
770
+ "documents",
771
+ "lines"
772
+ ],
773
+ "properties": {
774
+ "documents": {
775
+ "type": "integer"
776
+ },
777
+ "lines": {
778
+ "type": "integer"
779
+ }
780
+ }
781
+ },
753
782
  "warnings": {
754
783
  "type": "array",
755
784
  "items": {
@@ -0,0 +1,45 @@
1
+ # 终末地主线导航
2
+
3
+ `corpus_navigate` 是确定性的本地主线目录入口,不调用云端检索,也不生成剧情内容。玩家不必先知道数据包中的系列名或任务名。
4
+
5
+ ```json
6
+ {}
7
+ {"query":"最新主线"}
8
+ {"query":"1.6的主线"}
9
+ {"query":"丹青渡主线"}
10
+ {"query":"第二章第八进程"}
11
+ {"query":"进程VIII"}
12
+ {"query":"第二章第三进程"}
13
+ ```
14
+
15
+ 仅支持可选 `query` 和 `game:"endfield"`。省略 query 返回完整目录。目录包括序章和第一、二章共13个进程。返回值绑定当前 `data_version`,每个进程提供多个梗概及其 `corpus_search`/`corpus_read` 参数、任务集合入口、上下进程入口和逐项 `availability`。默认目录文本约2300字符,完整分类和来源元数据保留在结构化结果中。
16
+
17
+ 最新仅指目录中当前本地可定位的最新进程,不宣称实时游戏版本;旧资料包会降到实际覆盖边界,同时保留新进程的未安装说明。缺少正文不会被解释为没有剧情。读取时原样使用返回的 `read` 定位,合集续页使用 `page.continuation`,不推断文件名中的时间先后。
18
+
19
+ ## 三种信息分别取证
20
+
21
+ - **任务分类**:原始 `MissionRuntimeAsset/{missionId}.json` 的 `missionType`、`charId`、`missionChapterBitmask`、`sortId`、`missionName.key`。导出器生成的 e/c 前缀分类独立保存。唐唐的 c27m1–c27m5 原始 `missionType=0`,属于第二章进程III的主线导航,且有角色关联。角色 Wiki 的文档类型不等于任务分类。
22
+ - **显示目录与任务顺序**:社区显式有序目录提供旧进程的章/进程/任务顺序。英文任务名经 TextTable.id→官方 EN/CN I18n 表精确连接至原始任务ID,不凭英文同名强行选ID。`sortId` 全为0,不能用于排序;`missionChapterBitmask` 不当作章编号。两处 Work Preparation 与一处 Freedom of Expansion 保留候选和未决状态。第二章进程VIII目前只证实五篇正文成员,跨任务顺序 `unknown`,逐任务 `order=null`。
23
+ - **公开版本归属**:版本主题与完整内部构建号分开。1.1进程III、1.4进程VII有官方开放边界;1.2进程IV–VI来自前后官方边界的推导;1.6进程VIII由社区资料支持。1.0、1.3、1.5的新增进程映射保留未知,不表示该版本无新增主线。返回 `mapping_status`、`evidence`,不截取内部构建号推断公开版本。
24
+
25
+ 第二章进程III跨“意外来袭”和“风平水不静”两个生成系列,因此返回两份梗概;第二章进程I的现有生成梗概名为“准备工作”,保留可验证的现有标题。任务名与进程名同名时,“上一段/下一段”不会把任务前后关系偷换成进程前后关系;结果明确 `navigation_level:"process"` 并说明锚点。
26
+
27
+ ## 重建与验证
28
+
29
+ 目录文件为 `resources/endfield-mainline.json`,构建器为 `bin/build-mainline-catalog.py`。构建器仅用Python标准库,按保存的来源快照和固定资料构建,不在运行工具时联网。默认输入为本仓库的最新导出和同机拆包目录,可通过脚本参数指定;先运行 `--help` 查看可配置路径。
30
+
31
+ ```sh
32
+ python3 -B bin/build-mainline-catalog.py
33
+ node --test test/navigation.test.js
34
+ npm test
35
+ ```
36
+
37
+ 父仓库还提供 `agent/evals/endfield-mainline.mjs RELEASES_DIR [REPORT]`:用实际 CorpusStore 运行玩家问法,读取所有梗概以及最新进程的五个任务原文,验证返回定位器而非只验证结果非空。
38
+
39
+ 来源:
40
+
41
+ - https://endfield.wiki.gg/wiki/Mission/Main?oldid=63259
42
+ - https://endfield.wiki.gg/wiki/Sanctuary_of_Ink?oldid=65256
43
+ - https://endfield.gryphline.com/en-us/news/8907
44
+ - https://endfield.gryphline.com/en-us/news/0751
45
+ - https://endfield.gryphline.com/en-us/news/5200