构建 1.0.358(code 358)· 2026-10-02 02:31:11 · sha256 f932caf5baf3ae21…
开发日志(本轮摘要)
# 贵霜帝国 · 内网开发版 · 本轮开发记录
生成时间:2026-10-02 02:30:38 | 本包:kushan-1.0.358.apk(1.0.358 / code 358,sha256 f932caf5baf3ae21…)
## 最近开发任务证据(evidence/*/readme.md,新→旧,取 10 份)
### TASK-MAP-MODULE-DEPS (2026-10-02 02:24:20)
# TASK-MAP-MODULE-DEPS · MAP-08 单向依赖(**已收口**)
**日期**:2026-10-02 02:20 | **口径**:68 号 MAP-08(行数项 2026-09-26 已作废 ⇒ 见 G-010「只查职责不查行数」)
## 新护栏 `tools/verify_map_module_deps.py`(已自动纳入全量)
判据:四模块内部引用**只能是** `map_screen → {map_nav, map_data, map_render}`;基础件不得反向引用 `map_screen`、彼此不得互引、不得成环。
**正测(现仓)**:
```
map_nav → (无)
map_data → (无)
map_render → (无)
map_screen → map_data、map_nav、map_render
✔ 单向成立:3 条边全部向下,无反向边、无环 / RESULT=PASS
```
**反测(临时往 `map_nav.gd` 注入 `preload("res://scripts/room/map_screen.gd")`,跑完 `git checkout` 还原)**:
```
✗ map_nav ↔ map_screen(成环)/ ✗ map_screen ↔ map_nav(成环)/ RESULT=FAIL(2 处)
```
护栏**非空过**(正反两测均如预期)。**职责项**(一功能一文件)由既有 `tools/verify_one_job_per_file.py` 把关,本件不重复。
**行数(仅记录,不作判据)**:`map_nav` 97/`map_data` 167/`map_render` 62/`map_screen` 267 行。
### TASK-MAP-WALK (2026-10-02 02:15:43)
# TASK-MAP-WALK · 四层地图真机验收(MAP-01/02/03/04/07 · **已收口**)
**日期**:2026-10-02 01:20-02:05 | **包**:1.0.356(真机 `R3CR704Q72V`,真屏 display 3 `4630947232161729155`)
**工具**:`tools/qa_map_walk.sh`(三层取图 + 切换)、`tools/qa_map_back_ab.sh`(退层 + 开关 A/B)、`tools/map_shot_metrics.py`(量图)
**驱动**:cfg 通道 `MAP_QA_LAYER` / `MAP_QA_STEPS`(`jump|back|down|layer:N`)——**不用 Params**:安卓上 `user://` 是应用私有目录,推参数够不着(技能 §8 实测),故把自检接到**外部 kushan.cfg**(与 `AUTO_ENTER`/`TRADE_QA_SAY` 同一条)。
## 关键读数(原始输出)
**MAP-01 全屏不裁切**(`tools/map_shot_metrics.py`,基准 `map.safe_px=24`):
```
L1.png (960,2616) 四边留白={'左':32,'右':33,'上':32,'下':1457} 内容带=7 均亮=22.7 ⇒ 四边留白达标
L2.png (960,2616) 四边留白={'左':32,'右':33,'上':32,'下':1721} 内容带=8 均亮=22.2 ⇒ 四边留白达标
L3.png (960,2616) 四边留白={'左':32,'右':33,'上':32,'下':1997} 内容带=6 均亮=20.6 ⇒ 四边留白达标
```
机身实测:`mDisplayCutout insets=Rect(0,0-960,93)`(顶部挖孔带 93px)、`navigationBars mFrame=[0,2490][960,2616]`;**两条系统栏在本 App 内 `mVisible=false`**(沉浸)⇒ 地图内容(上边距 32px、底边距 ≥1457px)既不压系统栏也不进挖孔带。
**MAP-02 四层可进可退**(同一跑内,层号 1→2→3;`back` 两步 3→2→1):
```
KUSHAN_MAP_LAYER 落层 want=1(回总图) KUSHAN_MAP_QA 落层 want=1 now=1
KUSHAN_MAP_LAYER 落层 want=2 lid=2 rid=0 ok=true KUSHAN_MAP_QA 落层 want=2 now=2
KUSHAN_MAP_LAYER 落层 want=3 lid=2 rid=100 ok=true KUSHAN_MAP_QA 落层 want=3 now=3
KUSHAN_MAP_BLOCKS L1 rooms=287 locs=10 blocks=10 key0_type=4
KUSHAN_MAP_BLOCKS L2 lid=2
KUSHAN_MAP_QA step=1 cmd=back 前层=3 / KUSHAN_MAP_BACK ok=true 层后=2
KUSHAN_MAP_QA step=2 cmd=back 前层=2 / KUSHAN_MAP_BACK ok=true 层后=1
```
三层截图 **md5 互不相同**(`b20e4c78` / `ac64a08a` / `6aa265f1`)= 视图真的随层变了。
**MAP-03 总图⇄场景切换**(点两次「切换」= 调按钮同一函数 `_on_jump`):
```
step=1 cmd=jump 前层=2 / KUSHAN_MAP_JUMP 层前=2 / step=1 cmd=jump 后层=1
step=2 cmd=jump 前层=1 / KUSHAN_MAP_JUMP 层前=1 / step=2 cmd=jump 后层=1 ← 第二次无效(见 B-072)
```
帧序列 `J2=b20e4c78(L1) / J3=ac64a08a(L2) / J4..J6=b20e4c78(L1)` = 同一次运行里两种视图都拍到。
**MAP-04 三层画法一致**:三层内容带 7/8/6、均由同一 `map_render` 模块出图(`show_tiles`/`show_rows` 同一套块状画法,代码同一处),三层截图各具(见上 md5)。
**MAP-07 可回退**(同一包,只改 cfg):
```
abA_开关关.png md5=bb8fc1b6 四边留白={'左':0,'右':1,'上':0,'下':1} 均亮=25.0(世界视图)
### TASK-MAP-05-06 (2026-10-02 00:06:35)
# TASK-MAP-05-06 · 地图线两项评测(**已收口**)
**日期**:2026-10-02 00:05-00:15 | 来源:68 号 §补录十九(四层地图)
## MAP-05 · 客户端 L3 楼栋房间集合 == `tools/map_building.py` 同栋集合
**口径**:抽样 10 栋**比集合**(脚本),不看截图。**本脚本比全部 67 栋**(比抽样更强),抽样 10 栋逐条打印。
**新护栏** `tools/verify_map_building_rooms.py`(自动纳入全量):
- **从客户端源码真读**判据:正则取 `scripts/room/map_data.gd` 的 `const STREET_TYPES := [...]`
(不另抄一份,否则护栏自己就是一个新的漂移源);
- 字尾表从 **params** 读:`map.tail_street`/`map.tail_inner`(`data/params.json` 的 `params` 袋内,点号键);
- 按**客户端判据**重算(街面=type ∈ 表 或 名字 `·` 后段以字尾结尾;楼内=type==interior 且名字不以街面字结尾,
无 type 时客户端另有 `tail_inner` 兜底——本脚本照抄);再与工具 `map_building.buildings()` 比集合。
**实跑输出**:
```
客户端源码 STREET_TYPES=['street','alley','road','junction','plaza','court']
params tail_street=['街','巷','道','路','市口','广场','口']
params tail_inner=['院','厅','房','室','屋','阁','堂','厨','厩','库','铺','店','间','廊','楼','亭','舍','庄','宅','居']
楼栋数:客户端判据 67 栋 | 工具 67 栋
抽样 房2 外院 ⇒ 同 抽样 房5 内院 ⇒ 同 抽样 房17 旅店 ⇒ 同
抽样 房103 白杨甸甸门 ⇒ 同 抽样 房104 沙枣屯屯口 ⇒ 同 抽样 房105 石桥驿驿站 ⇒ 同
抽样 房110 香妃堡前庭 ⇒ 同 抽样 房211 村中巷 ⇒ 同 抽样 房221 庄中街 ⇒ 同 抽样 房231 甸中巷 ⇒ 同
✔ MAP-05:67 栋全部同集合(客户端判据 == 工具判据),抽样 10 栋逐条一致
```
> 自查中发现并当场修掉一个**假绿**:首版取参数只查 JSON 顶层 ⇒ `tail_street=[]`(空表),
> 比集合虽然也「全同」,但那是**空表比的假绿**(名字判据根本没参与)。修正为「先查 `params` 袋、再回落」后才取到真表
> ——教训:**护栏自己取不到数据时必须报错/跳过,不能拿空表比出绿**。
## MAP-06 · 新增参数/文案/色全有值、无硬编码
**验法**:`bash tools/run_verify_all.sh` 全绿。**实跑**:`PASS=87 FAIL=0`(读数 86→87 = 本轮新增 MAP-05 护栏)。
### TASK-B069-B071 (2026-10-01 23:52:25)
# TASK-B069-B071 · 卖路径「挑错货 / 拼错键」收口(**已收口**)
**日期**:2026-10-01 23:44-23:52 | **包**:1.0.352(已装机)
## 一、真因(三轮才追到,前两轮修丁都不够)
1. **镜像缺键**:`trade_bridge.mirror_items()` 读了服务端 `item_key` 却**没写进镜像条目**(已修);但**启动来源不是它** ——
启动背包来自**本地存档**(`game_state.gd:639 inventory = parsed.get("inventory", [])`),条目 `id=-2`、无 `item_key`。
2. **名字匹配挑错货(B-071,真凶)**:弱匹配把「**共享一个字**」当成命中 ⇒ 说「我卖一份蜂蜜馕」时,
`name_hit("干馕", …)`=0,但 `shares_char("干馕","我卖一份蜂蜜馕")` 命中「馕」⇒ 唯一的 weak 候选被采纳 ⇒ 选中**开局背包的「干馕」**(
非商店货、id=-2)⇒ 拼出 **`shop_21_-2`** ⇒ 服务端以「商品不在该商店货架」拒。
注:「干馕」是 `data/params.json` 的开局背包物(水囊/干馕),与服务端 `data/world.json` 的「蜂蜜馕」**不是同一件货**;
`grep 干馕` 全仓只此一处(开局背包),非数据打架 ⇒ 问题在**匹配过宽**。
## 二、修法(纯客户端,不动网关契约)
- **弱匹配收严**(`dialogue_trade.gd` 两处:商店匹配 + 背包匹配):货名 **≥3 字**才允许「共享一个字」算命中。
⇒ 2 字的「干馕」不再能靠一个「馕」混进来;而「我买串」→`烤羊肉串`(4 字、共享尾字)这类口语简写**依旧可用**。
- **键不合法就不发请求**(B-069 加固):拿不到 `^shop_\d+_\d+$` 形状的键时**直接回一句明白话**(复用既有 i18n 键 `ui.room.t165`),
**绝不**拿硬拼键去撞服务端、也绝不把撞出来的错回执当成交易结果报给玩家。