版本 1.0.381 开发日志

构建 1.0.381(code 381)· 2026-10-02 17:21:11 · sha256 fe17b7a98fa707e7…

下载:公网 · Tailscale · 局域网

开发日志(本轮摘要)

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

生成时间:2026-10-02 17:16:06 | 本包:kushan-1.0.381.apk(1.0.381 / code 381,sha256 fe17b7a98fa707e7…)

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

### 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 各有独立组合为证,且「依据」在文案里明写。

## 边界(未做,不谎报)
1. **L1 的真实记录世界数据里还没有**:`/api/world` 里只有 4 间房带 `owner_character_id`/`privacy`,**没有一条 `attrs.dress`**。
   组合③ 用的是 **QA 注入**(环境变量 `KUSHAN_QA_OWNER_JSON`,不动产品参数值/不改世界数据),验证的是**解析链与优先级**。
   真实记录要等「场所主权面板」(B-17:主人改规矩走界面)写入 `attrs.dress` 后**自动生效,无需改本模块**。
2. **出图取证的限制(如实记)**:NPC 卡片正文在 `ScrollContainer` 里,`--render-panel` 静态出图只拿到首屏 ⇒
   「装束」一节在折叠线以下**图上看不到**(我加了 `KUSHAN_RENDER_SCROLL=bottom` 门控想滚到底,实测**出图 md5 不变**=没生效)。
   ⇒ 本轮文字证据以 `KUSHAN_DRESS` **自报原文**为准(真实代码路径)+ 卡片整图作旁证;**取证工具这条局限另记待改**。

### TASK-DRESS-TIER  (2026-10-02 14:34:50)

# TASK-DRESS-TIER —— 装扮档位(场合 × 社会地位 → 档位与构件)落地 + 取证

## 判据(68 号)
- **D-12 装扮档位参数化**:立绘读 `dress_spec.json` tiers,界面代码零硬编码 | `grep` 界面脚本无饰具名硬编码
- **B-16 场合互斥生效**:街面绝不出现内室构件;内室绝不穿罩袍 | 街面截图 + 内室截图对照
- 关联:**D-10 外出罩袍外形** / **D-11 内外两形立绘**(这两条的「形」要美术层,见下「边界」)

## 本轮做了什么(此前客户端**零消费** `dress_spec.json`:只有 tools 读它)
1. **新模块** `scripts/dress_tier.gd`(123 行,纯算不画,单一职责)
   - 读真源 `data/dress_spec.json`(70 号 §四 外出罩袍+内室香妃式饰具;用户令 2026-09-23)
   - 场合:房间 `type` ∈ 参数 `dress.inner_room_types` ⇒ 内室,否则外出
   - 档位:参数 `dress.tier_from_status`(`地位:档位` 映射)+ `dress.max_tier` 封顶
   - 适用性别:参数 `dress.apply_gender`(默认 female ⇒ 只有女眷出装束行)
   - **档名/构件名/规则文字全部读数据文件**,代码里没有一件饰具名(满足 D-12 后半句)
2. **呈现**:NPC 卡片(`scripts/npc_panel.gd`)新增「装束」一节(`_dress_text()`)
3. **参数**(`data/params.json`,组 `装扮`):`dress.inner_room_types` / `dress.tier_from_status` / `dress.max_tier` / `dress.apply_gender`
4. **文案**(`data/i18n.json`):`npc.panel.dress_title` / `dress_na` / `dress_tier_n`