Skip to content

Commit adc29dd

Browse files
author
NBL Agent
committed
fix(v3): LIFESTEAL regression test + 号令 uses semantic command event, not forged afterKill
Second-round review corrective pass (no gameplay expansion). - LIFESTEAL was already live (engine applyDamage reads the stat and heals the source per HP damage, distinct from skill drainRatio). Add a regression test proving a generated card's LIFESTEAL actually heals, so the inert-field concern is locked against regression instead of a no-op change. - event/号令 no longer emits afterKill (forging a factual kill event). Register a dedicated 'command' trigger event; 号令 emits command and cards get a matching command trigger (command returns a little resource). command never fires afterKill listeners; explicit afterKill actions still reach theirs. - Sync stale docs/CI: verify.yml comment now describes v3 verify:release (= verify + diversity) not the old v1.2.3 health/BattlePower gates; report §7 uniqueActionStructures updated to the regenerated 10337 (noting it drifts with the 3000-sample rerun); report §1 anchors on the content commit instead of chasing the rolling tip hash. Suite: 196 tests pass, npm run verify PASS (102-file manifest audited), diversity regenerated with command event/trigger coverage.
1 parent bd1c109 commit adc29dd

9 files changed

Lines changed: 119 additions & 45 deletions

File tree

.github/workflows/verify.yml

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,10 @@ jobs:
2020
with:
2121
node-version: 22
2222

23-
# v1.2.3 (audit §15): 普通 PR 只跑快速 verify;正式版本提交(push 到 main)
24-
# 必须跑 verify:release(verify + health-metrics n=3000 + adjacent-rarity-matrix
25-
# + BattlePower calibration + matched-rarity + Similar-BP)。
23+
# v3 (Simple Outside, Deep Inside): 普通 PR 只跑快速 verify;
24+
# 正式 push 到 main 必须跑 verify:release(verify + diversity 3000 样本)。
25+
# 旧 v1.2.3 的 health-metrics / adjacent-rarity / BattlePower / matched-rarity /
26+
# Similar-BP 公平性 gate 已移出 release 阻断,仅保留在 npm run diagnostics:legacy。
2627
- name: Verify (PR fast gate)
2728
if: github.event_name == 'pull_request'
2829
run: npm run verify

RELEASE-MANIFEST.json

Lines changed: 17 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,12 @@
66
"formulaParser": "Acorn 8.15.0 + restricted AST evaluator",
77
"scope": "full-main-release",
88
"scopeNote": "Complete auditable file set of the current official main release, generated from `git ls-files` (runtime + docs + qa artifacts + deployment files: .nojekyll, .github/workflows/verify.yml, docs/superpowers plans). Self-auditing meta files RELEASE-MANIFEST.json and scripts/generate-manifest.js are excluded from their own listing (their hashes are inherently unstable under regeneration); scripts/static-check.js audits both directly against the working tree.",
9-
"verifiedTests": 193,
9+
"verifiedTests": 196,
1010
"files": [
1111
{
1212
"path": ".github/workflows/verify.yml",
13-
"size": 782,
14-
"sha256": "54bd3a153b0fb93e7f4a313cb3a701b854f25ac673b2074141fa8d444d114a60"
13+
"size": 883,
14+
"sha256": "6e1653d0b7895727d558d0712ca067438d0d7cf600201592e1e1e51bb1ca7cd5"
1515
},
1616
{
1717
"path": ".gitignore",
@@ -70,8 +70,8 @@
7070
},
7171
{
7272
"path": "docs/GENERATOR-V3-REPORT.md",
73-
"size": 15292,
74-
"sha256": "a416ef6880b903b8b6097c7476ba0b7de7934de2691321b315cd39b96cc62acf"
73+
"size": 17249,
74+
"sha256": "66682aae73c1efcd06272b67611466bde913c86f999185a6c31ca688f81ea3cd"
7575
},
7676
{
7777
"path": "docs/GENERATOR-V3.md",
@@ -85,8 +85,8 @@
8585
},
8686
{
8787
"path": "docs/NUMERIC-COMPONENT-CATALOG.md",
88-
"size": 24197,
89-
"sha256": "c98bb8c3a6749c571582458612a90cefff12e2b31246ba3804ddfd9816c2c2e7"
88+
"size": 24246,
89+
"sha256": "82a137bffd8980ce89cb7e1145160c9ff1beac3bf0492d396ec1c9568062d3a8"
9090
},
9191
{
9292
"path": "docs/PLUGIN-API.md",
@@ -95,8 +95,8 @@
9595
},
9696
{
9797
"path": "docs/numeric-component-catalog.json",
98-
"size": 63079,
99-
"sha256": "0fa18836cb3c93557510da19516a379d7ae814224fa181c6ed83b52d356a2975"
98+
"size": 63282,
99+
"sha256": "e29e60db9204ac9b69f569a00982849b48a47fad1aee4b0d83d70b9b8e739b6f"
100100
},
101101
{
102102
"path": "docs/superpowers/plans/2026-01-power-rarity-model.md",
@@ -160,8 +160,8 @@
160160
},
161161
{
162162
"path": "qa/diversity-v3.json",
163-
"size": 1922,
164-
"sha256": "6a458ca9b72963eee6fcdf1e92c7d61e47d5853ac5622f725599505fd99f583a"
163+
"size": 1963,
164+
"sha256": "b69e1c61400fb6bbc57484c37a3336a4e867d708ee3c22b8997943fba58e67f9"
165165
},
166166
{
167167
"path": "qa/health-metrics.json",
@@ -290,8 +290,8 @@
290290
},
291291
{
292292
"path": "src/components.js",
293-
"size": 40052,
294-
"sha256": "fae97ac2d434194b8cb5cce086471a9a52a3270c569c6fa105ff5ee9f00ebf05"
293+
"size": 40073,
294+
"sha256": "58b1147eabff5061add20e4f4a4d9829bdc2111c20493dbff5a4293d54259502"
295295
},
296296
{
297297
"path": "src/content.js",
@@ -340,8 +340,8 @@
340340
},
341341
{
342342
"path": "src/gen-v3.js",
343-
"size": 11183,
344-
"sha256": "48aca1472eb4cbceb15a6d2de028089e384f58221292fc4bbf28f5ca2bd0fca6"
343+
"size": 11387,
344+
"sha256": "79fccb3a16b1d3372cdc7bb2a69267e07d4f403d02a846a10bee852618e77815"
345345
},
346346
{
347347
"path": "src/generator.js",
@@ -455,8 +455,8 @@
455455
},
456456
{
457457
"path": "tests/gen-v3.test.js",
458-
"size": 2552,
459-
"sha256": "4af756c8a54824cfc4ab65ab7c5ec6c84863d0b9a9f95af9b62310ead4d8426a"
458+
"size": 6413,
459+
"sha256": "42f37995c958ce7df78c7dd7ec80bc9c6f76701d1d47db19c18819118bb0a3e4"
460460
},
461461
{
462462
"path": "tests/generator.test.js",

docs/GENERATOR-V3-REPORT.md

Lines changed: 19 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,10 @@
55
66
## 1. Git 基线
77

8-
- **OLD HEAD**: `b590eb7` `manifest: refresh .github/workflows/verify.yml size entry`(分支 `main`,旧 v1.2.3)
9-
- **NEW HEAD(远端 `origin/main`**: `2a6c118`(已 fast-forward push,无 force push)
10-
- **提交链(6 个)**
11-
1. `3fc145a` v3 主体(30 文件)+1091/−266
12-
2. `1ef06f1` docs: 记录报告 NEW HEAD 哈希
13-
3. `c7db5ad` docs: 报告 NEW HEAD 引用定稿
14-
4. `b3d4667` manifest: 匹配定稿报告 blob
15-
5. `01b213b` docs: 报告 NEW HEAD 定稿 + 刷新清单
16-
6. `2a6c118` docs: 钉死报告 NEW HEAD 至远端 tip + 刷新清单
8+
- **OLD HEAD**: `b590eb7`(旧 v1.2.3,branch `main`
9+
- **内容主提交**: `3fc145a`(v3 主体,30 文件 +1091/−266)—— 本轮实现的稳定锚点
10+
- **推送**: 已 fast-forward push 到 `origin/main`,全程无 force push
11+
- **说明**: 本文档不追踪自身及其书签提交的滚动哈希(避免"为更新 HEAD 又产生新 HEAD"的自引用追赶)。当前 `origin/main` tip 以 `git rev-parse origin/main` 为准;截稿期 tip 由本地与远端一致确认。
1712
- **本地未提交基线**:接手时工作树已含上一轮 v3 实现(未提交),本报告基于该实现做完整验证并整理提交。
1813

1914
## 2. 修改文件
@@ -91,7 +86,7 @@ resourceCoverage: RAGE/SOUL/CHRONO/ENERGY/HP → 5 种
9186
statusInteractionCoverage: stack/refresh/replace/dynamic/apply/toggle/consume/targetHas/missing → 10 项
9287
targetCoverage: self/enemy/ally/query(query:enemy/query:ally)/source → 7 项
9388
triggerCoverage: roundStart/roundEnd/afterDamageTaken/afterDamageDealt/afterKill → 5 项
94-
uniqueFingerprints: 3000 uniqueActionStructures: 7628 duplicateRate: 0
89+
uniqueFingerprints: 3000 uniqueActionStructures: 10337 duplicateRate: 0
9590
nonDamage: 1227 multiHeal: 79 multiShield: 262 multiStatus: 562
9691
```
9792

@@ -145,7 +140,7 @@ nonDamage: 1227 multiHeal: 79 multiShield: 262 multiStatus: 562
145140

146141
### 13.1 3000/3000 唯一 Fingerprint(不是过度碎片化的证据)
147142

148-
`mechanicFingerprint()` 里所有具体数字先折叠为 `'#'``typeof x==='number' → '#'`),省略 `id/name/displayName/_budget/tags`,seed 从不进入;公式**符号/运算符**保留(如 `'ATK * #'` vs `'MAX_HP * # + RAGE * #'`,区分资源缩放与 ATK 缩放——这是有意的机制差异)。因此同结构仅改数值 → 归为同一 fingerprint。0 重复说明组合器确实在结构空间里展开(17 effect × 6 event × 5 资源 × 8 伤害类型 × 3 叠加策略 × 目标查询等),而非把数值差算成结构差。另单独统计 `uniqueActionStructures=7628`(逐 Action 结构)。如审阅怀疑,可直接验证:改任一 coefficient 或数值,fingerprint 应不变。
143+
`mechanicFingerprint()` 里所有具体数字先折叠为 `'#'``typeof x==='number' → '#'`),省略 `id/name/displayName/_budget/tags`,seed 从不进入;公式**符号/运算符**保留(如 `'ATK * #'` vs `'MAX_HP * # + RAGE * #'`,区分资源缩放与 ATK 缩放——这是有意的机制差异)。因此同结构仅改数值 → 归为同一 fingerprint。0 重复说明组合器确实在结构空间里展开(17 effect × 6 event × 5 资源 × 8 伤害类型 × 3 叠加策略 × 目标查询等),而非把数值差算成结构差。另单独统计 `uniqueActionStructures`(逐 Action 结构,随 3000 样本 regenerate 会略有波动,当前为准)。如审阅怀疑,可直接验证:改任一 coefficient 或数值,fingerprint 应不变。
149144

150145
### 13.2 Condition / Event 覆盖偏窄(合理的 v3.1 加强项)
151146

@@ -155,8 +150,18 @@ nonDamage: 1227 multiHeal: 79 multiShield: 262 multiStatus: 562
155150

156151
该 sanity probe 是**镜像**构造(C 与 XS 用同一 `rarity-probe-i` seed),结构与机制完全一致、仅预算量级不同。当双方都是纯续航/双龟时,镜像走向 maxRounds → Draw。数据含义:一旦见胜负 XS 从不输(25:0),但 48 局中 23 局到界。这是**镜像续航现象**,不是生成器批量制造"无法收尾"的卡;本方向允许防御镜像判 Draw(§13),且不要求精确胜率。审阅时可定向核对 Draw 局是否多为 sustain 镜像,而非系统性 stall。
157152

158-
> 关于报告文字:正文 §1 已列全 **6 个提交**(不再称"4 个")
153+
> 关于报告文字:正文 §1 不再逐字追踪滚动 tip(避免自引用追赶);产品实现锚点为内容主提交 `3fc145a`
159154
160-
## 14. Ready for Human Review
155+
## 14. Human Review Corrective Pass(v3 正式验收前)
161156

162-
**是。** 外部极简、内部高维差分、AI 自动对战可观赏、创建/选择/观察为主行为;193 测试 + verify + diversity + browser(mobile/desktop) 全部通过,工作树整理后干净,符合 §38 完成标准。
157+
第二轮独立评审后做的最小 corrective pass,不扩大玩法,仅修订验收指出的三处:
158+
159+
1. **LIFESTEAL 并非惰性字段 —— 已用回归测试锁定**。评审认为 `LIFESTEAL` 生成但未参与战斗,经核对实际不成立:引擎 `applyDamage``engine.js:197`)在每次 HP 伤害后按 `LIFESTEAL` 统计吸取生命(区别于 `effects.js` 的 skill 级 `drainRatio`)。新增测试证明:生成卡 `LIFESTEAL` 高时,对敌造成 200 点真实 HP 伤害后施法者按 `LIFESTEAL/100` 回血,并有 heal 日志。生成器**不需要改**(该数字真实生效);以测试锁死该行为,防止未来回归。`tests/gen-v3.test.js`
160+
2. **`号令`(event family) 不再伪造 `afterKill`**。原实现 `emitEvent('afterKill')` 会在未真正击杀时人为触发"击杀后"类效果,语义错误。已改为发射**独立语义事件 `command`**(在 `components.js` 注册为标准 trigger event),并在生成 `command` 时给卡附带对应 `command` trigger(发令回收资源),与真实 `afterKill` 完全隔离。测试断言:`command` 不放 `afterKill` 事件、`command` 不触发 afterKill listener,而显式 afterKill action 仍能到达其 listener。`src/components.js``src/gen-v3.js``tests/gen-v3.test.js`
161+
3. **文档/数字同步**`qa/diversity-v3.json` 当前 `uniqueActionStructures=10337`(报告原写 7628 已更新,并注明该数随 3000 样本 regenerate 微波动);`verify.yml` 过时注释已改为描述 v3 的 `verify:release = verify + diversity`(旧公平性 gate 仅 `diagnostics:legacy`);报告 §1 改为引用内容主提交、不再自引用追逐 tip。
162+
163+
Condition/Event 覆盖偏窄不在本 pass 范围(已判定为 v3.1 加强项)。本次通过后,Human Review 即可基于现有 v3 侧重点直接体验自动观战。
164+
165+
## 15. Ready for Human Review
166+
167+
**是。** 外部极简、内部高维差分、AI 自动对战可观赏、创建/选择/观察为主行为;193+ 测试 + verify + diversity + browser(mobile/desktop) 全部通过,工作树整理后干净,符合 §38 完成标准。

docs/NUMERIC-COMPONENT-CATALOG.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
这不是角色技能表,而是整个战斗系统的“积木盒”。角色、技能、状态只允许组合这些通用积木和参数;新增普通内容不应要求修改 BattleEngine。
44

5-
当前固定能力面:**91 个参数旋钮 / 18 个 Effect 组件 / 27 个 Condition 组件 / 8 个 Target 组件 / 28 个 Event 插入点 / 8 个 Damage Type**
5+
当前固定能力面:**91 个参数旋钮 / 18 个 Effect 组件 / 27 个 Condition 组件 / 8 个 Target 组件 / 29 个 Event 插入点 / 8 个 Damage Type**
66

77
当前内容只是示例组合:20 个实体、63 个技能、33 个状态。理论组合空间远大于这些示例。
88

@@ -232,6 +232,7 @@
232232
- `afterStatusApplied` — 标准 Trigger Event:获得状态后。
233233
- `afterStatusInflicted` — 标准 Trigger Event:施加状态后。
234234
- `afterStatusRemoved` — 标准 Trigger Event:状态移除后。
235+
- `command` — 标准 Trigger Event:号令。
235236
- `roundStart` — 标准 Trigger Event:回合开始。
236237
- `roundEnd` — 标准 Trigger Event:回合结束。
237238

docs/numeric-component-catalog.json

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
"effects": 18,
77
"conditions": 27,
88
"targets": 8,
9-
"events": 28,
9+
"events": 29,
1010
"damageTypes": 8,
1111
"units": 20,
1212
"skills": 63,
@@ -1966,6 +1966,14 @@
19661966
"fields": [],
19671967
"kind": "trigger"
19681968
},
1969+
"command": {
1970+
"id": "command",
1971+
"name": "号令",
1972+
"human": "标准 Trigger Event:号令。",
1973+
"ai": "trigger-event command",
1974+
"fields": [],
1975+
"kind": "trigger"
1976+
},
19691977
"roundStart": {
19701978
"id": "roundStart",
19711979
"name": "回合开始",

qa/diversity-v3.json

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
"status": 4274,
1515
"cleanse": 743,
1616
"damage": 7051,
17-
"gain": 8428,
17+
"gain": 9006,
1818
"repeat": 2498,
1919
"toggleStatus": 579,
2020
"conditional": 3401,
@@ -34,10 +34,11 @@
3434
"missingStatus": 711
3535
},
3636
"eventCoverage": {
37-
"afterKill": 4212,
37+
"command": 1193,
3838
"ModifyDamageTaken": 2028,
3939
"afterDamageTaken": 2649,
4040
"afterDamageDealt": 574,
41+
"afterKill": 3597,
4142
"roundStart": 582,
4243
"roundEnd": 626
4344
},
@@ -52,10 +53,10 @@
5253
"frost": 1079
5354
},
5455
"resourceCoverage": {
55-
"RAGE": 5031,
56+
"RAGE": 5210,
5657
"HP": 2760,
57-
"SOUL": 4960,
58-
"CHRONO": 4895,
58+
"SOUL": 5170,
59+
"CHRONO": 5084,
5960
"ENERGY": 582
6061
},
6162
"statusInteractionCoverage": {
@@ -72,7 +73,7 @@
7273
},
7374
"targetCoverage": {
7475
"ally": 1336,
75-
"self": 10392,
76+
"self": 10970,
7677
"query": 2401,
7778
"query:ally": 729,
7879
"source": 2028,
@@ -83,6 +84,7 @@
8384
"afterDamageTaken": 2649,
8485
"afterDamageDealt": 574,
8586
"afterKill": 3597,
87+
"command": 578,
8688
"roundStart": 582,
8789
"roundEnd": 626
8890
},

src/components.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -271,7 +271,7 @@
271271
[
272272
['afterDamageTaken','受伤后'],['afterDamageDealt','造成伤害后'],['afterHealTaken','受到治疗后'],['afterHealDealt','造成治疗后'],
273273
['afterDefeated','被击倒后'],['afterKill','击杀后'],['afterStatusApplied','获得状态后'],['afterStatusInflicted','施加状态后'],
274-
['afterStatusRemoved','状态移除后'],['roundStart','回合开始'],['roundEnd','回合结束']
274+
['afterStatusRemoved','状态移除后'],['command','号令'],['roundStart','回合开始'],['roundEnd','回合结束']
275275
].forEach(([id,name])=>registerEventComponent(id,{name,kind:'trigger',human:`标准 Trigger Event:${name}。`,ai:`trigger-event ${id}`}));
276276

277277
function describeParameter(id,value){const def=PARAMETER_CATALOG[id];if(!def)return null;return{...def,value,summary:`${def.name}: ${value ?? def.defaultValue}${def.unit?` ${def.unit}`:''}${def.effect}`};}

src/gen-v3.js

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@
6868
];
6969
const count=pick([2,3,3,3,3,4,4,4,4,5,6]);
7070
const actions=[];
71+
let hasCommand=false; // an 'event' family action emits a semantic 'command' signal
7172
const condition=()=>pick([{type:'hpPctBelow',value:.5},{type:'targetHpPctBelow',value:.4},{type:'resourceAtLeast',resource,value:3},{type:'targetHasStatus',status:dotId},{type:'missingStatus',status:buffId}]);
7273
for(let i=0;i<count;i++){
7374
const weighted=FAMILIES.concat(ROLES[archetype],ROLES[archetype]);
@@ -89,7 +90,7 @@
8990
case 'resource':a.effects=[{type:pick(['gain','resource']),resource,amount:2+r.random(3)}];break;
9091
case 'convert':a.effects=[{type:'convertResource',from:'ENERGY',to:resource,amount:2,ratio:2}];break;
9192
case 'cooldown':a.target='ally';a.effects=[{type:'cooldownReduce',amount:1+r.random(2)}];break;
92-
case 'event':a.effects=[{type:'emitEvent',event:'afterKill',eventSubject:'actor'}];break;
93+
case 'event':hasCommand=true;a.effects=[{type:'emitEvent',event:'command',eventSubject:'actor'}];break;
9394
case 'toggle':a.kind='status';a.effects=[{type:'toggleStatus',status:buffId,duration}];break;
9495
}
9596
// Independently compose secondary effects, control flow, target queries and costs.
@@ -109,7 +110,8 @@
109110
actions.push(a);
110111
}
111112
const triggers=[{event:pick(['roundStart','roundEnd','afterDamageTaken','afterDamageDealt','afterKill']),target:'self',effects:[{type:pick(['heal','shield']),formula:'MAX_HP * '+round(.025*scale)}]},
112-
{event:'afterKill',target:'self',effects:[{type:'gain',resource,amount:3}]}];
113+
{event:'afterKill',target:'self',effects:[{type:'gain',resource,amount:3}]},
114+
...(hasCommand?[{event:'command',target:'self',effects:[{type:'gain',resource,amount:2}]}]:[])];
113115
const name=N.generateDisplayName({seed,rarity,level,archetype});
114116
const card={id,identity,seed,generatorVersion:3,rarity,level,archetype,name,displayName:name,generationBudget,quality:N.qualityFactor(seed),stats,actions,statuses,triggers,resourceRegens,
115117
resources:{ENERGY:{max:8,regen:2},[resource]:{max:8,regen:1}},resistances:{[type]:round(.15*scale)},affinities:{[type]:round(.12*scale)},

0 commit comments

Comments
 (0)