Category: 更新日志

版本迭代与开发进度记录

  • 版本 1.0.384 开发日志

    构建 1.0.384(code 384)· 2026-10-02 19:15:28 · sha256 ffefd21b349ec249…

    下载:公网 · Tailscale · 局域网

    开发日志(本轮摘要)

    # 贵霜帝国 · 内网开发版 · 本轮开发记录
    
    生成时间:2026-10-02 19:11:00 | 本包:kushan-1.0.384.apk(1.0.384 / code 384,sha256 ffefd21b349ec249…)
    
    ## 最近开发任务证据(evidence/*/readme.md,新→旧,取 10 份)
    
    ### TASK-TUTORIAL-WALLET-STEP  (2026-10-02 19:05:05)
    
    # TASK-TUTORIAL-WALLET-STEP —— 引导「钱包基础」步两处真问题(2026-10-02,为取 B-041 画面级证据而查出来)
    
    ## 怎么查出来的
    B-041 要画面级证据「动作栏背包照常/无交易面板入口」,但真机两次都停在**新手引导层**(`[2/6]`)。
    按纪律不当通过、继续攻这道墙,于是:
    ① `TUTORIAL_AUTOPLAY=0` 无效(`tutorial_autoplay.gd:_ready()` 里 `cfg_value("TUTORIAL_AUTOPLAY") != "1"` 即 return ⇒ **只关自动播放,不禁引导**)
    ② OCR 行级 bbox 找「一键跳过全部」⇒ 分词把整行并成一句,**无可点词框** ⇒ 点不掉
    ③ 本地缓存 `user://tutorial_<wallet16>.json` —— 设备上**不存在**(状态在服务端)
    ④ 服务端状态表 `player_tutorial_state`:`local_0` step_idx=1 已有 finished_at;**`local_1`/`local_59` = step_idx 1、finished_at NULL** ⇒ 卡住的就是这两个
    ⑤ 开 `TUTORIAL_AUTOPLAY=1` 让它自己走:从 `[2/6]` 推进到 **`[5/6]`「钱包基础」** 后**卡死** ——
       `KUSHAN_TUTORIAL_AUTOPLAY step=wallet(idx=4) act=no_keeper_here idx=4->4 ok=false` **每 6 秒重试一次、连续 5 次不前进**
    
    ## 由此查出的两处真问题(都已立项)
    ### 1. 引导文案还指着**已退场的「商铺」面板**(用户令 2026-10-01 交易一律靠对话)
    `data/i18n.json` `ui.tutorial.t014` 原文:
    > 点击右下角 [b]轨迹球[/b] → 「商铺」\n用 [b]KSC[/b] 货币购买商品,完成交易。\n这是西域最常见…
    
    按 B-041 口径(面板已退场、交易一律靠与掌柜对话),这句在**教玩家点一个已经不存在的入口**。
    ### 2. 引导 autoplay 的 wallet 步**走不过去**(`no_keeper_here` 循环)
    `scripts/tutorial_autoplay.gd` 里 wallet 步的真实流程注释写的是「先移到掌柜房 → 再说话买」,
    实际是卡在找不到掌柜、也不换房(`idx=4->4` 原地重试)⇒ **引导 6/6 走不完** ⇒ 直接影响:
    - 新手第一次进游戏推不动(真机体验)
    - 所有「要画面级证据」的真机取证都被引导层挡在外面(B-041 的缺口就是这么来的)
    
    ## 本轮对 B-041 的处置
    B-041 保持 **⚠️ 半验**(代码级判据全过、画面级缺口未补)——**不因「查到别的问题」就把它记成已验**。
    本文件夹是这两处新问题的**证据与立项依据**,与 B-041 的缺口互为因果、分开登记。
    
    ## 原始读数
    - `logcat_b041e.txt`:`KUSHAN_TUTORIAL_AUTOPLAY step=wallet … no_keeper_here` 连续 5 条
    - `ocr_room.txt`:`[5/6] 钱包基础 点击右下角轨迹球—「商铺」用KSC货币购买商品…`
    
    ## 本轮修法与真机复现口径(2026-10-02)
    - **B-048 已改**:`data/i18n.json` 与 `scripts/tutorial_steps.gd`(同一句双真源、护栏要求逐字一致)⇒「点击右下角轨迹球 → 找 [b]掌柜[/b] 说话 / 用 [b]KSC[/b] 货币买下商品,完成交易。」
      基线键表 `evidence/TASK-E20/keymap.tsv` 的「改前字面量」按 C1.3 口径同步(只动含 `t014` 的行)⇒ `verify_client_i18n` 652 条全一致。
    - **B-049 已改**:`scripts/tutorial_autoplay.gd` wallet 步把 `no_keeper_here` 拆成 **`no_keepers_data(sk=0,items=N)`**(店主/商品数据为空)/**`no_keeper_hop(sk=N,items=N,from=房间)`**(数据在、从本房走不到店主房),真机日志一次读出病因。
    
    ### TASK-B041-RETIRED  (2026-10-02 18:45:39)
    
    # TASK-B041-RETIRED —— 面板路线退场 收口取证(2026-10-02)
    
    ## 对账(先查实况,不认状态列)
    68 号 B-041 状态列原写「⬜ 未做(2026-10-01)」,且带一条 2026-10-01 21:44 的订正(说 `scripts/shop_price.gd` 仍在、19 处站点仍在)。
    **本轮实查实况(2026-10-02)**:
    - `scripts/shop_price.gd` **不在磁盘、不在 git 跟踪**
    - `scenes/generated/shops/*.tscn` **0 个**
    - `project.godot` **无 ShopPrice autoload**
    - `data/i18n.json` **无 `ui.shop_price.*` 键**(原文记 5 键)
    - `data/ui_config.json` **无指向 `shop_price.gd` 的站点**(原文记 19 处)
    ⇒ (a)(b)(c)(d) 四步在码里已成;状态列过时。
    
    ## 判据核对(68 号 B-041 原文)
    | 判据 | 实证 |
    |---|---|
    | 商店/交易面板从代码里退场 | `room_view._show_room_shop()` 函数体=`_keeper_in_room()` → `_on_npc_pressed(k)`(找掌柜说话),无面板调用;`main.gd` 旧路渲染自检自报 `KUSHAN_TRADE RETIRED old panel path gone` 后正常退出 |
    | 背包面板必须保住 | `scripts/room/bag_panel.gd` 在册,`room_view.gd` preload 它、`_show_inventory()` 调 `show_panel`/`show_inventory()` |
    | 动作栏「背包」照常、无交易面板入口 | 见 `menu_items.txt`(真机 `MENU_DUMP=1` 自报)+ `device_room.png` |
    | 全量护栏红项=0 | `bash tools/run_verify_all.sh` ⇒ 见提交信息 |
    
    ## 真机读数(三星 R3CR704Q72V,1.0.383)
    见 `menu_items.txt`/`logcat_full.txt`/`device_room.png`。
    
    ## 新增常驻护栏
    `tools/verify_trade_panel_retired.py`(R.1-R.8):防「面板路线复活」——删件类改动最怕后续悄悄加回入口/autoload,
    而复活不报错,只会让玩家又看到一条已被用户令否定的路。
    
    ## ⚠️ 订正(2026-10-02 二次取证):真机「动作栏自报」**没取到** —— 记为半验,不当通过
    第一次取证(`logcat_full.txt`/`device_room.png`)拍到的其实是**新手引导层**(画面 OCR=「场景移动…[2/6]」+「下一步/跳过此步/一键跳过全部」),
    所以 `KUSHAN_MENUITEM` 自报 **0 条** —— 房间的动作栏那一步根本没走到。这不是「查过了没有商店入口」,而是**没查成**。
    第二次尝试(`logcat_b041b.txt`/`step1_after_launch.png`):
    - `TUTORIAL_AUTOPLAY=0` 只关掉**自动播放**(`tutorial_autoplay.gd:_ready()` 里 `cfg_value("TUTORIAL_AUTOPLAY") != "1"` 就 return),**引导本身还在**;
    - 想用 OCR 定位「一键跳过全部」再点掉 ⇒ `image_to_data` 分词把整行并成 `下一步跳过此步一键跳过全部`,**没有可点的词框命中** ⇒ 没点成。
    ⇒ 结论:**「无交易面板入口」的代码级判据(护栏 R.6/`main.gd` RETIRED)成立;真机画面级自报仍是缺口**,
    需要先让引导走完(或找到引导态的落盘标记清掉)再取一次。**这一条按「未验不许当通过」记 ⚠️ 半验**。
    
    ## 真机读数(第二轮,仍为引导态)
    - `MENUITEM` 条数 0(引导层挡住)/`shop_price` 0/`SCRIPT ERROR` 0/`Parse Error` 0
    - 引导层 OCR 原文:`ocr_room.txt`
    
    ### 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)
    
  • 版本 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 各有独立组合为证,且「依据」在文案里明写。
    
    ## 边界(未做,不谎报)
    
  • 版本 1.0.382 开发日志

    构建 1.0.382(code 382)· 2026-10-02 17:35:01 · sha256 5be2f4a75a21c1dc…

    下载:公网 · Tailscale · 局域网

    开发日志(本轮摘要)

    # 贵霜帝国 · 内网开发版 · 本轮开发记录
    
    生成时间:2026-10-02 17:30:12 | 本包:kushan-1.0.382.apk(1.0.382 / code 382,sha256 5be2f4a75a21c1dc…)
    
    ## 最近开发任务证据(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`
    
    
  • 版本 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`
    
    
  • 版本 1.0.380 开发日志

    构建 1.0.380(code 380)· 2026-10-02 17:12:34 · sha256 fae08a06830d165e…

    下载:公网 · Tailscale · 局域网

    开发日志(本轮摘要)

    # 贵霜帝国 · 内网开发版 · 本轮开发记录
    
    生成时间:2026-10-02 17:07:42 | 本包:kushan-1.0.380.apk(1.0.380 / code 380,sha256 fae08a06830d165e…)
    
    ## 最近开发任务证据(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`
    
    
  • 版本 1.0.379 开发日志

    构建 1.0.379(code 379)· 2026-10-02 16:04:10 · sha256 9d3807f683e3c625…

    下载:公网 · Tailscale · 局域网

    开发日志(本轮摘要)

    # 贵霜帝国 · 内网开发版 · 本轮开发记录
    
    生成时间:2026-10-02 15:59:27 | 本包:kushan-1.0.379.apk(1.0.379 / code 379,sha256 9d3807f683e3c625…)
    
    ## 最近开发任务证据(evidence/*/readme.md,新→旧,取 10 份)
    
    ### 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`
    
    ## 真机/渲染原文(`KUSHAN_DRESS` 自报,`KUSHAN_DRESS_DUMP=1` 门控;命令:headless 跑 `--render-panel npccard:<cid>`)
    ```
    cid=10   gender=female status=4 room=6   rtype=interior tier=4
            text=内室(家内/内院)(隐秘(深宫禁锢):蒙眼睛·勒嘴(封口)·双手在后(背缚 + 腕链)·双手合十被反绑)
    cid=6    gender=female status=3 room=587 rtype=interior tier=3
            text=内室(家内/内院)(高阶(勋贵/宗教世家):束腰·脚链 + 三寸金莲·臂环·长手链(链束双腕)·大腿环 + 贞操带·乳环 + 金链·肚脐红宝石)
    cid=17   gender=female status=1 room=5   rtype=court    tier=1
            text=内室(家内/内院)(常备(平民内室):束腰·脚链 + 三寸金莲·臂环·长手链(链束双腕))
    cid=120  gender=female status=1 room=19  rtype=street   tier=1
            text=外出(街面)·罩袍覆身、面纱只露双目、手不外露;男子须有男性亲属陪同(L-02)
    cid=77   gender=female status=1 room=522 rtype=street   tier=1   text=(同外出口径)
    ```
    ⇒ **B-16 场合互斥在数据/规则层成立**:街面**只**给罩袍、内室**只**给饰具,两套互斥;档位按地位分(1/3/4 档各不同)。
    
    ## 本轮抓到的两个真问题(都已修)
    1. **`room_id` 是 float**:JSON 数字在 GDScript 里是 float ⇒ `str(6.0)` = `"6.0"` 与房键 `"6"` 不匹配 ⇒ 判定**一律落「外出」**(我第一版就是这结果,被自报逮到)。
       修法=`str(int(room_id))` 归一(`dress_tier.gd` + 自报同源)。**教训**:客户端所有「拿 id 当字典键」的地方都要按 `str(int(...))` 归一。
    2. **i18n 护栏抓真问题**:`dress_tier.gd` 里 `"%d 档"` 是玩家可见字面量 ⇒ 外置成 `npc.panel.dress_tier_n`(这正是 C1.1 存在的意义)。
    
    ## 边界(未做,不谎报)
    - **D-10「外出罩袍外形」/D-11「内外两形立绘」仍是美术缺口**:`assets/portraits/` 里**没有**罩袍/饰具图层(`find` 无 `veil/chador/cloak/overlay` 命中),
      `dress_spec.modes.*.art` 也写着「新增外出外形一层;既有 111 张立绘不重画」 ⇒ 本轮只把**档位与构件名算对并呈现**,
    
    ### TASK-D0078-UI-THEME  (2026-10-02 14:05:45)
    
    # TASK-D0078-UI-THEME —— 四界面同主题目视核对(68 号 D-007 / D-008)
    
    真机:三星 R3CR704Q72V · **1.0.372** · 960×2616(真屏 id `4630947232161729155`)· 房 19「富贵街北」(香妃镇)
    取图方式:**真点**(轨迹球物理 `(880,2525)` → 菜单自报各项中心 → 点其物理中心),非 QA 脚本跑
    
    ## 目视四图(`md5` 各不相同 ⇒ 确实换了界面)
    | 图 | 界面 | md5 前 8 | 在哪取的 |
    |---|---|---|---|
    | `d007_room.png` | 房间页 | `a783fe4a` | 进房后直接截 |
    | `d008_shop.png` | 商店 | `9df36a75` | 菜单「🛒 商店」中心逻辑 `(519.5,883)`→物理 `(692,1177)` |
    | `d008_dialogue_say.png` | 对话(旅伴聊天面板) | `29485006` | 菜单「🧭 旅伴」中心逻辑 `(519.5,547)`→物理 `(692,729)` |
    | `d008_battle2.png` | 战斗(遭遇战·选敌) | `2d359718` | 菜单「⚔ 遭遇战」中心逻辑 `(519.5,967)`→物理 `(692,1289)` |
    
    
  • 版本 1.0.378 开发日志

    构建 1.0.378(code 378)· 2026-10-02 15:56:05 · sha256 809824f6ea528b1d…

    下载:公网 · Tailscale · 局域网

    开发日志(本轮摘要)

    # 贵霜帝国 · 内网开发版 · 本轮开发记录
    
    生成时间:2026-10-02 15:51:04 | 本包:kushan-1.0.378.apk(1.0.378 / code 378,sha256 809824f6ea528b1d…)
    
    ## 最近开发任务证据(evidence/*/readme.md,新→旧,取 10 份)
    
    ### 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`
    
    ## 真机/渲染原文(`KUSHAN_DRESS` 自报,`KUSHAN_DRESS_DUMP=1` 门控;命令:headless 跑 `--render-panel npccard:<cid>`)
    ```
    cid=10   gender=female status=4 room=6   rtype=interior tier=4
            text=内室(家内/内院)(隐秘(深宫禁锢):蒙眼睛·勒嘴(封口)·双手在后(背缚 + 腕链)·双手合十被反绑)
    cid=6    gender=female status=3 room=587 rtype=interior tier=3
            text=内室(家内/内院)(高阶(勋贵/宗教世家):束腰·脚链 + 三寸金莲·臂环·长手链(链束双腕)·大腿环 + 贞操带·乳环 + 金链·肚脐红宝石)
    cid=17   gender=female status=1 room=5   rtype=court    tier=1
            text=内室(家内/内院)(常备(平民内室):束腰·脚链 + 三寸金莲·臂环·长手链(链束双腕))
    cid=120  gender=female status=1 room=19  rtype=street   tier=1
            text=外出(街面)·罩袍覆身、面纱只露双目、手不外露;男子须有男性亲属陪同(L-02)
    cid=77   gender=female status=1 room=522 rtype=street   tier=1   text=(同外出口径)
    ```
    ⇒ **B-16 场合互斥在数据/规则层成立**:街面**只**给罩袍、内室**只**给饰具,两套互斥;档位按地位分(1/3/4 档各不同)。
    
    ## 本轮抓到的两个真问题(都已修)
    1. **`room_id` 是 float**:JSON 数字在 GDScript 里是 float ⇒ `str(6.0)` = `"6.0"` 与房键 `"6"` 不匹配 ⇒ 判定**一律落「外出」**(我第一版就是这结果,被自报逮到)。
       修法=`str(int(room_id))` 归一(`dress_tier.gd` + 自报同源)。**教训**:客户端所有「拿 id 当字典键」的地方都要按 `str(int(...))` 归一。
    2. **i18n 护栏抓真问题**:`dress_tier.gd` 里 `"%d 档"` 是玩家可见字面量 ⇒ 外置成 `npc.panel.dress_tier_n`(这正是 C1.1 存在的意义)。
    
    ## 边界(未做,不谎报)
    - **D-10「外出罩袍外形」/D-11「内外两形立绘」仍是美术缺口**:`assets/portraits/` 里**没有**罩袍/饰具图层(`find` 无 `veil/chador/cloak/overlay` 命中),
      `dress_spec.modes.*.art` 也写着「新增外出外形一层;既有 111 张立绘不重画」 ⇒ 本轮只把**档位与构件名算对并呈现**,
    
    ### TASK-D0078-UI-THEME  (2026-10-02 14:05:45)
    
    # TASK-D0078-UI-THEME —— 四界面同主题目视核对(68 号 D-007 / D-008)
    
    真机:三星 R3CR704Q72V · **1.0.372** · 960×2616(真屏 id `4630947232161729155`)· 房 19「富贵街北」(香妃镇)
    取图方式:**真点**(轨迹球物理 `(880,2525)` → 菜单自报各项中心 → 点其物理中心),非 QA 脚本跑
    
    ## 目视四图(`md5` 各不相同 ⇒ 确实换了界面)
    | 图 | 界面 | md5 前 8 | 在哪取的 |
    |---|---|---|---|
    | `d007_room.png` | 房间页 | `a783fe4a` | 进房后直接截 |
    | `d008_shop.png` | 商店 | `9df36a75` | 菜单「🛒 商店」中心逻辑 `(519.5,883)`→物理 `(692,1177)` |
    | `d008_dialogue_say.png` | 对话(旅伴聊天面板) | `29485006` | 菜单「🧭 旅伴」中心逻辑 `(519.5,547)`→物理 `(692,729)` |
    | `d008_battle2.png` | 战斗(遭遇战·选敌) | `2d359718` | 菜单「⚔ 遭遇战」中心逻辑 `(519.5,967)`→物理 `(692,1289)` |
    
    
  • 版本 1.0.377 开发日志

    构建 1.0.377(code 377)· 2026-10-02 15:47:30 · sha256 95e546c06f930088…

    下载:公网 · Tailscale · 局域网

    开发日志(本轮摘要)

    # 贵霜帝国 · 内网开发版 · 本轮开发记录
    
    生成时间:2026-10-02 15:42:45 | 本包:kushan-1.0.377.apk(1.0.377 / code 377,sha256 95e546c06f930088…)
    
    ## 最近开发任务证据(evidence/*/readme.md,新→旧,取 10 份)
    
    ### 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`
    
    ## 真机/渲染原文(`KUSHAN_DRESS` 自报,`KUSHAN_DRESS_DUMP=1` 门控;命令:headless 跑 `--render-panel npccard:<cid>`)
    ```
    cid=10   gender=female status=4 room=6   rtype=interior tier=4
            text=内室(家内/内院)(隐秘(深宫禁锢):蒙眼睛·勒嘴(封口)·双手在后(背缚 + 腕链)·双手合十被反绑)
    cid=6    gender=female status=3 room=587 rtype=interior tier=3
            text=内室(家内/内院)(高阶(勋贵/宗教世家):束腰·脚链 + 三寸金莲·臂环·长手链(链束双腕)·大腿环 + 贞操带·乳环 + 金链·肚脐红宝石)
    cid=17   gender=female status=1 room=5   rtype=court    tier=1
            text=内室(家内/内院)(常备(平民内室):束腰·脚链 + 三寸金莲·臂环·长手链(链束双腕))
    cid=120  gender=female status=1 room=19  rtype=street   tier=1
            text=外出(街面)·罩袍覆身、面纱只露双目、手不外露;男子须有男性亲属陪同(L-02)
    cid=77   gender=female status=1 room=522 rtype=street   tier=1   text=(同外出口径)
    ```
    ⇒ **B-16 场合互斥在数据/规则层成立**:街面**只**给罩袍、内室**只**给饰具,两套互斥;档位按地位分(1/3/4 档各不同)。
    
    ## 本轮抓到的两个真问题(都已修)
    1. **`room_id` 是 float**:JSON 数字在 GDScript 里是 float ⇒ `str(6.0)` = `"6.0"` 与房键 `"6"` 不匹配 ⇒ 判定**一律落「外出」**(我第一版就是这结果,被自报逮到)。
       修法=`str(int(room_id))` 归一(`dress_tier.gd` + 自报同源)。**教训**:客户端所有「拿 id 当字典键」的地方都要按 `str(int(...))` 归一。
    2. **i18n 护栏抓真问题**:`dress_tier.gd` 里 `"%d 档"` 是玩家可见字面量 ⇒ 外置成 `npc.panel.dress_tier_n`(这正是 C1.1 存在的意义)。
    
    ## 边界(未做,不谎报)
    - **D-10「外出罩袍外形」/D-11「内外两形立绘」仍是美术缺口**:`assets/portraits/` 里**没有**罩袍/饰具图层(`find` 无 `veil/chador/cloak/overlay` 命中),
      `dress_spec.modes.*.art` 也写着「新增外出外形一层;既有 111 张立绘不重画」 ⇒ 本轮只把**档位与构件名算对并呈现**,
    
    ### TASK-D0078-UI-THEME  (2026-10-02 14:05:45)
    
    # TASK-D0078-UI-THEME —— 四界面同主题目视核对(68 号 D-007 / D-008)
    
    真机:三星 R3CR704Q72V · **1.0.372** · 960×2616(真屏 id `4630947232161729155`)· 房 19「富贵街北」(香妃镇)
    取图方式:**真点**(轨迹球物理 `(880,2525)` → 菜单自报各项中心 → 点其物理中心),非 QA 脚本跑
    
    ## 目视四图(`md5` 各不相同 ⇒ 确实换了界面)
    | 图 | 界面 | md5 前 8 | 在哪取的 |
    |---|---|---|---|
    | `d007_room.png` | 房间页 | `a783fe4a` | 进房后直接截 |
    | `d008_shop.png` | 商店 | `9df36a75` | 菜单「🛒 商店」中心逻辑 `(519.5,883)`→物理 `(692,1177)` |
    | `d008_dialogue_say.png` | 对话(旅伴聊天面板) | `29485006` | 菜单「🧭 旅伴」中心逻辑 `(519.5,547)`→物理 `(692,729)` |
    | `d008_battle2.png` | 战斗(遭遇战·选敌) | `2d359718` | 菜单「⚔ 遭遇战」中心逻辑 `(519.5,967)`→物理 `(692,1289)` |
    
    
  • 版本 1.0.376 开发日志

    构建 1.0.376(code 376)· 2026-10-02 15:39:15 · sha256 4cb1b829ed3f137c…

    下载:公网 · Tailscale · 局域网

    开发日志(本轮摘要)

    # 贵霜帝国 · 内网开发版 · 本轮开发记录
    
    生成时间:2026-10-02 15:34:26 | 本包:kushan-1.0.376.apk(1.0.376 / code 376,sha256 4cb1b829ed3f137c…)
    
    ## 最近开发任务证据(evidence/*/readme.md,新→旧,取 10 份)
    
    ### 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`
    
    ## 真机/渲染原文(`KUSHAN_DRESS` 自报,`KUSHAN_DRESS_DUMP=1` 门控;命令:headless 跑 `--render-panel npccard:<cid>`)
    ```
    cid=10   gender=female status=4 room=6   rtype=interior tier=4
            text=内室(家内/内院)(隐秘(深宫禁锢):蒙眼睛·勒嘴(封口)·双手在后(背缚 + 腕链)·双手合十被反绑)
    cid=6    gender=female status=3 room=587 rtype=interior tier=3
            text=内室(家内/内院)(高阶(勋贵/宗教世家):束腰·脚链 + 三寸金莲·臂环·长手链(链束双腕)·大腿环 + 贞操带·乳环 + 金链·肚脐红宝石)
    cid=17   gender=female status=1 room=5   rtype=court    tier=1
            text=内室(家内/内院)(常备(平民内室):束腰·脚链 + 三寸金莲·臂环·长手链(链束双腕))
    cid=120  gender=female status=1 room=19  rtype=street   tier=1
            text=外出(街面)·罩袍覆身、面纱只露双目、手不外露;男子须有男性亲属陪同(L-02)
    cid=77   gender=female status=1 room=522 rtype=street   tier=1   text=(同外出口径)
    ```
    ⇒ **B-16 场合互斥在数据/规则层成立**:街面**只**给罩袍、内室**只**给饰具,两套互斥;档位按地位分(1/3/4 档各不同)。
    
    ## 本轮抓到的两个真问题(都已修)
    1. **`room_id` 是 float**:JSON 数字在 GDScript 里是 float ⇒ `str(6.0)` = `"6.0"` 与房键 `"6"` 不匹配 ⇒ 判定**一律落「外出」**(我第一版就是这结果,被自报逮到)。
       修法=`str(int(room_id))` 归一(`dress_tier.gd` + 自报同源)。**教训**:客户端所有「拿 id 当字典键」的地方都要按 `str(int(...))` 归一。
    2. **i18n 护栏抓真问题**:`dress_tier.gd` 里 `"%d 档"` 是玩家可见字面量 ⇒ 外置成 `npc.panel.dress_tier_n`(这正是 C1.1 存在的意义)。
    
    ## 边界(未做,不谎报)
    - **D-10「外出罩袍外形」/D-11「内外两形立绘」仍是美术缺口**:`assets/portraits/` 里**没有**罩袍/饰具图层(`find` 无 `veil/chador/cloak/overlay` 命中),
      `dress_spec.modes.*.art` 也写着「新增外出外形一层;既有 111 张立绘不重画」 ⇒ 本轮只把**档位与构件名算对并呈现**,
    
    ### TASK-D0078-UI-THEME  (2026-10-02 14:05:45)
    
    # TASK-D0078-UI-THEME —— 四界面同主题目视核对(68 号 D-007 / D-008)
    
    真机:三星 R3CR704Q72V · **1.0.372** · 960×2616(真屏 id `4630947232161729155`)· 房 19「富贵街北」(香妃镇)
    取图方式:**真点**(轨迹球物理 `(880,2525)` → 菜单自报各项中心 → 点其物理中心),非 QA 脚本跑
    
    ## 目视四图(`md5` 各不相同 ⇒ 确实换了界面)
    | 图 | 界面 | md5 前 8 | 在哪取的 |
    |---|---|---|---|
    | `d007_room.png` | 房间页 | `a783fe4a` | 进房后直接截 |
    | `d008_shop.png` | 商店 | `9df36a75` | 菜单「🛒 商店」中心逻辑 `(519.5,883)`→物理 `(692,1177)` |
    | `d008_dialogue_say.png` | 对话(旅伴聊天面板) | `29485006` | 菜单「🧭 旅伴」中心逻辑 `(519.5,547)`→物理 `(692,729)` |
    | `d008_battle2.png` | 战斗(遭遇战·选敌) | `2d359718` | 菜单「⚔ 遭遇战」中心逻辑 `(519.5,967)`→物理 `(692,1289)` |
    
    
  • 版本 1.0.375 开发日志

    构建 1.0.375(code 375)· 2026-10-02 15:27:35 · sha256 ac6225f6d66d9a8f…

    下载:公网 · Tailscale · 局域网

    开发日志(本轮摘要)

    # 贵霜帝国 · 内网开发版 · 本轮开发记录
    
    生成时间:2026-10-02 15:22:40 | 本包:kushan-1.0.375.apk(1.0.375 / code 375,sha256 ac6225f6d66d9a8f…)
    
    ## 最近开发任务证据(evidence/*/readme.md,新→旧,取 10 份)
    
    ### 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`
    
    ## 真机/渲染原文(`KUSHAN_DRESS` 自报,`KUSHAN_DRESS_DUMP=1` 门控;命令:headless 跑 `--render-panel npccard:<cid>`)
    ```
    cid=10   gender=female status=4 room=6   rtype=interior tier=4
            text=内室(家内/内院)(隐秘(深宫禁锢):蒙眼睛·勒嘴(封口)·双手在后(背缚 + 腕链)·双手合十被反绑)
    cid=6    gender=female status=3 room=587 rtype=interior tier=3
            text=内室(家内/内院)(高阶(勋贵/宗教世家):束腰·脚链 + 三寸金莲·臂环·长手链(链束双腕)·大腿环 + 贞操带·乳环 + 金链·肚脐红宝石)
    cid=17   gender=female status=1 room=5   rtype=court    tier=1
            text=内室(家内/内院)(常备(平民内室):束腰·脚链 + 三寸金莲·臂环·长手链(链束双腕))
    cid=120  gender=female status=1 room=19  rtype=street   tier=1
            text=外出(街面)·罩袍覆身、面纱只露双目、手不外露;男子须有男性亲属陪同(L-02)
    cid=77   gender=female status=1 room=522 rtype=street   tier=1   text=(同外出口径)
    ```
    ⇒ **B-16 场合互斥在数据/规则层成立**:街面**只**给罩袍、内室**只**给饰具,两套互斥;档位按地位分(1/3/4 档各不同)。
    
    ## 本轮抓到的两个真问题(都已修)
    1. **`room_id` 是 float**:JSON 数字在 GDScript 里是 float ⇒ `str(6.0)` = `"6.0"` 与房键 `"6"` 不匹配 ⇒ 判定**一律落「外出」**(我第一版就是这结果,被自报逮到)。
       修法=`str(int(room_id))` 归一(`dress_tier.gd` + 自报同源)。**教训**:客户端所有「拿 id 当字典键」的地方都要按 `str(int(...))` 归一。
    2. **i18n 护栏抓真问题**:`dress_tier.gd` 里 `"%d 档"` 是玩家可见字面量 ⇒ 外置成 `npc.panel.dress_tier_n`(这正是 C1.1 存在的意义)。
    
    ## 边界(未做,不谎报)
    - **D-10「外出罩袍外形」/D-11「内外两形立绘」仍是美术缺口**:`assets/portraits/` 里**没有**罩袍/饰具图层(`find` 无 `veil/chador/cloak/overlay` 命中),
      `dress_spec.modes.*.art` 也写着「新增外出外形一层;既有 111 张立绘不重画」 ⇒ 本轮只把**档位与构件名算对并呈现**,
    
    ### TASK-D0078-UI-THEME  (2026-10-02 14:05:45)
    
    # TASK-D0078-UI-THEME —— 四界面同主题目视核对(68 号 D-007 / D-008)
    
    真机:三星 R3CR704Q72V · **1.0.372** · 960×2616(真屏 id `4630947232161729155`)· 房 19「富贵街北」(香妃镇)
    取图方式:**真点**(轨迹球物理 `(880,2525)` → 菜单自报各项中心 → 点其物理中心),非 QA 脚本跑
    
    ## 目视四图(`md5` 各不相同 ⇒ 确实换了界面)
    | 图 | 界面 | md5 前 8 | 在哪取的 |
    |---|---|---|---|
    | `d007_room.png` | 房间页 | `a783fe4a` | 进房后直接截 |
    | `d008_shop.png` | 商店 | `9df36a75` | 菜单「🛒 商店」中心逻辑 `(519.5,883)`→物理 `(692,1177)` |
    | `d008_dialogue_say.png` | 对话(旅伴聊天面板) | `29485006` | 菜单「🧭 旅伴」中心逻辑 `(519.5,547)`→物理 `(692,729)` |
    | `d008_battle2.png` | 战斗(遭遇战·选敌) | `2d359718` | 菜单「⚔ 遭遇战」中心逻辑 `(519.5,967)`→物理 `(692,1289)` |