cortico-world-simple-trpg-check 0.3.0 → 0.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/AGENTS.md ADDED
@@ -0,0 +1,12 @@
1
+ # AGENTS.md
2
+
3
+ Before changing this world extension, read [Cortico extension contracts](https://github.com/Pal-AI-Lab/Cortico/blob/main/docs/extensions.md).
4
+
5
+ - Use a Cortico checkout at tag `v0.2.1` for framework checks; the sibling deployment checkout may run an older version. Match `cortico.api` to the printed contract version.
6
+ - After changing tools, events or environment prompt templates, run `pnpm describe:extension <dir>` from the v0.2.1 checkout. Commit the generated file and ship it through `cortico.describe` and `files`.
7
+ - Before publishing, select applicable tags from [TAGS.md](https://github.com/Pal-AI-Lab/cortico-extensions/blob/main/TAGS.md), including notice and warning tags. Maintainer tags take precedence; withdrawing a published warning requires a maintainer request.
8
+ - Add a `## <version>` section to CHANGELOG.md and include it in the package. Declare npm `os` only for supported platform restrictions.
9
+ - Run the package tests, TypeScript checks and WebUI build, then `pnpm check:extension <dir>` from the v0.2.1 checkout. Check the packed file list for the entry, browser assets, self-description and changelog.
10
+ - Build browser assets in an isolated copy while a deployed bot uses this checkout. Use fake deployments and scripted models for verification.
11
+ - Stage explicit paths and make local Conventional Commits. Push, publish and submit maintainer requests only when the user asks.
12
+ - For tag changes, certification, superseding packages or appeals, follow the [index request format](https://github.com/Pal-AI-Lab/cortico-extensions#readme). Certification requires a maintainer review.
package/CHANGELOG.md ADDED
@@ -0,0 +1,17 @@
1
+ # Changelog
2
+
3
+ ## 0.4.1 - 2026-10-17
4
+
5
+ - Reject pending and subsequent requests when the multilingual Laya worker exits.
6
+ - Default to Jev; document backend setup and retaining an existing Laya selection.
7
+ - Declare the official external-setup, local and requires-key tags.
8
+
9
+ ## 0.4.0 - 2026-10-16
10
+
11
+ - Target Cortico 0.2.1 and world contract 6.
12
+ - Rename browser contributions, imports and bundles to WebUI.
13
+ - Add extension self-description, official index tags and publishing guidance.
14
+
15
+ ## 0.3.0
16
+
17
+ - Last release using the previous Cortico extension contract.
package/CLAUDE.md ADDED
@@ -0,0 +1 @@
1
+ Read AGENTS.md
package/README.md CHANGED
@@ -1,4 +1,4 @@
1
- <!-- Owner: src/definition.ts, src/world.ts, src/config.ts, src/request.ts, src/model.ts, src/ENV_PROMPT.md, src/display.ts, src/contest.ts -->
1
+ <!-- Owner: src/definition.ts, src/world.ts, src/config.ts, src/request.ts, src/model.ts, src/multilingual-laya.ts, src/ENV_PROMPT.md, src/display.ts, src/contest.ts -->
2
2
 
3
3
  # Simple TRPG Check
4
4
 
@@ -6,9 +6,11 @@ Cortico 的 TRPG 技能检定 World 扩展,检定规则由COC7规则启发。a
6
6
 
7
7
  支持单项检定、组合检定、对抗检定,以及奖励骰、惩罚骰和困难/极难任务。技能名可以自定义,不需要预先填写数值角色卡。
8
8
 
9
+ 使用前须配置评分后端。默认使用远程 Jev,需填写服务密钥;使用本地 Laya 时须安装对应依赖或 Python 环境。
10
+
9
11
  ## 安装与配置
10
12
 
11
- 要求 Node.js 22 或更新版本,以及 Cortico World API 5。在 Cortico 仓库的 `extensions/` 目录安装:
13
+ 要求 Node.js 22 或更新版本,以及 Cortico World API 6。在 Cortico 仓库的 `extensions/` 目录安装:
12
14
 
13
15
  ```powershell
14
16
  Set-Location "<Cortico 仓库路径>\extensions"
@@ -19,12 +21,14 @@ corepack pnpm add --ignore-workspace cortico-world-simple-trpg-check
19
21
 
20
22
  | 判定模型 | 配置方式 |
21
23
  | --- | --- |
22
- | 远程决策模型 | 选择 TypeSafe、OpenRouter 或自定义服务,填写对应密钥与模型 ID。自定义服务还需配置服务地址。 |
23
- | 多语言 Laya(默认) | 选择已安装 `laya` 的 Conda Python 环境。首次使用可能下载模型权重,后续使用本地缓存。 |
24
+ | 远程 Jev(默认) | 选择 TypeSafe、OpenRouter 或自定义服务,填写对应密钥与模型 ID。自定义服务还需配置服务地址。 |
25
+ | 多语言 Laya | 选择已安装 `laya` 的 Conda Python 环境。首次使用可能下载模型权重,后续使用本地缓存。 |
24
26
  | 英文 Laya | 选择“本地 Laya(英文)”,通过可选依赖 `@receptron/laya` 在本机运行。 |
25
27
 
26
28
  现有评测中,本地 Laya 对不同技能水平证据的区分较弱,建议使用 Jev。选择后点击“测试连接”验证服务与密钥;该操作不掷骰。
27
29
 
30
+ 部署中已保存的 `backend` 保持原值;未配置该键时使用 Jev。需要保留原默认后端的部署应显式设置 `worlds.simple-trpg-check.backend: "laya-multilingual"`。
31
+
28
32
  “决策模型”输入框提供预设,也接受其他模型 ID;留空使用所选服务的默认 JEV 模型。OpenRouter 支持 Luna Decisions 预设 `openai/gpt-6-luna-decisions`。自定义服务须支持 SystemOne 请求和答案格式。配置键 `backend: "jev"`、`jevSource`、`jevEndpoint` 继续使用,模型 ID 保存在 `jevModel`。
29
33
 
30
34
  默认 JEV 模型使用现有技能评分校准。Luna 与其他模型显示“未校准”,按档位代表值取期望;财富与信用评级仍使用专用标尺。
@@ -112,7 +116,7 @@ corepack pnpm install
112
116
  corepack pnpm test
113
117
  corepack pnpm typecheck
114
118
  corepack pnpm typecheck:web
115
- corepack pnpm build:console
119
+ corepack pnpm build:webui
116
120
  ```
117
121
 
118
122
  在 Cortico 仓库根目录运行 `corepack pnpm check:extension <本包绝对路径>` 验证扩展。构建控制台前需停止本机正在运行的 bot 进程。
@@ -120,3 +124,7 @@ corepack pnpm build:console
120
124
  评测方法、语料和离线读出训练见仓库内的 `evals/README.md`。运行时使用包内已有的读出表;`evals/` 和 `tests/` 不随 npm 包发布。
121
125
 
122
126
  MIT License,见 [LICENSE](LICENSE)。
127
+
128
+ ## Cortico 0.2.1
129
+
130
+ 发布前从 Cortico 仓库运行 `pnpm describe:extension <本包目录>` 和 `pnpm check:extension <本包目录>`。工具与默认提示词通过 `describe.json` 提供给扩展市场;发布规则见 [AGENTS.md](AGENTS.md),更新说明见 [CHANGELOG.md](CHANGELOG.md)。
package/describe.json ADDED
@@ -0,0 +1,413 @@
1
+ {
2
+ "tools": [
3
+ {
4
+ "name": "simple_trpg_check_roll",
5
+ "description": "按 checks 里的人物证据判定角色在每项技能上的水平作为难度,再逐项掷骰出结果。回执末尾的「显示:」块就是面向用户的完整格式,照抄即可。checks[i].bonus 是情景代偿的奖励/惩罚骰:整数,标尺 -2(巨大劣势)…2(巨大优势),0 或省略即基准;掷 1+|bonus| 颗,优势取最低、劣势取最高,|bonus|≥3 时不调用判定模型、直接自动成功/失败。checks[i].grade 取 1(困难)或 2(极难),把档位标签整体下压;只在难度本身是任务因素时使用。顶层 joint 取 and/or 把本调用里的所有技能合成一次投掷(and 取最小难度与最小 bonus、or 取最大),出现时至少两项技能且不得带 grade。模型失败、答案无效、输入超出预算或字段非法时不掷骰。",
6
+ "parameters": {
7
+ "type": "object",
8
+ "additionalProperties": false,
9
+ "properties": {
10
+ "character": {
11
+ "type": "object",
12
+ "additionalProperties": false,
13
+ "description": "角色层面、跨技能共享的事实。",
14
+ "properties": {
15
+ "traits": {
16
+ "type": "string",
17
+ "minLength": 1,
18
+ "maxLength": 600,
19
+ "description": "长期特质、背景、词条。"
20
+ },
21
+ "condition": {
22
+ "type": "string",
23
+ "minLength": 1,
24
+ "maxLength": 600,
25
+ "description": "伤势、疲劳、体温、精神与消耗状态。"
26
+ }
27
+ },
28
+ "required": [
29
+ "traits",
30
+ "condition"
31
+ ]
32
+ },
33
+ "situation": {
34
+ "type": "object",
35
+ "additionalProperties": false,
36
+ "description": "这一时刻的情境;某项确实无影响就写“无”,不要省略。",
37
+ "properties": {
38
+ "weather": {
39
+ "type": "string",
40
+ "minLength": 1,
41
+ "maxLength": 600,
42
+ "description": "天气与介质。"
43
+ },
44
+ "gear": {
45
+ "type": "string",
46
+ "minLength": 1,
47
+ "maxLength": 600,
48
+ "description": "工具与装备。"
49
+ },
50
+ "info": {
51
+ "type": "string",
52
+ "minLength": 1,
53
+ "maxLength": 600,
54
+ "description": "已知信息与资料的秩序。"
55
+ },
56
+ "time": {
57
+ "type": "string",
58
+ "minLength": 1,
59
+ "maxLength": 600,
60
+ "description": "时限。"
61
+ },
62
+ "senses": {
63
+ "type": "string",
64
+ "minLength": 1,
65
+ "maxLength": 600,
66
+ "description": "可见度与感官条件。"
67
+ }
68
+ },
69
+ "required": [
70
+ "weather",
71
+ "gear",
72
+ "info",
73
+ "time",
74
+ "senses"
75
+ ]
76
+ },
77
+ "checks": {
78
+ "type": "array",
79
+ "minItems": 1,
80
+ "description": "按判定顺序列出本时刻的技能;顺序只决定回执排列,不表示依赖关系。",
81
+ "items": {
82
+ "type": "object",
83
+ "additionalProperties": false,
84
+ "properties": {
85
+ "skill": {
86
+ "type": "string",
87
+ "minLength": 1,
88
+ "maxLength": 120,
89
+ "description": "技能名。"
90
+ },
91
+ "goal": {
92
+ "type": "string",
93
+ "minLength": 1,
94
+ "maxLength": 300,
95
+ "description": "这项技能要达成的直接结果。"
96
+ },
97
+ "evidence": {
98
+ "type": "string",
99
+ "minLength": 1,
100
+ "maxLength": 300,
101
+ "description": "角色在该技能上的水平证据;不写数字、不写档位名,也不把情境写进去。"
102
+ },
103
+ "bonus": {
104
+ "type": "integer",
105
+ "description": "情景代偿的奖励/惩罚骰,整数;标尺 -2(巨大劣势)…2(巨大优势),0 或省略即基准。填合计数:情境修正与本项花掉的 banked 奖励骰 1:1 抵消后相加。|bonus|≥3 时不调用判定模型,直接自动成功/失败。"
106
+ },
107
+ "grade": {
108
+ "type": "integer",
109
+ "enum": [
110
+ 1,
111
+ 2
112
+ ],
113
+ "description": "难度等级:1 = 困难(困难成功视为普通成功、成功视为失败),2 = 极难(极难成功视为成功、其余视为失败)。只用于难度本身是任务因素的场合;大成功与大失败不受影响;与顶层 joint 不能同时出现。"
114
+ }
115
+ },
116
+ "required": [
117
+ "skill",
118
+ "goal",
119
+ "evidence"
120
+ ]
121
+ }
122
+ },
123
+ "joint": {
124
+ "type": "string",
125
+ "enum": [
126
+ "and",
127
+ "or"
128
+ ],
129
+ "description": "组合检定:and = 列表里每项都要成功(按最小难度与最小 bonus 判),or = 任意一项成功即可(按最大难度与最大 bonus 判)。出现时 checks 至少两项,且各项不得带 grade。"
130
+ }
131
+ },
132
+ "required": [
133
+ "character",
134
+ "situation",
135
+ "checks"
136
+ ]
137
+ },
138
+ "tags": [
139
+ "act"
140
+ ]
141
+ },
142
+ {
143
+ "name": "simple_trpg_check_roll_contest",
144
+ "description": "对抗检定:分别判定我方与对方的同一时刻水平,比成功等级定胜负。actor 与 opponent 各写完整一套(name、traits、condition、situation 五维、checks);每侧各发一次判定请求,任一侧失败则整次失败且不掷骰。bonus 与 joint 挂在侧上:每侧只投一次骰,侧内 checks 多于一项时必须 joint。不支持 grade。自动判定按序短路:我方 bonus≥3 → 我方胜;对方≤-3 → 我方胜;我方≤-3 → 对方胜;对方≥3 → 对方胜。命中时两侧都不调用判定模型,未定调的一侧显示「跳过检定」。比档:大成功>极难>困难>普通成功>普通失败>大失败,同级比难度数值高者胜、相等则我方胜。对抗不触发奖励骰。",
145
+ "parameters": {
146
+ "type": "object",
147
+ "additionalProperties": false,
148
+ "properties": {
149
+ "actor": {
150
+ "type": "object",
151
+ "additionalProperties": false,
152
+ "description": "我方;一份完整的判定请求。",
153
+ "properties": {
154
+ "name": {
155
+ "type": "string",
156
+ "minLength": 1,
157
+ "maxLength": 120,
158
+ "description": "我方角色的名字,用于回执与显示串。"
159
+ },
160
+ "traits": {
161
+ "type": "string",
162
+ "minLength": 1,
163
+ "maxLength": 600,
164
+ "description": "长期特质、背景、词条。"
165
+ },
166
+ "condition": {
167
+ "type": "string",
168
+ "minLength": 1,
169
+ "maxLength": 600,
170
+ "description": "伤势、疲劳、体温、精神与消耗状态。"
171
+ },
172
+ "situation": {
173
+ "type": "object",
174
+ "additionalProperties": false,
175
+ "description": "我方所面对的处境;某项确实无影响就写“无”,不要省略。",
176
+ "properties": {
177
+ "weather": {
178
+ "type": "string",
179
+ "minLength": 1,
180
+ "maxLength": 600,
181
+ "description": "天气与介质。"
182
+ },
183
+ "gear": {
184
+ "type": "string",
185
+ "minLength": 1,
186
+ "maxLength": 600,
187
+ "description": "工具与装备。"
188
+ },
189
+ "info": {
190
+ "type": "string",
191
+ "minLength": 1,
192
+ "maxLength": 600,
193
+ "description": "已知信息与资料的秩序。"
194
+ },
195
+ "time": {
196
+ "type": "string",
197
+ "minLength": 1,
198
+ "maxLength": 600,
199
+ "description": "时限。"
200
+ },
201
+ "senses": {
202
+ "type": "string",
203
+ "minLength": 1,
204
+ "maxLength": 600,
205
+ "description": "可见度与感官条件。"
206
+ }
207
+ },
208
+ "required": [
209
+ "weather",
210
+ "gear",
211
+ "info",
212
+ "time",
213
+ "senses"
214
+ ]
215
+ },
216
+ "checks": {
217
+ "type": "array",
218
+ "minItems": 1,
219
+ "description": "参与对抗的技能;多于一项时必须同时给 joint。",
220
+ "items": {
221
+ "type": "object",
222
+ "additionalProperties": false,
223
+ "properties": {
224
+ "skill": {
225
+ "type": "string",
226
+ "minLength": 1,
227
+ "maxLength": 120,
228
+ "description": "技能名。"
229
+ },
230
+ "goal": {
231
+ "type": "string",
232
+ "minLength": 1,
233
+ "maxLength": 300,
234
+ "description": "这一侧要靠它达到的直接结果。"
235
+ },
236
+ "evidence": {
237
+ "type": "string",
238
+ "minLength": 1,
239
+ "maxLength": 300,
240
+ "description": "这一侧在该技能上的水平证据;不写数字与档位名。"
241
+ }
242
+ },
243
+ "required": [
244
+ "skill",
245
+ "goal",
246
+ "evidence"
247
+ ]
248
+ }
249
+ },
250
+ "joint": {
251
+ "type": "string",
252
+ "enum": [
253
+ "and",
254
+ "or"
255
+ ],
256
+ "description": "这一侧的组合检定:and 取最小难度、or 取最大。侧内 checks 多于一项时必须指定。"
257
+ },
258
+ "bonus": {
259
+ "type": "integer",
260
+ "description": "这一侧的奖励/惩罚骰合计值:情境修正 + 这一侧花掉的 banked 奖励骰,0 或省略即基准。|bonus|≥3 时两侧都不调用判定模型,直接定胜负。"
261
+ }
262
+ },
263
+ "required": [
264
+ "name",
265
+ "traits",
266
+ "condition",
267
+ "situation",
268
+ "checks"
269
+ ]
270
+ },
271
+ "opponent": {
272
+ "type": "object",
273
+ "additionalProperties": false,
274
+ "description": "对方;与 actor 同形,写对方自己的处境与证据。",
275
+ "properties": {
276
+ "name": {
277
+ "type": "string",
278
+ "minLength": 1,
279
+ "maxLength": 120,
280
+ "description": "对方的名字,用于回执与显示串。"
281
+ },
282
+ "traits": {
283
+ "type": "string",
284
+ "minLength": 1,
285
+ "maxLength": 600,
286
+ "description": "长期特质、背景、词条。"
287
+ },
288
+ "condition": {
289
+ "type": "string",
290
+ "minLength": 1,
291
+ "maxLength": 600,
292
+ "description": "伤势、疲劳、体温、精神与消耗状态。"
293
+ },
294
+ "situation": {
295
+ "type": "object",
296
+ "additionalProperties": false,
297
+ "description": "对方所面对的处境;某项确实无影响就写“无”,不要省略。",
298
+ "properties": {
299
+ "weather": {
300
+ "type": "string",
301
+ "minLength": 1,
302
+ "maxLength": 600,
303
+ "description": "天气与介质。"
304
+ },
305
+ "gear": {
306
+ "type": "string",
307
+ "minLength": 1,
308
+ "maxLength": 600,
309
+ "description": "工具与装备。"
310
+ },
311
+ "info": {
312
+ "type": "string",
313
+ "minLength": 1,
314
+ "maxLength": 600,
315
+ "description": "已知信息与资料的秩序。"
316
+ },
317
+ "time": {
318
+ "type": "string",
319
+ "minLength": 1,
320
+ "maxLength": 600,
321
+ "description": "时限。"
322
+ },
323
+ "senses": {
324
+ "type": "string",
325
+ "minLength": 1,
326
+ "maxLength": 600,
327
+ "description": "可见度与感官条件。"
328
+ }
329
+ },
330
+ "required": [
331
+ "weather",
332
+ "gear",
333
+ "info",
334
+ "time",
335
+ "senses"
336
+ ]
337
+ },
338
+ "checks": {
339
+ "type": "array",
340
+ "minItems": 1,
341
+ "description": "参与对抗的技能;多于一项时必须同时给 joint。",
342
+ "items": {
343
+ "type": "object",
344
+ "additionalProperties": false,
345
+ "properties": {
346
+ "skill": {
347
+ "type": "string",
348
+ "minLength": 1,
349
+ "maxLength": 120,
350
+ "description": "技能名。"
351
+ },
352
+ "goal": {
353
+ "type": "string",
354
+ "minLength": 1,
355
+ "maxLength": 300,
356
+ "description": "这一侧要靠它达到的直接结果。"
357
+ },
358
+ "evidence": {
359
+ "type": "string",
360
+ "minLength": 1,
361
+ "maxLength": 300,
362
+ "description": "这一侧在该技能上的水平证据;不写数字与档位名。"
363
+ }
364
+ },
365
+ "required": [
366
+ "skill",
367
+ "goal",
368
+ "evidence"
369
+ ]
370
+ }
371
+ },
372
+ "joint": {
373
+ "type": "string",
374
+ "enum": [
375
+ "and",
376
+ "or"
377
+ ],
378
+ "description": "这一侧的组合检定:and 取最小难度、or 取最大。侧内 checks 多于一项时必须指定。"
379
+ },
380
+ "bonus": {
381
+ "type": "integer",
382
+ "description": "这一侧的奖励/惩罚骰合计值:情境修正 + 这一侧花掉的 banked 奖励骰,0 或省略即基准。|bonus|≥3 时两侧都不调用判定模型,直接定胜负。"
383
+ }
384
+ },
385
+ "required": [
386
+ "name",
387
+ "traits",
388
+ "condition",
389
+ "situation",
390
+ "checks"
391
+ ]
392
+ }
393
+ },
394
+ "required": [
395
+ "actor",
396
+ "opponent"
397
+ ]
398
+ },
399
+ "tags": [
400
+ "act"
401
+ ]
402
+ }
403
+ ],
404
+ "events": [],
405
+ "prompts": [
406
+ {
407
+ "key": "worlds.simple-trpg-check.envPrompt",
408
+ "title": "Simple TRPG Check · 环境提示词",
409
+ "role": "envPrompt",
410
+ "content": "仅在结果不确定且情境具有戏剧性时酌情进行技能判定:平静地走过明亮门廊无需判定;被怪物追赶时穿过遍布瓦砾的狭窄走廊则适合判定。需要判定时调用 simple_trpg_check_roll;两个会推理、会反应的智能体互相比试时,改用 simple_trpg_check_roll_contest。\n\n投骰之前,若你已能判断玩家的方法在此情境下几乎不可能奏效(需要的东西完全不在手边、方法与目标不相干),先提醒玩家可以考虑更换方式;不要强迫,玩家坚持就照投。投过骰之后就不要再提醒,那已成既定事实。\n\n调用时填写 character、situation 与 checks。\n- `character.traits`:长期特质、背景、词条。\n- `character.condition`:伤势、疲劳、体温、精神与消耗状态(如 SAN)。\n- `situation.weather`:天气与介质。`situation.gear`:工具与装备。`situation.info`:已知信息与资料的秩序。`situation.time`:时限。`situation.senses`:可见度与感官条件。\n- `checks`:按顺序列出本时刻要判定的技能。`skill` 是技能名,`goal` 是这项技能要达成的直接结果,`evidence` 是角色在该技能上的水平证据。\n\n技能名可以用下面这套常用表(有子分类的写具体项,例如格斗的「斗殴」「刀」、射击的「手枪」「步枪」、科学的「化学」「生物学」;表外的技能名也可以用,前后保持一致):\n\n力量、体质、敏捷、智力(别名灵感)、意志、教育(别名知识)、说服、取悦、恐吓、欺骗、侦查、聆听、运动、潜行(别名乔装、追踪)、巧手(别名偷窃、伪造、锁匠)、驾驶、骑乘、导航、格斗、射击、闪避、投掷、爆破、急救、医学、科学、神秘学、历史、财富(别名信用、信用评级)。\n\n财富的 `evidence` 写已确立的收入、资产、生活水准和可支配资金,判定按贫困至富裕的档位衡量。\n\n`situation` 的五个字段加上 `character.condition` 就是情境的全部六个维度,一个都不能省略;某项确实没有影响就写「无」。\n\n难度由角色在该技能上的水平决定:判定模型只从 `evidence` 读出角色属于哪一档,因此 `evidence` 是唯一直接决定难度的字段,必须写得足以把角色定位到某一档。`goal` 与六个维度仍要如实填写——它们是判定记录,也是你决定代偿幅度的依据。\n\n情景不进 `evidence`:顺手的工具、大雨、性格与体型上的优势写进 `bonus`,任务本身的难度写进 `grade`,两者都不改变判定模型看到的水平。\n\n## 情景代偿\n\n情景上的优劣势用 `checks[i].bonus` 表达,标尺是 -2…2:-2 为巨大劣势(若干个劣势叠加,但还不至于让人物不可能完成),-1 为劣势,0 是基准(可省略),1 为优势,2 为巨大优势(若干个优势叠加,但还不至于让检定无需进行)。优势与劣势 1:1 抵消,你把这一时刻的所有因素在心里加总之后,只填**合计值**。\n\n奖励/惩罚骰的门槛比想象中高:它挪动的不是几个百分点,别拿它记小账。一个因素如果轻微到只会让成败概率挪几个百分点,就当作不存在,不要写进 `bonus`。小雨里开车不算惩罚;暴雨里雨刷开到满速仍然几乎看不见,那才算。绝大多数时候只给 ±1;只有情势**极其**有利或极其不利时,才给 ±2。\n\n`bonus` 只作用于掷骰:判定模型照旧只回答「角色在这项技能上处于哪一档」,随后掷 1+|bonus| 颗 d100,优势取最低、劣势取最高,再拿选中的那颗去比难度。\n\n合计值超过 ±2 是允许的,而且**鼓励**:假如压上两颗 banked 奖励骰把合计推到 3,这一次检定就直接自动成功,不消耗判定模型、也不掷骰。要让玩家「跳过」一次检定,这是唯一通路。\n\n任务本身的难度用 `checks[i].grade` 表达,只取 1 或 2。它说的是**这件事本身有多难**,与角色强不强无关:1 是连专业人士都要认真对待的挑战,2 是连专家都要吃力的挑战,已经贴近人类可能性的边界。同一件事不会因为角色弱就升档、因为角色强就降档。机制上,1 是困难(困难成功视为普通成功、普通成功视为失败),2 是极难(极难成功视为成功,其余视为失败),大成功与大失败不受影响。`grade` 与 `bonus` 可以同时用,但那属于特例,尽量少用;`grade` 不能与 `joint` 同时出现。\n\n## 奖励骰(账本由你维护)\n\n奖励与惩罚是**你的**职责,插件只给建议:奖励骰的授予、记账与落盘都由你在外部工具里完成,回执不会替你记。\n\n- 大成功要发奖励,且**当场落档**。二选一:白得关键信息或有利位置(纯叙事,不进资源),或者一颗奖励骰。\n- 困难成功与极难成功的奖励是**酌情**的:给不给、给多大全看你当场判断,不必落档。仍在上面那两类里挑。\n- 拿到奖励骰就当场记账,写成 `奖励骰:N 即时奖励骰:M`,值有变化时重写一遍。\n- **即时奖励骰**只能在紧接的下一次检定用掉,过期作废;**banked 奖励骰**没有期限,由玩家自己决定何时消耗(你可以提示玩家消耗奖励骰)。因为 `bonus` 是发起调用时一次写定的,同一次调用里后面的技能用不上前面刚发的即时骰——即时骰只对**后续调用**有效。\n- 战斗轮里的大成功按常规反馈、**不给**奖励骰:敌人的失误多给你一次行动、暴击效果拔群、命中弱点。\n\n## 组合检定\n\n如果 `goal` 要同时满足若干个独立事件的达成,用 `joint: \"and\"`;只要任意一个达成即可,用 `joint: \"or\"`。组合检定按有效难度判:`and` 取各项难度里最小的、`or` 取最大的;`bonus` 同样按 `and` 取最小、`or` 取最大。整组只掷**一次**骰。出现 `joint` 时 `checks` 至少要两项,且各项都不能带 `grade`。\n\n## 对抗检定\n\n会推理、会反应的智能体之间用 `simple_trpg_check_roll_contest`;无推理、只会被动感知的障碍(犬、传感器、摄像头)不适用,按环境写进 `checks`。近战行动几乎总会触发对抗。\n\n典型配对:力量对力量、格斗对闪避、说服(或其它交涉技能)对意志、潜行对聆听或侦查、巧手对侦查。不必照抄,比例合适即可。\n\n`actor` 与 `opponent` 各写完整的一套——`name`、`traits`、`condition`、各自的 `situation` 五维、各自的 `checks`(各自的 `skill`/`goal`/`evidence`)。对抗不接受 `grade`。`bonus` 与 `joint` 挂在**侧**上,因为每侧只掷一次骰;某一侧的 `checks` 多于一项时,必须给这一侧指定 `joint`。\n\n胜负先比成功等级:大成功 > 极难成功 > 困难成功 > 普通成功 > 普通失败 > 大失败;等级相同就比较难度数值,**数值高的一方胜出**,数值相等判玩家胜。换句话说,对抗永远有一个赢家。对抗检定的大成功、极难成功与困难成功都**不触发奖励**。\n\n某一侧的合计 `bonus` 达到 ±3 时直接定胜负:两侧都不判定、不掷骰,回执里未参与结算的一侧显示「跳过检定」。\n\n写作纪律:\n\n1. 情境必须自带全部相关上下文:判定模型只看得到这一次请求,看不到之前的对话。角色的状态、随身物与余量、已知的破法、上一次失败的原因,都要写进对应字段,不能写「同上」。\n2. 不写数字,也不写档位名(不要出现「技能 65」「职业级」这类字样)。水平只用证据表达:是否以此谋生、训练与实操年限、学历或认证、同行中的相对地位、过往的成败。\n3. 只写已经确立的事实。若有会影响结果的事实尚未确定(走廊多宽、锁是哪一种),先在叙事里定下来再调用;定不下来就不要调用。\n4. 有智慧的因素不构成本项难度的修正:人的态度、巡逻、守门走对抗检定,不要写进 `checks`。被动感知型的障碍(犬、传感器、摄像头)按环境写;人造装置算不算对手,由你判断。\n5. 需要「先 A 再 B」的链条时分两次调用:先投 A;A 成为事实、环境改变之后,第二次调用要把改变后的六个维度重新写一遍,不要复用上一段的文字。\n6. 同一次调用里的多项技能默认是同一时刻的独立检定,顺序只决定回执的排列,不表示依赖关系;需要它们共同决定一个 `goal` 时改用 `joint`。\n7. 一场对抗与一个战斗回合都是**一次**调用。回合变化后重新写两侧的六个维度,不要复用上一段的文字。\n\n请求长度按 token 估算(英文字符约 0.3、中文字符约 0.6、其他字符按 1 计,判定时另留一成余量):整份请求不超过 1000、情境不超过 768、单项问题不超过 256。对抗检定把两侧当作两份请求,各自按这三条检测。超出会被拒绝,按回执提示精简后重试。\n\n根据工具回执继续叙事。回执末尾的「显示:」块就是面向用户的成品格式:把里面的每一行**原样照抄**,不要再改写档位名、补数字或调整标点。难度是达成概率的百分数(钳在 1–99),骰值不超过难度才算成功;带 `bonus` 时回执已经列出每一颗骰值并用 `=>` 标出选中那颗,带 `grade` 时回执里的档位名已经下压过。模型请求失败或输入被拒绝时,按回执处理:精简重写后重试,不编造骰值。\n"
411
+ }
412
+ ]
413
+ }
@@ -70,7 +70,7 @@ var SIMPLE_TRPG_CHECK_CONFIG_GROUP = {
70
70
  }
71
71
  };
72
72
 
73
- // src/console/client.ts
73
+ // src/webui/client.ts
74
74
  async function mountConfig(ctx) {
75
75
  const { ui } = ctx;
76
76
  const options = await ctx.invoke("options");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cortico-world-simple-trpg-check",
3
- "version": "0.3.0",
3
+ "version": "0.4.1",
4
4
  "type": "module",
5
5
  "description": "Cortico 的 TRPG 技能检定 World 扩展,使用本地或远程模型评估技能水平,支持 d100 掷骰、组合与对抗检定。",
6
6
  "license": "MIT",
@@ -24,27 +24,38 @@
24
24
  "main": "./src/index.ts",
25
25
  "cortico": {
26
26
  "kind": "world",
27
- "api": 5,
28
- "consoleClient": "dist/console.js"
27
+ "api": 6,
28
+ "webUiClient": "dist/webui.js",
29
+ "tags": [
30
+ "game",
31
+ "external-setup",
32
+ "local",
33
+ "requires-key"
34
+ ],
35
+ "describe": "describe.json"
29
36
  },
30
37
  "files": [
31
38
  "src",
32
39
  "python",
33
40
  "dist",
34
- "README.md"
41
+ "README.md",
42
+ "describe.json",
43
+ "CHANGELOG.md",
44
+ "AGENTS.md",
45
+ "CLAUDE.md"
35
46
  ],
36
47
  "scripts": {
37
48
  "test": "vitest run",
38
49
  "typecheck": "tsc --noEmit",
39
50
  "typecheck:web": "tsc -p tsconfig.web.json --noEmit",
40
- "build:console": "esbuild src/console/client.ts --bundle --format=esm --outfile=dist/console.js",
51
+ "build:webui": "esbuild src/webui/client.ts --bundle --format=esm --outfile=dist/webui.js",
41
52
  "eval:scenarios": "tsx evals/run.ts",
42
53
  "eval:probe": "tsx evals/probe.ts",
43
54
  "eval:train": "tsx evals/train.ts"
44
55
  },
45
56
  "devDependencies": {
46
57
  "@types/node": "^22.10.0",
47
- "cortico": "^0.1.3",
58
+ "cortico": "^0.2.1",
48
59
  "esbuild": "^0.28.2",
49
60
  "tsx": "^4.19.2",
50
61
  "typescript": "^5.7.2",
package/src/config.ts CHANGED
@@ -33,7 +33,7 @@ export interface SimpleTrpgCheckConfigSection {
33
33
 
34
34
  export const SIMPLE_TRPG_CHECK_DEFAULTS: SimpleTrpgCheckConfigSection = {
35
35
  enabled: false,
36
- backend: 'laya-multilingual',
36
+ backend: 'jev',
37
37
  jevSource: 'typesafe',
38
38
  jevEndpoint: '',
39
39
  jevModel: '',
package/src/definition.ts CHANGED
@@ -1,7 +1,7 @@
1
- import type { WorldDefinition } from 'cortico/world.ts';
1
+ import { describeTools, type WorldDefinition } from 'cortico/world.ts';
2
2
  import { discoverCondaPythonOptions } from './conda-environments.ts';
3
3
  import { SIMPLE_TRPG_CHECK_DEFAULTS, type SimpleTrpgCheckConfigSection } from './config.ts';
4
- import { SimpleTrpgCheckWorld } from './world.ts';
4
+ import { SIMPLE_TRPG_CHECK_TOOL_DECLS, SimpleTrpgCheckWorld } from './world.ts';
5
5
 
6
6
  export const SIMPLE_TRPG_CHECK: WorldDefinition<SimpleTrpgCheckConfigSection> = {
7
7
  id: 'simple-trpg-check',
@@ -9,4 +9,5 @@ export const SIMPLE_TRPG_CHECK: WorldDefinition<SimpleTrpgCheckConfigSection> =
9
9
  defaults: () => ({ ...SIMPLE_TRPG_CHECK_DEFAULTS }),
10
10
  configOptions: (kind) => kind === 'simple-trpg-check-conda-python' ? discoverCondaPythonOptions() : [],
11
11
  create: (ctx) => new SimpleTrpgCheckWorld(ctx),
12
+ describe: { tools: describeTools(SIMPLE_TRPG_CHECK_TOOL_DECLS, {}), events: [] },
12
13
  };
@@ -9,7 +9,7 @@ export interface MultilingualLayaModel {
9
9
  close(): Promise<void>;
10
10
  }
11
11
 
12
- class PythonLayaModel implements MultilingualLayaModel {
12
+ export class PythonLayaModel implements MultilingualLayaModel {
13
13
  private nextId = 0;
14
14
  private readonly pending = new Map<string, { resolve(value: unknown): void; reject(error: Error): void }>();
15
15
  private readonly ready: Promise<void>;
@@ -39,6 +39,7 @@ class PythonLayaModel implements MultilingualLayaModel {
39
39
  });
40
40
  worker.once('error', () => { clearTimeout(timeout); reject(new Error('Python 进程无法启动')); });
41
41
  worker.once('exit', (code) => {
42
+ this.closed = true;
42
43
  clearTimeout(timeout);
43
44
  const error = new Error(`Laya multilingual 进程已退出:${code ?? 'unknown'}`);
44
45
  reject(error);
@@ -1,4 +1,4 @@
1
- import type { ConsoleClientBundle, ConsolePanelContext } from 'cortico/web/shared/client-panel.ts';
1
+ import type { WebUiClientBundle, WebUiPanelContext } from 'cortico/web/shared/client-panel.ts';
2
2
  import { decisionModel, decisionModelPresets, DEFAULT_DECISION_MODELS, hasCalibratedReadout, SIMPLE_TRPG_CHECK_CONFIG_GROUP, type SimpleTrpgCheckConfigSection } from '../config.ts';
3
3
 
4
4
  interface ConfigState {
@@ -6,7 +6,7 @@ interface ConfigState {
6
6
  keySet: boolean;
7
7
  }
8
8
 
9
- async function mountConfig(ctx: ConsolePanelContext): Promise<void> {
9
+ async function mountConfig(ctx: WebUiPanelContext): Promise<void> {
10
10
  const { ui } = ctx;
11
11
  const options = await ctx.invoke<Array<{ value: string; label: string }>>('options');
12
12
  let state = await ctx.invoke<ConfigState>('state');
@@ -128,5 +128,5 @@ async function mountConfig(ctx: ConsolePanelContext): Promise<void> {
128
128
  }
129
129
  }
130
130
 
131
- const client: ConsoleClientBundle = { panels: { config: { mount: mountConfig } } };
131
+ const client: WebUiClientBundle = { panels: { config: { mount: mountConfig } } };
132
132
  export default client;
package/src/world.ts CHANGED
@@ -1,6 +1,7 @@
1
+ import type { ToolSpec } from 'cortico/core/types.ts';
1
2
  import { randomInt } from 'node:crypto';
2
3
  import { fileURLToPath } from 'node:url';
3
- import type { ToolDef, ToolOutcome, World, WorldConsoleDecl, WorldHost } from 'cortico/core/types.ts';
4
+ import type { ToolDef, ToolOutcome, World, WorldWebUiDecl, WorldHost } from 'cortico/core/types.ts';
4
5
  import type { WorldContext } from 'cortico/world.ts';
5
6
  import {
6
7
  autoVerdict,
@@ -189,193 +190,15 @@ export class SimpleTrpgCheckWorld implements World {
189
190
 
190
191
  tools(): ToolDef[] {
191
192
  return [{
192
- name: 'simple_trpg_check_roll',
193
- description: '按 checks 里的人物证据判定角色在每项技能上的水平作为难度,再逐项掷骰出结果。回执末尾的「显示:」块就是面向用户的完整格式,照抄即可。'
194
- + 'checks[i].bonus 是情景代偿的奖励/惩罚骰:整数,标尺 -2(巨大劣势)…2(巨大优势),0 或省略即基准;掷 1+|bonus| 颗,优势取最低、劣势取最高,|bonus|≥3 时不调用判定模型、直接自动成功/失败。'
195
- + 'checks[i].grade 取 1(困难)或 2(极难),把档位标签整体下压;只在难度本身是任务因素时使用。'
196
- + '顶层 joint 取 and/or 把本调用里的所有技能合成一次投掷(and 取最小难度与最小 bonus、or 取最大),出现时至少两项技能且不得带 grade。'
197
- + '模型失败、答案无效、输入超出预算或字段非法时不掷骰。',
198
- tags: ['act'],
199
- parameters: {
200
- type: 'object',
201
- additionalProperties: false,
202
- properties: {
203
- character: {
204
- type: 'object',
205
- additionalProperties: false,
206
- description: '角色层面、跨技能共享的事实。',
207
- properties: {
208
- traits: { type: 'string', minLength: 1, maxLength: 600, description: '长期特质、背景、词条。' },
209
- condition: { type: 'string', minLength: 1, maxLength: 600, description: '伤势、疲劳、体温、精神与消耗状态。' },
210
- },
211
- required: ['traits', 'condition'],
212
- },
213
- situation: {
214
- type: 'object',
215
- additionalProperties: false,
216
- description: '这一时刻的情境;某项确实无影响就写“无”,不要省略。',
217
- properties: {
218
- weather: { type: 'string', minLength: 1, maxLength: 600, description: '天气与介质。' },
219
- gear: { type: 'string', minLength: 1, maxLength: 600, description: '工具与装备。' },
220
- info: { type: 'string', minLength: 1, maxLength: 600, description: '已知信息与资料的秩序。' },
221
- time: { type: 'string', minLength: 1, maxLength: 600, description: '时限。' },
222
- senses: { type: 'string', minLength: 1, maxLength: 600, description: '可见度与感官条件。' },
223
- },
224
- required: ['weather', 'gear', 'info', 'time', 'senses'],
225
- },
226
- checks: {
227
- type: 'array',
228
- minItems: 1,
229
- description: '按判定顺序列出本时刻的技能;顺序只决定回执排列,不表示依赖关系。',
230
- items: {
231
- type: 'object',
232
- additionalProperties: false,
233
- properties: {
234
- skill: { type: 'string', minLength: 1, maxLength: 120, description: '技能名。' },
235
- goal: { type: 'string', minLength: 1, maxLength: 300, description: '这项技能要达成的直接结果。' },
236
- evidence: { type: 'string', minLength: 1, maxLength: 300, description: '角色在该技能上的水平证据;不写数字、不写档位名,也不把情境写进去。' },
237
- bonus: {
238
- type: 'integer',
239
- description: '情景代偿的奖励/惩罚骰,整数;标尺 -2(巨大劣势)…2(巨大优势),0 或省略即基准。'
240
- + '填合计数:情境修正与本项花掉的 banked 奖励骰 1:1 抵消后相加。|bonus|≥3 时不调用判定模型,直接自动成功/失败。',
241
- },
242
- grade: {
243
- type: 'integer',
244
- enum: [1, 2],
245
- description: '难度等级:1 = 困难(困难成功视为普通成功、成功视为失败),2 = 极难(极难成功视为成功、其余视为失败)。'
246
- + '只用于难度本身是任务因素的场合;大成功与大失败不受影响;与顶层 joint 不能同时出现。',
247
- },
248
- },
249
- required: ['skill', 'goal', 'evidence'],
250
- },
251
- },
252
- joint: {
253
- type: 'string',
254
- enum: ['and', 'or'],
255
- description: '组合检定:and = 列表里每项都要成功(按最小难度与最小 bonus 判),or = 任意一项成功即可(按最大难度与最大 bonus 判)。'
256
- + '出现时 checks 至少两项,且各项不得带 grade。',
257
- },
258
- },
259
- required: ['character', 'situation', 'checks'],
260
- },
261
- handler: (args) => this.runCheck(args),
193
+ ...SIMPLE_TRPG_CHECK_TOOL_DECLS[0],
194
+ handler: (args) => this.runCheck(args)
262
195
  }, {
263
- name: 'simple_trpg_check_roll_contest',
264
- description: '对抗检定:分别判定我方与对方的同一时刻水平,比成功等级定胜负。actor 与 opponent 各写完整一套(name、traits、condition、situation 五维、checks);'
265
- + '每侧各发一次判定请求,任一侧失败则整次失败且不掷骰。bonus 与 joint 挂在侧上:每侧只投一次骰,侧内 checks 多于一项时必须 joint。不支持 grade。'
266
- + '自动判定按序短路:我方 bonus≥3 → 我方胜;对方≤-3 → 我方胜;我方≤-3 → 对方胜;对方≥3 → 对方胜。命中时两侧都不调用判定模型,未定调的一侧显示「跳过检定」。'
267
- + '比档:大成功>极难>困难>普通成功>普通失败>大失败,同级比难度数值高者胜、相等则我方胜。对抗不触发奖励骰。',
268
- tags: ['act'],
269
- parameters: {
270
- type: 'object',
271
- additionalProperties: false,
272
- properties: {
273
- actor: {
274
- type: 'object',
275
- additionalProperties: false,
276
- description: '我方;一份完整的判定请求。',
277
- properties: {
278
- name: { type: 'string', minLength: 1, maxLength: 120, description: '我方角色的名字,用于回执与显示串。' },
279
- traits: { type: 'string', minLength: 1, maxLength: 600, description: '长期特质、背景、词条。' },
280
- condition: { type: 'string', minLength: 1, maxLength: 600, description: '伤势、疲劳、体温、精神与消耗状态。' },
281
- situation: {
282
- type: 'object',
283
- additionalProperties: false,
284
- description: '我方所面对的处境;某项确实无影响就写“无”,不要省略。',
285
- properties: {
286
- weather: { type: 'string', minLength: 1, maxLength: 600, description: '天气与介质。' },
287
- gear: { type: 'string', minLength: 1, maxLength: 600, description: '工具与装备。' },
288
- info: { type: 'string', minLength: 1, maxLength: 600, description: '已知信息与资料的秩序。' },
289
- time: { type: 'string', minLength: 1, maxLength: 600, description: '时限。' },
290
- senses: { type: 'string', minLength: 1, maxLength: 600, description: '可见度与感官条件。' },
291
- },
292
- required: ['weather', 'gear', 'info', 'time', 'senses'],
293
- },
294
- checks: {
295
- type: 'array',
296
- minItems: 1,
297
- description: '参与对抗的技能;多于一项时必须同时给 joint。',
298
- items: {
299
- type: 'object',
300
- additionalProperties: false,
301
- properties: {
302
- skill: { type: 'string', minLength: 1, maxLength: 120, description: '技能名。' },
303
- goal: { type: 'string', minLength: 1, maxLength: 300, description: '这一侧要靠它达到的直接结果。' },
304
- evidence: { type: 'string', minLength: 1, maxLength: 300, description: '这一侧在该技能上的水平证据;不写数字与档位名。' },
305
- },
306
- required: ['skill', 'goal', 'evidence'],
307
- },
308
- },
309
- joint: {
310
- type: 'string',
311
- enum: ['and', 'or'],
312
- description: '这一侧的组合检定:and 取最小难度、or 取最大。侧内 checks 多于一项时必须指定。',
313
- },
314
- bonus: {
315
- type: 'integer',
316
- description: '这一侧的奖励/惩罚骰合计值:情境修正 + 这一侧花掉的 banked 奖励骰,0 或省略即基准。'
317
- + '|bonus|≥3 时两侧都不调用判定模型,直接定胜负。',
318
- },
319
- },
320
- required: ['name', 'traits', 'condition', 'situation', 'checks'],
321
- },
322
- opponent: {
323
- type: 'object',
324
- additionalProperties: false,
325
- description: '对方;与 actor 同形,写对方自己的处境与证据。',
326
- properties: {
327
- name: { type: 'string', minLength: 1, maxLength: 120, description: '对方的名字,用于回执与显示串。' },
328
- traits: { type: 'string', minLength: 1, maxLength: 600, description: '长期特质、背景、词条。' },
329
- condition: { type: 'string', minLength: 1, maxLength: 600, description: '伤势、疲劳、体温、精神与消耗状态。' },
330
- situation: {
331
- type: 'object',
332
- additionalProperties: false,
333
- description: '对方所面对的处境;某项确实无影响就写“无”,不要省略。',
334
- properties: {
335
- weather: { type: 'string', minLength: 1, maxLength: 600, description: '天气与介质。' },
336
- gear: { type: 'string', minLength: 1, maxLength: 600, description: '工具与装备。' },
337
- info: { type: 'string', minLength: 1, maxLength: 600, description: '已知信息与资料的秩序。' },
338
- time: { type: 'string', minLength: 1, maxLength: 600, description: '时限。' },
339
- senses: { type: 'string', minLength: 1, maxLength: 600, description: '可见度与感官条件。' },
340
- },
341
- required: ['weather', 'gear', 'info', 'time', 'senses'],
342
- },
343
- checks: {
344
- type: 'array',
345
- minItems: 1,
346
- description: '参与对抗的技能;多于一项时必须同时给 joint。',
347
- items: {
348
- type: 'object',
349
- additionalProperties: false,
350
- properties: {
351
- skill: { type: 'string', minLength: 1, maxLength: 120, description: '技能名。' },
352
- goal: { type: 'string', minLength: 1, maxLength: 300, description: '这一侧要靠它达到的直接结果。' },
353
- evidence: { type: 'string', minLength: 1, maxLength: 300, description: '这一侧在该技能上的水平证据;不写数字与档位名。' },
354
- },
355
- required: ['skill', 'goal', 'evidence'],
356
- },
357
- },
358
- joint: {
359
- type: 'string',
360
- enum: ['and', 'or'],
361
- description: '这一侧的组合检定:and 取最小难度、or 取最大。侧内 checks 多于一项时必须指定。',
362
- },
363
- bonus: {
364
- type: 'integer',
365
- description: '这一侧的奖励/惩罚骰合计值:情境修正 + 这一侧花掉的 banked 奖励骰,0 或省略即基准。'
366
- + '|bonus|≥3 时两侧都不调用判定模型,直接定胜负。',
367
- },
368
- },
369
- required: ['name', 'traits', 'condition', 'situation', 'checks'],
370
- },
371
- },
372
- required: ['actor', 'opponent'],
373
- },
196
+ ...SIMPLE_TRPG_CHECK_TOOL_DECLS[1],
374
197
  handler: (args) => this.runContest(args),
375
198
  }];
376
199
  }
377
200
 
378
- console(): WorldConsoleDecl {
201
+ webUi(): WorldWebUiDecl {
379
202
  const source = this.ctx.cfg.jevSource;
380
203
  const keySet = this.hasJevKey();
381
204
  const signature = this.currentModelSignature();
@@ -590,3 +413,190 @@ export class SimpleTrpgCheckWorld implements World {
590
413
  }
591
414
  }
592
415
  }
416
+
417
+ export const SIMPLE_TRPG_CHECK_TOOL_DECLS: ToolSpec[] = [
418
+ {
419
+ name: 'simple_trpg_check_roll',
420
+ description: '按 checks 里的人物证据判定角色在每项技能上的水平作为难度,再逐项掷骰出结果。回执末尾的「显示:」块就是面向用户的完整格式,照抄即可。'
421
+ + 'checks[i].bonus 是情景代偿的奖励/惩罚骰:整数,标尺 -2(巨大劣势)…2(巨大优势),0 或省略即基准;掷 1+|bonus| 颗,优势取最低、劣势取最高,|bonus|≥3 时不调用判定模型、直接自动成功/失败。'
422
+ + 'checks[i].grade 取 1(困难)或 2(极难),把档位标签整体下压;只在难度本身是任务因素时使用。'
423
+ + '顶层 joint 取 and/or 把本调用里的所有技能合成一次投掷(and 取最小难度与最小 bonus、or 取最大),出现时至少两项技能且不得带 grade。'
424
+ + '模型失败、答案无效、输入超出预算或字段非法时不掷骰。',
425
+ tags: ['act'],
426
+ parameters: {
427
+ type: 'object',
428
+ additionalProperties: false,
429
+ properties: {
430
+ character: {
431
+ type: 'object',
432
+ additionalProperties: false,
433
+ description: '角色层面、跨技能共享的事实。',
434
+ properties: {
435
+ traits: { type: 'string', minLength: 1, maxLength: 600, description: '长期特质、背景、词条。' },
436
+ condition: { type: 'string', minLength: 1, maxLength: 600, description: '伤势、疲劳、体温、精神与消耗状态。' },
437
+ },
438
+ required: ['traits', 'condition'],
439
+ },
440
+ situation: {
441
+ type: 'object',
442
+ additionalProperties: false,
443
+ description: '这一时刻的情境;某项确实无影响就写“无”,不要省略。',
444
+ properties: {
445
+ weather: { type: 'string', minLength: 1, maxLength: 600, description: '天气与介质。' },
446
+ gear: { type: 'string', minLength: 1, maxLength: 600, description: '工具与装备。' },
447
+ info: { type: 'string', minLength: 1, maxLength: 600, description: '已知信息与资料的秩序。' },
448
+ time: { type: 'string', minLength: 1, maxLength: 600, description: '时限。' },
449
+ senses: { type: 'string', minLength: 1, maxLength: 600, description: '可见度与感官条件。' },
450
+ },
451
+ required: ['weather', 'gear', 'info', 'time', 'senses'],
452
+ },
453
+ checks: {
454
+ type: 'array',
455
+ minItems: 1,
456
+ description: '按判定顺序列出本时刻的技能;顺序只决定回执排列,不表示依赖关系。',
457
+ items: {
458
+ type: 'object',
459
+ additionalProperties: false,
460
+ properties: {
461
+ skill: { type: 'string', minLength: 1, maxLength: 120, description: '技能名。' },
462
+ goal: { type: 'string', minLength: 1, maxLength: 300, description: '这项技能要达成的直接结果。' },
463
+ evidence: { type: 'string', minLength: 1, maxLength: 300, description: '角色在该技能上的水平证据;不写数字、不写档位名,也不把情境写进去。' },
464
+ bonus: {
465
+ type: 'integer',
466
+ description: '情景代偿的奖励/惩罚骰,整数;标尺 -2(巨大劣势)…2(巨大优势),0 或省略即基准。'
467
+ + '填合计数:情境修正与本项花掉的 banked 奖励骰 1:1 抵消后相加。|bonus|≥3 时不调用判定模型,直接自动成功/失败。',
468
+ },
469
+ grade: {
470
+ type: 'integer',
471
+ enum: [1, 2],
472
+ description: '难度等级:1 = 困难(困难成功视为普通成功、成功视为失败),2 = 极难(极难成功视为成功、其余视为失败)。'
473
+ + '只用于难度本身是任务因素的场合;大成功与大失败不受影响;与顶层 joint 不能同时出现。',
474
+ },
475
+ },
476
+ required: ['skill', 'goal', 'evidence'],
477
+ },
478
+ },
479
+ joint: {
480
+ type: 'string',
481
+ enum: ['and', 'or'],
482
+ description: '组合检定:and = 列表里每项都要成功(按最小难度与最小 bonus 判),or = 任意一项成功即可(按最大难度与最大 bonus 判)。'
483
+ + '出现时 checks 至少两项,且各项不得带 grade。',
484
+ },
485
+ },
486
+ required: ['character', 'situation', 'checks'],
487
+ }
488
+ },
489
+ {
490
+ name: 'simple_trpg_check_roll_contest',
491
+ description: '对抗检定:分别判定我方与对方的同一时刻水平,比成功等级定胜负。actor 与 opponent 各写完整一套(name、traits、condition、situation 五维、checks);'
492
+ + '每侧各发一次判定请求,任一侧失败则整次失败且不掷骰。bonus 与 joint 挂在侧上:每侧只投一次骰,侧内 checks 多于一项时必须 joint。不支持 grade。'
493
+ + '自动判定按序短路:我方 bonus≥3 → 我方胜;对方≤-3 → 我方胜;我方≤-3 → 对方胜;对方≥3 → 对方胜。命中时两侧都不调用判定模型,未定调的一侧显示「跳过检定」。'
494
+ + '比档:大成功>极难>困难>普通成功>普通失败>大失败,同级比难度数值高者胜、相等则我方胜。对抗不触发奖励骰。',
495
+ tags: ['act'],
496
+ parameters: {
497
+ type: 'object',
498
+ additionalProperties: false,
499
+ properties: {
500
+ actor: {
501
+ type: 'object',
502
+ additionalProperties: false,
503
+ description: '我方;一份完整的判定请求。',
504
+ properties: {
505
+ name: { type: 'string', minLength: 1, maxLength: 120, description: '我方角色的名字,用于回执与显示串。' },
506
+ traits: { type: 'string', minLength: 1, maxLength: 600, description: '长期特质、背景、词条。' },
507
+ condition: { type: 'string', minLength: 1, maxLength: 600, description: '伤势、疲劳、体温、精神与消耗状态。' },
508
+ situation: {
509
+ type: 'object',
510
+ additionalProperties: false,
511
+ description: '我方所面对的处境;某项确实无影响就写“无”,不要省略。',
512
+ properties: {
513
+ weather: { type: 'string', minLength: 1, maxLength: 600, description: '天气与介质。' },
514
+ gear: { type: 'string', minLength: 1, maxLength: 600, description: '工具与装备。' },
515
+ info: { type: 'string', minLength: 1, maxLength: 600, description: '已知信息与资料的秩序。' },
516
+ time: { type: 'string', minLength: 1, maxLength: 600, description: '时限。' },
517
+ senses: { type: 'string', minLength: 1, maxLength: 600, description: '可见度与感官条件。' },
518
+ },
519
+ required: ['weather', 'gear', 'info', 'time', 'senses'],
520
+ },
521
+ checks: {
522
+ type: 'array',
523
+ minItems: 1,
524
+ description: '参与对抗的技能;多于一项时必须同时给 joint。',
525
+ items: {
526
+ type: 'object',
527
+ additionalProperties: false,
528
+ properties: {
529
+ skill: { type: 'string', minLength: 1, maxLength: 120, description: '技能名。' },
530
+ goal: { type: 'string', minLength: 1, maxLength: 300, description: '这一侧要靠它达到的直接结果。' },
531
+ evidence: { type: 'string', minLength: 1, maxLength: 300, description: '这一侧在该技能上的水平证据;不写数字与档位名。' },
532
+ },
533
+ required: ['skill', 'goal', 'evidence'],
534
+ },
535
+ },
536
+ joint: {
537
+ type: 'string',
538
+ enum: ['and', 'or'],
539
+ description: '这一侧的组合检定:and 取最小难度、or 取最大。侧内 checks 多于一项时必须指定。',
540
+ },
541
+ bonus: {
542
+ type: 'integer',
543
+ description: '这一侧的奖励/惩罚骰合计值:情境修正 + 这一侧花掉的 banked 奖励骰,0 或省略即基准。'
544
+ + '|bonus|≥3 时两侧都不调用判定模型,直接定胜负。',
545
+ },
546
+ },
547
+ required: ['name', 'traits', 'condition', 'situation', 'checks'],
548
+ },
549
+ opponent: {
550
+ type: 'object',
551
+ additionalProperties: false,
552
+ description: '对方;与 actor 同形,写对方自己的处境与证据。',
553
+ properties: {
554
+ name: { type: 'string', minLength: 1, maxLength: 120, description: '对方的名字,用于回执与显示串。' },
555
+ traits: { type: 'string', minLength: 1, maxLength: 600, description: '长期特质、背景、词条。' },
556
+ condition: { type: 'string', minLength: 1, maxLength: 600, description: '伤势、疲劳、体温、精神与消耗状态。' },
557
+ situation: {
558
+ type: 'object',
559
+ additionalProperties: false,
560
+ description: '对方所面对的处境;某项确实无影响就写“无”,不要省略。',
561
+ properties: {
562
+ weather: { type: 'string', minLength: 1, maxLength: 600, description: '天气与介质。' },
563
+ gear: { type: 'string', minLength: 1, maxLength: 600, description: '工具与装备。' },
564
+ info: { type: 'string', minLength: 1, maxLength: 600, description: '已知信息与资料的秩序。' },
565
+ time: { type: 'string', minLength: 1, maxLength: 600, description: '时限。' },
566
+ senses: { type: 'string', minLength: 1, maxLength: 600, description: '可见度与感官条件。' },
567
+ },
568
+ required: ['weather', 'gear', 'info', 'time', 'senses'],
569
+ },
570
+ checks: {
571
+ type: 'array',
572
+ minItems: 1,
573
+ description: '参与对抗的技能;多于一项时必须同时给 joint。',
574
+ items: {
575
+ type: 'object',
576
+ additionalProperties: false,
577
+ properties: {
578
+ skill: { type: 'string', minLength: 1, maxLength: 120, description: '技能名。' },
579
+ goal: { type: 'string', minLength: 1, maxLength: 300, description: '这一侧要靠它达到的直接结果。' },
580
+ evidence: { type: 'string', minLength: 1, maxLength: 300, description: '这一侧在该技能上的水平证据;不写数字与档位名。' },
581
+ },
582
+ required: ['skill', 'goal', 'evidence'],
583
+ },
584
+ },
585
+ joint: {
586
+ type: 'string',
587
+ enum: ['and', 'or'],
588
+ description: '这一侧的组合检定:and 取最小难度、or 取最大。侧内 checks 多于一项时必须指定。',
589
+ },
590
+ bonus: {
591
+ type: 'integer',
592
+ description: '这一侧的奖励/惩罚骰合计值:情境修正 + 这一侧花掉的 banked 奖励骰,0 或省略即基准。'
593
+ + '|bonus|≥3 时两侧都不调用判定模型,直接定胜负。',
594
+ },
595
+ },
596
+ required: ['name', 'traits', 'condition', 'situation', 'checks'],
597
+ },
598
+ },
599
+ required: ['actor', 'opponent'],
600
+ }
601
+ }
602
+ ];