版本 1.0.383 开发日志

构建 1.0.383(code 383)· 2026-10-02 18:03:54 · sha256 db227e9405379aba…

下载:公网 · Tailscale · 局域网

开发日志(本轮摘要)

# 贵霜帝国 · 内网开发版 · 本轮开发记录

生成时间:2026-10-02 17:58:55 | 本包:kushan-1.0.383.apk(1.0.383 / code 383,sha256 db227e9405379aba…)

## 最近开发任务证据(evidence/*/readme.md,新→旧,取 10 份)

### TASK-PANEL-SWEEP  (2026-10-02 17:42:22)

# TASK-PANEL-SWEEP —— NPC 卡片「分节单拍」批量目视巡检(用户令:评测必走目视)

## 做法
```
KUSHAN_RENDER_SECTION=<节名> xvfb-run -a <godot> --path . \
  --render-panel npccard:<cid> --render-output /绝对/路径.png
```
节名(真名,取自 `scripts/npc_panel.gd` 的 `_find("<名>")`):**Dignity / Dress / Life / Story / Wishes**
(页眉控件 AreaLine / NameLine 不是节、不在可单拍之列)。

## 本轮实拍(shots/,2 人 × 4 节 = 8 张,全部 `hidden=4 err=0`)
`c17_*`=丫鬟阿娜(房 5 内院,官户)|`c21_*`=掌柜(巴扎馔摊,商户)

OCR 回读(`ocr_sweep.txt`,多预处理并集;识别噪声保留原样):
- `c17_Dignity` ⇒ 称呼 / 折算 **×2.00**  `c21_Dignity` ⇒ 称呼「柜上」/ 折算 **×1.00** ⇒ 两行当名分不同
- `c17_Dress` ⇒ 装束:金莲/手链/默认…  `c21_Dress` ⇒ 装束:金莲/手链/贞操/世界/默认… ⇒ **装束节首次有目视证据**(此前只有自报原文)
- `c17_Life` ⇒ 1582 启蒙 / 1576 / 富商 …  `c21_Life` ⇒ 1578 启蒙 / 1572 / 广场 … ⇒ 经历节
- `*_Wishes` ⇒ 心愿(c17 有「西行」一条)

## 顺带修的护栏
`tools/verify_panel_sections.py`(进全量回归,PASS 90→**91**):
P.1 面板有 `isolate_section`;P.2 `main.gd` 在**截图之前**按 `KUSHAN_RENDER_SECTION` 调用;
P.3 每个 `_find("<名>")` 都有对应 `name = "<名>"` 赋值;P.4 挂了 `_section` 且已命名的节都能被 `_find` 取到。
—— 写完当场抓到两件事:① 我把心愿节名写成 `Wish`(真名 `Wishes`)⇒ 那一张图根本没单拍;② P.4 初版判据把页眉控件也算节 ⇒ 假阳性。两条都已改。

### TASK-B15-ESCORT  (2026-10-02 16:23:23)

# TASK-B15-ESCORT —— 68 号 B-15「着装拦截(女性独自离镇被拦下并给提示)」落地 + 真机自检取证

## 判据(68 号 B-15)
> 用户令 2026-09-23(L-01/L-02):女性角色独自离镇被巡查拦下并给提示 | 玩家手点路径:女性角色单独出门 → 截图

## 落地(全参数化、一功能一文件)
- **新模块** `scripts/law_escort_gate.gd`(纯算不碰 UI):出门前判「律令是否拦」——
  · 性别判据走参数 `dress.apply_gender`(与装扮线同源)
  · 场合判据复用 `scripts/dress_tier.gd` 的 `mode_for_room`(内室名单走 `dress.inner_room_types`)
  · 律令 → 效果 走参数 `laws.escort_effects`(形如 `L-02:street.block_unaccompanied=1`),只与 `law.enabled_ids` 相交时生效
  · 同行判据=同房有无男性(`DataStore.npcs_in_room`)
  · 律令名读 `data/laws_xiangfei.json`(真源,不编)
- **接线**:`scripts/room_view.gd` 的 `_move_to`(房间页唯一出门回调)——被拦则**原地不动**+弹提示;
  自报 `KUSHAN_ESCORT blocked=… law=… why=…`(ASCII)
- **文案**:`ui.room.escort_blocked`「巡查拦下:%s」/`ui.room.escort_by`「依 %s %s」(I18n,键已入 `_meta.keymap`)
- **受控自检**(真机可得证):cfg `ESCORT_QA=selftest` + `ESCORT_QA_GENDER=<male|female>` + `ESCORT_QA_ROOM=<房号>`
  ⇒ 进房后逐个出口跑同一个 `check()` 并自报;被拦的那条会真弹提示(同一 UI 路径)。
  为什么要它:**真机登录身份来自端上存档/服务端 wallet→player 映射**,端上建女号之前无法手点验证;
  自检只注入「判据输入」(性别/房间),走的是同一份代码与真数据。

## 真机实证(1.0.380 · 三星 R3CR704Q72V · 原文见 selftest_female.txt / selftest_male.txt)
- **女眷口径**(`ESCORT_QA_ROOM=113` 村口水井,房内无男):
  `to=583 type=street mode=street blocked=true law=L-02` / `to=660 alley blocked=true` / `to=440 road blocked=true`
- **男口径对照**:`to=583/660/440 blocked=false why=gender=male`
⇒ 拦与不拦**只由性别×场合×同行×律令决定**,三条街面/巷/路出口全拦,同房有男性时不拦(参数 `escort.qa_male_present` 可对照)。
- 冒烟:进房 1 条、SCRIPT ERROR=0、缺参=0;出包 1.0.380 三链已同步 nw。

## 边界(不谎报)
1. **「玩家手点路径」这一半未验**:真机登录身份来自端上存档(`GameState.setup_from_world` 取世界 players/本地存档),
   我建的 QA 女号(characters id=347「验身女」+钱包)未被登录采纳 ⇒ 未跑成「女号手点出门」。
   闸已挂在真实出门回调上,**端上建女号(主菜单建号表单支持 female)后即可手点复现**。
2. **提示语的截图未抓到**:`_toast_msg` 是瞬时提示(约 2–3 秒),我按 6 秒/45 秒采样都错过 ⇒
   本项目视只到「门没开出去 + 房间页正常」;提示文案本身由 I18n 键+同一调用路径保证。
   (若要抓到:把提示驻留时间做成参数,或进门后立刻连拍。)
3. **QA 女号未清**:`characters id=347`+其钱包仍在库里(INSERT-only 纪律,未删);若要清待人一句。

### TASK-D14-AUDIT  (2026-10-02 15:12:02)

# TASK-D14-AUDIT —— 68 号 D-14「改动留痕可回滚」落地 + 回滚演练

## 判据(68 号 D-14)
> 每次改动有审计记录(谁/何时/哪场所/改前改后),可回滚 | 审计回读 + 回滚演练

## 落地(写库仍只走 `tools/wdb.sh`,闸门=系列备份)
- **审计表**:`docs/migrations/20261002a_domain_rule_audit.sql` → `domain_rule_audit`
  (只追加:scope_kind / scope_id / actor / action / reason / before_json / after_json / src_audit_id / created_at)
- **唯一写入端**:`tools/domain_rule_write.py`
  - `set`:读改前 → 同事务 upsert `domain_authority` + **追加审计行**(不会出现「改了没留痕」)
  - `rollback --audit-id K`:把 K 的 `before_json` 写回,并**再追加一条** action=rollback 的审计行(回滚本身留痕)
  - `history`:按场所回读全部改动(谁/何时/改前改后)
- **常驻护栏**:`tools/verify_domain_audit.py`(D.1 迁移件在册 · D.2 写 `domain_authority` 只许本写入端 ·
  D.3 规则写与审计 INSERT 同文件 · D.4 审计列面含「谁/哪场所/改前改后」)⇒ 已进 `run_verify_all.sh` 自动集

## 演练实证(原文见 `drill.txt`;真库真跑,地点=room 670 巴扎馕摊,主人=掌柜 21)
1. **改前**:无规则行、审计 0 行
2. **改①**(内室档 3):`[审计] id=1 action=set actor=QA-D14 before=None after={"inner":{"xiangfei_tier":3}}`
3. **改②**(内室档 5 + 禁用乳环):`[审计] id=2 … before={"inner":{"xiangfei_tier":3}} after={…5…}`
   ⇒ **改前改后都留下**,两笔改动可逐笔追溯
4. **回滚①**(回到改②之前):`[回滚后] rules={"inner":{"xiangfei_tier":3}}`=**字节级还原改①的值**;
   `[审计] id=3 action=rollback src_audit_id=2`
5. **回滚②**(回到「本无规则」):`rules=None`(置空、**不做 DELETE**);`[审计] id=4 action=rollback src_audit_id=1`
6. **审计回读**:4 行齐全(谁=actor / 何时=created_at / 哪场所=scope_kind+scope_id / 改前改后=before/after_json)
7. **护栏**:`VERIFY domain_audit: PASS`(D.1/D.2/D.3/D.4 全绿)

## 边界与口径(不谎报)
- **不做 DELETE**:回滚到「本无规则」时把 `rules_json` 置空(语义=无规则),避免删数据;表内留一行空规则。
- 演练留在 `domain_rule_audit` 的 4 行是**真实操作痕迹**(actor=QA-D14,reason=D-14 演练①②/回滚①②),
  按「只追加」纪律**不删**;`domain_authority` 中 room 670 现为 `rules_json=NULL`(=无规则,玩家不可见)。
- **本轮无客户端改动** ⇒ 不重出包(1.0.374 仍为最新)。
- 依赖模型链的「对话改世界」入口(B-19/B-26)仍挂外部额度;一旦通电,写入端就是本工具,**留痕/回滚自动生效**。

### TASK-B18-PRECEDENCE  (2026-10-02 14:56:53)

# TASK-B18-PRECEDENCE —— 68 号 B-18「三级优先级逐层兜底」实现 + 组合实证

## 判据(68 号 B-18)
> 主人规则 > 地方律令 > 世界默认,逐层兜底 | 三种组合各截一张图对照

## 实现(真源=`data/domain_rules.json` 的 `precedence` 原文,一级没规定才落下一级)
| 层 | 口径 | 本轮落地 |
|---|---|---|
| **L1 场所主人自定义规则** | `domain_rules.json`:伯克/店主人/一家之主/团队头目对自己地盘说了算(含女性着装) | 读**房间 `attrs.dress`**(客户端可得的口袋);`xiangfei_tier` 可覆盖档位、`ban_pieces`/`add_pieces` 出附加说明 |
| **L2 地方律令** | `data/laws_xiangfei.json`(L-01 着装令 / L-02 同行令…) | 生效编号 × 参数 `laws.dress_effects`(结构化映射,形如 `L-01:street.enforce_veil=1`)⇒ 只在与 `law.enabled_ids` 相交时生效 |
| **L3 世界默认** | `data/dress_spec.json` 的 modes/tiers | 前两层都没规定时的兜底 |

面板「装束」行末尾会写明**依据哪一层**(例如「(依 L-01 着装令)」「(依 世界默认)」),玩家看得见规矩从哪来。

## 组合实证(`KUSHAN_DRESS` 自报原文,`combos.txt`)
- **① L2 生效**:`cid=120 rtype=street tier=1 source=law by=L-01 着装令` ⇒ 街面罩袍强制靠地方律令
- **② L2 失效落 L3**(`KUSHAN_QA_CHAIN=l2off`):`source=world by=世界默认`
- **③ L1 最高**(QA 注入主人规矩「内室档位 5 + 禁用乳环」):`cid=10 source=owner tier=5`(**覆盖**由地位推得的 4 档)`…|本场所禁用:乳环(依 本场所主人规矩)`
- **④ 内室无律令命中落 L3**:`cid=10 source=world tier=4`
⇒ **逐层兜底成立**:L1 > L2 > L3 各有独立组合为证,且「依据」在文案里明写。

## 边界(未做,不谎报)