merge: 同步 master(闸按改动域分跑批 60c25ca3)入卡doc分支
This commit is contained in:
commit
d6b0c9fc86
13 files changed
+179
-39
No files matched your search
@@ -16,7 +16,7 @@
|
||||
|
||||
## 运行与验证(工作目录 = 含 project.godot 的切片目录)
|
||||
|
||||
- **交付前验收 = 跑 `tools/run_all_gates.sh`(不带 `--quick`),全绿才合回 master**——它是全部验证闸的唯一入口与唯一清单,闸件增减只改它,文档禁另列清单。跑完写回执 `game/tests/.gates/receipt.json`,合回时由 worktree-guard 校验(缺回执/quick 模式/回执 commit ≠ HEAD/含红项四况一律拦截)
|
||||
- **交付前验收 = 跑 `tools/run_all_gates.sh`,按本批改动域选档(`--code` 只跑代码域/`--docs` 只跑文档域/不带参数=两域全量),所选域全绿才合回 master**——它是全部验证闸的唯一入口与唯一清单,**域划分也只写在它这一个地方**,闸件增减与分域调整都只改它,文档禁另列清单。改闸件本身、或一次改动同时跨两域时跑全量档(§6.2 共享件例外)。跑完写回执 `game/tests/.gates/receipt.json`(含 `domain` 字段),合回时由 worktree-guard 按 diff 路径推出该验哪个域再校验回执(缺回执/quick 模式/回执 commit ≠ HEAD/域不覆盖本次改动/含新增红,五况一律拦截)
|
||||
- 单件直跑(调试期最窄确认用,**不构成验收**):系统级断言面 `python tools/run_smoke.py`(名带 smoke 但不止存活检查,术语见 `game/tests/README.md`)· 场景探针 `…console.exe --headless --path . res://smoke/ui_probe.tscn` · 单元 `… --script res://tests/run_all.gd`
|
||||
- 调参批量:`--script res://smoke/adventure_monte_carlo.gd`(全冒险期望)· `res://smoke/reward_expectation.gd`(奖励期望);通关率/难度类测量一律走全冒险
|
||||
- 数值验证:`python tools/balance_verifier.py`(采集端 `res://smoke/verifier_batch.gd`,报告落 `design/qa/`)
|
||||
@@ -33,6 +33,7 @@
|
||||
- `game/assets/art/` — 已验收终稿(入包被游戏加载);`game/` — 游戏工程本体(代码 + smoke/ 冒烟 + tests/ 单元集成)
|
||||
- `sim-data/` — 机器读数据产物;数据件不入 git(入库例外 = README+analyze.py+growth_model.py);MC 锚与读数常驻留盘,无数值调整禁重跑
|
||||
- `tools/` — python 工具脚本;`archive/` — 收口归档 zip(gitignored)
|
||||
- `reports/` — 迭代批产出物(实现报告/代拍板追认单/GDD 反哺点清单);**实现批唯一可写目录**,只放 .md,不是事实源(细则见 `reports/README.md`)
|
||||
- 根目录:`active.md`(会话状态唯一状态源,退役规则见 doc-conventions 第 10 节)、`roadmap.md`(里程碑)、`stage.txt`/`review-mode.txt`(运行开关)
|
||||
|
||||
## 规范导航(动手前先查表)
|
||||
@@ -53,7 +54,8 @@
|
||||
|
||||
- 主工作区禁止直接改动与提交——一切改动走独立 worktree(豁免:active.md 会话收口、.zcode 钩子配置)
|
||||
- 测试执行两入口铁规——只有迭代 loop 开发与用户点名两个触发入口;其余会话零测试,需测试支撑先请示并注明未跑测
|
||||
- 交付前验收 = 跑 `tools/run_all_gates.sh` 全绿(唯一入口与唯一闸清单,禁在文档另列清单);合回 master 由 worktree-guard 校验回执强制(process-conventions §3)
|
||||
- 交付前验收 = 跑 `tools/run_all_gates.sh` **按改动域**全绿(唯一入口、唯一闸清单、唯一域划分,禁在文档另列);合回 master 由 worktree-guard 按 diff 推域校验回执强制(process-conventions §3)
|
||||
- **两类批**——**迭代批**(代码实现,`/iter` 发射):**禁写 `design/` 整个目录**(含新增/修改/删除),设计文档对它只读、是写测试与实现代码的输入依据;产出物只准写 `reports/`;批末只跑代码域闸。**非迭代批**(工具/规则/文档改动):不占 roadmap 迭代号,按改动域跑闸。文档内容的落地由用户在会话中讨论后进行,**无专用的文档编写入口/skill**
|
||||
- MC/模拟:无数值改动零跑;档位硬闸见 `sim-data/README.md`「MC 采集档位表」,一档只能由用户发起
|
||||
- 闸机轮次纪律:新码先孤立验证 → 出红只跑红件最小单元 → 长批先最小参数试车 → 开工扫坑册(process-conventions §6)
|
||||
- 变异验证——新增/改写验收函数必须变异变红才算验收成立;无变异红记录只写「已实现,拦截能力未证」(test-conventions §6)
|
||||
|
||||
@@ -146,6 +146,16 @@
|
||||
| **render_gate**(视觉 golden) | `tools/render_gate.sh` | 窗口化渲染闸:①shot_probe 七屏截图 → shot_checker A-D 语义断言;②fx_shot_probe / board_shot_probe 取证(超时/零字节/文件缺失任一 = FAIL);③棋盘 golden 逐像素容差比对(差 ≤16 / changed ≤2%,provisional)+ 菜单 golden;④批末孤儿残留清零 | 段级 FAIL 输出 + 汇总退出码 | 查退出码在位(每段 timeout 守卫 + 退出码/取证文件双检);**SCRIPT ERROR 扫描缺失**;断言数下限不适用(判定主体 = golden 容差与取证文件存在性,非断言计数) |
|
||||
| **static_scan**(静态扫描域) | `game/tests/static_scan.sh` | 静态扫描域 S1–S10 可执行载体(grep 规则 + `tools/audit_type_hints.py`〔S9〕+ `tools/audit_file_headers.py`〔S10〕) | fail 聚合退出码(任一硬失败规则命中即 exit 1) | 查退出码在位;SCRIPT ERROR 扫描与断言数下限不适用(逐规则命中即红,无运行时错误面与断言计数);S3 现为 warning 级、不参与 fail 判定(见下表) |
|
||||
|
||||
### 改动域划分(2026-10-17 用户裁「代码批只跑代码闸、文档批只跑文档闸」)
|
||||
|
||||
| 域 | 闸 | 本批改动落在这些路径即属该域 |
|
||||
|---|---|---|
|
||||
| **代码域** | run_smoke、run_all、probe_gate `--click`、probe_gate `--full`、render_gate、static_scan、回放确定性对拍(条件件) | `game/`、`tools/`、`artwork/` |
|
||||
| **文档域** | gdd_lint、doc_link_audit、doc_term_audit、active.md 体量闸 | `design/`、`active.md`、`roadmap.md`、`CLAUDE.md` |
|
||||
| **两域都含** | repo_structure_audit(熵增闸:顶层目录/根文件/图像白名单/退役路径/design 子目录五查——文档批新增或挪动 `.md` 同样会绊它;纯文本扫描秒级) | — |
|
||||
|
||||
口径三条:**域划分的唯一出处是 `tools/run_all_gates.sh`**(`--code`/`--docs`/不带参数=全量),本表是它的现状登记、不是第二份清单;改闸件本身或一次改动跨两域 → 跑全量档(§6.2 共享件例外);合回闸按 merge diff 路径推出应验的域,再校验回执的 `domain` 字段与新增红。代码域跑前先过**素材齐盘预检**——`game/assets/art/**` 与一切 `*.import` 边车都不入 git(2026-09-29 裁定),worktree 冷检出必空盘,空盘态冒烟 V1 成片假红、渲染闸更是一段 golden 都不比。
|
||||
|
||||
### 三条判定硬闸现行状态
|
||||
|
||||
| 硬闸 | run_smoke | run_all | probe_gate | render_gate | static_scan |
|
||||
|
||||
@@ -115,7 +115,7 @@
|
||||
| sim-data 白名单 | 只留 README、日志、分析工具 .py、现行 MC 对拍锚、设计文档 md;可再生原始数据不入库 |
|
||||
| 出口清理项 | 每迭代出口检查单必含:本迭代中间产物逐份核对(结论已吸收、未决已迁移)后删除 |
|
||||
| 暂存地非档案库 | `design/research/` 与 `design/qa/` 是中间文档暂存地,入内文件默认到期即死 |
|
||||
| 迭代实现报告 | 实现批收口的迭代级报告(各卡状态/代拍板追认单/GDD 反哺点节),属中间文档:反哺点未决先迁 06,裁决落文吸收后按「证据只留最新」退役 |
|
||||
| 迭代实现报告 | 实现批收口的迭代级报告(各卡状态/代拍板追认单/GDD 反哺点清单),属中间文档,**落 `reports/` 不落 `design/`**(2026-10-17 用户裁:实现批禁写 `design/`,`reports/` 是其唯一可写目录):反哺点裁决落文吸收后按「证据只留最新」退役 |
|
||||
| 证据只留最新 | 测试证据只保留最新一次迭代或最新一轮同类审计版本;新证据落位后同批删上一份;待人清单过人闸后同规退役 |
|
||||
|
||||
**删除方式**:按 `design/dev/concept-conventions.md` 第 2 节删除概念 SOP 口径——先查引用面(grep 文件名于阻塞域),改写或删除指路句,再删文件,收尾跑死链检查全绿。
|
||||
@@ -22,7 +22,7 @@
|
||||
|---|---|---|
|
||||
| 一·设计 | GDD 文档或 art-bible | 用户提起或从 `06-待办清单` 取出;会话内进行、用户主导、禁无监管自跑;经用户点头确认心智一致才许写入文档档;完成判据=待裁点清零,带未裁点的是讨论稿;不自动进写卡段,写卡须用户另行发令 |
|
||||
| 二·写需求卡片 | 需求卡片(写作规范见用户级技能 req-card) | 设计定稿后用户发令;例外=缺陷修复/工程债/测试基建/重构凭 bug 证据或 06 条目直接进入;新玩法/新系统/新内容必须有设计定稿前置;卡片必经 grilling 多轮复核、零待裁点才成立;入库必在 06 登记一条(防遗忘);编入后续未完成迭代(roadmap 迭代行),无空位与用户确认开新一轮 |
|
||||
| 三·实现 | 代码与配置,可运行的游戏 | 需求卡片永不单独施工,统一由用户指定迭代号触发(=U0 开工令),按 /iter 技能与本文 §3 执行;设计权威文档(GDD/art-bible)批内一律不更新,实现批只出迭代实现报告(见 §3 第 12 条);完成人闸=实机目检清单+代拍板追认单登记交用户即完成批内义务 |
|
||||
| 三·实现 | 代码与配置,可运行的游戏 | 需求卡片永不单独施工,统一由用户指定迭代号触发(=U0 开工令),按 /iter 技能与本文 §3 执行;**本段是代码实现批:禁写 `design/` 与根规则文件,产出物只落 `reports/`(见 §3 第 13 条)**;设计权威文档(GDD/art-bible)批内一律不更新;完成人闸=实机目检清单+代拍板追认单登记交用户即完成批内义务 |
|
||||
|
||||
## 3. 批次工作流标准清单
|
||||
|
||||
@@ -44,10 +44,10 @@
|
||||
9. **验收函数变异验证**——新增/改写的验收函数(冒烟断言/探针自检/闸机判据)必须配一次变异验证:故意把被测件改坏成该类缺陷形态 → 跑最窄那道闸确认变红 → `git checkout` 精确还原;变异点/跑的闸/红输出摘要/还原结论四件记入实现报告变异验证记录表;**无变异红记录的断言不写「已验收」,只写「已实现,拦截能力未证」**(细则见 test-conventions §6)
|
||||
|
||||
**完成**
|
||||
10. **批末全量验收才合并**——在任务 worktree 内跑 `tools/run_all_gates.sh`(不带 `--quick`),全绿才合回 master。该脚本是全部验证闸的**唯一入口与唯一清单**,闸件增减只改它;**本文与其他文档禁另列闸件清单**(清单散落多处正是 iter27 漏跑 `probe_gate --full` 的成因)。跑完写回执 `game/tests/.gates/receipt.json`,合回时由 worktree-guard 强制校验
|
||||
10. **批末按域验收才合并**——在任务 worktree 内跑 `tools/run_all_gates.sh`,**按本批改动域选档**:只碰 `game/`、`tools/`、`artwork/` → `--code`;只碰 `design/`、`active.md`、`roadmap.md`、`CLAUDE.md` → `--docs`;同时跨两域、或改的是全部闸共用的壳(闸脚本本身/探针包装器)→ 全量档不带参数。所选域全绿才合回 master。该脚本是全部验证闸的**唯一入口、唯一清单与唯一域划分**,闸件增减、分域调整都只改它;**本文与其他文档禁另列闸件清单**(清单散落多处正是 iter27 漏跑 `probe_gate --full` 的成因)。跑完写回执 `game/tests/.gates/receipt.json`(含 `domain` 字段),合回时由 worktree-guard 按 diff 路径推域强制校验
|
||||
11. **合并后主区全量复跑**——合并可能引入冲突或环境差异,落回 master 后再跑全套
|
||||
12. **会话完成落记**——active.md 快照与状态记录更新(含删除已完结旧条目,doc-conventions 第 10 节);跨会话教训写入记忆库;漏删旧条目由 `tools/audit_active_state.py` 在全量档抓(合回闸强制,脚本即清单)
|
||||
13. **文档同步闸**——机械面文档(roadmap 行/registry/active.md 记账/README 工具注)合并前顺带更新完毕方算完成;设计权威文档批内不更新:实现批完成出迭代实现报告(`design/qa/iter<N>-实现报告-<日期>.md`:各卡状态+代拍板追认单+GDD 反哺点节),反哺点同步登记 `06-待办清单`,用户逐点裁决后另批写入文档;反哺点未写入期间相关 GDD 节视为待反哺,读前先查 06;需求卡片必列「关联文档同步面」按两层标注
|
||||
12. **会话完成落记**——active.md 快照与状态记录更新(含删除已完结旧条目,doc-conventions 第 10 节);跨会话教训写入记忆库;漏删旧条目由 `tools/audit_active_state.py` 在全量档抓(合回闸强制,脚本即清单)。**例外**:active.md 是会话状态唯一源、不是文档产出物,实现批照写(合回闸钩子本就对它放行);`design/` 与根规则文件(`CLAUDE.md`/`roadmap.md`)实现批禁写,见 §13
|
||||
13. **文档同步闸(2026-10-17 改口径:实现批禁写文档)**——实现批**禁写 `design/` 整个目录与根规则文件**,要往外交代的东西一律落 `reports/`:实现报告 `reports/iter<N>-实现报告-<日期>.md`(各卡状态+实机目检总清单+遗留读数义务)、代拍板追认单 `reports/iter<N>-代拍板-<日期>.md`、GDD 反哺点清单 `reports/iter<N>-反哺点-<日期>.md`(每条写清哪个文件哪一节、改成什么、依据是什么)。设计权威文档(GDD/art-bible)**任何批都不直改**:用户在会话中逐点裁决后,由非迭代批(工具/规则/文档批)写入 `design/`,并把反哺条目从 `reports/` 清掉(吸收即死)。机械面文档(roadmap 行/registry/README 工具注)同样归非迭代批在合并前更新。反哺点未落地期间相关 GDD 节视为待反哺,读前先查 `reports/` 与 06;需求卡片必列「关联文档同步面」按两层标注
|
||||
14. **实机目检归用户,只登记不阻塞**——真人检测项登记进实现报告目检总清单与 07-体验验证清单即完成批内义务;禁把等目检当停工理由或完成前置
|
||||
15. **中间产物吸收即死**——细则见 CLAUDE.md 硬性红线与 doc-conventions 第 11 节
|
||||
|
||||
@@ -57,7 +57,7 @@
|
||||
|
||||
**MC/verifier 触发细则**:仅在数值验证场景触发(改数值前后取证);normal 档为主、perfect 按需;**无数值改动的会话禁跑 MC**;结构位移类「存档锚」须先向用户请示定性再跑;需求卡片预设步骤的前提消失时(如「带值裁定后重锚」实际未投值)停问勿照字面执行。
|
||||
|
||||
**分级周期口径**:全部验证闸收编在 `tools/run_all_gates.sh` 一个入口下(**脚本即清单**),分两档——`--quick` 档跳过渲染闸(需开显示器)与静态扫描,供迭代中段自检;**完成出库一律跑全量档(不带 `--quick`)**,回执由合回闸强制校验。真人实机目检归用户不进自动闸。
|
||||
**分级周期口径**:全部验证闸收编在 `tools/run_all_gates.sh` 一个入口下(**脚本即清单,且即域划分的唯一出处**),分三档——`--code`/`--docs` 按本批改动域只跑该域闸(代码域=冒烟/单元/探针两档/渲染闸/静态扫描/回放对拍;文档域=gdd_lint/doc_link/doc_term/active.md 体量;熵增闸两域都含);`--quick` 是全量档的中段自检形态(跳过渲染闸与静态扫描);**不带参数=两域全量**,用于改闸件本身或一次改动跨两域。完成出库按所选域全绿,回执由合回闸按 diff 推域强制校验。真人实机目检归用户不进自动闸。
|
||||
|
||||
## 5. MC 采集档位细则
|
||||
|
||||
|
||||
@@ -11,6 +11,18 @@
|
||||
3. 行动点用尽——或者你主动停手——这一轮结束,轮末自动结算:你这一轮扫雷攒下的因,逐行兑现成战斗的果。你拿的增益打了出去,你控制的巢穴变成怪物的虚弱,你惊动的巢穴变成怪物的警觉(护甲变硬、伤害账轮末才落)。
|
||||
4. 敌人没死光、你还活着,就进下一轮,同一张图接着挖;图开完了,发清图奖励,换一张新图接着打。
|
||||
|
||||
```
|
||||
开局:装一张新雷图 + 发本轮行动点
|
||||
→ 用行动点做排雷决策(挖安全格拿增益/道具 · 插旗控制巢穴 · 点错惊动巢穴)
|
||||
→ 行动点用尽 或 主动停手 → 轮末自动结算:本轮攒的因逐行兑现成战斗的果
|
||||
→ 判定(先判你,再判敌人)
|
||||
你倒下 → 战斗失败 → 冒险结束(见「死了之后」)
|
||||
敌人死光 → 战斗胜利 → 回关卡节点链推进
|
||||
两边都还有活的 → 进下一轮:
|
||||
本图已开完 → 发清图奖励 → 换一张新图接着挖
|
||||
本图未开完 → 同一张图接着挖
|
||||
```
|
||||
|
||||
所以自动战斗不是过场,是阅卷:每一行结算都能指回你刚才的哪一步棋。
|
||||
|
||||
### 数值语言
|
||||
|
||||
@@ -602,6 +602,14 @@
|
||||
|
||||
### 文档与结构
|
||||
|
||||
#### 闸按改动域分跑批的两处代拍板 + 旧实现报告迁位欠账(2026-10-17 登记,闸分域批)
|
||||
|
||||
**现状**:用户裁定「改代码的批只跑代码闸、改文档的批只跑文档闸」与「迭代批禁写 `design/`」两条,落地时撞上一处既有规则没覆盖的缝隙,本批按推荐方案先做并登记待追认:① **active.md 记账例外**——§3.12 要求每批更新 active.md 快照与状态记录,而合回闸钩子本就对 active.md 放行(它被定为「会话状态唯一源」而非文档产出物);本批把禁写面落在 `design/` 与根规则文件(`CLAUDE.md`/`roadmap.md`),**active.md 实现批照写**,读法若不符请推翻。② **`reports/` 新顶层目录**——熵增闸「新增顶层目录须同批改 CLAUDE.md Project Layout + 脚本白名单两处」,本批已同批改齐(`repo_structure_audit.py` TOP_DIRS + CLAUDE.md 第 36 行 + `reports/README.md` 立目)。③ **旧实现报告未迁位**——`design/qa/iter27-实现报告-2026-10-14.md`、`iter28-实现报告-2026-10-15.md` 两份现行报告仍在 `design/qa/`,且 active.md 第 32 行待追认指针指向该路径;新口径要求实现批产出物归 `reports/`。本批未搬(搬动会改动两份历史报告的落位与引用面,属文档动作,不属闸改造)。
|
||||
|
||||
**归属**:①②待用户追认(本批已生效);③归下一次文档批
|
||||
|
||||
**状态**:待办——①②待追认,③待迁位(迁时同批改 active.md:32 指针并按死链检查复验)
|
||||
|
||||
#### 文档/结构闸存量欠账(2026-10-08 迭代 19 总完成批登记)
|
||||
|
||||
新重闸口径首次全闸套跑翻出;经 T0 基点 9160942 复测确认为存量/外部批旧债,非本迭代新增。
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"note": "存量基线·首次自动生成;闸况变化时本脚本会判红,需人工确认后重生成",
|
||||
"note": "存量基线·首次自动生成;闸况变化时本脚本会判红,需人工确认后重生成;2026-10-17 人工确认重生成 06 条目:af94ce97 把 gdd_lint 记为存量红(01 篇 R5),该红已由 fcb8c3f8 补流程图消除,实测 --docs 域 06 exit=0,故基线改回绿。",
|
||||
"gates": {
|
||||
"01 冒烟全量": {
|
||||
"exit": "0",
|
||||
@@ -22,8 +22,8 @@
|
||||
"fp": "green"
|
||||
},
|
||||
"06 gdd_lint": {
|
||||
"exit": "1",
|
||||
"fp": "454510f66168"
|
||||
"exit": "0",
|
||||
"fp": "green"
|
||||
},
|
||||
"07 doc_link": {
|
||||
"exit": "0",
|
||||
@@ -50,4 +50,4 @@
|
||||
"fp": "skip"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
**Engine**: Godot 4.7.2-stable(GDScript 单语言)
|
||||
**Test Framework**: 双载体——`--headless --script` 单元套件 + headless 场景探针;gdUnit4 **deferred**(D2 OQ1 关闭形态——本仓库不引第三方测试框架,需求见下「门禁」节)
|
||||
**门禁**: 本地闸——`tools/run_all_gates.sh`(全量,收口唯一验收入口)/`--quick`(中段自检)。本仓库**无远端、无 CI**(`.github/workflows/` 已删除,不建不留不参考);闸清单只在本脚本定义,文档不另列
|
||||
**门禁**: 本地闸——`tools/run_all_gates.sh`(收口唯一验收入口,按改动域选档 `--code`/`--docs`,跨两域或改闸件本身不带参数=全量;`--quick` 为中段自检)。本仓库**无远端、无 CI**(`.github/workflows/` 已删除,不建不留不参考);闸清单与域划分只在本脚本定义,文档不另列
|
||||
**Setup date**: 2026-09-12(2026-09-27 `7f24b640` 三层收敛:根 tests/ 并入 `game/tests/`、docs 并入 `design/dev/`、production 解散)
|
||||
**参考实现**: `game/smoke/`(冒烟断言 + 场景探针族——正式工程的活样板;断言计数双口径见下节)
|
||||
|
||||
@@ -114,9 +114,9 @@ timeout 120s godot --headless --script tests/run_all.gd
|
||||
|
||||
## 本地闸(唯一验收门禁)
|
||||
|
||||
本仓库无远端、无 CI——**验收只在本地发生**,跑 `tools/run_all_gates.sh`(全量)是收口唯一入口,
|
||||
跑完写回执 `game/tests/.gates/receipt.json`,合回 master 由 worktree-guard 校验回执。
|
||||
闸清单的唯一定义处就是那个脚本,本文档不另列清单(清单散落多份文档正是历史漏跑闸的成因)。
|
||||
本仓库无远端、无 CI——**验收只在本地发生**,跑 `tools/run_all_gates.sh` 是收口唯一入口,按本批改动域选档(`--code`/`--docs`/跨两域或改闸件本身=不带参数全量),
|
||||
跑完写回执 `game/tests/.gates/receipt.json`(含 `domain` 字段),合回 master 由 worktree-guard 按 diff 推域校验回执。
|
||||
闸清单与域划分的唯一定义处就是那个脚本,本文档不另列清单(清单散落多份文档正是历史漏跑闸的成因)。
|
||||
|
||||
**判定三硬闸**(各闸一律适用,缺一即判定有洞):
|
||||
①**退出码为唯一权威判据**——日志里的 PASS 串只是日志,不作判据;
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
# reports/ — 迭代批的产出物存放处
|
||||
|
||||
迭代(代码实现)批**禁写 `design/` 整个目录**——那里是设计权威文档与规范,对实现批只读。
|
||||
实现批自己要往外交代的东西,一律落在本目录。
|
||||
|
||||
## 放什么
|
||||
|
||||
| 文件 | 内容 | 谁写 |
|
||||
|---|---|---|
|
||||
| `iter<N>-实现报告-<日期>.md` | 各任务卡状态、实机目检总清单、遗留读数义务 | 迭代批 |
|
||||
| `iter<N>-代拍板-<日期>.md` | 批内 agent 替你拍的决定清单,等你追认或推翻 | 迭代批 |
|
||||
| `iter<N>-反哺点-<日期>.md` | 需要写进 `design/` 权威文档的点,逐条列清"哪个文件哪一节、改成什么、依据是什么" | 迭代批 |
|
||||
|
||||
## 不放什么
|
||||
|
||||
- **不放设计结论。** 本目录的文件是"待处理的申请单",不是事实源。反哺点由你在会话中讨论定稿后,
|
||||
另开文档批写进 `design/`,写完即可删除本目录对应条目。
|
||||
- **不放图。** 图像白名单不含本目录,放了会被熵增闸判红。
|
||||
- **不放进度状态。** 会话状态的唯一来源是根目录 `active.md`。
|
||||
|
||||
## 生命周期
|
||||
|
||||
一个迭代号一组文件,落档即死:反哺点被 `design/` 吸收后,对应文件删除,历史进 git commit log
|
||||
(与"历史决策记录只进 git log、不设决策文档"这条红线同口径)。
|
||||
+2
-2
@@ -105,10 +105,10 @@
|
||||
2. **T0 开工**:建 worktree + 基线复读记档(冒烟/探针/三闸/MC 对拍基准)+ `git status` 归属核对(并行遗留只标旗不碰)。
|
||||
3. **循环骨架**:一轮 = 一个任务卡——读卡 → 对事实源 → 实装 → 轻闸全绿 → 提交(代拍板随 commit 记录)→ 状态记录追加(查撞号)→ 下一轮。单 worktree 多点提交一次合并;轮内禁开第二个 worktree;基线每轮开工时点重测;断点续跑 = `git log` 对任务卡定轮次 + 重跑当轮轻闸。
|
||||
4. **轻闸**(每轮必跑,headless):`tools/run_all_gates.sh --quick`(迭代中段自检档)。
|
||||
5. **重闸**(批末 Tn):`tools/run_all_gates.sh` 全量档全绿——闸件清单与顺序只在该脚本里定义,本文件与 CLAUDE.md/规约均不另列;MC 逐位对拍(涉核心)+ 素材批加机检断言另行。
|
||||
5. **重闸**(批末 Tn):`tools/run_all_gates.sh` **按本批改动域跑**(只碰 `game/`、`tools/`、`artwork/` → `--code`;只碰 `design/` 与根规则文件 → `--docs`;跨两域或改闸件本身 → 不带参数全量),所选域全绿——闸件清单、顺序与域划分只在该脚本里定义,本文件与 CLAUDE.md/规约均不另列;MC 逐位对拍(涉核心)+ 素材批加机检断言另行。
|
||||
6. **人闸**(停机交付):真人目检清单 + 代拍板追认清单 + 异步终审触点(样张类)——本循环内唯一必经人审。
|
||||
7. **异常分级**:L1 自救(一次重试)→ L2 记录继续(连续 3 次同红灯升级)→ L3 停机写报告等用户。
|
||||
8. **收口五步**:①active.md 快照 ②状态记录条目 ③里程碑移动更新本文件 ④阶段变更改 stage.txt ⑤批末闸序列全绿留痕(loop_dashboard 已随目录收敛退役,透视职责由 06+状态记录承接)。
|
||||
8. **收口五步**:①active.md 快照 ②状态记录条目 ③里程碑移动更新本文件——**实现批禁写本文件**(2026-10-17 用户裁:代码实现批只准写 `reports/`),该步归文档批做,实现批在 `reports/iter<N>-反哺点-<日期>.md` 列明该改哪一行 ④阶段变更改 stage.txt ⑤批末闸序列**按本批改动域**全绿留痕(loop_dashboard 已随目录收敛退役,透视职责由 06+状态记录承接)。
|
||||
|
||||
## 维护纪律
|
||||
|
||||
|
||||
@@ -14,7 +14,8 @@
|
||||
# ③ 棋盘 golden 逐像素(T2):board 整屏帧按 board_meta.json 取样坐标裁剪棋盘区
|
||||
# (tools/golden_board_crop.py——禁硬编码像素)→ tools/golden_compare.py 对
|
||||
# game/tests/golden/ 三张基线(容差 provisional:差 ≤16/changed ≤2%,S2 停机点口径)。
|
||||
# 素材缺位=SKIP 不判红(Q5 运行时口径;基线生成期素材缺位=L3 停机——基线禁锚回退态)。
|
||||
# 素材缺位=判红(2026-10-17 改;此前是 SKIP 不判红——但缺盘时这一段根本不对比,闸末仍打
|
||||
# 「全绿」exit 0,等于空跑判绿。基线生成期素材缺位=L3 停机,运行期同样无权出验收结论)。
|
||||
# ④ 基准档 1280 单档(Q4)。1920 档注记:16:9 全屏下 stretch=canvas_items/aspect=keep 等比
|
||||
# 1.35×+左右黑边各 96px,构图与基准档一致(迭代 9 分辨率案甲)——分辨率维度不重复建闸。
|
||||
# (原由 ui_layout_probe 两档断言承担,探针已随 2026-10-15 裁「无使用的探针删除」退役,
|
||||
@@ -112,7 +113,9 @@ if [ ! -d "$REPO/game/tests/golden" ]; then
|
||||
elif [ ! -s "$BD/board_meta.json" ]; then
|
||||
echo "[render_gate] SKIP — board_meta.json 缺失(取证段已红),golden 对比不判"
|
||||
else
|
||||
# Q5 运行时素材缺位=SKIP:基线锚在素材齐盘态,缺盘态对比必误红——SKIP 不判红,注记在案
|
||||
# 素材缺位=判红(2026-10-17 由 SKIP 改):缺盘态下这一段一个像素都不比,而闸末照样打印
|
||||
# 「全绿」退出码 0——那是空跑判绿,比误红危险得多(2026-10-16 菜单废基线事故正是靠它蒙过
|
||||
# worktree 自检的)。基线锚在素材齐盘态(Q5),缺盘态本就无权出验收结论。
|
||||
_asset_miss=0
|
||||
for a in assets/fonts/ui_font_fusion_pixel_zh_hans.ttf \
|
||||
assets/art/board/flag_seal.png \
|
||||
@@ -120,7 +123,7 @@ else
|
||||
assets/art/board/terrain_wall_n_dungeon.png assets/art/board/terrain_floor_dungeon_1.png \
|
||||
assets/art/board/terrain_wall_n_underdark.png assets/art/board/terrain_floor_underdark_1.png \
|
||||
assets/art/board/terrain_wall_n_deep_underdark.png assets/art/board/terrain_floor_deep_underdark_1.png; do
|
||||
[ -f "$GAME/$a" ] || { echo "[render_gate] SKIP — 棋盘素材缺位: $a(Q5——基线禁锚回退态,运行时 SKIP)"; _asset_miss=1; break; }
|
||||
[ -f "$GAME/$a" ] || { echo "[render_gate] FAIL — 棋盘素材缺位: $a(Q5:基线锚素材齐盘态,缺盘不得出验收结论)"; _asset_miss=1; }
|
||||
done
|
||||
if [ "$_asset_miss" -eq 0 ]; then
|
||||
CROP="$TMP/crop"
|
||||
@@ -143,7 +146,7 @@ if [ ! -s "$REPO/game/tests/golden/menu_boot.png" ]; then
|
||||
elif [ ! -s "$SHOT/shot_menu.png" ]; then
|
||||
echo "[render_gate] SKIP — shot_menu.png 缺失(shot_probe 段已红),菜单 golden 不判"
|
||||
elif [ ! -f "$GAME/assets/fonts/ui_font_fusion_pixel_zh_hans.ttf" ] || [ ! -f "$GAME/assets/art/ui/button_stone.png" ]; then
|
||||
echo "[render_gate] SKIP — 菜单素材缺位(字体/按钮石纹——Q5 口径:基线禁锚回退态,运行时 SKIP)"
|
||||
echo "[render_gate] FAIL — 菜单素材缺位(字体/按钮石纹):基线锚素材齐盘态,缺盘不得出验收结论(2026-10-17 由 SKIP 改判红)"; _rc=1
|
||||
else
|
||||
MENUG="$TMP/menugolden"
|
||||
mkdir -p "$MENUG"
|
||||
|
||||
@@ -21,7 +21,9 @@ from pathlib import Path
|
||||
ROOT = Path(__file__).resolve().parent.parent
|
||||
|
||||
# 1) 顶层目录白名单(点开头目录一律忽略)
|
||||
TOP_DIRS = {"archive", "artwork", "design", "game", "sim-data", "tools"}
|
||||
# reports/ 收编(2026-10-17 用户裁「迭代批禁写 design/」):迭代实现报告/代拍板追认单/GDD 反哺点
|
||||
# 清单的家——实现批只准往这里写,design/ 全目录对它只读。只放 .md,不放图(图像白名单不含此目录)。
|
||||
TOP_DIRS = {"archive", "artwork", "design", "game", "sim-data", "tools", "reports"}
|
||||
# 2) 根目录文件白名单
|
||||
ROOT_FILES = {"CLAUDE.md", "active.md", "roadmap.md", "stage.txt", "review-mode.txt",
|
||||
".gitignore", ".zcodeignore"}
|
||||
|
||||
+95
-16
@@ -6,16 +6,25 @@
|
||||
# 跑完写回执 game/tests/.gates/receipt.json(commit + 各闸退出码 + 存量基线比对 + 时间戳);
|
||||
# .zcode/hooks/worktree-guard.sh 在主工作区 git merge 时校验本回执,缺或红即拦截。
|
||||
#
|
||||
# 用法:tools/run_all_gates.sh [--quick]
|
||||
# --quick 跳过渲染闸(窗口化,需开显示器)与静态扫描(纯文本扫描),供迭代中段自检;
|
||||
# 收口出库禁用,必须不带本参数跑全量。
|
||||
# 用法:tools/run_all_gates.sh [--code|--docs|--quick]
|
||||
# --code 只跑代码域闸(01 冒烟/02 单元/03 探针 --click/04 探针 --full/05 渲染闸/09 静态扫描
|
||||
# /11 回放确定性对拍+10 熵增闸)——本批改动只碰 game/、tools/、artwork/ 时用本档。
|
||||
# --docs 只跑文档域闸(06 gdd_lint/07 doc_link/08 doc_term/08b active.md 体量+10 熵增闸)
|
||||
# ——本批改动只碰 design/、active.md、roadmap.md、CLAUDE.md 时用本档。
|
||||
# --quick 全量档的中段自检形态:跳过渲染闸(窗口化,需开显示器)与静态扫描;完成出库禁用。
|
||||
# 不带参数 = 全量档(两域全跑)。改闸件本身、或改动同时跨两域时,按 process-conventions §6.2
|
||||
# 共享件例外跑全量。
|
||||
# **域划分只在本脚本里定义一处**——文档只写「按改动域跑本脚本」,禁另列闸清单(清单散落多处
|
||||
# 正是 iter27 漏跑 probe_gate --full 的成因)。
|
||||
#
|
||||
# 判定口径:每道闸与 game/tests/.gates/baseline.json 里的存量基线比对——
|
||||
# 基线绿 + 现绿 → 绿
|
||||
# 基线红 + 现红且清单未变 → 「存量红·对基线零新增」,不判失败(但会在回执里留名)
|
||||
# 基线红 + 现红但清单新增 → 红(新增红项)
|
||||
# 任意闸 exit 与基线不符 → 红
|
||||
# 本次未跑的闸(--quick 跳过/条件闸件未触面)→ 不比闸况,只记进回执 not_run
|
||||
# 基线文件首次自动生成(把当次结果当基线),此后每次跑批与之比。
|
||||
# 分域跑批只比本域跑过的闸——域外闸不入 NAMES,不产生「基线绿 → 现红」的假变化。
|
||||
#
|
||||
# 退出码:0=全绿或仅存量红零新增;1=有新增红/闸况变化;2=用法错
|
||||
set -u
|
||||
@@ -25,8 +34,21 @@ STAMP_DIR="$GAME/tests/.gates"
|
||||
RECEIPT="$STAMP_DIR/receipt.json"
|
||||
BASELINE="$STAMP_DIR/baseline.json"
|
||||
QUICK=0
|
||||
[ "${1:-}" = "--quick" ] && QUICK=1
|
||||
[ $# -gt 1 ] && { echo "用法: tools/run_all_gates.sh [--quick]"; exit 2; }
|
||||
DOMAIN="full"
|
||||
for _a in "$@"; do
|
||||
case "$_a" in
|
||||
--quick) QUICK=1 ;;
|
||||
--code|--docs)
|
||||
[ "$DOMAIN" = "full" ] || { echo "用法: --code 与 --docs 互斥"; exit 2; }
|
||||
DOMAIN="${_a#--}" ;;
|
||||
*) echo "用法: tools/run_all_gates.sh [--code|--docs|--quick]"; exit 2 ;;
|
||||
esac
|
||||
done
|
||||
|
||||
# 控制台编码:本仓库闸件输出中文与 ✅/✗ 符号。Windows GBK 控制台下 python 打印这些字符会抛
|
||||
# UnicodeEncodeError——06/10 两闸直接崩(假红)、09 的 FAIL 行被写成乱码字节(指纹漂移,
|
||||
# 与基线比必判「红项清单变化」假红)、末尾汇总自身崩掉打不出结论。2026-10-16 实踩。
|
||||
export PYTHONIOENCODING=utf-8
|
||||
|
||||
# 回执与日志是中间产物(不入库),但 .gitignore 本身必须入库——漏了就等于把它们纳入版本管理。
|
||||
# 每次跑批前自愈重建,避免「清目录重跑」把 .gitignore 一起带走(2026-10-15 实踩)。
|
||||
@@ -53,10 +75,45 @@ case "$PY" in
|
||||
*/Scripts/python.exe|*/Scripts/python) export PATH="$(dirname "$PY"):$PATH" ;;
|
||||
esac
|
||||
|
||||
# ── 域判定:这道闸属哪个域、本次跑不跑 ──
|
||||
in_domain() { # in_domain <code|docs|both>
|
||||
local dom="$1"
|
||||
[ "$dom" = "both" ] && return 0
|
||||
[ "$DOMAIN" = "full" ] && return 0
|
||||
[ "$DOMAIN" = "$dom" ] && return 0
|
||||
return 1
|
||||
}
|
||||
|
||||
# ── 素材齐盘预检(2026-10-17 加)──
|
||||
# game/assets/art/** 与一切 *.import 边车都不入 git(.gitignore 2026-09-29 裁定「素材不入 git」),
|
||||
# 所以 git worktree 检出的工程天生缺全部美术素材:main_theme.tres 解析失败 → 按钮五个样式盒全空
|
||||
# → 冒烟 V1 成片假红;渲染闸又会因素材缺位把两段 golden 直接跳过(见 render_gate.sh)。
|
||||
# 缺位即拒跑——禁带着空盘跑完,把「什么都没比」的回执当全绿合回。
|
||||
if in_domain code; then
|
||||
_miss=""
|
||||
for a in assets/art/ui/button_stone.png assets/fonts/ui_font_fusion_pixel_zh_hans.ttf \
|
||||
assets/art/board/flag_seal.png assets/art/character/character_hero_idle.png \
|
||||
assets/art/board/terrain_wall_n_dungeon.png assets/art/board/terrain_floor_dungeon_1.png \
|
||||
assets/art/board/terrain_wall_n_underdark.png assets/art/board/terrain_floor_underdark_1.png \
|
||||
assets/art/board/terrain_wall_n_deep_underdark.png assets/art/board/terrain_floor_deep_underdark_1.png; do
|
||||
[ -f "$GAME/$a" ] || _miss="$_miss $a"
|
||||
[ -f "$GAME/$a.import" ] || _miss="$_miss $a.import"
|
||||
done
|
||||
[ -f "$GAME/.godot/uid_cache.bin" ] || _miss="$_miss .godot/uid_cache.bin(导入未刷)"
|
||||
if [ -n "$_miss" ]; then
|
||||
echo "[gates] 素材齐盘预检 FAIL — 本工作区缺:$_miss"
|
||||
echo " 处置:cp -r <主工作区>/game/assets/art/. <本区>/game/assets/art/"
|
||||
echo " 再刷导入:$GODOT --headless --path <本区>/game --editor --quit"
|
||||
echo " (--import 不生成新素材的 .import 边车,必须走 --editor --quit)"
|
||||
exit 1
|
||||
fi
|
||||
echo "[gates] 素材齐盘预检 PASS(10 件素材+边车+导入缓存在位)"
|
||||
fi
|
||||
|
||||
declare -a NAMES=() EXITS=() LOGS=()
|
||||
FAILED=0
|
||||
|
||||
echo "重闸全量跑批 · 仓库 $REPO"
|
||||
echo "重闸跑批 · 域=$DOMAIN · 仓库 $REPO"
|
||||
echo "起跑 commit: $(git -C "$REPO" rev-parse --short HEAD 2>/dev/null || echo '非git目录')"
|
||||
echo "python: $PY"
|
||||
echo "godot: $GODOT"
|
||||
@@ -98,6 +155,8 @@ gate_unit() { # run_all 是 --script 模式,退出码为唯一判据
|
||||
return 0
|
||||
}
|
||||
|
||||
# ══ 代码域(1–5)══
|
||||
if in_domain code; then
|
||||
# ── 1. 系统级断言面(冒烟三闸:退出码+摘要失败数+SCRIPT ERROR 扫描,判定无洞)──
|
||||
gate "01 冒烟全量" "smoke.log" "$PY" "$REPO/tools/run_smoke.py"
|
||||
# ── 2. 单元套件 ──
|
||||
@@ -113,13 +172,18 @@ else
|
||||
NAMES+=("05 渲染闸"); EXITS+=("SKIP"); LOGS+=("")
|
||||
echo ""; echo "[gates] 05 渲染闸 → SKIP(--quick)"
|
||||
fi
|
||||
fi
|
||||
# ══ 文档域(6–8b)══
|
||||
if in_domain docs; then
|
||||
# ── 6. 文档三闸 ──
|
||||
gate "06 gdd_lint" "gdd_lint.log" "$PY" "$REPO/tools/gdd_lint.py"
|
||||
gate "07 doc_link" "doc_link.log" "$PY" "$REPO/tools/doc_link_audit.py" --strict
|
||||
gate "08 doc_term" "doc_term.log" "$PY" "$REPO/tools/doc_term_audit.py" --strict
|
||||
# ── 6b. 状态文件体量闸(active.md 只记现在:条目数/快照行数/编号/死指针四条硬上限)──
|
||||
gate "08b active.md 体量" "active_state.log" "$PY" "$REPO/tools/audit_active_state.py"
|
||||
# ── 6c. 回放确定性对拍(条件闸件——迭代 29 T3 收编)──
|
||||
fi
|
||||
# ── 6c. 回放确定性对拍(条件闸件——迭代 29 T3 收编;属代码域)──
|
||||
if in_domain code; then
|
||||
# 触面判定:本批 diff 相对 master 分叉点,命中下列路径面之一即「触面」。
|
||||
# 触面也**不自动跑**——对拍调的是 MC 脚本,属「MC/统计类跑批发起前须用户批准」红线管辖;
|
||||
# 闸只负责每次跑批都把触面判定做一遍并显式要求批准,杜绝「该跑却没人记得」。
|
||||
@@ -157,23 +221,29 @@ else
|
||||
echo "[gates] $_parity_name → 本批 diff 触及数值/存档/RNG/结算面,对拍需用户批准发起"
|
||||
echo " (批准后设 PARITY_APPROVED=1 重跑本脚本即实跑;不得借机重锚)"
|
||||
fi
|
||||
fi
|
||||
|
||||
# ── 7. 静态扫描域 + 熵增闸 ──
|
||||
# ── 7. 静态扫描(代码域)+ 熵增闸(两域都跑:它查顶层目录/根文件/图像白名单/退役路径,
|
||||
# 文档批新增或挪动 design/ 下的文件同样会绊它;纯文本扫描,秒级)──
|
||||
if [ $QUICK -eq 0 ]; then
|
||||
gate "09 静态扫描" "static_scan.log" bash "$GAME/tests/static_scan.sh"
|
||||
if in_domain code; then
|
||||
gate "09 静态扫描" "static_scan.log" bash "$GAME/tests/static_scan.sh"
|
||||
fi
|
||||
gate "10 熵增闸" "entropy.log" "$PY" "$REPO/tools/repo_structure_audit.py"
|
||||
else
|
||||
NAMES+=("09 静态扫描"); EXITS+=("SKIP"); LOGS+=("")
|
||||
NAMES+=("10 熵增闸"); EXITS+=("SKIP"); LOGS+=("")
|
||||
echo ""; echo "[gates] 09 静态扫描 / 10 熵增闸 → SKIP(--quick)"
|
||||
if in_domain code; then
|
||||
NAMES+=("09 静态扫描"); EXITS+=("SKIP"); LOGS+=("")
|
||||
fi
|
||||
NAMES+=("10 熵增闸"); EXITS+=("SKIP"); LOGS+=("")
|
||||
echo ""; echo "[gates] 09 静态扫描(代码域)/ 10 熵增闸 → SKIP(--quick)"
|
||||
fi
|
||||
|
||||
# ── 与存量基线比对 ──
|
||||
"$PY" - "$BASELINE" "$RECEIPT" "$QUICK" "$REPO" "${NAMES[@]}" -- "${EXITS[@]}" -- "${LOGS[@]}" <<'PYEOF'
|
||||
"$PY" - "$BASELINE" "$RECEIPT" "$QUICK" "$DOMAIN" "$REPO" "${NAMES[@]}" -- "${EXITS[@]}" -- "${LOGS[@]}" <<'PYEOF'
|
||||
import hashlib, json, os, subprocess, sys
|
||||
|
||||
baseline_path, receipt_path, quick, repo = sys.argv[1:5]
|
||||
rest = sys.argv[5:]
|
||||
baseline_path, receipt_path, quick, domain, repo = sys.argv[1:6]
|
||||
rest = sys.argv[6:]
|
||||
sep = rest.index("--")
|
||||
names = rest[:sep]
|
||||
tail = rest[sep + 1:]
|
||||
@@ -227,7 +297,7 @@ else:
|
||||
with open(baseline_path, encoding="utf-8") as f:
|
||||
base = json.load(f).get("gates", {})
|
||||
|
||||
changed, inherited_red = [], []
|
||||
changed, inherited_red, not_run = [], [], []
|
||||
for n in names:
|
||||
b, c = base.get(n), now[n]
|
||||
if b is None:
|
||||
@@ -235,6 +305,11 @@ for n in names:
|
||||
continue
|
||||
if b["exit"] == c["exit"] and b["fp"] == c["fp"]:
|
||||
continue
|
||||
if c["fp"] == "skip":
|
||||
# 本闸这次没跑(--quick 跳过/条件闸件未触面)——没有结果可比,不判闸况变化。
|
||||
# 2026-10-17 修:此前 SKIP 落进「基线绿 → 现红」分支,任何 --quick 回执都自带假红。
|
||||
not_run.append(n)
|
||||
continue
|
||||
if b["exit"] != "0" and c["exit"] != "0" and b["fp"] == c["fp"]:
|
||||
continue # 同款存量红,清单未变
|
||||
if b["exit"] == "0" and c["exit"] != "0":
|
||||
@@ -253,10 +328,12 @@ with open(receipt_path, "w", encoding="utf-8") as f:
|
||||
"commit_short": head()[:8],
|
||||
"timestamp": subprocess.run(["date", "-Iseconds"], capture_output=True, text=True).stdout.strip(),
|
||||
"quick_mode": quick == "1",
|
||||
"domain": domain,
|
||||
"first_baseline_run": first_run,
|
||||
"gates": [{"name": n, "exit": e, "fingerprint": now[n]["fp"]} for n, e in zip(names, exits)],
|
||||
"changed_vs_baseline": changed,
|
||||
"inherited_red": inherited_red,
|
||||
"not_run": not_run,
|
||||
"all_green": all_green,
|
||||
}, f, ensure_ascii=False, indent=1)
|
||||
|
||||
@@ -273,6 +350,8 @@ if inherited_red:
|
||||
print("【改善】")
|
||||
for c in inherited_red:
|
||||
print(" ↓ " + c)
|
||||
if not_run:
|
||||
print("【本次未跑(域外/--quick/未触面)】" + "、".join(not_run))
|
||||
stock = [n for n, e in zip(names, exits) if e not in ("0", "SKIP")]
|
||||
if stock:
|
||||
print("【存量红·对基线零新增】" + "、".join(stock))
|
||||
|
||||
Reference in new issue
Block a user