Category: Uncategorized

  • 版本 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)` |
    
    
  • 版本 1.0.374 开发日志

    构建 1.0.374(code 374)· 2026-10-02 14:55:54 · sha256 c845e48e24079e88…

    下载:公网 · Tailscale · 局域网

    开发日志(本轮摘要)

    # 贵霜帝国 · 内网开发版 · 本轮开发记录
    
    生成时间:2026-10-02 14:51:07 | 本包:kushan-1.0.374.apk(1.0.374 / code 374,sha256 c845e48e24079e88…)
    
    ## 最近开发任务证据(evidence/*/readme.md,新→旧,取 10 份)
    
    ### 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)` |
    
    菜单全 12 项自报(`MENU_DUMP=1` 原文,含新上的 B-14「📜 告示」):
    ```
    生命 51/50 | 克什 300.00 · 第纳尔 0.00 | 声望 +0 | 1620 冬   (519.5,354.5)
    五维 外交 2 | 军事 3 | 管理 6 | 谋略 2 | 学识 3            (519.5,451.5)
    🧭 旅伴  (519.5,547)   🎒 背包 (519.5,631)   📜 任务 (519.5,715)   🗺 世界地图 (519.5,799)
    🛒 商店  (519.5,883)   ⚔ 遭遇战 (519.5,967)  📜 告示 (519.5,1051)  💾 保存进度 (519.5,1135)
    设置 (519.5,1219)      🚪 退出游戏 (519.5,1303)
    ```
    
    ## D-008 判定:**同主题(已验·目视)**
    四张图都是「同一套纸面板 + 金/木色键面 + 同一字体」的外观;代码侧同源:
    - 四个界面全部经**同一套 UI 助手**与**同一个主题色表**出画:`scripts/room/room_layout.gd`(房间页,`theme.*` 10 处、UI 助手 16 处)、
      `scripts/battle_view.gd`(战斗,UI 助手 17 处)、`scripts/room/room_chat.gd`(对话/聊天)、商店面板同走 `UIConf`/`UI`。
    - 字体单一真源=参数 `ui.font.path`(`ui.font.enabled` 总开关)⇒ 四界面不可能各自换字体。
    - 因此判据「对话/商店/战斗套用同一主题」**成立**。
    
    老实说的两点:
    1. 战斗那张取的是**遭遇战的选敌面板**(在打斗界面的入口);真正回合内画面见前几轮 `evidence/TASK-B014-*`、`TASK-ROOM-POPUP-SHELL` 的真机图。
    2. 本机 `tools/qa_shot_ocr.py` 这次没吐出文字(OCR 链不可用)⇒ 文字证据改由 **`MENU_DUMP` 自报 + 截图** 双轨给出。
    
    ## D-007 判定:**未达标(半验)**——实测数字如下
    `KUSHAN_ROOM_LINES` 自报(逻辑坐标,窗口逻辑高 ≈ 2616/1.3333 ≈ 1962):
    - 主卡片 `框y=17 框高=1079` ⇒ 卡片自屏顶起、占到屏高 **55%**(17→1096)。
    - 卡内:房间名 `y=50 h=53`、描述 `y=129 h=18`、状态条 `y≈354`、五维 `y≈451`,随后是人物/出口区。
    - 69 号 §六 要求:**顶部状态条** + **上部 38–42% 场景图区** + **中部 33–36% 文本区** + **下部 22–26% 选项两列**。
    - **差异**:现布局**没有独立的「上部场景图区」**(场景图=全屏背景),文字卡片自顶部起贯通到 55%,
      状态条/五维/人物/出口**全在卡内**,没有「中部文本 / 下部选项两列」的竖向分区。
    
    ### TASK-B014-NOTICE  (2026-10-02 13:56:01)
    
    # TASK-B014-NOTICE —— 68 号 B-14「告示牌(律令入口)」落地 + 真机验证(2026-10-02 · 1.0.371)
    
    ## 判据(68 号 B-14 原文)
    > 镇上立告示木牌,点开列出当前生效律令 | 验法:真机点告示 → 截图
    
    ## 本轮做了什么(此前**客户端零实现**:`scripts/` 里 grep 不到「告示/律令」)
    1. **新面板** `scripts/room/notice_panel.gd`(一功能一文件):把「当前生效律令」画进房间交互区。
       - 真源:`data/laws_xiangfei.json`(70 号 §二《律令政策真源》的 `laws` 数组)+ 参数 `law.enabled_ids`(生效编号)。
       - **只读不裁**:名单读参数、正文读数据文件,面板零硬编码(文案走 I18n、条数上限走参数、样式走 UI/UIConf)。
    2. **入口(镇上才出)**:`scripts/room/room_layout.gd` 新增 `notice_here()` —— 按**所在地类型**判(`locations.type`),
       类型名单走参数 `law.notice_loc_types`(默认 `town,city,village,castle,district`);主菜单里多一项「📜 告示」。
    3. **参数**(`data/params.json`,与 `law.*` 同组):`law.notice_loc_types`(str)、`law.notice_max`(int,默认 12,min1/max40)。
    4. **文案**(`data/i18n.json`):`label.tag.notice` =「📜 告示」;`ui.room.t181~t184` = 面板标题/空表/页脚/日志。
       踩坑记:起初插到 `t017~t020`,**那几个号早被占用**(`t017` 已是「房间不存在」)⇒ JSON 允许重复键、`I18n` 取到旧值;
       改取**空闲号 t181~t184** 才对(教训:加 i18n 键前先查该命名空间的已用号)。
    
    ## 真机验证(**1.0.372** · 三星 R3CR704Q72V)—— 参数改名 `law.notice_*`→`notice.*` 后**重出包复验**
    (1.0.371 那次也 PASS,但包内参数是改名前的旧键;最终口径以本段的 1.0.372 为准:10/10 律令+页脚,附件 `b14_notice_room19_372.png`/`logcat_menuitems_372.txt`)
    - 房间:`KUSHAN_ROOM_LINES … 文="富贵街北"`(香妃镇 19 号房;location type=town ⇒ 入口出现 ✓)。
    - 菜单:真点轨迹球物理 `(880,2525)` → 菜单自报出现「📜 告示」→ 真点其中心逻辑 `(519.5,1051)`=物理 `(692,1401)`。
    - 面板自报(节选,`logcat_menuitems.txt`):
    ```
    文=禁酒令 · 设计在册 | 文=教育限令 · 设计在册 | 文=宵禁与巡查 · 设计在册 | 文=告示即法 · 设计在册
    文=广场立告示木牌…(正文) | 文=税与贡 · 设计在册 | 文=劝善戒恶队 · 设计在册
    文=律令由香妃领颁行…(页脚)
    ```
    - **判据对账**:`law.enabled_ids` 10 条 ⇒ 面板里**命中的条目名 10 个**(着装令、同行令、禁乐令、禁酒令、教育限令、
      宵禁与巡查、告示即法、税与贡、禁影像、劝善戒恶队)+页脚在场 ⇒ **RESULT=PASS**;截图 `b14_notice_room19.png`(目视)。
    
    ## 老实说的边界(未做/待定)
    - **外观形态**:判据说「立告示**木牌**」,本轮落的是**菜单入口**(镇上可见、点开即列),**场景里那根木牌的美术外形没做**
      (mid 层场景点归 `room/layer_rects.gd` 布局体系管,属另一条线)⇒ 记作后续外观项,不谎报「木牌已立」。
    - `laws_xiangfei.json` 里每条律令的 `open` 字段(如 L-01「罚金数额待人否」)**没有上屏** —— 那属「待人否」清单,按纪律不擅自编数。
    
    ### TASK-B014-BAG-USE  (2026-10-02 13:12:06)
    
    
  • 版本 1.0.373 开发日志

    构建 1.0.373(code 373)· 2026-10-02 14:32:24 · sha256 7c0247ee94eb1811…

    下载:公网 · Tailscale · 局域网

    开发日志(本轮摘要)

    # 贵霜帝国 · 内网开发版 · 本轮开发记录
    
    生成时间:2026-10-02 14:27:38 | 本包:kushan-1.0.373.apk(1.0.373 / code 373,sha256 7c0247ee94eb1811…)
    
    ## 最近开发任务证据(evidence/*/readme.md,新→旧,取 10 份)
    
    ### 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)` |
    
    菜单全 12 项自报(`MENU_DUMP=1` 原文,含新上的 B-14「📜 告示」):
    ```
    生命 51/50 | 克什 300.00 · 第纳尔 0.00 | 声望 +0 | 1620 冬   (519.5,354.5)
    五维 外交 2 | 军事 3 | 管理 6 | 谋略 2 | 学识 3            (519.5,451.5)
    🧭 旅伴  (519.5,547)   🎒 背包 (519.5,631)   📜 任务 (519.5,715)   🗺 世界地图 (519.5,799)
    🛒 商店  (519.5,883)   ⚔ 遭遇战 (519.5,967)  📜 告示 (519.5,1051)  💾 保存进度 (519.5,1135)
    设置 (519.5,1219)      🚪 退出游戏 (519.5,1303)
    ```
    
    ## D-008 判定:**同主题(已验·目视)**
    四张图都是「同一套纸面板 + 金/木色键面 + 同一字体」的外观;代码侧同源:
    - 四个界面全部经**同一套 UI 助手**与**同一个主题色表**出画:`scripts/room/room_layout.gd`(房间页,`theme.*` 10 处、UI 助手 16 处)、
      `scripts/battle_view.gd`(战斗,UI 助手 17 处)、`scripts/room/room_chat.gd`(对话/聊天)、商店面板同走 `UIConf`/`UI`。
    - 字体单一真源=参数 `ui.font.path`(`ui.font.enabled` 总开关)⇒ 四界面不可能各自换字体。
    - 因此判据「对话/商店/战斗套用同一主题」**成立**。
    
    老实说的两点:
    1. 战斗那张取的是**遭遇战的选敌面板**(在打斗界面的入口);真正回合内画面见前几轮 `evidence/TASK-B014-*`、`TASK-ROOM-POPUP-SHELL` 的真机图。
    2. 本机 `tools/qa_shot_ocr.py` 这次没吐出文字(OCR 链不可用)⇒ 文字证据改由 **`MENU_DUMP` 自报 + 截图** 双轨给出。
    
    ## D-007 判定:**未达标(半验)**——实测数字如下
    `KUSHAN_ROOM_LINES` 自报(逻辑坐标,窗口逻辑高 ≈ 2616/1.3333 ≈ 1962):
    - 主卡片 `框y=17 框高=1079` ⇒ 卡片自屏顶起、占到屏高 **55%**(17→1096)。
    - 卡内:房间名 `y=50 h=53`、描述 `y=129 h=18`、状态条 `y≈354`、五维 `y≈451`,随后是人物/出口区。
    - 69 号 §六 要求:**顶部状态条** + **上部 38–42% 场景图区** + **中部 33–36% 文本区** + **下部 22–26% 选项两列**。
    - **差异**:现布局**没有独立的「上部场景图区」**(场景图=全屏背景),文字卡片自顶部起贯通到 55%,
      状态条/五维/人物/出口**全在卡内**,没有「中部文本 / 下部选项两列」的竖向分区。
    
    ### TASK-B014-NOTICE  (2026-10-02 13:56:01)
    
    # TASK-B014-NOTICE —— 68 号 B-14「告示牌(律令入口)」落地 + 真机验证(2026-10-02 · 1.0.371)
    
    ## 判据(68 号 B-14 原文)
    > 镇上立告示木牌,点开列出当前生效律令 | 验法:真机点告示 → 截图
    
    ## 本轮做了什么(此前**客户端零实现**:`scripts/` 里 grep 不到「告示/律令」)
    1. **新面板** `scripts/room/notice_panel.gd`(一功能一文件):把「当前生效律令」画进房间交互区。
       - 真源:`data/laws_xiangfei.json`(70 号 §二《律令政策真源》的 `laws` 数组)+ 参数 `law.enabled_ids`(生效编号)。
       - **只读不裁**:名单读参数、正文读数据文件,面板零硬编码(文案走 I18n、条数上限走参数、样式走 UI/UIConf)。
    2. **入口(镇上才出)**:`scripts/room/room_layout.gd` 新增 `notice_here()` —— 按**所在地类型**判(`locations.type`),
       类型名单走参数 `law.notice_loc_types`(默认 `town,city,village,castle,district`);主菜单里多一项「📜 告示」。
    3. **参数**(`data/params.json`,与 `law.*` 同组):`law.notice_loc_types`(str)、`law.notice_max`(int,默认 12,min1/max40)。
    4. **文案**(`data/i18n.json`):`label.tag.notice` =「📜 告示」;`ui.room.t181~t184` = 面板标题/空表/页脚/日志。
       踩坑记:起初插到 `t017~t020`,**那几个号早被占用**(`t017` 已是「房间不存在」)⇒ JSON 允许重复键、`I18n` 取到旧值;
       改取**空闲号 t181~t184** 才对(教训:加 i18n 键前先查该命名空间的已用号)。
    
    ## 真机验证(**1.0.372** · 三星 R3CR704Q72V)—— 参数改名 `law.notice_*`→`notice.*` 后**重出包复验**
    (1.0.371 那次也 PASS,但包内参数是改名前的旧键;最终口径以本段的 1.0.372 为准:10/10 律令+页脚,附件 `b14_notice_room19_372.png`/`logcat_menuitems_372.txt`)
    - 房间:`KUSHAN_ROOM_LINES … 文="富贵街北"`(香妃镇 19 号房;location type=town ⇒ 入口出现 ✓)。
    - 菜单:真点轨迹球物理 `(880,2525)` → 菜单自报出现「📜 告示」→ 真点其中心逻辑 `(519.5,1051)`=物理 `(692,1401)`。
    - 面板自报(节选,`logcat_menuitems.txt`):
    ```
    文=禁酒令 · 设计在册 | 文=教育限令 · 设计在册 | 文=宵禁与巡查 · 设计在册 | 文=告示即法 · 设计在册
    文=广场立告示木牌…(正文) | 文=税与贡 · 设计在册 | 文=劝善戒恶队 · 设计在册
    文=律令由香妃领颁行…(页脚)
    ```
    - **判据对账**:`law.enabled_ids` 10 条 ⇒ 面板里**命中的条目名 10 个**(着装令、同行令、禁乐令、禁酒令、教育限令、
      宵禁与巡查、告示即法、税与贡、禁影像、劝善戒恶队)+页脚在场 ⇒ **RESULT=PASS**;截图 `b14_notice_room19.png`(目视)。
    
    ## 老实说的边界(未做/待定)
    - **外观形态**:判据说「立告示**木牌**」,本轮落的是**菜单入口**(镇上可见、点开即列),**场景里那根木牌的美术外形没做**
      (mid 层场景点归 `room/layer_rects.gd` 布局体系管,属另一条线)⇒ 记作后续外观项,不谎报「木牌已立」。
    - `laws_xiangfei.json` 里每条律令的 `open` 字段(如 L-01「罚金数额待人否」)**没有上屏** —— 那属「待人否」清单,按纪律不擅自编数。
    
    ### TASK-B014-BAG-USE  (2026-10-02 13:12:06)
    
    # TASK-B014-BAG-USE ——「背包 / 物品可用」真机验证成立(2026-10-02 · 1.0.370 · 三星 R3CR704Q72V)
    
    ## 结论
    **「物品可用」成立**:真机点开背包 → 真点「使用」→ 客户端 hp **30 → 42**(+12,恰等于 `heal.food_amount`)、
    干馕数量 **2 → 1**。原文(`logcat_b014_bag_use.txt`,同一会话、连续三条读数):
    ```
    KUSHAN_BAG_QA prep drop=20 hp=30 max=50 food_id=-2 name=干馕 qty=2
    KUSHAN_BAG_QA t=51 hp=30/50 food_id=-2 name=干馕 qty=2 heal=12
    KUSHAN_BAG_QA t=54 hp=42/50 food_id=-2 name=干馕 qty=1 heal=12     ← 真点「使用」之后
    ```
    真点链:轨迹球物理 `(880,2525)` 收到 `KUSHAN_TOUCH (659.9999, 1893.751)` → 菜单自报出现 →
    点「🎒 背包」逻辑 `(519.5,631)`=物理 `(693,841)` → 背包面板打开(自报「生命 30/50 | 克什 300.00」)→
    点「使用」逻辑 `(519.5,612.5)`=物理 `(693,817)` → hp/数量在下一拍就变了。
    截图 `b014_bag_after_use.png`(目视:面板与读数)。**判据=hp 变大 且数量变小(RESULT=PASS)**。
    
    ## 为什么直到今天才验成(根因,值得记住)
    `GameState.use_item()` 的治疗分支要求 `stats.hp < Params.int_val("combat.player_max_hp")`;
    而**服务端战斗是权威账、不回写客户端 `stats.hp`** —— 打完架客户端血量不变,
    真机自报也一直是 `生命 51/50`(hp 还大于上限)⇒ 永远落在 `combat.hp_full` 分支、不消耗物品。
    即「客户端 hp」与「服务端战斗 hp」是**两本账**。
    
    ## 本轮处置=加一个 cfg 门控的自检驱动(不改玩法)
    `scripts/bag_qa.gd`(由 `room_view.gd` 按 `SETTINGS_QA` 同款门控挂上,**不写 cfg 零副作用**):
    把客户端 hp 压到 `max - N`(默认 20)并每 3 秒打一条 `KUSHAN_BAG_QA`(hp/件 id/名/数量/该件回血)。
    ⇒ 让「真点使用」这一步能落到治疗分支上**被观察到**,而不是靠改产品数值或改断言。
    用法:`kushan.cfg` 里 `BAG_QA=watch`(或 `BAG_QA=watch:35` 调压血量)。
    
    ## 顺带记一条工具坑(这次踩了)
    `KUSHAN_MENUITEM` 自报里菜单项**带 emoji**(`文=🎒 背包`)⇒ grep 用 `文=背包` 匹配不到(少了一层通配),
    害我第一次脚本取到 `(0,0)` 点位。正确写法:`grep '文=.*背包'`(或只 grep 关键词)。
    
    ## 仍待人拍板(设计岔路,不在本轮擅自改)
    **两本账要不要统一**:① 服务端战斗结果回写客户端 `stats.hp`(客户端血量跟服务端走);
    ② 明确「客户端 hp」只用作出征前快照 / 展示,治疗类物品的服务端权威路径另立。
    两条都属玩法口径,**开发方倾向 ①(一处真源,省掉两套数值)**,但影响面涉及存档与战斗结算,故登记待人否。
    
    ### TASK-GUARD-DELETE-TOLERANT  (2026-10-02 12:55:47)
    
    # TASK-GUARD-DELETE-TOLERANT(2026-10-02)——「参数键存在性」新护栏 + 5 个护栏的删件容忍
    
    ## 一、补上真实盲区:代码引用的参数键,没人静态扫(→ 新护栏 `tools/verify_param_keys_missing.py`)
    
    **起因(真机逮到)**:1.0.366/367 真机日志有 `ERROR: [Params] 缺参(键不在任何层,也不在出厂 schema): net.quest_timeout_sec`。