cortico 0.1.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 (311) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +47 -0
  3. package/package.json +43 -0
  4. package/src/boot.ts +46 -0
  5. package/src/bot.ts +1342 -0
  6. package/src/config-file.ts +41 -0
  7. package/src/core/README.md +92 -0
  8. package/src/core/billing.ts +26 -0
  9. package/src/core/blobs.ts +106 -0
  10. package/src/core/bus.ts +311 -0
  11. package/src/core/config-schema.ts +201 -0
  12. package/src/core/config.ts +262 -0
  13. package/src/core/core.ts +699 -0
  14. package/src/core/cost.ts +244 -0
  15. package/src/core/event-store.ts +304 -0
  16. package/src/core/fork.ts +113 -0
  17. package/src/core/generation.ts +118 -0
  18. package/src/core/instance-lock.ts +215 -0
  19. package/src/core/ipc-logger.ts +105 -0
  20. package/src/core/language.ts +62 -0
  21. package/src/core/log-context.ts +38 -0
  22. package/src/core/loop.ts +1858 -0
  23. package/src/core/markers.ts +48 -0
  24. package/src/core/prefix.ts +121 -0
  25. package/src/core/run.ts +95 -0
  26. package/src/core/secrets.ts +18 -0
  27. package/src/core/session.ts +56 -0
  28. package/src/core/sessions.ts +261 -0
  29. package/src/core/state.ts +104 -0
  30. package/src/core/template.ts +72 -0
  31. package/src/core/timers.ts +129 -0
  32. package/src/core/tool-log.ts +135 -0
  33. package/src/core/transcript.ts +92 -0
  34. package/src/core/truncate.ts +121 -0
  35. package/src/core/types.ts +1140 -0
  36. package/src/core/usage-log.ts +79 -0
  37. package/src/core/util.ts +386 -0
  38. package/src/deploy-listing.ts +160 -0
  39. package/src/deploy.ts +162 -0
  40. package/src/extensions/README.md +72 -0
  41. package/src/extensions/dry-mount.ts +390 -0
  42. package/src/extensions/manifest.ts +153 -0
  43. package/src/extensions/runtime.ts +42 -0
  44. package/src/extensions.ts +519 -0
  45. package/src/launcher.ts +285 -0
  46. package/src/paths.ts +155 -0
  47. package/src/protocol/open-responses/LICENSE +201 -0
  48. package/src/protocol/open-responses/README.md +14 -0
  49. package/src/protocol/open-responses/context-helpers.ts +27 -0
  50. package/src/protocol/open-responses/context-log.ts +59 -0
  51. package/src/protocol/open-responses/context.ts +89 -0
  52. package/src/protocol/open-responses/generated.ts +115 -0
  53. package/src/protocol/open-responses/index.ts +33 -0
  54. package/src/protocol/open-responses/openapi.json +4230 -0
  55. package/src/protocol/open-responses/stream.ts +146 -0
  56. package/src/protocol/open-responses/tokens.ts +31 -0
  57. package/src/providers/README.md +86 -0
  58. package/src/providers/base.ts +111 -0
  59. package/src/providers/configuration.ts +97 -0
  60. package/src/providers/console/config.ts +21 -0
  61. package/src/providers/console/settings.ts +443 -0
  62. package/src/providers/console/strings.ts +74 -0
  63. package/src/providers/console/types.ts +11 -0
  64. package/src/providers/llamacpp/archive.ts +148 -0
  65. package/src/providers/llamacpp/catalog.ts +247 -0
  66. package/src/providers/llamacpp/config.ts +36 -0
  67. package/src/providers/llamacpp/console/client.ts +6 -0
  68. package/src/providers/llamacpp/console/models-panel.ts +120 -0
  69. package/src/providers/llamacpp/console/runtime-panel.ts +173 -0
  70. package/src/providers/llamacpp/console/server.ts +153 -0
  71. package/src/providers/llamacpp/index.ts +95 -0
  72. package/src/providers/llamacpp/native.ts +51 -0
  73. package/src/providers/llamacpp/options.ts +117 -0
  74. package/src/providers/llamacpp/runtime-store.ts +139 -0
  75. package/src/providers/llamacpp/runtime.ts +214 -0
  76. package/src/providers/llamacpp/server.ts +286 -0
  77. package/src/providers/llamacpp/strings.ts +230 -0
  78. package/src/providers/openai-responses-compat/config.ts +20 -0
  79. package/src/providers/openai-responses-compat/index.ts +89 -0
  80. package/src/providers/openai-responses-compat/native.ts +127 -0
  81. package/src/providers/openai-responses-compat/strings.ts +19 -0
  82. package/src/providers/pricebook.ts +75 -0
  83. package/src/providers/registry.ts +137 -0
  84. package/src/providers/strings.ts +71 -0
  85. package/src/providers/transport/chat.ts +156 -0
  86. package/src/providers/transport/errors.ts +48 -0
  87. package/src/providers/transport/history.ts +68 -0
  88. package/src/providers/transport/native-input.ts +83 -0
  89. package/src/providers/transport/native-types.ts +13 -0
  90. package/src/providers/transport/response-assembly.ts +191 -0
  91. package/src/providers/transport/response-http.ts +196 -0
  92. package/src/providers/transport/response-meters.ts +27 -0
  93. package/src/providers/transport/responses-input.ts +56 -0
  94. package/src/web/README.md +84 -0
  95. package/src/web/client/console-pages/builtins/llm-settings/panel.ts +625 -0
  96. package/src/web/client/console-pages/builtins/llm-settings/pricing-panel.ts +168 -0
  97. package/src/web/client/console-pages/builtins/llm-settings/strings.ts +213 -0
  98. package/src/web/client/console-pages/builtins.ts +10 -0
  99. package/src/web/client/console-pages/context.ts +135 -0
  100. package/src/web/client/console-pages/host.ts +633 -0
  101. package/src/web/client/console-pages/loader.ts +132 -0
  102. package/src/web/client/console-pages/strings.ts +90 -0
  103. package/src/web/client/console-pages/tools/strings.ts +49 -0
  104. package/src/web/client/console-pages/tools/view.ts +215 -0
  105. package/src/web/client/core/api.ts +200 -0
  106. package/src/web/client/core/language.ts +56 -0
  107. package/src/web/client/core/lifecycle.ts +132 -0
  108. package/src/web/client/core/router.ts +199 -0
  109. package/src/web/client/core/stream.ts +157 -0
  110. package/src/web/client/core/websocket.ts +82 -0
  111. package/src/web/client/features/appearance/index.ts +333 -0
  112. package/src/web/client/features/appearance/strings.ts +87 -0
  113. package/src/web/client/features/config/index.ts +11 -0
  114. package/src/web/client/features/config/strings.ts +41 -0
  115. package/src/web/client/features/config/view.ts +428 -0
  116. package/src/web/client/features/core/events.ts +197 -0
  117. package/src/web/client/features/core/index.ts +254 -0
  118. package/src/web/client/features/core/run.ts +56 -0
  119. package/src/web/client/features/core/runlog.ts +214 -0
  120. package/src/web/client/features/core/sessions.ts +53 -0
  121. package/src/web/client/features/core/strings.ts +157 -0
  122. package/src/web/client/features/extensions/index.ts +422 -0
  123. package/src/web/client/features/extensions/strings.ts +164 -0
  124. package/src/web/client/features/feature.ts +93 -0
  125. package/src/web/client/features/live/context.ts +280 -0
  126. package/src/web/client/features/live/fork.ts +156 -0
  127. package/src/web/client/features/live/index.ts +392 -0
  128. package/src/web/client/features/live/onboarding.ts +113 -0
  129. package/src/web/client/features/live/protocol.ts +122 -0
  130. package/src/web/client/features/live/sessions.ts +71 -0
  131. package/src/web/client/features/live/status.ts +68 -0
  132. package/src/web/client/features/live/strings.ts +252 -0
  133. package/src/web/client/features/live/timeline.ts +590 -0
  134. package/src/web/client/features/prompts/editor.ts +292 -0
  135. package/src/web/client/features/prompts/index.ts +346 -0
  136. package/src/web/client/features/prompts/strings.ts +136 -0
  137. package/src/web/client/features/prompts/view.ts +256 -0
  138. package/src/web/client/features/providers/index.ts +100 -0
  139. package/src/web/client/features/providers/strings.ts +23 -0
  140. package/src/web/client/features/settings/general.ts +28 -0
  141. package/src/web/client/features/settings/index.ts +82 -0
  142. package/src/web/client/features/settings/strings.ts +33 -0
  143. package/src/web/client/features/storage/index.ts +1 -0
  144. package/src/web/client/features/storage/strings.ts +45 -0
  145. package/src/web/client/features/storage/view.ts +154 -0
  146. package/src/web/client/features/usage/chart.ts +486 -0
  147. package/src/web/client/features/usage/index.ts +407 -0
  148. package/src/web/client/features/usage/labels.ts +46 -0
  149. package/src/web/client/features/usage/range.ts +36 -0
  150. package/src/web/client/features/usage/state.ts +37 -0
  151. package/src/web/client/features/usage/strings.ts +191 -0
  152. package/src/web/client/features/usage/tooltip.ts +86 -0
  153. package/src/web/client/features/usage/types.ts +96 -0
  154. package/src/web/client/features/worlds/index.ts +424 -0
  155. package/src/web/client/features/worlds/strings.ts +121 -0
  156. package/src/web/client/main.ts +273 -0
  157. package/src/web/client/shell/avatar.ts +233 -0
  158. package/src/web/client/shell/index.ts +549 -0
  159. package/src/web/client/shell/strings.ts +89 -0
  160. package/src/web/client/theme/handoff.ts +43 -0
  161. package/src/web/client/theme/palette.ts +109 -0
  162. package/src/web/client/theme/registry.ts +130 -0
  163. package/src/web/client/theme/storage.ts +72 -0
  164. package/src/web/client/theme/strings.ts +123 -0
  165. package/src/web/client/theme/studio.ts +366 -0
  166. package/src/web/client/ui/actions.ts +109 -0
  167. package/src/web/client/ui/data.ts +185 -0
  168. package/src/web/client/ui/dom.ts +23 -0
  169. package/src/web/client/ui/fields.ts +191 -0
  170. package/src/web/client/ui/format.ts +75 -0
  171. package/src/web/client/ui/icons.ts +183 -0
  172. package/src/web/client/ui/images.ts +91 -0
  173. package/src/web/client/ui/index.ts +89 -0
  174. package/src/web/client/ui/lamp.ts +125 -0
  175. package/src/web/client/ui/log.ts +98 -0
  176. package/src/web/client/ui/overlay.ts +184 -0
  177. package/src/web/client/ui/page.ts +8 -0
  178. package/src/web/client/ui/prompt-input.tsx +311 -0
  179. package/src/web/client/ui/sheet.ts +131 -0
  180. package/src/web/client/ui/strings.ts +71 -0
  181. package/src/web/console-pages.ts +367 -0
  182. package/src/web/files.ts +107 -0
  183. package/src/web/path-picker.ts +296 -0
  184. package/src/web/public/index.html +17 -0
  185. package/src/web/public/styles.css +1715 -0
  186. package/src/web/server.ts +1883 -0
  187. package/src/web/shared/client-panel.ts +603 -0
  188. package/src/web/shared/console-protocol.ts +560 -0
  189. package/src/web/shared/css.d.ts +1 -0
  190. package/src/web/shared/path-picker.ts +23 -0
  191. package/src/web/shared/theme.ts +256 -0
  192. package/src/web/theme-store.ts +37 -0
  193. package/src/world.ts +449 -0
  194. package/src/worlds/bilibili/ENV_PROMPT.md +5 -0
  195. package/src/worlds/bilibili/README.md +176 -0
  196. package/src/worlds/bilibili/audience-admission.ts +995 -0
  197. package/src/worlds/bilibili/client.ts +528 -0
  198. package/src/worlds/bilibili/coalescing-buffer.ts +125 -0
  199. package/src/worlds/bilibili/config.ts +185 -0
  200. package/src/worlds/bilibili/console/client.ts +202 -0
  201. package/src/worlds/bilibili/definition.ts +35 -0
  202. package/src/worlds/bilibili/gift-frame.ts +131 -0
  203. package/src/worlds/bilibili/normalize.ts +452 -0
  204. package/src/worlds/bilibili/overlay/announcement.ts +76 -0
  205. package/src/worlds/bilibili/overlay/assets.ts +82 -0
  206. package/src/worlds/bilibili/overlay/model.ts +494 -0
  207. package/src/worlds/bilibili/overlay/project.ts +202 -0
  208. package/src/worlds/bilibili/overlay/server.ts +286 -0
  209. package/src/worlds/bilibili/overlay/types.ts +217 -0
  210. package/src/worlds/bilibili/overlay/web/app.js +690 -0
  211. package/src/worlds/bilibili/overlay/web/editor.css +297 -0
  212. package/src/worlds/bilibili/overlay/web/editor.html +102 -0
  213. package/src/worlds/bilibili/overlay/web/editor.js +2116 -0
  214. package/src/worlds/bilibili/overlay/web/overlay.html +13 -0
  215. package/src/worlds/bilibili/overlay/web/styles.css +258 -0
  216. package/src/worlds/bilibili/protobuf.ts +123 -0
  217. package/src/worlds/bilibili/wire.ts +94 -0
  218. package/src/worlds/bilibili/world.ts +1389 -0
  219. package/src/worlds/console-fixture/ENV_PROMPT.md +1 -0
  220. package/src/worlds/console-fixture/console/client.ts +72 -0
  221. package/src/worlds/console-fixture/world.ts +73 -0
  222. package/src/worlds/index.ts +15 -0
  223. package/src/worlds/minecraft/ENV_PROMPT.md +32 -0
  224. package/src/worlds/minecraft/ENV_PROMPT_CAMERA.md +2 -0
  225. package/src/worlds/minecraft/LICENSE-mineflayer-pathfinder.txt +21 -0
  226. package/src/worlds/minecraft/README.md +917 -0
  227. package/src/worlds/minecraft/blueprint-plan.ts +915 -0
  228. package/src/worlds/minecraft/blueprint-registry.ts +566 -0
  229. package/src/worlds/minecraft/blueprint-repair.ts +236 -0
  230. package/src/worlds/minecraft/blueprint-resource.ts +272 -0
  231. package/src/worlds/minecraft/blueprint.ts +492 -0
  232. package/src/worlds/minecraft/body-lease.ts +340 -0
  233. package/src/worlds/minecraft/bridge.ts +749 -0
  234. package/src/worlds/minecraft/check.ts +651 -0
  235. package/src/worlds/minecraft/chests.ts +292 -0
  236. package/src/worlds/minecraft/client-launch.ts +272 -0
  237. package/src/worlds/minecraft/client-options.ts +85 -0
  238. package/src/worlds/minecraft/client-skins.ts +164 -0
  239. package/src/worlds/minecraft/client-window.ps1 +91 -0
  240. package/src/worlds/minecraft/client.ts +423 -0
  241. package/src/worlds/minecraft/combat-context.ts +109 -0
  242. package/src/worlds/minecraft/combat.ts +1552 -0
  243. package/src/worlds/minecraft/config.ts +499 -0
  244. package/src/worlds/minecraft/console/access.ts +218 -0
  245. package/src/worlds/minecraft/console/client.ts +244 -0
  246. package/src/worlds/minecraft/console/log.ts +110 -0
  247. package/src/worlds/minecraft/console/mount.ts +420 -0
  248. package/src/worlds/minecraft/console/skin.ts +257 -0
  249. package/src/worlds/minecraft/console/style.css +154 -0
  250. package/src/worlds/minecraft/console/world.ts +449 -0
  251. package/src/worlds/minecraft/deaths.ts +135 -0
  252. package/src/worlds/minecraft/definition.ts +18 -0
  253. package/src/worlds/minecraft/engine-child.ts +274 -0
  254. package/src/worlds/minecraft/engine-ipc.ts +98 -0
  255. package/src/worlds/minecraft/entity-facts.ts +132 -0
  256. package/src/worlds/minecraft/escape.ts +407 -0
  257. package/src/worlds/minecraft/executor.ts +14494 -0
  258. package/src/worlds/minecraft/explored.ts +156 -0
  259. package/src/worlds/minecraft/geometry.ts +205 -0
  260. package/src/worlds/minecraft/goal-plan.ts +647 -0
  261. package/src/worlds/minecraft/item-break.ts +126 -0
  262. package/src/worlds/minecraft/item-facts.ts +146 -0
  263. package/src/worlds/minecraft/item-pick.ts +67 -0
  264. package/src/worlds/minecraft/level-dat.ts +176 -0
  265. package/src/worlds/minecraft/log.ts +132 -0
  266. package/src/worlds/minecraft/mineflayer-fixes.ts +1180 -0
  267. package/src/worlds/minecraft/names.ts +377 -0
  268. package/src/worlds/minecraft/pathfinder-lib.d.ts +13 -0
  269. package/src/worlds/minecraft/pathfinder-perf.ts +796 -0
  270. package/src/worlds/minecraft/piglin.ts +49 -0
  271. package/src/worlds/minecraft/policy.ts +547 -0
  272. package/src/worlds/minecraft/precheck.ts +655 -0
  273. package/src/worlds/minecraft/proxy.ts +445 -0
  274. package/src/worlds/minecraft/ranged.ts +635 -0
  275. package/src/worlds/minecraft/readouts.ts +102 -0
  276. package/src/worlds/minecraft/round.ts +26 -0
  277. package/src/worlds/minecraft/search-observation.ts +92 -0
  278. package/src/worlds/minecraft/server-config.ts +467 -0
  279. package/src/worlds/minecraft/server.ts +658 -0
  280. package/src/worlds/minecraft/show.ts +84 -0
  281. package/src/worlds/minecraft/skills.ts +1763 -0
  282. package/src/worlds/minecraft/terrain.ts +1735 -0
  283. package/src/worlds/minecraft/window.ts +96 -0
  284. package/src/worlds/minecraft/works.ts +179 -0
  285. package/src/worlds/minecraft/world.ts +6438 -0
  286. package/src/worlds/qq/ENV_PROMPT.md +5 -0
  287. package/src/worlds/qq/config.ts +108 -0
  288. package/src/worlds/qq/console/client.ts +126 -0
  289. package/src/worlds/qq/console/events.ts +155 -0
  290. package/src/worlds/qq/console/gate.ts +232 -0
  291. package/src/worlds/qq/console/roster.ts +203 -0
  292. package/src/worlds/qq/console/style.css +65 -0
  293. package/src/worlds/qq/conversation.ts +28 -0
  294. package/src/worlds/qq/definition.ts +62 -0
  295. package/src/worlds/qq/driver.ts +375 -0
  296. package/src/worlds/qq/history-tools.ts +209 -0
  297. package/src/worlds/qq/normalize.ts +323 -0
  298. package/src/worlds/qq/qface-map.ts +282 -0
  299. package/src/worlds/qq/vision-prompt.ts +27 -0
  300. package/src/worlds/qq/vision.ts +642 -0
  301. package/src/worlds/qq/vlm.ts +142 -0
  302. package/src/worlds/qq/world.ts +1503 -0
  303. package/src/worlds/terminal/ENV_PROMPT.md +11 -0
  304. package/src/worlds/terminal/config.ts +9 -0
  305. package/src/worlds/terminal/definition.ts +14 -0
  306. package/src/worlds/terminal/world.ts +700 -0
  307. package/src/worlds/websearch/ENV_PROMPT.md +1 -0
  308. package/src/worlds/websearch/brave-client.ts +261 -0
  309. package/src/worlds/websearch/config.ts +75 -0
  310. package/src/worlds/websearch/definition.ts +20 -0
  311. package/src/worlds/websearch/world.ts +171 -0
@@ -0,0 +1,407 @@
1
+ /**
2
+ * mc_escape 的重生点解析、候选排序、传送指令和回执。
3
+ * World 负责清空队列、发送指令与等待位置变化。
4
+ */
5
+ import { existsSync, readFileSync } from 'node:fs';
6
+ import { join } from 'node:path';
7
+ import { gunzipSync } from 'node:zlib';
8
+ import { zhDimension } from './names.ts';
9
+
10
+ export const SET_SPAWN_TRANSLATE = 'block.minecraft.set_spawn';
11
+ const NEAR_SPAWN_BLOCKS = 4;
12
+ export const ESCAPE_TP_MS = 3_000;
13
+
14
+ type SpawnSource = 'bed' | 'anchor' | 'world' | 'mark';
15
+
16
+ export interface SpawnTarget {
17
+ x: number;
18
+ y: number;
19
+ z: number;
20
+ dimension: string;
21
+ source: SpawnSource;
22
+ /** `mark` 的路标原名;别的来源没有名字 */
23
+ name?: string;
24
+ }
25
+
26
+ export interface Vec3like {
27
+ x: number;
28
+ y: number;
29
+ z: number;
30
+ }
31
+
32
+ export function normalizeDimension(raw: string | undefined | null): string {
33
+ const s = (raw ?? '').trim();
34
+ if (!s) return 'minecraft:overworld';
35
+ return s.includes(':') ? s : `minecraft:${s}`;
36
+ }
37
+
38
+ export function spawnAt(
39
+ pos: Vec3like,
40
+ dimension: string | undefined | null,
41
+ source: SpawnSource,
42
+ name?: string,
43
+ ): SpawnTarget {
44
+ return {
45
+ x: pos.x, y: pos.y, z: pos.z,
46
+ dimension: normalizeDimension(dimension),
47
+ source,
48
+ ...(name !== undefined ? { name } : {}),
49
+ };
50
+ }
51
+
52
+ function worldSpawnOf(pos: Vec3like | null | undefined): SpawnTarget | null {
53
+ if (!pos) return null;
54
+ return spawnAt(pos, 'minecraft:overworld', 'world');
55
+ }
56
+
57
+ /** 个人重生点优先;没有则世界出生点。同维度没有候选时的兜底口径。 */
58
+ export function resolveEscapeTarget(
59
+ personal: SpawnTarget | null,
60
+ world: Vec3like | null | undefined,
61
+ ): SpawnTarget | null {
62
+ return personal ?? worldSpawnOf(world);
63
+ }
64
+
65
+ /** 一个候选安全锚,连同它此刻的读数 */
66
+ interface EscapeCandidate {
67
+ target: SpawnTarget;
68
+ /** 与当前位置的水平直线距离;不同维度算不了,给 null */
69
+ distance: number | null;
70
+ /** 她自己圈的危险区名字(这一格落在里面);不在任何危险区里为空数组 */
71
+ danger: readonly string[];
72
+ /** 世界读数确认锚点或正邻格可站立;缺少判据或读数时为 null。 */
73
+ standable: boolean | null;
74
+ /** 世界读数确认的具体落脚格;没有确认时保留锚坐标。 */
75
+ landing: Vec3like | null;
76
+ }
77
+
78
+ function anchorKey(t: SpawnTarget): string {
79
+ return `${normalizeDimension(t.dimension)}:${Math.floor(t.x)},${Math.floor(t.y)},${Math.floor(t.z)}`;
80
+ }
81
+
82
+ /**
83
+ * 安全锚按个人重生点、世界出生点、路标的顺序收集,同维度同格保留首项。
84
+ * 同维度候选优先,明确不可站立的候选降级,其余按水平距离排序;未知可站性不降级。
85
+ */
86
+ export function escapeCandidates(
87
+ personal: SpawnTarget | null,
88
+ world: Vec3like | null | undefined,
89
+ marks: readonly SpawnTarget[],
90
+ here: Vec3like,
91
+ hereDimension: string,
92
+ dangerAt?: (t: SpawnTarget) => readonly string[],
93
+ landingAt?: (t: SpawnTarget) => Vec3like | false | null,
94
+ ): EscapeCandidate[] {
95
+ const dim = normalizeDimension(hereDimension);
96
+ const seen = new Set<string>();
97
+ const out: EscapeCandidate[] = [];
98
+ for (const t of [personal, worldSpawnOf(world), ...marks]) {
99
+ if (!t) continue;
100
+ const key = anchorKey(t);
101
+ if (seen.has(key)) continue;
102
+ seen.add(key);
103
+ const landing = landingAt?.(t) ?? null;
104
+ out.push({
105
+ target: t,
106
+ distance: normalizeDimension(t.dimension) === dim
107
+ ? Math.hypot(t.x - here.x, t.z - here.z)
108
+ : null,
109
+ danger: dangerAt?.(t) ?? [],
110
+ standable: landing === null ? null : landing !== false,
111
+ landing: landing === false ? null : landing,
112
+ });
113
+ }
114
+ return out.sort((a, b) => {
115
+ if (a.distance === null) return b.distance === null ? 0 : 1;
116
+ if (b.distance === null) return -1;
117
+ const aBad = a.standable === false ? 1 : 0;
118
+ const bBad = b.standable === false ? 1 : 0;
119
+ if (aBad !== bBad) return aBad - bBad;
120
+ return a.distance - b.distance;
121
+ });
122
+ }
123
+
124
+ /** 采用已排序候选中的首个同维度锚;没有同维度候选时使用个人重生点,其次世界出生点。 */
125
+ export function pickEscapeTarget(
126
+ candidates: readonly EscapeCandidate[],
127
+ personal: SpawnTarget | null,
128
+ world: Vec3like | null | undefined,
129
+ ): SpawnTarget | null {
130
+ const nearest = candidates.find((c) => c.distance !== null);
131
+ return nearest?.target ?? resolveEscapeTarget(personal, world);
132
+ }
133
+
134
+ function anchorText(t: SpawnTarget): string {
135
+ if (t.source === 'world') return '世界出生点';
136
+ if (t.source === 'anchor') return '重生锚';
137
+ if (t.source === 'mark') return `你圈的「${t.name ?? '路标'}」`;
138
+ return '床重生点';
139
+ }
140
+
141
+ /** 多个候选时报告坐标、距离、可站性及登记的危险区。 */
142
+ export function formatCandidates(
143
+ candidates: readonly EscapeCandidate[],
144
+ chosen: SpawnTarget | null,
145
+ ): string {
146
+ if (candidates.length < 2) return '';
147
+ const one = (c: EscapeCandidate): string => {
148
+ const at = `(${Math.round(c.target.x)}, ${Math.round(c.target.y)}, ${Math.round(c.target.z)})`;
149
+ const far = c.distance === null
150
+ ? `在${zhDimension(c.target.dimension)}`
151
+ : `${Math.round(c.distance)} 格`;
152
+ const danger = c.danger.length > 0 ? `,在你圈的危险区「${c.danger.join('」「')}」里` : '';
153
+ const footing = c.standable === false ? ',落点按读数站不住人' : '';
154
+ const mark = chosen && anchorKey(c.target) === anchorKey(chosen) ? '←去的是这个' : '';
155
+ return `${anchorText(c.target)} ${at} ${far}${danger}${footing}${mark}`;
156
+ };
157
+ return `候选安全锚 ${candidates.length} 个,按远近排:${candidates.map(one).join(';')}。`;
158
+ }
159
+
160
+ export function alreadyNear(
161
+ here: Vec3like,
162
+ dimension: string,
163
+ target: SpawnTarget,
164
+ radius = NEAR_SPAWN_BLOCKS,
165
+ ): boolean {
166
+ if (normalizeDimension(dimension) !== target.dimension) return false;
167
+ return Math.hypot(here.x - target.x, here.y - target.y, here.z - target.z) < radius;
168
+ }
169
+
170
+ export function tpLine(name: string, target: SpawnTarget): string {
171
+ const x = Math.floor(target.x) + 0.5;
172
+ const z = Math.floor(target.z) + 0.5;
173
+ return `execute in ${target.dimension} run tp ${name} ${x} ${target.y} ${z}`;
174
+ }
175
+
176
+ export function playerDatPath(serverDir: string, levelName: string, uuid: string): string {
177
+ return join(serverDir, levelName, 'playerdata', `${uuid}.dat`);
178
+ }
179
+
180
+ export function readPlayerDatFile(path: string): SpawnTarget | null {
181
+ if (!existsSync(path)) return null;
182
+ return parsePlayerDat(readFileSync(path));
183
+ }
184
+
185
+ /** 从 gzip 或未压缩的 player.dat 读取 Spawn*;缺少坐标时返回 null。 */
186
+ export function parsePlayerDat(buf: Buffer): SpawnTarget | null {
187
+ const nbt = decodeRootCompound(maybeGunzip(buf));
188
+ if (!nbt) return null;
189
+ const x = asNumber(nbt.SpawnX);
190
+ const y = asNumber(nbt.SpawnY);
191
+ const z = asNumber(nbt.SpawnZ);
192
+ if (x === null || y === null || z === null) return null;
193
+ const dim = typeof nbt.SpawnDimension === 'string' ? nbt.SpawnDimension : 'minecraft:overworld';
194
+ return spawnAt(
195
+ { x, y, z },
196
+ dim,
197
+ normalizeDimension(dim) === 'minecraft:the_nether' ? 'anchor' : 'bed',
198
+ );
199
+ }
200
+
201
+ function formatCleared(cleared: string | null): string {
202
+ return cleared ?? '队列本来就是空的';
203
+ }
204
+
205
+ /** 逃生回执显示目标坐标;提供同维度出发点时附目标水平距离,传送失败时另附实测当前位置。 */
206
+ export function formatEscapeReceipt(
207
+ kind: 'already' | 'arrived' | 'rejected',
208
+ target: SpawnTarget,
209
+ cleared: string | null,
210
+ here?: Vec3like & { dimension?: string },
211
+ from?: Vec3like & { dimension?: string },
212
+ repeat?: string | null,
213
+ candidates?: string | null,
214
+ ): string {
215
+ const at = `[${zhDimension(target.dimension)}] (${Math.round(target.x)}, ${Math.round(target.y)}, ${Math.round(target.z)})`;
216
+ const place = anchorText(target);
217
+ const queue = `${candidates ? `${candidates}` : ''}${formatCleared(cleared)}`;
218
+ const moved = from && from.dimension
219
+ && normalizeDimension(from.dimension) === normalizeDimension(target.dimension)
220
+ ? `,离你出发那儿 ${Math.round(Math.hypot(target.x - from.x, target.z - from.z))} 格`
221
+ : '';
222
+ const again = repeat ? `${repeat}` : '';
223
+ if (kind === 'already') return `已经在${place}附近 ${at}。${again}${queue}`;
224
+ if (kind === 'arrived') return `已回到${place} ${at}${moved}。${again}${queue}`;
225
+ const now = here
226
+ ? `还在${here.dimension ? `[${zhDimension(here.dimension)}] ` : ' '}`
227
+ + `(${Math.round(here.x)}, ${Math.round(here.y)}, ${Math.round(here.z)})。`
228
+ : '';
229
+ return `没能传送(服务器拒了指令,多半没有 op)。本来会把你送到${place} ${at}${moved}。${now}${again}${queue}`;
230
+ }
231
+
232
+ interface EscapeBot {
233
+ entity: { position: Vec3like };
234
+ game?: { dimension?: string };
235
+ spawnPoint?: Vec3like;
236
+ }
237
+
238
+ interface EscapeDeps {
239
+ getBot: () => EscapeBot | null;
240
+ playerName: string;
241
+ personalSpawn: SpawnTarget | null;
242
+ /** 登记为家或床的安全路标;空数组时仍可使用个人重生点与世界出生点。 */
243
+ safeMarks?: readonly SpawnTarget[];
244
+ dangerAt?: (t: SpawnTarget) => readonly string[];
245
+ /**
246
+ * 返回确认能站的具体落脚格;false 表示全不可站,null 表示读不到。
247
+ * 不可站只作排序降级键,不做硬过滤。
248
+ */
249
+ landingAt?: (t: SpawnTarget) => Vec3like | false | null;
250
+ clearQueue: () => string | null;
251
+ /** 托管服 stdin;成功返回 true,否则调用方改走 bot 聊天 */
252
+ sendConsole: (line: string) => boolean;
253
+ chat: (text: string) => void;
254
+ hold: (ms: number) => void;
255
+ waitMove: (ms: number) => Promise<boolean>;
256
+ timeoutMs?: number;
257
+ /** 在传送核验前记录本次选择的逃生目标并生成重复说明;无说明时可返回空串或 null。 */
258
+ noteRepeat?: (target: SpawnTarget) => string | null;
259
+ }
260
+
261
+ export async function runEscape(deps: EscapeDeps): Promise<string> {
262
+ const bot = deps.getBot();
263
+ if (!bot?.entity) return '[mc_escape 失败] 未连接服务器';
264
+ const dim = normalizeDimension(bot.game?.dimension);
265
+ const from = {
266
+ x: bot.entity.position.x, y: bot.entity.position.y, z: bot.entity.position.z,
267
+ dimension: dim,
268
+ };
269
+ const candidates = escapeCandidates(
270
+ deps.personalSpawn, bot.spawnPoint, deps.safeMarks ?? [], from, dim, deps.dangerAt, deps.landingAt,
271
+ );
272
+ const anchor = pickEscapeTarget(candidates, deps.personalSpawn, bot.spawnPoint);
273
+ if (!anchor) return '[mc_escape 失败] 还不知道出生点,也没有圈过可以去的地方';
274
+ const chosen = candidates.find((c) => c.target === anchor);
275
+ const target = chosen?.landing ? { ...anchor, ...chosen.landing } : anchor;
276
+ const chosenBad = chosen?.standable === false;
277
+ const footing = chosenBad
278
+ ? '所有候选落点按世界读数都站不住人(落点和它周围一格要么实心要么悬空),去的是其中最近的。'
279
+ : '';
280
+ const list = `${footing}${formatCandidates(candidates, anchor)}`;
281
+
282
+ const timeoutMs = deps.timeoutMs ?? ESCAPE_TP_MS;
283
+ deps.hold(timeoutMs + 500);
284
+ const repeat = deps.noteRepeat?.(target) ?? null;
285
+ const cleared = deps.clearQueue();
286
+ const inLanding = (p: Vec3like): boolean => Math.floor(p.x) === Math.floor(target.x)
287
+ && Math.floor(p.y) === Math.floor(target.y) && Math.floor(p.z) === Math.floor(target.z);
288
+ if (alreadyNear(from, dim, target) && (!chosen?.landing || inLanding(from))) {
289
+ return formatEscapeReceipt('already', target, cleared, undefined, undefined, repeat, list);
290
+ }
291
+
292
+ const line = tpLine(deps.playerName, target);
293
+ if (!deps.sendConsole(line)) deps.chat(`/${line}`);
294
+ await deps.waitMove(timeoutMs);
295
+ const herePos = deps.getBot()?.entity.position ?? bot.entity.position;
296
+ const hereDim = normalizeDimension(deps.getBot()?.game?.dimension ?? dim);
297
+ const here = { x: herePos.x, y: herePos.y, z: herePos.z, dimension: hereDim };
298
+ if (alreadyNear(here, hereDim, target) && (!chosen?.landing || inLanding(here))) {
299
+ return formatEscapeReceipt('arrived', target, cleared, undefined, from, repeat, list);
300
+ }
301
+ return formatEscapeReceipt('rejected', target, cleared, here, from, repeat, list);
302
+ }
303
+
304
+ function asNumber(v: unknown): number | null {
305
+ return typeof v === 'number' && Number.isFinite(v) ? v : null;
306
+ }
307
+
308
+ function maybeGunzip(buf: Buffer): Buffer {
309
+ if (buf.length >= 2 && buf[0] === 0x1f && buf[1] === 0x8b) return gunzipSync(buf);
310
+ return buf;
311
+ }
312
+
313
+ const TAG_END = 0;
314
+ const TAG_BYTE = 1;
315
+ const TAG_SHORT = 2;
316
+ const TAG_INT = 3;
317
+ const TAG_LONG = 4;
318
+ const TAG_FLOAT = 5;
319
+ const TAG_DOUBLE = 6;
320
+ const TAG_BYTE_ARRAY = 7;
321
+ const TAG_STRING = 8;
322
+ const TAG_LIST = 9;
323
+ const TAG_COMPOUND = 10;
324
+ const TAG_INT_ARRAY = 11;
325
+ const TAG_LONG_ARRAY = 12;
326
+
327
+ class NbtCursor {
328
+ constructor(private readonly buf: Buffer, private i = 0) {}
329
+
330
+ u8(): number {
331
+ if (this.i >= this.buf.length) throw new Error('nbt eof');
332
+ return this.buf[this.i++];
333
+ }
334
+ i16(): number {
335
+ const v = this.buf.readInt16BE(this.i);
336
+ this.i += 2;
337
+ return v;
338
+ }
339
+ i32(): number {
340
+ const v = this.buf.readInt32BE(this.i);
341
+ this.i += 4;
342
+ return v;
343
+ }
344
+ f32(): number {
345
+ const v = this.buf.readFloatBE(this.i);
346
+ this.i += 4;
347
+ return v;
348
+ }
349
+ f64(): number {
350
+ const v = this.buf.readDoubleBE(this.i);
351
+ this.i += 8;
352
+ return v;
353
+ }
354
+ skip(n: number): void { this.i += n; }
355
+ str(): string {
356
+ const n = this.buf.readUInt16BE(this.i);
357
+ this.i += 2;
358
+ const s = this.buf.subarray(this.i, this.i + n).toString('utf8');
359
+ this.i += n;
360
+ return s;
361
+ }
362
+ }
363
+
364
+ function decodeRootCompound(buf: Buffer): Record<string, number | string> | null {
365
+ try {
366
+ const c = new NbtCursor(buf);
367
+ if (c.u8() !== TAG_COMPOUND) return null;
368
+ c.str();
369
+ return readCompound(c);
370
+ } catch {
371
+ return null;
372
+ }
373
+ }
374
+
375
+ function readCompound(c: NbtCursor): Record<string, number | string> {
376
+ const out: Record<string, number | string> = {};
377
+ for (;;) {
378
+ const type = c.u8();
379
+ if (type === TAG_END) return out;
380
+ const name = c.str();
381
+ const v = readPayload(c, type);
382
+ if (v !== undefined) out[name] = v;
383
+ }
384
+ }
385
+
386
+ function readPayload(c: NbtCursor, type: number): number | string | undefined {
387
+ switch (type) {
388
+ case TAG_BYTE: return c.u8();
389
+ case TAG_SHORT: return c.i16();
390
+ case TAG_INT: return c.i32();
391
+ case TAG_LONG: c.skip(8); return undefined;
392
+ case TAG_FLOAT: return c.f32();
393
+ case TAG_DOUBLE: return c.f64();
394
+ case TAG_BYTE_ARRAY: c.skip(c.i32()); return undefined;
395
+ case TAG_STRING: return c.str();
396
+ case TAG_LIST: {
397
+ const inner = c.u8();
398
+ const n = c.i32();
399
+ for (let i = 0; i < n; i++) readPayload(c, inner);
400
+ return undefined;
401
+ }
402
+ case TAG_COMPOUND: readCompound(c); return undefined;
403
+ case TAG_INT_ARRAY: c.skip(c.i32() * 4); return undefined;
404
+ case TAG_LONG_ARRAY: c.skip(c.i32() * 8); return undefined;
405
+ default: throw new Error(`nbt type ${type}`);
406
+ }
407
+ }