# 📦 版本历史 > 完整版本更新记录。最新版本在顶部。 ### V104 - 探索导航决策走 gemma 小模型 `[feature/v62-emotion-engine]` > 落地 V100 远景的「大模型定宏观任务 → 小模型实时导航」双层架构的最后一块:把林瑶在街上探索(look_around / explore_direction / call_taxi)的微观导航决策从主模型 deepseek 剥离到 gemma。根因——探索决策一直挂在主模型上,一次探索弧光烧 6~10 次 deepseek `AGENT_DECISION` 配额(15min 5次),配额耗尽后角色站桩发呆。gemma 走独立 client、不限流,探索不再吃 deepseek 配额。 #### V104-A - gemma_structured 底层封装(每场景独立 prompt + 独立 schema) - ➕ `gemma_gateway.gemma_structured(prompt, *, schema, system, thinking="MINIMAL")` → `Optional[dict]`:极薄底层,只负责组装 `GenerateContentConfig`(response_mime_type + response_schema + thinking_config + system_instruction)+ 调用 + JSON 解析 + 失败返回 None。**不含业务逻辑**——prompt/schema/system 全部参数传入,供后续其他 gemma 用途复用 - 📌 gemma-4 能力(用户用官方示例确认):原生 `response_schema`(enum 当物理白名单)、`thinking_config`、`system_instruction` - 📌 与现有 `gemma_chat` / `gemma_json` 并列,不改它们 #### V104-B - 导航场景(独立 prompt + 独立 schema) - ➕ 新文件 `engines/explore_navigator.py`: - `NAV_SYSTEM_PROMPT`:导航专用系统提示词(与主模型 prompt 完全分离) - `_build_nav_schema()`:导航专用 response_schema,action enum 覆盖 5 动作(look_around / explore_direction / call_taxi / go_to / call_ride_home),direction/step_m 同为 enum 白名单 - `_build_nav_prompt(state)`:精简导航上下文(坐标/饥饿/精力/钱/天气/已知地点/目的推断/最近探索结果),**不含完整记忆/性格** - `run_explore_navigation(state)`:导航态分流入口。返回 None(非导航态)/ `{"_fallback": True}`(gemma 失败降级)/ 同构 decision 字典(成功) - 📌 导航态判定(干净切分缝):`location == "Street" and pending_destination == ""`,transit 中不误进 - 📌 复用 `SISTER_TOOL_HANDLERS` 分发执行,工具函数本身不动 - 📌 chain_next 恒 False(每 tick 一步,保持时间与现实 1:1);下一个 tick 由 game_tick 入口重新进入导航态 #### V104-C - 决策分流 - ♻️ `game_tick.py` online 分支:`run_sister_agent` 前插入 `run_explore_navigation`——None/`_fallback` 走主模型,成功用 gemma 结果。chain 循环同步走导航分流 - ♻️ `sister_agent.ACTION_TOOLS` 补登 `explore_direction`(改变位置/游荡坐标,降级路径需正确识别为行动工具 + chain_next 正确);`_build_sister_decision_from_results` 补 explore_direction 分支(降级路径描述正确) #### V104-D - 测试 - ➕ `test_explore_navigator.py`(20 测试):导航态判定(街/店/家/transit)、gemma 失败降级(None/非法动作/工具失败)、动作映射执行(命中/张望)、decision 字典同构、chain_next 恒 False、NAV_SCHEMA enum 白名单、动作→工具参数映射、prompt 精简性 - 📊 **回归**:2002 passed(+20 新增),3 个 `TestTripTransport` 预先存在失败(与 V104 无关,main 上也红) ### V103 - 入住须知 + 房租机制 `[feature/v62-emotion-engine]` > 给林瑶补上「生存压力」经济循环 + 持久背景知识。入住时系统交付一份入住须知(welcome_guide),含房租规则、周边信息(便利店需自己找、菜市场远)、作息守则;同时启动房租——每周 300 元从林瑶 pocket_money 扣,余额不够即游戏结束。 #### V103-A - 入住须知(welcome_guide 持久字段) - ➕ `GameState.welcome_guide: str`(持久,区别于瞬态 sister_perceptions / 滚动 core_memories) - ♻️ `_init_user_state`(routes.py)填充须知:房租规则 + 周边 + 作息;**删除**旧的「记得附近有个便利店」种子感知(被须知取代) - ➕ `agent_prompt_builder` 把 welcome_guide 注入 `_memory_parts` 置顶(每次决策可见) #### V103-B - 房租机制(真实,墙钟 7 天周期) - ➕ `GameState.last_rent_charge_realtime: float`(unix 秒,0.0=未初始化) - ➕ `game_tick` Phase F.0(每 tick,早于 F.1 自然日 guard):到期(`now - last >= 7*86400`)+ 余额≥300 → 扣 300 + 写感知「交了房租 300 元」;余额<300 → `set_game_over("BE_RENT_BANKRUPT")` - ♻️ `pocket_money` 默认 200→500(入住扣首周 300 剩 200) - ♻️ `_init_user_state` 入住即扣首周 + 设 baseline - ➕ 新 reason code `BE_RENT_BANKRUPT` - ✅ 老存档迁移:`last_rent_charge_realtime=0.0` → 首 tick 建 baseline 不追溯扣费 - 📌 周期口径:入住日锚定的 7 真实天(非固定周一——可能周五入住) #### V103-C - 便利店探索化 - ♻️ `known_locations` / `player_known_locations` 默认移除 `Street_Convenience`(入住须知只说「附近 500m 有」,需 look_around / explore_direction 发现) - 认知门 `_sister_go_to`(未知地点拒 go_to 引导 look_around)自动生效,无需改 #### V103-D - 测试 - ➕ `test_welcome_guide.py`(13 测试):须知填充 + 首周扣租 + 7 天到期扣费/破产 + 迁移 + prompt 渲染 + reset 干净 - ♻️ `test_known_locations` / `test_exploration` / `test_places_known_api` / `test_prompt_opacity` / `test_place_perception`:便利店默认未知后的断言更新 - 📊 **回归**:1982 passed(+9 净增),仅 3 个 `TestTripTransport` 预先存在失败(与 V103 无关,main 上也红) #### 不改的部分 - `set_game_over` / `adjust_pocket_money` 机制——复用 - 认知门 `_sister_go_to`——已有逻辑,便利店移出 known 后自动生效 - `mark_location_known`——探索发现走现有路径 - V102 transit 移动机制——不动 ### V102 - 林瑶移动时间真实化(消灭瞬移分支) `[feature/v62-emotion-engine]` > V101 让感知层物理化,V102 让**移动**也物理化——消灭「移动不耗游戏时间」的瞬移路径。根因:`RealtimeClock` 完全由真实墙钟推导(`sync_to_realtime` 算 `elapsed = int((now - start)/60)`),游戏时间只在真实秒数流逝、决策被 `action_until_realtime` 阻塞时推进。`_sister_go_to` 的**即时分支**设 `until_realtime = now + 5`(5 秒)→ `int(5/60) = 0` 游戏分钟 → 移动不耗时间 = 瞬移,违反「时间与现实 1:1」核心设定。受影响:家↔街(2min)、街↔近店(蜜雪/茶百道/便利店 3-4min、药店 5min)全部即时到达。 #### 核心变更:分支条件从「距离阈值」改为「室内/室外」 transit 机制本身完全正确(`pending_destination` + `until = travel_min*60`,真实分钟流逝)。问题只是分支条件 `travel_min > 5` 把短距离室外移动错误地归到了即时分支。 | 移动类型 | 旧分支 | 新分支 | |---|---|---| | 室内↔室内(客厅↔厨房) | 即时(≤5min) | **即时**(`indoor_switch=True`,不变) | | 家↔街 / 街↔近店 / 店↔街 | 即时(≤5min,**瞬移**) | **transit**(真实分钟窗口) | | 街↔远店(瑞幸+) | transit(不变) | transit(不变) | #### V102-A - `_sister_go_to` 分支条件(agent_tools.py:805) - ♻️ 分支条件 `if travel_min > TRANSIT_THRESHOLD_MIN:` → `if not indoor_switch:`(非室内切换 → 一律 transit) - ♻️ 删除多余的 `is_outdoor` 三元(旧 `move_to("Street" if is_outdoor else "Street")` 两支都是 Street) - ➕ 店→街 边界:目的地即 `Street` 时不设 `pending_destination`(到街即终点) - 📝 `TRANSIT_THRESHOLD_MIN` 常量保留(`MovementResolver.requires_transit` 仍用),go_to 不再用它做分支 #### V102-B - `_sister_explore_direction` 到店分支(agent_tools.py:1955) - ♻️ 命中店不再即时 `move_to(found_key)`,改为走 transit(`move_to("Street")` + `set_pending_destination(found_key)` + `until = travel_min*60`) - ♻️ 发现标记(`mark_location_known`)+ 丰富"打量"到达感知 + 清游荡坐标 → **延迟到 game_tick transit 到达**(与 go_to 一致) - ♻️ 探索发起感知从「发现了 X」改为「看见 X 的招牌,正走过去」(到店时再补"打量") #### V102-C - 测试更新 - ✅ `test_explore_sister.py`:4 个 hit 测试改为断言 transit 行为(位置留 Street、记 pending、发现标记/清坐标延迟到到达) - ✅ `test_cut_e_travel.py`:新增 `TestSisterGoToTransitV102`——近店走 transit、店→街无 pending、室内切换仍即时;`test_living_to_street_is_short` 更新断言 until 消耗真实时间 - 📊 **回归**:1973 passed(+4 净增) #### 不改的部分 - transit 到达机制(game_tick:359-370)——已正确 - `_sister_explore_direction` 漫游分支——已正确(`until = max(5, travel_min*60)`) - schedule/handler 强制覆盖(生病/垂死回床、加入玩家)——异常处理,保持即时 - 玩家侧 `/api/move` `/api/explore`——用户选只修林瑶侧,留后续 - V45 目击不对称(即时到达目击、异步到达不目击)——`test_no_witness_on_transit_arrival` 锁住,可接受 ### V101 - Perceiver 接入 look_around(感知层落地) `[feature/v96-gemma-npc]` > V100 做了纯世界层 Perceiver(地基①),V101 把它接入 agent 层第一个消费者——`_sister_look_around`。旧 look_around 用 `world.travel("Street", key)` 的 Dijkstra 距离 + 0.5km 无遮挡半径,**永远从家算**且完全忽略游荡坐标,物理上不合理(站便利店门口张望,算的还是家到店的距离)。V101 改用 `Perceiver.perceive(wander_coords)`,从当前游荡坐标算可见性,复现真实遮挡:同路 75m 直视、路口 30m 窥见、不同路被建筑遮挡。**保留渐进发现语义**(每次只揭示一家最近的未知店)。 #### 核心变更:测距基准 + 物理遮挡 | | 旧 look_around | 新 look_around(接 Perceiver) | |---|---|---| | 测距基准 | `world.travel("Street", key)` Dijkstra,**永远从家算** | `Perceiver.perceive(wander_coords)`,**从当前位置算** | | 半径 | 0.5km(超物理视线) | 同路 75m + 路口窥见 30m | | 遮挡 | 无(0.5km 内全可见) | 有(不同路被建筑遮挡) | | 游荡坐标 | **完全忽略** | **使用** `wander_coords_km`(None→家原点),与 explore_direction 一致 | | 发现节奏 | 每次一家(保留) | 每次一家(保留) | #### V101-A - `_sister_look_around` 重写(agent_tools.py) - ♻️ 观察者坐标改用 `wander_coords_km`(None→`HOME_COORDINATES_KM`),与 `_sister_explore_direction` 同口径 - ♻️ 可见性计算改调 `Perceiver(get_world()).perceive(current_coords, hour=state.clock.hour)` - ➕ 返回新增 `intersections` 字段(可见路口的 direction/distance_m/cross_road,供 LLM 判断"往哪走") - ➕ 空 spots + 无路口时换提示文案「这一带看不到什么店铺的招牌。沿路走一段,或者到路口看看」 - 🗑️ `LOOK_AROUND_MAX_DISTANCE_KM` 常量保留但标注废弃(`_TAXI_FAR_SHOP_THRESHOLD_KM` 仍引用它做远店阈值,语义从"look_around 半径"变为"步行难自发现的距离") - 📝 look_around 工具描述更新,说明遮挡模型 + 路口信息 + "站家附近可能看不到店,要走一段" #### V101-B - 测试更新 - ✅ `test_exploration.py`(32 测试):TestLookAround 重写——站家原点空感知、同路直视、不同路遮挡、远店永不可见;TestLookAroundDiscovery 重写——渐进发现基于新可见性、新增游荡坐标生效测试;TestLookAroundNoValueLeak 适配新位置;端到端测试改为先走到店附近再发现 - ✅ `test_taxi_npc.py::test_full_discovery_pipeline` 修复:look_around 前先 set_wander_coords 到便利店附近(V101 需物理可见才能发现) - 📊 **回归**:1969 passed(+6 净增)。3 个 `test_cut_e_travel::TestTripTransport` 失败为**预先存在**(玩家端,本版未触及) #### 行为变化说明 站家(0,0)在三泉路上,75m 内无任何店(最近茶百道 250m),路口也在 100m 外超 glimpse 30m → 初始 look_around 返回纯空感知。这是物理真实的:家门口看不到店招牌,需要 `explore_direction` 走一段或到路口才能发现。look_around 与 explore_direction 配合使用——边走边看,符合真实探索体验。 #### 不改的部分 - `_sister_explore_direction`——游荡坐标 + 直线距离语义已正确,不动 - 玩家侧 `/api/explore`——用户选只改 sister 侧 - `place_perception.py`——到达感知,与 Perceiver 互补,不动 ### V100 - World-layer Perceiver 世界层感知器 `[feature/v96-gemma-npc]` > V99 做了 MovementResolver(执行层:「能不能走这一步 + 要多少代价」),V100 做它的感知层双子——Perceiver(「站在这里能看到什么」)。给定观察者坐标,算出此刻可见的店铺和路口,复现人眼的真实遮挡:同路可见(75m 内)、路口可窥(30m 内能瞥见垂直方向的街景)、不同路被建筑遮挡。纯世界层、零 model 依赖、纯增量(3 新文件,零修改现有文件)。这是地基①,为后续 deliberative+reactive 主循环(大模型定任务 → 小模型实时导航)铺感知底座。 #### 设计决策:坐标优先,不读 along_m - 用户判断「坐标点更适合,而不是那条路的多少米」——以任意点为原点描述方向/距离/路口,正是坐标系强项 - 代码现状验证:`along_m` 是死数据(运行时无人读取,测试里全是 `along_m=0` 占位),真正在用的位置信息是 `coords_km` - **explore_engine 已实现一半**:`relative_coords` / `classify_direction` / `distance_km` 三个纯函数就是观察者坐标系。Perceiver 直接复用,不重写 - `along_m` 字段保留不动(零碰 world_data.py) #### V100-A - `app/services/world_geometry.py`(纯几何层) - 📐 **`Intersection` frozen dataclass**:路口(坐标 + 相交路名 Tuple) - 🔧 **8 个纯函数**:`point_to_segment_distance_km` / `point_to_polyline_distance_km` / `nearest_point_on_polyline`(点→线段/折线距离,含垂足计算)/ `on_which_road`(点落在哪条路,tol_m=20)/ `segment_intersect`(通用线段求交,平行/共线返回 None)/ `road_intersections` / `all_intersections`(全图路口去重)/ `nearest_intersection` / `intersections_ahead`(按方向+距离过滤的路口列表) - ✅ **通用线段求交**,不假设路网正交——数据换掉也能跑 - ✅ 路网验证:6 个路口 `(−0.5,−0.1)(−0.5,0.2)(0,−0.1)(0,0.2)(0.35,−0.1)(0.35,0.2)`,9 建筑 + 家全部 0m 偏差落在路上 #### V100-B - `app/services/perceiver.py`(感知器 + 描述层) - 👁️ **3 个 frozen dataclass**:`VisibleShop`(key/name/road/direction/distance_m/open_now/glimpse)/ `VisibleIntersection`(coords/roads/direction/distance_m/cross_road)/ `Perception`(observer_coords/observer_road/visible_shops/visible_intersections) - 🧠 **`Perceiver` 类**:`perceive(coords_km, *, hour=None) -> Perception`。常量 `SIGHT_RADIUS_M=75`(同路直视)/ `INTERSECTION_GLIMPSE_M=30`(路口窥见)。遮挡模型:①同路+距离≤75m→直视 ②路口≤30m→路口可见+路口另一条路上的店若距路口≤30m 也能瞥见(标 glimpse=True)③其余遮挡。hour 可选——传入查 `world.is_open` 填 open_now,不传全 None(纯几何模式) - 📝 **`describe_perception(p)`**:渲染层,把 Perception 转自然语言("沿三泉路向南 50m 是「茶百道」"/"前方向东 20m 有个路口,四惠路在那边穿过")。与数据严格一致,不编造 #### V100-C - 测试 - ✅ **`tests/unit/test_perceiver.py`**(47 测试):TestWorldGeometry(25:点到线段/路归属/线段求交/6 路口坐标/最近路口/前方路口)+ TestPerceiver(17:同路可见/距离截断/路口窥见/不同路遮挡/空感知/营业状态)+ TestDescribePerception(5:空/单店/路口/glimpse 标记/数据一致) - 📊 **回归**:1963 passed(+47 新测试)。3 个 `test_cut_e_travel::TestTripTransport` 失败为**预先存在**(玩家端 trip transport,本版未触及)。 #### 不在本轮范围(远景留痕) - 地基② known_locations metadata 升级(agent 层记忆格式:方向/路口描述) - 地基③ NPC 知识分布(每个 NPC 知道 2-3 个地方,"门口左拐再右拐有xxx"式记忆) - 地基④ driving loop 状态机(deliberative+reactive 主循环) - ~~Perceiver 接入 agent_tools(look_around 改调 Perceiver)~~ → **V101 已完成**(explore_direction 游荡坐标语义已正确,未改) ### V99 - MovementResolver 世界层移动仲裁器 `[feature/v62-emotion-engine]` > 把"能否合法走这一步 + 走这一步要花多少"从 `agent_tools._sister_go_to` 的校验链抽出来,变成纯世界层、无副作用、不读 `GameState`/`SisterState` 的 `MovementResolver`。这是世界能力底座 + agent 能力底座——与具体哪个 agent 接入无关。林瑶是第一个接入的 agent,跑通后能力可泛化到未来的多 agent 共居。玩家出门 UI 暂不动(往后放)。 #### V99-A - 新建 `app/services/movement.py` - 🧭 **`MovementResolver` 类 + `MoveResolution` dataclass(frozen)**:纯世界层,构造时注入 `WorldEngine`(生产 `get_world()`,测试可换桩)。`resolve_step(from, to, *, hour, weather_signal, mode, has_ebike, current_energy, check_hours=True) -> MoveResolution`。6 项职责:①存在性 ②hub-spoke 拓扑(indoor↔shop/shop↔indoor/shop↔shop 必经 Street)③营业时间 ④极端天气(severity=severe 拦 indoor→outdoor)⑤`world.travel()` 耗时/精力/费用 ⑥中转阈值(`TRANSIT_THRESHOLD_MIN=5`)。 - 🔧 **`check_hours` 开关**:默认查营业;盲探索发现店铺时传 `check_hours=False`——能走到关着门的店门口(只不能进去消费),保留 explore 既有语义。 - 📌 **认知层门控不进 resolver**:`known_locations` 是 agent 主观认知,调用方在 `resolve_step` 之前自行判断。 #### V99-B - `_sister_go_to` 校验链替换 - ♻️ **校验前端替换**:旧校验链(存在性/拓扑/营业/天气/`world.travel`)下沉到 resolver 一次调用。**所有错误文案逐字保持不变**(217 个既有导航/天气/探索测试全绿)。`indoor_locs` 从硬编码 `{"LivingRoom",...}` 改读 `world_data.INDOOR_LOCATIONS`(SSOT)。中转阈值 `> 5` 改读 `TRANSIT_THRESHOLD_MIN`。 - 🧠 **职责边界**:resolver 管"世界层合法性";agent 层保留①认知门控(known_locations,需 old_loc 生成 hint 文案)②能量可行性(天气 `outdoor_penalty` 倍率叠加后自行判,不传 `current_energy` 给 resolver 避免双重判)③`energy < 10` 虚弱出门门控。 #### V99-C - `_sister_explore_direction` 命中店算账 - ♻️ **命中店门控走 resolver**:营业/天气/拓扑与 go_to 同口径(单一真相源),但传 `check_hours=False`(盲探索能到关着门的店门口)。距离仍用 `explore_engine` 的直线 `dist_km`(从游荡坐标算,非家→店 Dijkstra),保留 explore 语义。`WALK_SPEED_KMH` 硬编码改读 SSOT `world_data.TRANSPORT_MODES["walk"].speed_kmh`。 #### V99-D - 测试 - ✅ **`tests/unit/test_movement.py`**(33 测试):6 项职责 × 全路径(存在性/拓扑 3 条/营业含 check_hours 开关/天气 4 态/耗时精力数值/中转阈值/数据契约 frozen)。 - ✅ **`tests/unit/test_game_tick_transit.py`**(6 测试):锁住中转到达序列(pending_destination 清除 + 移动到 dest + 清游荡坐标 + 注入到达感知),**显式锁住已知不对称契约**——异步到达不触发 V45 目击(即时到达才触发)。未来补齐时测试会失败提醒同步改。 - 📊 **回归**:1915 passed(+39 新测试)。3 个 `test_cut_e_travel::TestTripTransport` 失败为**预先存在**(玩家端 `/api/move`,本版未触及,clean tree 同样失败)。 > V96 的 `ask_shopkeeper` 工具此前只有林瑶能用。本次补齐玩家侧:玩家进店后可向店员提一个自由文本问题,gemma(小模型 NPC)转述 SSOT 作答,失败降级关键词模板。镜像姐妹端校验链(必须在 `Street_*` 店内 + 必须是 SSOT 真店 + 问题非空),但读 `game_state.location`(玩家位置)。**免费**——不扣行动力、不推进时间、不改持久状态。gemma 红线不变:只转述(营业时间/招工/促销/粗类),不报精确 SKU 与价格。 #### V98 - 跨栈联动(REST + WS + 前端) - 🌐 **`/api/ask_shopkeeper` 端点**(+ WS `action.ask_shopkeeper`):校验 game_over(硬抛)/ 必须在 Street_* 店内(软拒绝)/ 问题非空(软拒绝)。复用姐妹端 `build_shopkeeper_prompt` + `gemma_chat`(max_tokens=100, timeout=30)→ 失败走 `_fallback_response` → 再失败用通用兜底。返回 `InteractionResponse`,`speaker_name="<店名>店员"` 让对话框显示店员身份。无状态变更,不持久化。 - 🎨 **前端 AskShopkeeperModal**:自由文本输入(80 字上限,Ctrl+Enter 提交),店内门控。GamePage 右上角新增 💬 问店员按钮(`Street_*` 店内高亮,店外/街上半透明)。store.askShopkeeper / api.askShopkeeper。 - ♻️ **复用 V96 积木**:`build_shopkeeper_prompt` / `_fallback_response` / `gemma_chat` / `format_promotion_summary` 原样调用,全部玩家安全(只读 `state.daily_promotion` 这个 GameState 字段,不依赖 SisterState)。未引入新 LLM 通路。 ### V97 - 方向探索(玩家 + 林瑶盲探索)`[feature/v96-gemma-npc]` > 玩家和林瑶都能在街上盲探索:选方向(东/西/南/北)+ 步进(50m/100m),沿当前游荡坐标逐步移动,沿途遇到第一个未发现的店就停下,触发首次发现。发现后该店加入已知地图,后续可直接 go_to。方向相对当前坐标(非家原点),符合生活描述。与 V94 不透明感知世界一致——规律需自我探索。 #### V97-C - 林瑶侧 explore_direction 工具 - 🧭 **`_sister_explore_direction`**(agent_tools.py):Street 位置暴露的新工具。命中未发现店 → move_to 进店 + 清游荡坐标 + inject_arrival_perception(走完整 mark_location_known 链路)+ 补 discovery 感知 + 消耗精力。没命中 → 游荡坐标沿方向移动 + 写 self 感知 + 消耗精力。 - 🔧 **SisterStateManager**:新增 `set_wander_coords` / `clear_wander_coords`(@_mutation 保护,与 pending_destination 一致)。 - ⏱️ **game_tick**:异步到达处理补 clear_wander_coords(到达目的地即不在游荡态)。 - 🤝 **与 look_around 互补**:look_around 是原地张望(揭示招牌),explore_direction 是主动走过去(进店)。 #### V97-B - 玩家侧方向探索 API + 前端 - 🌐 **`/api/explore` 端点**(+ WS `action.explore`):校验 game_over/on_trip/必须在Street/方向合法/步进合法/行动力≥1。命中 → location=店key + 清游荡坐标 + player_known_locations 加入 + 复用 `describe_first_visit_text` 生成 SSOT 派生描述 + 推进时间扣行动力。没命中 → 游荡坐标移动 + 提示文本 + 消耗。 - 📝 **`place_perception.describe_first_visit_text`**(新公开接口):纯函数,供玩家侧复用 SSOT 派生描述(不写林瑶感知——玩家自己发现的)。 - 🎨 **前端 ExploreModal**:方向选择器(4 方向)+ 步进选择器(50/100m)+ 位置显示。GamePage 右上角新增 🧭 探索按钮(Street 时高亮)。store.exploreDirection / player_wander_coords_km 同步。 - 🔒 **认知独立性**:玩家自己探索发现的店不写入林瑶的 known_locations(玩家独立认知)。 #### V97-A - 游荡坐标模型 + 方向探索引擎 - 🧮 **`app/services/explore_engine.py`**(新模块):`relative_coords` / `classify_direction`(八方位细分)/ `distance_km` / `walk_step` / `find_first_unknown_in_direction`(方向+距离+known 三层过滤,取最近未发现店)/ `step_travel_time_min` / `step_energy`。DIRECTIONS / EXPLORE_STEP_CHOICES SSOT。 - 📍 **游荡坐标字段**:`SisterState.wander_coords_km` + `GameState.player_wander_coords_km`(Optional[List[float]],None=在家/店内,向后兼容老存档)。方向判定基于此坐标(相对当前位置)。 - ✅ **测试**:1879 passed(+95 新测试:explore_engine 47 + explore_api 22 + explore_sister 26)。 ### V96 - Gemma 小模型接入(NPC 对话 + 司机认路 + 每日促销)`[feature/v96-gemma-npc]` > 接入 Google `gemma-4-26b-a4b-it`(Gemini API)用于低风险、轻结构化任务:出租车司机理解乘客需求、店员回应询问。主叙事(林瑶 Agent)仍走 deepseek。gemma 只转述不创造世界事实——营业时间/价格/货架全查 SSOT。 #### V96-D - 每日促销系统 - 🏷️ **`app/services/promotion_engine.py`**(新模块):`roll_daily_promotion()` 纯 Python 规则生成每日促销(1-2 家店、每店 1-2 SKU、折扣率 0.7-0.9)。只给饮品店/便利店/超市做促销(药店/菜市场/人才市场不参与)。候选从 `world_data.SHELVES` 动态派生。 - 💰 **`WorldEngine.price()` 加 `promo` 可选参数**(`app/services/world_engine.py`):促销折扣叠加在 ingredient 倍率之后。5 处价格消费者传 `state.daily_promotion`(妹妹买/看 + 玩家买/看/吃)。 - 📅 **每日重置 roll**:`GameState.daily_promotion` 字段,`game_tick` Phase F.2 + `time_engine` 离线追赶镜像同步。 - 🔴 **红线**:促销金额/折扣由 Python 算(不用 LLM),gemma 只在 ask_shopkeeper 时转述。 #### V96-C - ask_shopkeeper 店员 NPC 对话工具 - 🗣️ **`ask_shopkeeper` 工具**(`engines/agent_tools.py`):到店后可向店员询问营业时间/招工/促销/卖什么。`Street_*` 店铺位置自动暴露,Street/家不暴露。纯信息查询不改变状态。 - 👤 **`app/services/shopkeeper_prompt.py`**(新模块):从 SSOT 动态派生店员"知情范围"(营业时段/能力/招工/货架粗分类/每日促销)+ 9 种店员人设(按 building 映射:便利店老板爽利 / 药店店员专业 / 人才市场中介精明 / 蜜雪店员活泼可爱等)。 - 🔁 **双路径**:gemma 生成角色化回答(30 字内)→ 失败降级 `_fallback_response` 模板话术(按问题关键词匹配:营业/招工/促销/卖什么)。 - ✅ **E2E 验证**:蜜雪问营业时间→"10点开门"、便利店问促销→"热肉包和关东煮打折"、人才市场问招工→"发传单100/家教250/搬砖350"。 #### V96-B - call_taxi 升级(gemma 自然语言理解 + 关键词兜底) - 🚕 **`_gemma_match_taxi_destination`**(`engines/agent_tools.py`):gemma 从远店候选清单(distance > 0.5km:菜市场/人才市场/超市/喜茶)里选目的地。候选含 `building.note` + 货架粗分类(复用 `place_perception._shelf_cues`)。gemma 只能从候选里选,清单外 key 视为 null(防越界)。 - 🔄 **降级链**:先 gemma → 失败/越界 → 降级 `_match_taxi_destination` 关键词匹配 → 都失败"听不懂"提示。理解关键词覆盖不到的自然表达("想买点肉"→菜市场)。 #### V96-A - Gemma 基础设施(可插拔 client + 轻量网关) - 🔌 **`app/gemma_gateway.py`**(新模块):`gemma_chat`/`gemma_json` 轻量包装,失败 return None 不 raise(调用方降级)。 - ⚙️ **`app/config.py`**:`GEMMA_API_KEY`/`GEMMA_MODEL`/`GEMMA_PROXY` 配置(兼容 `GEMINI_API_KEY` 别名),client 延迟创建。`is_gemma_enabled()` 可插拔判定。 - 🌐 **代理解析**(`_resolve_proxy`):优先级 环境变量 > `.env GEMMA_PROXY` > Windows 注册表(支持 PAC 自动配置模式 `ProxyEnable=0` + `AutoConfigURL`)。通过 `httpx.Client(proxy=...)` 注入 `HttpOptions`(google-genai Client 构造时固定传输层,之后改环境变量无效)。 - 🔴 **红线**:gemma 只转述不创造世界事实,主叙事仍走 deepseek。 - ✅ **测试**:1755 passed(+71 新测试:gemma_gateway 20 + taxi V96 8 + promotion 24 + shopkeeper 29)。 ### V95 - 室友叙事(独立经济 + 陌生开局 + 逐步探索 + 术语净化)`[feature/v95-roommate-narrative]` > 范式转变:林瑶从「被父母留下的妹妹」变为「独立的生命体室友」。去掉经济依赖(给零花钱)、陌生开局(全新认识过程)、世界逐步探索(不再一上街就看到所有店)、术语全面净化(妹妹→林瑶)。 #### V95.4 - 陌生期行为门控 + 发现系统重构 + 术语迁移 - 🚫 **陌生期 ask_player 好感度门控**(`engines/instinct_engine.py`):好感度 <15(陌生人档)时,饥饿方案不枚举「告诉室友你饿了」(极端饥饿 ≥85 例外)、社交方案不枚举 want_company、依恋内在声音不触发「想跟室友一起吃」。修复陌生人与「主动问你吃了吗」的自相矛盾。 - 🔧 **工具暴露门控**(`engines/agent_tools.py`):`get_tools_for_location` 新增 `favorability` 参数,陌生期非紧急时不暴露 `ask_player` 工具。`engines/sister_agent.py` 传参接入。 - 🗺️ **发现系统重构——逐步探索**:便利店初始已知(种子感知「记得附近有个便利店,离家大概三百米」),其他店需探索。`look_around`(`engines/agent_tools.py`)重构:每次只揭示一家最近的未知店(按距离由近到远:蜜雪→茶百道→药店→瑞幸),已知店在 `spots` 标记 `known=True` 不重复写感知。prompt(`engines/agent_prompt_builder.py`)引导「每次只能注意到一家新店」。 - 🧹 **术语迁移 妹妹→林瑶**: - Prompt 端:`app/utils.py` 的 15 处 section header(`## 妹妹位置`→`## 林瑶位置` 等)、记忆压缩/提取/判断 prompt(`engines/memory_v2.py` 的 `L1_SUMMARY_PROMPT`、`app/services/memory.py` 的 `FACETED_CONSOLIDATION_PROMPT`、`app/services/intent.py`)、感知前缀「妹妹行为:」→「林瑶行为:」(`app/services/game_tick.py` 写端 4 处 + `engines/agent_prompt_builder.py` 读端原子绑定)、物品描述(`app/services/items.py`)、API 错误(`app/api/routes.py`、`engines/action.py`)。 - UI 端:`MemoryModal`(妹妹视角→林瑶视角)、`DiningModal`(喂妹妹→喂林瑶)、`InventoryModal`(动作文本)、`useGameStore` 开场白(改为室友叙事)、`helpers.ts` 坏结局文案。 - 关键词匹配数组保留「妹妹」做向后兼容(玩家口语输入仍能匹配)。 - ✅ **测试**:1700 passed, 2 skipped。新增陌生期门控测试(favor=8 不枚举 ask_player / extreme hunger 例外 / 依恋声音静默)、逐步发现测试(每次只揭示 1 家 / known 标记)。更新已知默认测试(便利店初始已知)。 #### V95.3 - 独立经济 + 陌生开局 + 微信门控 - 🚫 **去掉给零花钱功能**:删除 `GiveMoneyModal`/`ActionDock` 按钮/store/api/types + 后端 `/api/give_money` 端点 + WS。林瑶不再向玩家要钱。 - 💰 **林瑶初始 pocket_money=200**(`app/models.py`):靠打工自给,不依赖玩家给钱。 - 🆕 **陌生开局**(`app/models.py`):初始 `favorability=8` / `trust=15` / `warmth=15`(命中 Lv1 陌生人档)。玩家与林瑶互不认识,全新认识过程。 - 📱 **微信门控**:新增 `WeChatState.exchanged_wechat` 字段。未交换微信时不暴露 `check_wechat`/`reply_wechat` 工具(`engines/agent_tools.py` 的 `get_tools_for_location` 加 `wechat_exchanged` 参数)、前端微信按钮隐藏(`GamePage.tsx`)、`/api/sms` 拒绝发送。`InputArea.tsx` 未交换时强制走当面聊天模式。 - 🤝 **微信交换自然检测**(`engines/social_milestones.py`,新文件):关键词匹配("加微信"/"交换微信"/"留个联系方式"等)+ 好感度 ≥10 + 同意信号("好"/"行"/"可以"等)30 分钟窗口内。`game_tick.py` 每 tick 检测,命中后 `set_wechat_exchanged(True)` + `mark_milestone("wechat_exchanged")`。 - 🎭 **Lv1 陌生人模板性格化**(`app/utils.py`):去掉硬编码称谓("叫他喂/装没听见/绝对禁止叫他哥"),保留核心约束(社交距离/简短回应/不主动)但语气和称谓交给 LLM 按性格把握("按你的性格特点来")。 - ✅ **测试**:更新因默认值变更导致的测试(`test_sister_manager` favorability 断言、`test_sister_workflow` pocket_money=0 显式设置、`test_wechat` wechat_exchanged=True、`test_instinct_engine` trust/warmth 抬高)。 #### V95.2 - 内容开关前端 gate 点 - 🖥️ **`SettingsModal` + `useSettings`**(前端):呼吸动画/触摸开关 UI,本地持久化。 - 🔗 **`CharacterSprite` 触摸区过滤**:按 `sensitiveTouchEnabled()`/`touchEnabled()` 过滤触摸区渲染。 - 🔗 **`ActionDock` 试探/靠近按钮门控**:触摸禁用时不显示。 #### V95.1 - 内容开关架构(后端 SSOT) - 🆕 **`engines/content_mode.py`**(单一事实来源):`is_safe_mode`/`touch_enabled`/`sensitive_touch_enabled`/`allowed_touch_actions`/`desire_intimate_enabled`/`favor_max_level`/`content_mode_summary`。所有"敏感/亲密内容是否启用"判定走本模块。 - 🔧 **`app/config.py` env 开关**:`CONTENT_MODE`(full/safe)+ 细分(`TOUCH_MODE`/`DESIRE_INTIMATE`/`FAVOR_MAX`)+ `_resolve_bool` 显式优先。 - 🔗 **Gate 点接入**:`engines/action.py` `execute_action` 入口单点降级、`engines/desire_engine.py` + `engines/schedule.py` 防御、`app/utils.py` `get_favor_template` 按 `FAVOR_MAX` 钳制、前端 `GameState.content_mode` computed_field。 ### V94 - 不透明感知世界(阶段 1-6)`[feature/perception-world]` > 范式转变:世界规律从「写死在 prompt 里全透明」改造为「规律不透明,需通过探索/感知/记忆来认知」。阶段 1 落地记忆基础设施,阶段 2 落地到达感知注入,阶段 3 落地探索发现机制,阶段 4 落地出租车司机 NPC,阶段 5 落地 prompt 瘦身,阶段 6 落地玩家 UI 不透明化。 #### V94.6 - 玩家 UI 不透明化(收尾) - 🆕 **`/api/places/known` 端点**(`app/api/routes.py`):返回 `player_known_locations` 过滤的地点列表(`{key, name, is_indoor, is_shop, is_open}`)。营业状态实时算(`world.is_open`),但**不报具体营业时段**——规律不透明。配套 WS 包装 `places.known` + 前端 `api.placesKnown()`。 - 🖥️ **React `TripPlannerModal.tsx` 目的地列表认知门控**:`visibleDestinations` 按 `store.player_known_locations` 过滤——玩家只看到已发现的店,未发现的店不显示。默认仅家(无店铺可选),随探索渐进解锁。 - 🔇 **`TripPlannerModal` 移除 `hoursNote` 显示**:目的地选择不再预先显示"6:00-18:00"等营业时段,关门只显示"没开门"。营业状态仍实时从后端取(`shop.list` 的 `is_open`),但具体时段不透明。 - 🔗 **`useGameStore.ts` + `types.ts` 同步 `player_known_locations`**:GameState 接口 + initialGameState + `_applyState` 三处接入,字段随 WS `state.get`/`state.update` 自动同步。 - 🔧 **`routes.py:market_list` 关门错误去时段**:`"菜市场已关门,营业时间为 6:00-18:00"` → `"菜市场的卷帘门拉着,还没开门"`。 - ✅ **12 个新单测**(`test_places_known_api.py`):默认仅家/室内标记/Street 标记/室内恒开/已知店铺出现/未知店铺缺失/营业状态实时/不报时段字段/不报时段数字/发现增长/全店铺可发现 #### V94.5 - prompt 瘦身(认知层) - 🧹 **`agent_prompt_builder._location_rules` 去硬编码营业时段**(`engines/agent_prompt_builder.py`):7 个店铺规则全部移除"营业6:00-18:00"/"营业8:00-22:00"等具体时段,改为"用 list_available_food 看卖什么"——规律不透明,到店自己看。 - 🧹 **店铺规则去硬编码品类**:瑞幸"现制咖啡"、喜茶"现制茶饮"、蜜雪"冰淇淋/茶饮"等品类暗示移除,统一引导 `list_available_food` 自查。 - 🔗 **`_outing_flow` 加 call_taxi 引导**(阶段4 集成):出门流程提到远店可通过 `call_taxi` 向司机打听发现,与 `look_around`(近处)互补。 - 🧹 **`app/utils.py:ENVIRONMENT_RULES` 玩家侧动态化**:地点列表从 `player_known_locations` 动态生成(不再写死全部 8 店),移除"不存在奶茶店/早餐店"等失效断言(这些店现在存在了),移除"营业时间6:00-18:00"。 - 🔇 **`action.py`/`trip.py` 关门错误去时段**:`"菜市场已关门,营业时间为 6:00-18:00"` → `"菜市场的卷帘门拉着,还没开门(看起来要晚些时候才营业)"`。 - 🔧 **回归测试同步更新**:`test_cut_d_consumers`(trip/action 关门错误断言改"卷帘门"/"没开门")。 - ✅ **22 个新单测**(`test_prompt_opacity.py`):店铺规则无时段/街道工具引导/出门流程无硬编码/玩家侧动态地点/玩家侧无时段/action+trip 关门不报时段 #### V94.4 - 出租车司机 NPC(简化版,关键词模板匹配) - 🆕 **`call_taxi(description)` 工具**(`engines/agent_tools.py`):出租车司机 NPC——妹妹在街上找不到想去的店时,向司机描述需求(自然语言),司机推荐对应的远店并告知地点。 - 🗺️ **远店发现机制**:`look_around` 受 0.5km 阈值看不到远处店铺(菜市场 1.01km / 人才市场 1.65km / 超市 0.54km / 喜茶 0.51km),这些远店只能通过向出租车司机打听才能得知。司机满城跑最熟,是远店的发现渠道。 - 🤖 **简化版:关键词模板匹配,不调 LLM**。`_match_taxi_destination()` 把 description 映射到远店 key:菜/买菜/蔬菜/生鲜/猪肉→Market、打工/找工作/兼职→TalentMarket、超市/采购/日用品→Supermarket、喜茶/多肉→Heytea。无匹配则司机"没太听懂"。 - 🚫 **只发现不移动**:告知地点后妹妹自己 `go_to` 过去(与认知门控架构一致;移动统一入口不分裂)。**不花钱**(送过去才花钱,这里只告知地点)。 - 🧠 **首次发现写入认知**:`mark_location_known(target, also_player=True)`,写 discovery 感知"出租车司机告诉我街那头有家XX" + event。已知地点再问 → `already_known=True`,不重复写感知。 - 🔇 **规律不透明**:司机话术口语化包装("哦买菜啊,前面有个菜市场"),**不报精确距离/营业时段/价格**。 - 🔗 **与 look_around 互补**:近处店(便利店/药店/瑞幸/茶百道/蜜雪)走 look_around 发现,远店走 call_taxi 发现,两条发现路径覆盖全部店铺且不重叠。 - 🔧 **`_LOCATION_TOOL_SET`**:`call_taxi` 加入 Street(路边才能拦到司机)。 - 🔧 **回归测试同步更新**:`test_cut_e_travel.test_far_shop_requires_transit`(go_to Street_Market 前需 mark_location_known——认知门控)。 - ✅ **49 个新单测**(`test_taxi_npc.py`):工具注册/关键词模板匹配(含参数化)/室内拦截/缺description/远店发现/不泄露距离时段价格/只发现不移动/玩家同步/与look_around互补/完整发现管线 #### V94.3 - 探索发现机制(go_to 自由 string + look_around) - 🆕 **`look_around` 工具**(`engines/agent_tools.py`):在 Street 上环顾四周,发现附近步行可达的店铺(distance ≤ 0.5km 阈值)。首次发现 `mark_location_known`(写感知+事件)。远处店铺(菜市场 1.01km/人才市场 1.65km)看不到,需通过其他方式得知。 - 🔓 **go_to 移除 enum**(最大泄露点):`location` 改为自由 string + 认知门控。描述引导用 `look_around` 探索。妹妹只能去 `known_locations` 里的地点。 - 🚫 **go_to 认知门控**:未知地点 → 报错并引导"在 Street 上用 look_around";无效 key → "不知道这个地方"。 - 🔇 **go_to 关门不报时段**:错误消息从"营业时间为8:00-22:00"改为"卷帘门拉着,还没开门"——规律不透明。 - 👁️ **`check_own_status` 返回 `known_locations`**:Agent 可查看自己已知地点列表(name+key),决定去哪。 - 🧹 **`agent_prompt_builder._outing_flow` 去硬编码**:移除 `go_to(Street_Convenience)` 等具体店名 + 移除"菜市场6:00-18:00/瑞幸7:00-22:00"营业时段,改为引导 `go_to(Street) → look_around → go_to(发现的店)`。 - 🔧 **回归测试同步更新**:`test_cut_d_navigation`(enum→no-enum + 营业错误措辞 + 先认知)、`test_cut_d_consumers`、`test_weather`(go_to 店铺前需 mark_location_known)。 - ✅ **27 个新单测**(`test_exploration.py`):schema 无 enum/认知门控/关门不报时段/look_around 基础/认知写入/不泄露数值/check_own_status 返回/端到端探索 #### V94.2 - 到达感知(感知层) - 🆕 **`app/services/place_perception.py`**:到达感知生成器。`describe_arrival(state, loc) -> ArrivalPerception` 根据世界 SSOT(`BUILDINGS.note`/`CAPABILITIES`/`SHELVES`/`BUSINESS_HOURS`)派生"打量"描述;`inject_arrival_perception()` 便利函数写感知。 - 🧠 **数据驱动,规律不透明**:描述只暗示品类("冰柜里有饮料"/"门口贴着招工启事"),**不报具体价格/时段/SKU 数量**。营业状态只说"开着/快打烊/还没开门",不说"7:00-22:00"。 - 🔗 **两个注入点接线**: - `app/services/game_tick.py`(异步到达):`pending_destination` 处理后注入(店铺到达主路径) - `engines/agent_tools.py`(室内即时到达):`travel_min<=5` 的短距离移动(如 Street↔便利店)注入 - 🚪 **室内房间跳过**:家的 4 个房间 + Street 到达不触发"发现"(一开始就知道),`ArrivalPerception.skipped=True`。 - 🆕 **首次 vs 重访**:首次到达 → `mark_location_known()`(写 discovery 感知+事件)+ 丰富"打量"描述;重访 → 简短"又回到了XX"。 - ✅ **47 个新单测**(`test_place_perception.py`):室内跳过/首次标记/重访/营业状态暗示/能力暗示/货架品类暗示/不泄露数值/inject 写入/全店铺覆盖 #### V94.1 - 记忆基础设施(数据层) - 🆕 **`SisterState.known_locations: List[str]`**(`app/models.py`):妹妹主观已知存在的地点 key 列表。默认仅家(`LivingRoom`/`Bedroom`/`Kitchen`/`Bathroom`/`Street`),所有店铺初始未知。**认知层门控**("知道有这个地方"),区别于 `_LOCATION_TOOL_SET` 的**地理层门控**("身在此处才能用此工具")。 - 🆕 **`GameState.player_known_locations: List[str]`**(`app/models.py`):玩家侧同步已知列表,UI 只渲染这些地点。默认与妹妹同步。 - 🆕 **SisterStateManager helpers**(`engines/sister_state_manager.py`): - `is_location_known(loc)` / `is_player_location_known(loc)` — 只读判定 - `mark_location_known(loc, *, also_player=True, perception_note="")` — 首次加入写感知+事件,默认同步玩家侧;返回是否首次 - `mark_player_location_known(loc)` — 仅玩家侧(如玩家听说但妹妹尚未感知) - 🛡️ **向后兼容**:旧存档(无字段)反序列化用默认值;Pydantic 实例隔离验证通过(两个 GameState 不共享列表)。 - ✅ **23 个新单测**(`test_known_locations.py`):默认值/排除所有店铺/只读判定/首次重复/玩家同步/also_player=False/感知写入/事件/自定义备注/玩家侧提前已知/仅玩家侧/实例隔离/旧存档兼容 ### V93 - 测试套件深度审计与瘦身 `[feature/v62-emotion-engine]` > 三轮精简,测试函数 ~1475→~1180(-20%),代码 ~16422→~15016 行(-9%)。**首次达成全绿:1440 passed / 2 skipped / 0 failed。** - 🗑️ **第一梯队:删除冗余/死代码/虚假覆盖率测试(-68 测试,26 文件)**(`tests/unit/`):删除回归锁(守护已删代码)、fake-coverage 测试(测试 inline mock 逻辑而非真实代码)、跨文件重复、trivial 断言。整文件删除 `test_daily_summary_guard.py`。 - 🔧 **第二梯队:参数化合并同结构测试(-245 行,5 文件)**:`test_availability_parser`(38→14 函数)、`test_weather`(86→60)、`test_v62_emotion`/`test_v65_desire_wake`/`test_v68_mini_games` 同结构用例合并为 `@pytest.mark.parametrize`。 - 🔧 **第三梯队:剩余小簇参数化 + retrieval importorskip(-188 行,7 文件)**:`test_cut_d_consumers`/`test_instinct_engine`(删 TestTravelCost 与 test_cut_e_travel 重复的步行公式)/`test_metabolism`/`test_skill_rust`/`test_v69_cooking_skill`/`test_v87_cooldown` 参数化合并。 - 🐛 **修复 chromadb 假阳性**(`tests/unit/test_retrieval.py`):`test_index_and_recall_roundtrip`/`test_delete_removes_vector` 在 chromadb 缺失时静默 pass(index/delete no-op,recall 返回空,断言平凡成立)→ 加 `pytest.importorskip("chromadb")` 正确跳过。 - ✅ 测试套件 1517p/1f → **1440p/2s/0f**;函数 -295;代码行 -1402 ### V92 - 电动车物理位置(切法 E2)`[feature/v62-emotion-engine]` > 电动车从"拥有标志位"升级为"有物理停放位置的实体"。车随骑行段移动,人车分离时自动步行取车。 - 🆕 **`ebike_location` 字段**(`app/models.py`):空串=未拥有;非空=停在该 location key。旧存档兼容(items 有 ebike 但 ebike_location 空 → 视为停 LivingRoom)。双重真相源:items 里的 ebike ItemInstance = 购买凭证(unlock 判定),ebike_location = 位置权威。 - 🆕 **trip 步骤展开——人车分离自动取车**(`engines/trip.py`):ebike 骑行段若车不在当前地点,自动前置一段步行取车(walk-fetch)TripStep,玩家无感。取车段算 AP(室内首段+1)、算时长。骑行后车停到目的地(车随骑行段走)。update_trip_location/check_trip_completion 零改动——消费者只读 step.target/duration_min/transport。 - 🆕 **购买写入位置**(`app/api/routes.py`):shop_buy 购买电动车时 `ebike_location = shop_key`(在哪家店买就停哪家店门口)。 - 🎨 **前端位置感知**(`frontend-react/src/components/TripPlannerModal.tsx`):`estimateTravel` 镜像后端展开逻辑(walk-fetch + ride 分段);`hasEbike` → `ebikeLocation`(位置感知,含旧状态兼容);交通按钮 locked 状态 + "电动车停在 XX,将自动步行取车" tooltip。 - 🆕 **`GameState.ebike_location` 类型**(`frontend-react/src/types.ts`) - ✅ **15 个新单测**(`test_cut_e2_ebike_parking.py`):字段默认值/旧存档兼容/购买写入/人车同处单段/人车分离双段展开/取车 AP/时长叠加/无展开条件/多跳骑行/连续骑行/骑行后购物/完成留车/前端镜像一致性 ### V91 - 世界核心能力改造(Layer 0 + 切法 A→E)`[feature/v62-emotion-engine]` > 世界从"硬编码散落各处"改造为完整的 SSOT(Single Source of Truth)体系:地图层 → 品类货架 → 能力系统 → 消费者迁移 → 导航接入 → 交通系统。设计文档:`docs/品类货架设计_V1.md`。 #### V91.6 - 交通系统(切法 E1/E3/E4) - 🆕 **距离/交通方式 SSOT**(`app/services/world_data.py`):`TransportMode` dataclass + `TRANSPORT_MODES`(walk 4km/h 0¥ / bus 8km/h 2¥ / ebike 20km/h 5¥ unlock=item:ebike / taxi 60km/h 15¥)。 - 🆕 **`world.travel()` 引擎**(`app/services/world_engine.py`):Dijkstra 寻路 + 距离÷速度算时长、按交通方式算费用/精力/解锁门控。`TravelCost` 返回 time_min/money/energy。 - 🆕 **电动车道具**(`app/services/world_data.py` + `items.py`):`ebike` ItemDef,购买后 unlock ebike 交通方式。 - 🔧 **妹妹侧接入**(`engines/agent_tools.py`):`go_to`/`_calc_travel`/`ride_home` 接入 `world.travel()` 距离换算。 - 🔧 **玩家行程接入交通方式**(`engines/trip.py` + `app/models.py`):`TripStep.transport` 字段;submit_trip 按 transport 算时长/费用;**铁律 §3:移动 = 距离 ÷ 速度 + 成本**。 - 🎨 **TripPlanner 交通方式选择器**(`frontend-react/src/components/TripPlannerModal.tsx`):4 种交通方式按钮 + locked 状态 + 估价(时间/费用)。 - ✅ 8 个新单测(`test_cut_e_travel.py`) #### V91.5 - 导航接入(切法 D-Nav) - 🔧 **6 新店接入**(`engines/agent_tools.py` + `trip.py` + `routes.py`):pharmacy/supermarket/luckin/heytea/chabaidao/mixue 统一加 `Street_` 前缀。妹妹侧 go_to enum +6、shop_locs 从 BUILDINGS 派生、`_LOCATION_TOOL_SET` +6、buy_food type 放宽含 MEDICINE(药店买药);玩家侧 shop_list/shop_buy 货架感知;trip 完成补货架校验;constants + ~10 耦合集合同步。 - 🎨 **前端 TripPlanner +9 目的地**(`frontend-react/src/components/TripPlannerModal.tsx`):数据驱动,DESTINATIONS 从后端营业状态派生。 - ✅ 24 个新单测(`test_cut_d_navigation.py`) #### V91.4 - 消费者迁移(切法 D:营业时间 + 打工副本统一) - 🔧 **5 处 `6:00-18:00` 硬编码改查 `world.is_open()`**:agent_tools.py:687 / action.py:510 / trip.py:111 / routes.py:1956,1997;`/api/market/list` 返回 `is_open` 字段,前端 TripPlannerModal 改读后端。 - 🔧 **打工副本统一到 SSOT**(`engines/trip.py` JOB_INCOME/JOB_DURATION + `action.py` JOB_UNLOCK_COUNT + `agent_tools.py` _sister_list_jobs/_sister_start_work 三处副本)→ 改读 `world_data.JOBS`。玩家侧 construction energy 50→70(SSOT 裁决)。 - 🔧 **地点门控归位**:work_convenience 从人才市场归位到便利店(`via_market=False`)——`_LOCATION_TOOL_SET` 便利店增加 list_jobs/start_work。 - ✅ 11 个新单测(`test_cut_d_consumers.py`) #### V91.3 - 能力系统数据 SSOT(切法 C) - 🆕 **`BUSINESS_HOURS`**(`world_data.py`):10 店营业时段(Street_Market 6:00-18:00 等),None=24h。 - 🆕 **`CAPABILITIES` + 5 能力常量**(`world_data.py`):shop/dine_in/work/clinic/drink + 10 店能力声明。 - 🆕 **`JobDef` + `JOBS`**(`world_data.py`):统一 trip/action/agent_tools 三处 job 副本;job 归属模型(building + via_market + unlock + min_energy)。 - 🆕 **4 饮品店 Building 条目**:luckin/heytea/chabaidao/mixue。 - 🆕 **查询函数**(`world_engine.py`):`is_open(key,hour)` / `business_hours(key)` / `has_capability(key,cap)` / `capabilities(key)`。 - ⚠️ 本轮不改消费者(5 处营业时间硬编码留待切法 D) #### V91.2 - 货架消费者迁移(切法 B:合规修复 + 货架感知 + 价格倍率) - 🔧 **合规红线修复**(`app/services/items.py`):`CONVENIENCE_ITEM_IDS`/`MARKET_ITEM_IDS` 从硬编码集合改为从 `world_data.SHELVES` 派生——便利店不再错误包含 medicine_*(药品只能在药店)和 backpack_*(应只在超市)。 - 🔧 **货架感知**(`app/api/routes.py`):shop_list/shop_buy 按玩家所在店铺取货架 + `world.price()` 价格倍率。 #### V91.1 - 品类货架数据 SSOT(切法 A) - 🆕 **`SHELVES`**(`world_data.py`):店铺→SKU 集合映射(显式货架,店铺间交集关系非层级推导)。 - 🆕 **`DRINK_ITEMS`**(`items.py`):16 个饮品 SKU(蜜雪/茶百道/喜茶/瑞幸各 4),新 `drink` 品类。 - 🆕 **`PRICE_MULTIPLIERS`**:菜市场按 ingredient 子类打折(蔬菜 0.4× / 豆腐 0.5× / 肉类 0.7× 等)。 - 🆕 **`sells(building,sku)` / `price(building,sku)`**(`world_engine.py`) #### V91.0 - 地图层 SSOT(World Layer 0) - 🆕 **路网 + 建筑 + 距离 SSOT**(`app/services/world_data.py`):`Building` dataclass(key/name/x/y/note)+ `ROADS`(路网邻接)+ `BUILDINGS`(14 地点);室内(LivingRoom/Bedroom/Kitchen/Bathroom)+ 街道 + 9 店铺。 - 🆕 **`WorldEngine`**(`app/services/world_engine.py`):`travel(from,to,mode)` Dijkstra 寻路 + 距离计算;`get_world()` 单例 + `reset_world()` 测试注入。 - 🛠️ **世界布局工具**:路网可视化 + Dijkstra 寻路 + 持久化。 - 🐛 **幽灵 coffee 建筑清理**:4 饮品店坐标/距离校准。 #### V91 配套修复(本版本区间内) - 🐛 **auto-wake 半唤醒竞争条件修复(V43.4)**(`engines/schedule.py`):auto-wake 改为"自动解除睡眠不可打断",修复 `bf93fb8` 熬夜惩罚用真实时间换算后的 sleep_start_time 口径不一致。确定性复现测试 `2eb895f`。 - 🐛 **叙事层瞬移修复(P12+P11)**(`engines/sister_state_manager.py`):narrative_override 不再能 teleport sister。 - 🐛 **game_tick 持久化分离**(`app/services/game_tick.py`):game_tick 改为纯领域函数,persistence.auto_save 移至 scheduler 层(约束 9)——杜绝"幽灵用户"bug(测试写 saves/test_user/ 被启动时 load_all 当真实租户)。 - 🐛 **ghost user 多租户日志混淆根因**:日志注入 PID(`%(process)d`)+ user_id,消除跨用户误读。 - 🐛 **Python 3.11 f-string 嵌套引号语法错误** + sister agent tool schema(expose bench_rest, drop misleading go_to.duration_min) ### V90.9b - Brother Availability Extractor(哥哥忙碌语义提取)`[feature/v62-emotion-engine]` - 🐛 **根因修复**(`app/services/availability_parser.py`):玩家说「你乖乖吃东西。我要忙会儿工作。差不多到11点30分的样子」时 `_check_exclusion()` 因 `text.startswith("你")` 整句排除 → `brother_availability` 从未写入 → 妹妹反复 ask_brother → pending timeout + suspicion + "被忽视"记忆污染 - 🔧 **`_check_exclusion()` 分句信号检测**(`app/services/availability_parser.py`):改为"分句 + 第一人称【单数】信号检测 + 整文关联",任一句携带哥哥忙碌/离开/睡觉信号即不整体排除,保留跨句上下文("我要忙会儿工作"+"差不多到11点30分"关联)。排除「我们晚上打游戏吗?」这类共同提议问句(区分单数"我"与"我们") - 🔧 **`_RE_END_TIME_CN` 支持「点XX分」分钟形式**(`app/services/availability_parser.py`):原只匹配"点半"/`到16:00`,「到11点30分」被误解析为 11:00;现在正确解析为 11:30。`_RE_MINUTE` 加负向断言避免吞掉钟点里的"30分" - 🆕 **小模型语义提取器**(`app/services/brother_availability_extractor.py`):`BrotherAvailabilityExtractor` 经 RETRIEVAL 通道(qwen3.6-35b-a3b)从玩家消息提取结构化 JSON(availability/instruction/promise/followup/player_plan/social_suppression)。只产 raw JSON,不写状态、不做 schema 修正 - 🆕 **高召回 prefilter `availability_prefilter()`**:宁多触发不漏关键忙碌/离开/睡觉/回来/recovery 信号;普通闲聊不触发 - 🆕 **deterministic 三层 validator**(`app/services/brother_availability_validator.py`):`validate_extraction()` + `wrap_parser_result()` 做 JSON/schema/时间/业务归一化,**不写状态**。时间锚点红线:`expires_realtime` 必须按 game clock 重算,LLM 的 `until_iso` 只取钟点求 delta、ISO 时间戳本身不被采用;过去时间拒绝;>8h 低置信降级;模糊时间不编 iso - 🔧 **统一编排 hook**(`app/api/routes.py`):prefilter→extractor→(LLM失败则parser fallback)→validator→`_apply_validated_extraction` 单一写入口。旧 `_apply_brother_availability` 委托兼容 - 🆕 **`BrotherAvailability.until_text`**(`app/models.py`):原文时间短语(如"11点30分左右"),prompt 展示用;继续用现有 `expires_realtime` 表达 busy_until,不新增时间戳字段 - 🆕 **ambient presence 工具层硬规则**(`engines/agent_tools.py`):`_is_ambient_presence()`/`_convert_to_ambient_presence()` 在 `_sister_ask_brother` 创建 pending 前拦截"我就在旁边待一会儿/我不打扰你/你忙你的我安静待着/不用理我"等短语 → 转 `start_activity(resting)`,**绝不建 pending/timeout/relationship_event**(即便 Agent 错选 ask_brother) - 🔧 **focused gate 精确化**(`engines/agent_tools.py`):加 `and not is_expired(now)`;拒绝文案含时间边界"哥哥正在专注工作,预计到11:30,先不要打扰他" - 🔧 **busy 期 timeout 中性化**(`app/services/game_tick.py`):pending 超时/idle-auto-expire 时若哥哥忙碌,不加 suspicion、不写"被忽视"、不写负面 relationship_event,感知用中性事实"哥哥在忙工作,林瑶选择安静待着或回去休息,没有继续打扰" - 🔧 **prompt 展示忙碌时间边界**(`engines/agent_prompt_builder.py`):focused/light_busy 显示"预计到 HH:MM 左右"+行为建议"想陪哥哥时可以安静待着或自己休息";忙碌时不显示"哥哥的计划:无" - ✅ **28 个新单测**(`test_brother_availability_extractor.py`):Prefilter(2)/Extractor(4)/Validator(7)/RuntimeWrite(3)/ParserFix(4)/Prompt(1)/Gate(2)/Ambient(4参数化)/BusyTimeout(1) ### V90.8b - Segmented WeChat Bubbles(分段微信气泡)`[feature/v62-emotion-engine]` - 🆕 **分段文本分割器 `_split_wechat_text()`**(`engines/agent_tools.py`):将 reply_wechat 的长文本按优先级拆分为 1-4 条气泡——`` 标记 > `\n` 换行 > 句末标点 `。!?~…` > 逗号 `,`。后处理合并 <4 字短段、上限 4 段、相邻去重 - 🆕 **打字延迟计算 `_calc_typing_delay()`**(`engines/agent_tools.py`):`0.6 + len × uniform(0.08,0.14) + uniform(0,0.4)`,clamp [0.8, 3.5]s,模拟真人打字节奏 - 🔧 **`_sister_reply_wechat` 延迟创建**(`engines/agent_tools.py`):不再直接 `create_wechat_message()`,改为准备 segment_data 追加到 `state._pending_wechat_groups`。消息仅在实际投递时写入 state,取消的段永远不入 state - 🆕 **异步逐条投递 `_deliver_message_group()`**(`app/services/scheduler.py`):异步协程逐段推送 `wechat.typing` → sleep(delay) → `create_wechat_message()` → `wechat.new_message`。`finally` 中写入实际投递文本的 perception - 🆕 **Per-user 投递追踪**(`app/api/ws_manager.py`):`_active_deliveries` dict 按 user_id 管理活跃投递 Task,支持 `cancel_active_delivery()` 取消剩余段 - 🔧 **玩家回复取消**(`app/api/routes.py`):`send_sms` 在处理消息前取消活跃投递;`open_wechat` 返回 `sister_typing` 状态 - 🔧 **game_tick Phase E.5**(`app/services/game_tick.py`):消费 `_pending_wechat_groups`,调度异步投递 - 🆕 **WeChatMessage +3 字段**(`app/models.py`):`message_group_id`/`part_index`/`part_total`,旧消息默认兼容 - 🎨 **前端打字指示器**(React `WeChatModal.tsx` + Vue `WeChatModal.vue`):`wechat_typing` 状态 + bounce-dot 动画 + 消息 dedup(`wechat.new_message` 不设 typing=false 避免闪烁) - ✅ **25 个新单测**(`test_segmented_bubbles.py`):SplitText(12)/TypingDelay(3)/SegmentedReply(6)/BackwardCompat(2)/V90.8aRegression(2) ### V90.8a - Outgoing Communication Gate(出站通信闸门)`[feature/v62-emotion-engine]` - 🐛 **修复 reply_wechat 刷屏漏洞**(`engines/agent_tools.py`):Agent 每 30 秒 tick 通过 `reply_wechat` 主动发消息,绕过 `ask_brother` 的 5 层防护。根因:reply_wechat 零防护 + prompt 主动邀请"可以 reply_wechat" - 🆕 **`_has_reply_context()`**(`engines/agent_tools.py`):反向遍历微信消息,仅当最新一条是哥哥发的(妹妹还没回复)且 <5min 时返回 True。妹妹回复后 context 关闭 - 🆕 **`_outgoing_comm_gate()`**(`engines/agent_tools.py`):三层统一闸门——Layer 1: 60s 重复文本(所有场景)、Layer 2: waiting pending 拦截(仅 proactive)、Layer 3: 10min proactive cooldown(仅 proactive)。Emergency override (hunger≥85/health≤40) 只作用于 ask_brother - 🔧 **reply_wechat handler 改造**:无 reply context → 直接拒绝(error 含 "ask_brother");有 context → 仅 60s dup check;成功后只更新 dup cache,不更新 proactive cooldown - 🔧 **write_sticky_note handler 改造**:回复哥哥便利贴 = 回复场景(仅 dup check);主动留言 = proactive(受完整 gate) - 🔧 **ask_brother handler 补充**:新增 60s dup check;成功后更新三个 outgoing_comm 字段 - 🔧 **工具列表动态控制(双层防护)**:`get_tools_for_location` 新增 `has_wechat_reply_context` 参数,无 reply context 时 `reply_wechat` 不出现在工具列表中;`sister_agent.py` 传递该参数 - 🔧 **Prompt 改写**(`engines/agent_prompt_builder.py`):无新消息时从"可以 reply_wechat"改为"不要用 reply_wechat 主动发起聊天,请使用 ask_brother" - 🆕 **GameState +3 字段**(`app/models.py`):`outgoing_comm_text`/`outgoing_comm_realtime`/`outgoing_comm_proactive_realtime` - ✅ **10 个新单测**(`test_outgoing_comm_gate.py`):ReplyContext(3)/ReplyWechatGate(3)/ProactiveCommunicationGate(2)/AskBrotherIntegration(1)/EmergencyOverride(1) ### V90.7b - Item Provenance(物品购买来源追踪)`[feature/v62-emotion-engine]` - 🆕 **`bought_by` 字段**(`app/models.py`):`ItemInstance` 新增 `bought_by: str = ""`("player" | "sister" | ""),记录谁购买了该物品(provenance, not ownership) - 🔧 **所有购买路径设置 bought_by**:`routes.py` shop_buy/market_buy → "player"、`trip.py` check_trip_completion → "player"、`agent_tools.py` _sister_buy_food → "sister" - 🔧 **check_fridge / check_backpack 返回 bought_by**(`engines/agent_tools.py`):食物列表项增加 `bought_by` 字段("哥哥买的" / "自己买的" / "");购物袋工具描述改为"家里的购物袋"(共用临时容器),返回 `location: "家里的购物袋"` - 🔧 **进食事实感知**(`engines/sister_state_manager.py`):`execute_eat()` 中 `bought_by=="player"` 且非 `cooked_by=="player"` 时写入 `"吃了哥哥买回来的{name}"`,**不加 PAD、不加好感**——与 `cooked_by=="player"` 的奖励路径严格区分 - ✅ **11 个新单测**(`test_item_provenance.py`):Model(3)/CheckBackpack(3)/CheckFridge(2)/EatPerception(3) ### V90.7a - Observation Cleanliness(目击感知净化)`[feature/v62-emotion-engine]` - 🐛 **修复 Agent thinking 泄漏**(`app/services/game_tick.py`):移除 `log_action` 中的 `(想了:{_thinking})` 拼接,Agent 内部推理只进 `decision_log`,不再污染 `recent_actions` - 🐛 **修复 `_infer_brother_activity()` 误读妹妹动作**(`engines/agent_tools.py`):从盲读 `recent_actions[-1]` 改为反向遍历只匹配 `"哥哥"` 前缀条目,无匹配返回空串。切断「妹妹动作→目击感知→记忆污染」链路 - 🐛 **修复目击模板语法重复**(`engines/agent_tools.py`):`"他在{X}"` 改为 `"他{X}"`,消除 `"他在在便利店..."` 和 `"他在正在吃..."` 的双前缀问题 - ✅ **8 个新单测**(`test_observation_clean.py`):ThinkingNotInRecentActions(1)/InferBrotherActivity(4)/WitnessedTemplateNoDoubleZai(3) ### V90.6 - First-Time Milestone Tracker(首次里程碑追踪)`[feature/v62-emotion-engine]` - 🆕 **`mark_milestone()` 方法**(`engines/sister_state_manager.py`):幂等标记首次事件(返回 True/False),写入 `GameState.milestones` dict(key→game_day),生成感知 + event_log - 🆕 **两层标记格式**:`memory_eligible=True` → `[首次:key]`(可进入长期记忆),`memory_eligible=False` → `[首次:key:log]`(仅 event_log) - 🔧 **15 个确定性 event_key,覆盖 9 个代码触发位置**:first_self_cook / first_successful_cook / first_eat_brother_cooked / first_eat_self_cooked / first_shared_meal / first_shared_game / first_cook_together / first_watch_tv_together(log) / first_hungry_request_fulfilled / first_self_buy_food / first_go_talent_market(log) / first_work_attempt(log) / first_work_success / first_ask_brother_hungry / first_sticky_note(log) - 🔧 **记忆 Prompt 约束**(`app/services/memory.py`):DAILY_SUMMARY_PROMPT + FACETED_CONSOLIDATION_PROMPT 均禁止 LLM 自行推断"第一次",仅允许基于 `[首次:]` 标记描述 - 🔧 **MEMORY_FACETS milestones 维度**:从"关系里程碑"改为"成长里程碑(仅限系统 [首次:] 标记事件和明确的转折点)" - 🐛 **修复 `_sister_buy_food` 字段名**(`engines/agent_tools.py`):`template.item_id` → `template.id`(Item 模型字段名修正) - ✅ **13 个新单测**(`test_milestones.py`):MarkMilestone(5)/TriggerPoints(4)/Idempotent(2)/PromptRule(2) ### V90.5c - Pending Repeat Ask Suppression(重复催问抑制)`[feature/v62-emotion-engine]` - 🆕 **`ActivePlayerPlan` 模型**(`app/models.py`):短期行动意图追踪(buying_food/preparing_food),30 分钟窗口,自动过期或被实际行动 fulfill - 🆕 **确定性食物计划 Parser**(`app/api/routes.py`):`_detect_player_food_plan()` 关键词匹配玩家食物意图表达("去买点"→buying_food,"做饭"→preparing_food),宁漏勿误 - 🔧 **Same-intent 冷却**(`engines/agent_tools.py`):`_sister_ask_brother()` 新增 15 分钟 same-intent 冷却(fulfill/clear/expire 均触发),紧急覆写按 intent 相关性(hungry→hunger≥85,全局→health≤40) - 🔧 **ActivePlayerPlan 门控**(`engines/agent_tools.py`):哥哥已有食物行动计划时阻止重复 ask_brother(hungry),hunger≥85 紧急覆写 - 🔧 **Instinct 抑制**(`engines/instinct_engine.py`):`_enumerate_hunger_solutions()` 有 active plan 时不枚举 ask_brother 方案 - 🔧 **Agent Prompt 注入**(`engines/agent_prompt_builder.py`):注入哥哥计划状态(含经过时间)+ 软约束规则"不要再催" - 🔧 **闭合感知**(`app/interaction_result_applier.py` + `app/services/game_tick.py`):pending fulfill/reject 时写中性事实感知("哥哥回应了,说要去弄吃的"),防止 L1 摘要推断"哥哥未回复"。不用"答应/承诺"避免 Promise Validator 误判 - 🔧 **Plan 生命周期**(`app/services/game_tick.py` + `app/api/routes.py`):30 分钟自动过期 + hunger<40 自然清除 + shop_buy/cook 主动 fulfill - 🔧 **Resolved intent 追踪**(`engines/sister_state_manager.py`):fulfill/clear 均记录 `last_ask_resolved_realtime` + `last_ask_resolved_intent` - ✅ **14 个新单测**(`test_pending_repeat.py`):FoodPlanParser(3)/ActivePlayerPlanBlocks(2)/SameIntentCooldown(2)/PlanLifecycle(2)/ClosingPerception(2)/InstinctSuppression(1)/ResolvedIntentTracking(2) ### V90.5 - Brother Availability State(哥哥忙碌状态)`[feature/v62-emotion-engine]` - 🆕 **`BrotherAvailability` 模型**(`app/models.py`):5态(available/light_busy/focused/away/sleeping),含 `is_busy()`/`is_expired()` 方法,`GameState.brother_availability` 字段,旧存档兼容 - 🆕 **确定性 Parser**(`app/services/availability_parser.py`):纯正则+关键词,5步流水线(recovery→exclusion→busy-match→duration→end-time),14种忙碌动词,中文数词/结束时间解析,宁漏勿误 - 🔧 **聊天入口 hook**(`app/api/routes.py`):`interact()` 中 parser 自动检测忙碌/恢复表达,`_apply_brother_availability()` 设置状态 + intent-gated pending 清除(仅清除 casual intent,保留 hungry/need_money/pickup) - 🔧 **自动过期**(`app/services/game_tick.py`):Phase D.0 每 tick 检查 `is_expired()`,到期自动恢复 available - 🔧 **Agent Prompt 感知**(`engines/agent_prompt_builder.py`):哥哥位置描述含忙碌状态、focused 时隐藏 ask_brother 段落、忙碌时不压制睡眠 - 🔧 **工具层门控**(`engines/agent_tools.py`):`get_tools_for_location()` 新增 `brother_busy` 参数,focused 时移除 ask_brother 工具 + handler 层二次拦截(紧急需求 hunger≥85/health≤40/energy≤10 覆写) - 🔧 **本能引擎感知**(`engines/instinct_engine.py`):`_attachment_voice()` 忙碌时社交声音→"安静待在旁边"、不压制疲劳睡觉;`_enumerate_social_solutions()` focused 时跳过 ask_brother 方案 - 🔧 **Lv5 行为锚修正**(`app/utils.py`):「禁止冷淡敷衍」→「禁止真正冷漠地伤害」,允许日常简短回应和安静陪伴 - ✅ **61 个新单测**:`test_availability_parser.py`(38) + `test_brother_availability.py`(23) ### V90.5b - Shared Activity Satisfies Pending `[feature/v62-emotion-engine]` - 🔧 **共同活动满足 pending**(`engines/sister_state_manager.py`):`start_shared_activity()` 启动时自动清除兼容的 pending——社交类 intent(want_company/bored/play_game/watch_tv_together/chat)被任何共同活动满足;hungry 仅被 TOGETHER_EATING/TOGETHER_COOKING 满足且 hunger<85;need_money/unknown 保守保留。reason=`pending_satisfied_by_shared_activity`,不加 suspicion,写中性感知 - ✅ **7 个新单测**:`test_brother_availability.py` 扩展至 40 个(新增 `TestSharedActivitySatisfiesPendingV90_5b` 7 tests) ### V90.5a - Pending Defer Fix `[feature/v62-emotion-engine]` - 🔧 **Pending defer helper**(`app/api/routes.py`):`_should_defer_pending_on_busy(pending, state)` 替代硬编码 `_CASUAL_INTENTS` 集合,根据 intent + 妹妹当前状态决定是否 defer——casual 总是 defer、hungry 按 hunger<85 defer、energy 按 energy>10 defer、need_money/unknown 保守保留。defer 不算 rejection,不加 suspicion,不产生"没有收到回应"负面感知 - 🐛 **Stale tool_hint**(`engines/instinct_engine.py`):社交方案 `send_sms(text="...")` → `reply_wechat(text="...")`,修复 V84 遗留的工具名不一致 - ✅ **9 个新单测**:`test_brother_availability.py` 扩展至 33 个(新增 `TestPendingDeferV90_5a` 9 tests) ### V90.4a - Cooking Participation Reward(烹饪参与奖励)`[feature/v62-emotion-engine]` - 🆕 **`cooked_by` 字段**(`app/models.py`):`ItemInstance` 新增 `cooked_by: str`,标记 `"player"` / `"sister"` / `""`,旧存档兼容(默认空) - 🔧 **玩家做饭 70% 份量**(`engines/action.py`):`execute_cook` 玩家每份效果从 50%(÷2)提升至 70%(×7÷10),奖励亲手参与 - 🔧 **妹妹吃哥哥做的饭**(`engines/sister_state_manager.py`):`execute_eat` 检测 `cooked_by=="player"` 时触发额外 PAD 冲力(eat_brother_cooked: P+0.35/A+0.15/D+0.1)、好感 +0.5(含 V62 期望调节)、感知记录 - 🆕 **eat_brother_cooked PAD 事件**(`engines/emotion_rules.py`):独立 PAD 冲力,强于普通 eat_good - 🔧 **生鲜食材包改版**(`app/services/items.py` + `engines/action.py`):`grocery_pack` 从解锁旧 cook_meal 改为拆包获得 3-4 种随机基础食材(70% 常见 / 25% 含稀有肉类),价格 40→45 - 🔧 **旧 cook_meal 路径废弃**(`engines/action.py`):ACTION_MATRIX 简化为空壳,execute_action 返回引导消息指向食谱系统 - 🔧 **cooked_by 标记写入**:玩家路径 `routes.py:_apply_cook_completion` 写入 `"player"`,妹妹路径 `sister_state_manager.py:execute_sister_cook` 写入 `"sister"` - ✅ **20 个新单测**(`test_cooking_participation.py`):CookedByField(3)/PlayerPortionBonus(3)/EatBrotherCookedBonus(5)/EatBrotherCookedPAD(2)/GroceryPackUnpack(4)/OldCookMealRemoved(2)/PerceptionWritten(1) ### V90.4b - Food Mood PAD + Economy Quick Pass(食物情绪激活 + 经济速调)`[feature/v62-emotion-engine]` - 🆕 **`food_mood_impulse()`**(`engines/emotion_rules.py`):food item 的 `mood` 字段 → PAD 冲力映射,P = sign×(0.02+0.06×|mv|),A = 0.03×mv(正)/−0.02(负),输入 clamp [-5,5],防 PAD 爆炸 - 🔧 **execute_eat 集成**(`engines/sister_state_manager.py`):非中毒食物的 mood → PAD 冲力,与 eat_good/eat_brother_cooked 独立叠加 - 🔧 **食物分享路径**(`engines/action.py`):分享食物给妹妹时 mood → PAD × 0.5(随份量缩减) - 🔧 **便利店食物调价**(`app/services/items.py`):instant_noodles 8→12 / hot_bun 6→8 / rice_ball 7→10 / sandwich 12→16 / oden 15→18 / bento_store 25→28 / beef_bowl 35→38 / sushi_set 120→88 / energy_drink 15→18 - 🔧 **食材调价**:ing_tofu 3→5 / ing_vegetables 4→6 / ing_eggs 5→8 / ing_chicken 10→16 / ing_pork 12→18 / ing_oil_salt 6→8 - 🔧 **instant_noodles 数值**:hunger 30→26 / energy 2→1 / 新增 mood: −1(方便面情绪惩罚) - 🔧 **energy_drink 数值**:energy 25→22 - ✅ **12 个新单测**(`test_cooking_participation.py:TestFoodMoodPAD`):纯函数 5 个(mapping/scaling/negative/zero/clamp)+ 集成 7 个(oden/sushi/noodles/hot_bun/brother+mood stacking/sister-cooked/poison block) ### V90.3a - Memory Boundary Fix(记忆系统边界守卫)`[feature/v62-emotion-engine]` - 🆕 **`app/llm_response_guard.py`**:`safe_memory_content()` 截断守卫,拒绝 finish_reason 非 "stop" 的 LLM 响应写入记忆存储(5 个写入点:L1/L2 extract/L2 daily/L3 core/L3 facet) - 🆕 **Promise Validator**(`engines/memory_v2.py`):确定性 4 层验证(reject patterns → verb check → subject check → beneficiary check),替代 LLM 分类盲信 - 🆕 **Dual-track rejection**:harmless 事实(建议/告知/状态)降级为 behavior,harmful 推断(冷落/忽视/自我照料)整条 reject 不写入 memory_store - 🆕 **JSON Schema Validation**(`engines/memory_v2.py`):`_validate_extraction_response()` 顶层验证(无效→整批 reject)+ `_validate_memory_item()` 单条验证(无效→跳过该条) - 🔧 **Daily Summary Prompt 强化**(`app/services/memory.py`):`第 {day} 天` → `{date_str}`(日历日期由 game_tick caller 传入),删 `[承诺]`/`[变化]` 替换为 `[对话]`/`[客观变化]` + 约束段落(禁止关系推断/承诺判定/冷落改写) - 🔧 **L2 max_tokens**:300 → 600,减少截断频率 - 🗑️ **删除 promise 正则路径**(`app/services/game_tick.py`):daily_summary `[承诺]` 正则提取块移除,承诺仅从 L2 原子提取路径进入 - 🗑️ **`extract_memories()` 废弃**(`engines/memory_v2.py`):零调用方死代码,替换为 3 行废弃 stub - 🔧 **`test_faceted_memory.py`**:`_make_llm_response` mock 补充 `finish_reason="stop"`,适配截断守卫 - ✅ **44 个新单测**(`test_memory_boundary.py`):SafeMemoryContent(6)/PromiseValidator(16)/HarmfulInference(4)/ValidateMemoryItem(6)/ValidateExtractionResponse(4)/FinishReasonIntegration(3)/CalendarDate(2)/RegexRemoved(1)/DeprecatedStub(1)/AgeParseSafety(1) ### V90.2 - Skill Rust & Relearning(烹饪手生与肌肉记忆)`[feature/v62-emotion-engine]` - 🆕 **`engines/skill_rust.py`**:纯函数 `compute_rust_penalty()`(2天免疫+log2增长+技能因子缩放)+ `effective_cooking_skill()`(rust惩罚+retained_floor=min(current, peak×0.6)+绝不高于current)+ `compute_relearn_bonus()`(current severe_rain > extreme_heat > heat > rainy) - 🔧 **severity-based hard block**:从 `weather in {enum_set}` 改为 `signal.severity == "severe"`,允许大雨(wid 09, moderate)出门、暴雨(wid 10+, severe)阻止出门 - 🔧 **温度覆盖**:>=38°C→EXTREME_HEAT(severe),>=35°C→HEATWAVE(moderate),仅当 wid 结果非 severe 时覆盖 - 🔧 **future[0] 预报**:realtime 决定当前天气/hard block,future[0] 仅用于 today_forecast + movement_hint 预警 - 🔧 **WeatherSignal 扩展**(`app/models.py`):新增 aqi/wind/provider_city/raw_info/today_forecast 5 个可选字段 - 🔧 **stats_update 扩展**(`app/services/game_tick.py`):推送 12 个天气字段(source/provider_city/condition/temperature/humidity/raw_info/aqi/wind/comfort/movement_hint/today_forecast/updated_at) - 🔧 **`_SPECIAL_PENALTY`**:雾/霾/沙尘/雪 wid 的 outdoor_penalty 独立覆盖(1.2~2.0) - 🔧 **Instinct 天气问题**(`engines/instinct_engine.py`):从 enum set 改为 severity-based 检测 + 回家/店铺躲避双方案 - ✅ **53 个新单测**(`test_weather.py`):TestResolveJuhe(18) + TestJuheTemperatureOverride(8) + TestJuheForecast(4) + TestJuheProvider(10, mock HTTP) + TestJuheHardBlock(4) + TestWidMapCompleteness(4),9 种 error_code 参数化,API key 不泄露验证 ### V89.1 - WeatherSignal Foundation(天气信号基础设施)`[feature/v62-emotion-engine]` - 🆕 **`app/services/weather_service.py`**(~170行):WeatherProvider ABC + FixedWeatherProvider(测试注入)+ MockWeatherProvider(时间+城市hash确定性变化)+ WeatherService 缓存单例 + 天气→信号映射表(7种天气×5个维度) - 🔧 **Weather 枚举扩展**(`app/models.py`):新增 HEAVY_RAIN/THUNDERSTORM/EXTREME_HEAT 三个 severe 级天气,保留 HEATWAVE="heat" 不重命名 - 🆕 **WeatherSignal 模型**(`app/models.py`):condition/temperature/humidity/severity/comfort/outdoor_penalty/mood_hint/movement_hint,Environment 新增 weather_signal 可选字段(旧存档兼容) - 🔧 **game_tick Phase A.5**(`app/services/game_tick.py`):时间推进后、代谢前刷新天气信号,stats_update 推送 weather 字段(仅后端数据,不改前端 UI) - 🔧 **go_to 天气效果**(`engines/agent_tools.py`):极端天气阻止从室内出门(不困住已在户外的妹妹)+ 恶劣天气 `math.ceil(energy × outdoor_penalty)` 精力惩罚 - 🔧 **Agent prompt 天气注入**(`engines/agent_prompt_builder.py`):当前状态加天气行 + "天气不好不出门"→"天气不好出门更累,极端天气不出门" - 🔧 **check_weather 增强**(`engines/agent_tools.py`):返回 comfort + movement_hint - 🔧 **Instinct 天气问题**(`engines/instinct_engine.py`):户外+极端天气→severity 70 问题 + 回家/店里躲避方案 - 🔧 **RAIN_FRAGMENT 适配**(`engines/character_profile.py`):heavy_rain/thunderstorm 也触发雨天性格片段 - ✅ **26 个新单测**(`test_weather.py`):WeatherService 缓存/fallback/异常安全 + go_to block/penalty + check_weather + 序列化兼容 ### V88 - LifeTexture(生活纹理系统)`[feature/v62-emotion-engine]` - 🆕 **`engines/life_textures.py`**(~270行):10 种细粒度行为变体(looking_out_window/wash_cup/tidy_desk/stretch/hum_song/reread_old_chat/sit_nearby/turn_tv_on_off/check_fridge/doodle_notebook),为粗活动增加生活细节 - 🔧 **双层 severity 边界**:`build_texture_candidates()` + `is_texture_available()` 均检查 severity >= 60 → 不推荐/不接受生活小事,防止"快饿坏了还在窗边发呆" - 🆕 **`light_chore` 冷却组**(`cooldown_engine.py`):wash_cup/tidy_desk 归入新组,防止家务循环 - 🔧 **variant 级冷却**(`cooldown_engine.py`):直接重复检测升级为 variant 级——同活动不同 variant 允许切换,同 variant 仍拦截 - 🔧 **start_activity schema**(`agent_tools.py`):新增可选 `variant` 参数,工具层通过 `is_texture_available()` 复验,无效/不合法 variant 静默降级为中文 fallback - 🆕 **`_ACT_CN` 中文映射**(`agent_tools.py`):活动描述统一使用中文("休息""看手机"),不再出现英文 activity 名 - 🔧 **Prompt 注入**(`sister_agent.py`):在本能→冷却之间注入纹理候选段落,无候选时静默 - 🔧 **决策记录填充**:`DecisionLogEntry.variant` + `RecentActivityEntry.variant` 均填充 variant 值 - 🐛 **修复 `sister.mood` → `state.mood`**:mood 是 GameState 字段,life_textures 和测试中错误访问 SisterState.mood(被 `_force_sister_attr` 动态注入掩盖) - ✅ **37 个新单测**(`test_v88_life_texture.py`):覆盖数据完整性、候选预选、工具层复验、variant 级冷却、组解析、工具处理、决策记录、prompt 格式、序列化兼容 - ✅ **Smoke verification 18/18 passed**:9 场景覆盖 severity 门控 / 好感过滤 / 位置过滤 / 中文 fallback / memory_hook 无副作用 / light_chore 循环 / 跨组通行 / 工具层强制拒绝 ### V87 - ActivityCooldownEngine(行为冷却引擎)`[feature/v62-emotion-engine]` - 🆕 **`RecentActivityEntry` 模型 + `GameState.recent_activities`**(`app/models.py`):6 条环形缓冲,独立于 decision_log 的运行时冷却状态 - 🆕 **`engines/cooldown_engine.py`**(~140行):基于 cooldown_group 的两层防重复系统——7 种活动映射到 6 个冷却组(screen_idle / passive_entertainment / quiet_focus / quiet_rest / self_care / relationship_waiting) - 🔧 **meaningful response 门控**:高 severity 需求不是放行一切,而是只允许认真回应需求的行为覆盖冷却(fatigue→resting ✓,hunger→phone ✗)。eat_food/go_sleep 等工具本身不受冷却管控 - 🔧 **Prompt 层**:`build_cooldown_constraints()` 生成约束段落注入 Agent prompt,替换原 decision rule #4 的模糊指令 - 🔧 **工具层**:`_sister_start_activity()` 增加 `check_cooldown()` 硬拦截,直接重复或同组循环时返回错误 - 🔧 **DecisionLog 集成**:`_record_decision()` 填充 `cooldown_group` 字段并推送环形缓冲 - ✅ **41 个新单测**(`test_v87_cooldown.py`):覆盖映射、解析、检测(直接重复/同组循环)、meaningful response(fatigue/hunger/social/boredom 各场景)、环形缓冲、prompt 约束、idle_home 端到端、序列化兼容 - ✅ **Smoke verification 26/26 passed**:idle_home_10 / hungry_high_blocks_distraction / hungry_high_allows_food / cooldown_violation_reselect / relationship_deferred_boundary ### V86 - DecisionLog(可回放决策记录)`[feature/v62-emotion-engine]` - 🆕 **`RejectedOption` + `DecisionLogEntry` 模型**(`app/models.py`):Agent 决策结构化记录,含工具名、活动、主驱动问题、被选方案评分、被拒方案列表(附评分)、玩家可见摘要、调试摘要 - 🆕 **`GameState.decision_log`**:最近 20 条决策记录,旁路观测层(不写入 event_log,不进入 Agent 意识流) - 🆕 **`build_instinct_data()`**(`engines/instinct_engine.py`):暴露本能引擎结构化计算结果(问题/方案/内在声音),供 DecisionLog 和后续 CooldownEngine 使用 - 🆕 **`_record_decision()`**(`engines/sister_agent.py`):在 `run_sister_agent()` 工具循环结束后捕获决策上下文并写入 decision_log - 🔧 **`_build_visible_summary()` + `_match_tool_hint()`**:辅助函数——高 severity 时附加身体感受,精确/前缀匹配 tool_hint - ✅ **25 个新单测**(`test_decision_log.py`):覆盖模型创建/序列化、instinct 数据、tool_hint 匹配、visible_summary 阈值、记录追加/裁剪/不污染 event_log、存档兼容 ### V85 - Mission Architecture Phase 1(意识连续性)`[feature/v62-emotion-engine]` - 🆕 **`EventEntry` + `Mission` 模型**(`app/models.py`):人类可读事件日志条目 + 跨 tick 进程对象(有目标/进度/生命周期) - 🆕 **`GameState.event_log` + `GameState.missions`**:新增两个持久化字段,存档自动包含 - 🆕 **`SisterStateManager.emit_event()`**:在 13 个关键状态转换方法中追加人类可读事件(move/eat/cook/sleep/wake/interact/item/workflow/relationship/lock),保留最近 50 条 - 🆕 **`engines/mission_adapter.py`**(~120行):从旧字段(`pending_interaction`、`active_workflow`)单向同步到 `missions[]`,Phase 1 只读不写 - 🆕 **`engines/mission_runtime.py`**(~30行):`MissionRuntime.advance()` 在 game_tick Phase D.7 调用,同步 Mission 并发射事件 - 🔧 **`app/services/game_tick.py`**:新增 Phase D.7(Mission Runtime),在 Agent 决策前执行 - 🆕 **`_build_conscious_context()`**(`engines/sister_agent.py`):构建 Agent 意识上下文——最近事件 + 当前任务 + 刚完成任务,作为 prompt 第一个信息块 - 🗑️ **死代码清理**:删除 `shopping_trip`(SisterState)、`pending_deliveries`(SisterState + 前端双端)、`set_pending_deliveries()`(Manager)、game_tick 配送处理块、utils.py 购物覆写 - ✅ **27 个新单测**(`test_mission.py`):覆盖 emit_event 追加/裁剪/各方法触发、Mission 同步/清理/完成、ConsciousContext 输出、死代码验证、Runtime 集成 ### V84 - Communication as World Objects(微信世界对象化)`[feature/v62-emotion-engine]` - 🔧 **微信系统从同步 RPC 改为半异步世界对象模型**:`send_message()` → Message Store → 结束。妹妹在 Agent tick 中发现未读消息,自主决定是否/何时回复 - 🆕 **`WeChatMessage` + `WeChatState`**(`app/models.py`):消息作为持久世界对象,带 sender/read/read_at 状态追踪 - 🆕 **6 个 Helper 函数**(`app/helpers.py`):`can_check_phone()`、`create_wechat_message()`、`get_unread_for_sister/player()`、`mark_read_by_sister/player()` - 🔧 **`send_sms()` 改写**(`app/api/routes.py`):不再调用 LLM,仅存储玩家消息到 WeChatState。返回 `{sent, message_id, data_change}` - 🆕 **`open_wechat()` 端点**:替代 `read_proactive_sms()`,返回完整消息列表并标记已读 - 🔧 **Agent Tools**(`engines/agent_tools.py`):`send_sms` 拆分为 `check_wechat`(信息工具)+ `reply_wechat`(动作工具)。手机可用性由 `can_check_phone(activity)` 判定 - 🔧 **Agent Prompt**(`engines/sister_agent.py`):`_sms_section` → `_wechat_section`,根据手机可用性和未读数量动态生成。删除 `_hours_since_last_sms()` - 🆕 **WebSocket Push**:`wechat.new_message` 事件(`scheduler.py` + `game_tick.py`),agent 回复后实时推送到前端 - 🔧 **WS 路由**:新增 `wechat.send` / `wechat.open`,旧路由 `action.sms` / `sms.read` 保留兼容 - 🆕 **WeChatModal**(React + Vue):仿微信聊天 UI,左灰泡泡/右绿泡泡,自动滚底,实时推送追加 - 🔧 **前端 Store**(React + Vue):新增 `wechat_messages` / `wechat_unread_count`,`sendWechat()` / `openWechat()`,push handler 从 `sister.sms` → `wechat.new_message` - 🌟 **涌现行为**:秒回(玩手机时)、延迟回复(做饭中)、睡醒发现消息、已读不回(心情差)、主动发消息(社交需求高) - ✅ **25 个新单测**(`test_wechat.py`):覆盖手机可用性、消息创建/裁剪、未读查询、标记已读、Agent 工具、工具可用条件、序列化往返 ### V82 - Relationship Residue(关系残留)`[feature/v62-emotion-engine]` - 🆕 **`engines/attachment_engine.py`**(~110行):2 个长期变量 `trust`(默认80) / `warmth`(默认70) + 短期 `residues`(max 5条,指数衰减 half-life=1h) - 🆕 **4 种关系事件**:`fulfilled`(承诺兑现 → trust+3, warmth+2)、`timeout`(超时无回应 → trust-2)、`rejection`(玩家拒绝 → trust-5, warmth-3)、`comforted`(关怀行为 → warmth+2.5,不影响 trust) - 🔧 **experience voice 动态化**(`instinct_engine.py`):📝经验声音从静态模板升级为关系状态驱动。优先级 residue > frequency > trust > warmth,恰好输出 1 句话。无残留时降级回 V81 问题相关建议 - 🔗 **5 个事件钩子**:`fulfill_pending_interaction`(fulfilled)、`game_tick` 玩家挂机超时(timeout)、`routes.py` 玩家主动拒绝(rejection)、玩家做饭且妹妹在场(comforted)、玩家发微信且妹妹处于脆弱状态(comforted) - 🆕 **睡眠微信策略**(`utils.py:build_sms_prompt`):妹妹睡觉时收到微信,prompt 指示 LLM 以"醒来后才看到"的语气回复,不再表现为实时对话 - 📐 **设计约束**:不增加新声音、不增加新 prompt section、不增加新记忆系统、不超过 2 个长期变量 - ✅ **20 个新单测**(`test_v82_attachment.py`):覆盖事件结算(含 comforted)、trust/warmth 边界、指数衰减、优先级排序、频率统计、中性状态返回 None ### V81.1 - 妹妹做饭修复 + 食材保质期优先 `[feature/v62-emotion-engine]` - 🐛 **妹妹做饭不消耗食材**(`sister_state_manager.py`):`execute_sister_cook` 调用 `remove_item_by_id(uid)` 但该方法期望 item_id,uid 永远匹配不到导致食材从未被消耗。改为 `remove_item(uid)` 并跳过 `consumable=False` 的调料 - 🐛 **妹妹做饭辣椒不剥离**(`agent_tools.py`):玩家路径在匹配前剥离辣椒,妹妹路径不剥离导致含辣椒的组合匹配不到食谱。统一在匹配前 strip `ing_chili` - 🆕 **油盐酱醋自动补入**(`agent_tools.py`):LLM 选食材时易忽略调料,导致部分匹配+品质降级。先尝试原始匹配,失败或部分匹配时自动补 `ing_oil_salt` 重试以获得精确匹配 - 🆕 **check_fridge 返回可做食谱**(`agent_tools.py`):`cookable_recipes` 字段列出当前技能可做的食谱+对应 uid,LLM 不再猜测食材组合。无可做食谱时返回提示 - 🔧 **食材保质期优先消耗**(`sister_state_manager.py`):`remove_item_by_id` fallback 按 `expires_date` 升序排序后再选取,优先消耗快过期的食材 - 🔧 **check_fridge 保质期排序**(`agent_tools.py`):食物和食材列表按 `remaining_days` 升序返回,快过期的排在前面 - 🗑️ **移除 HTTP 烹饪端点**(`routes.py`):删除 `@app.post("/api/cook")` 和 `@app.get("/api/cooking/ingredients")` 装饰器,两个前端均使用 WebSocket ### V81 - 内在声音系统(Internal Voices)`[feature/v62-emotion-engine]` - 🆕 **5 个声音子系统**(`instinct_engine.py`):body(身体感受)、attachment(依恋)、reason(理智)、selfcare(自我照顾)、experience(关系经验)。每个子系统根据当前状态输出一句模板文字,按需触发(如 attachment 仅在哥哥在场/即将到来时发声) - 🏗️ **severity 控制声音激活**:依恋声音在 severity>=90 时自然沉默(身体太难受时人想不到关系),用模板选择替代数学衰减(`tolerance_factor` 被否决) - 🏗️ **Prompt 注入格式**:每个问题下先列内在声音(emoji+「文字」),再列可选方案。末尾追加"不需要选最优"指令,引导 LLM 做出带犹豫和妥协的选择 - 📐 **设计原则**:关系不是 bonus(`relationship_bonus` 被否决),而是多个子系统的声音竞争。LLM 负责协商,不预计算赢家。为 V82 依恋引擎预留 `_experience_voice()` 扩展接口 ### V80 - 本能模型(Instinct Engine)`[feature/v62-emotion-engine]` - 🆕 **`engines/instinct_engine.py`**:预计算妹妹当前面临的生理/心理问题→枚举可行方案→按 `effect/(effort+time+1)` 排序→格式化注入 Agent prompt - 🆕 **5 类问题检测**:饥饿(>40)、疲劳(energy<50)、健康(health<70/生病)、社交(>40)、无聊(>40)。按严重度降序排列,最多展示 3 个 - 🆕 **上下文感知方案枚举**:背包食物(0精力)、冰箱食物(在家时)、便利店买(需旅行)、告诉哥哥(同房间时)、座椅休息(街道)、回家睡觉(计算精力够不够)等 - 🆕 **Hub-Spoke 旅行成本**:`_calc_travel()` 计算任意两点间的精力消耗和时间。室内互切 0/2min,家↔街 10/15min,街↔店 5/10min。精力不够时标记不可行 - 🔧 **决策规则精简**(`sister_agent.py`):原 6 条通用规则 + 6 条条件规则缩减为 4 条通用 + 3 条条件。`satisfy_hunger`/`satisfy_social`/`find_fun` 等 HOW-TO 技能被本能引擎方案替代 - 🔧 **技能注入裁剪**(`agent_skills.py`):移除 `SKILL_SATISFY_HUNGER`/`SKILL_SATISFY_SOCIAL`/`SKILL_FIND_FUN` 的注入逻辑。保留 `spend_free_time`/`cook_meal`/`propose_shared`/`propose_game` - ✅ **33 个新单测**(`test_instinct_engine.py`):覆盖问题检测、旅行成本、方案枚举、声音激活/沉默、prompt 格式 ### V79.1 - 记忆管线修复 + Agent 状态语义化 `[feature/v62-emotion-engine]` - 🐛 **`extract_from_perceptions` force 参数修复**(`memory_v2.py`):`force=True` 时 counter=0 不再直接 return,改为取最近 20 条感知进行提取。修复午夜 L1 强制刷写完全无效的 bug - 🏗️ **Phase F 自然日标记替代游戏日比较**(`game_tick.py`):用 `last_consolidation_date != today` 替代 `new_day > old_day` 触发记忆摘要。每个自然日最多做一次 L2/L3,电脑休眠跳过午夜也不会漏。LLM 调用失败时不标记完成,下个 tick 自动重试 - 🆕 **`last_consolidation_date` 字段**(`models.py`):GameState 新增 ISO 日期字符串,记录最后一次成功摘要的自然日,旧存档默认空字符串自动兼容 - 🐛 **离线路径补写感知**(`game_tick.py`):离线 workflow 执行、新 workflow 选择、fallback 三个分支均补加 `write_perception()`,修复离线时 `perception_extract_counter` 永远不增长导致 L1 不触发的 bug - 🗑️ **删除 handlers.py 睡眠摘要死代码**:`action_type == "sleep"` 分支(含 L2/L3 整合)自 sleep 从 ACTION_MATRIX 移除后不可达,Phase F 已是唯一摘要触发点。同步清理 `MemoryManager` 和 `MINUTES_PER_DAY` 死导入 - 🔧 **Agent 身体感受层**(`sister_agent.py`):新增 `_build_body_feeling()` 将生理数值预消化为自然语言——10 档饥饿描述("肚子饱饱的"→"饿得受不了")、精力×饥饿交叉效应(太累时"比起吃东西更想先睡")、深夜疲劳提醒。移除 Prompt 中全部原始数值行(energy/hunger/health/social/fun/mood/PAD),改为 `## 身体感受` 段落注入。thinking_cache 从 3 条缩为 1 条(截断80字),修复感知行重复注入 ### V79 - 睡眠态 Prompt 隔离(注意力引擎补全)`[feature/v62-emotion-engine]` - 🐛 **NIGHT_PHASE 睡眠冲突修复**(`utils.py`):deep_night 分支先检查 `sister.activity == sleeping`,睡觉时始终走"已入睡"分支。修复好感>50+警惕<30 时注入"你其实也没睡着,在床上翻来覆去"与"正在睡觉"矛盾的问题 - 🐛 **PHYSIOLOGY_EMOTION 睡眠过滤**(`attention_engine.py`):sleeping profile 增加 `PHYSIOLOGY_EMOTION: SUPPRESS`,睡觉时不再注入"开心、兴奋、自信""笑容藏不住"等清醒态情绪描写 - 🏗️ **CHARACTER_CORE 拆分 4 子 section**(`attention_engine.py` + `utils.py`):新增 `BEHAVIOR_LIBRARY`(动态行为库)、`COOKING_ATTITUDE`(做饭态度)、`ENVIRONMENT_RULES`(环境设定)、`PERFORMANCE_TONE`(表演基调),全部在 sleeping profile 中 SUPPRESS。睡觉时 prompt 减少约 400 token - 🆕 **意图覆盖感知隔离**(`attention_engine.py` + `handlers.py`):`apply_intent_overrides()` 增加 `activity` 参数 + `PERCEPTION_BOUND_SECTIONS` 集合(6 个感知类 section)。睡觉时意图分类不再将 FRIDGE_FOOD 等 section 从 SUPPRESS 提升回 FULL - 🔧 **睡眠深度行为锚定**(`utils.py`):drowsy 和 almost_awake 阶段的描述增加回应规则(断句数量、连贯度、语言风格),LLM 不再自行猜测半睡状态该怎么说话 - 🐛 **响应格式 JSON 闭合修复**(`utils.py`):修复 JSON 模板缺少 `}` 的 bug - 🏗️ **睡眠态响应格式精简**(`utils.py`):睡觉时 JSON 模板从 15 字段缩减为 4 字段(text/mood/background/game_over),mood 从 8 种缩减为 2 种(normal/content),文本格式注意事项替换为睡眠专用版本。减少约 400 token + 消除 LLM 在睡梦中生成 shopping/request/selfie 等不可能动作的风险 ### V78 - 分面记忆重定义(行为模式代替结论)`[feature/v62-emotion-engine]` - 🔧 **patterns 分面重定义**(`memory.py`):描述从"情绪规律、互动偏好、沟通习惯"改为"重复出现的具体行为"。引导 LLM 写「哥哥经常主动做饭」而非「哥哥很体贴」 - 🔧 **self 分面重定义**(`memory.py`):描述从"自己的成长、变化"改为"学会的新技能、去过的新地方、具体的变化"。引导 LLM 写「学会了做蛋炒饭」而非「变得更独立了」 - 📐 **合并 prompt 新增规则 7-8**:明确禁止性格评价和笼统感悟进入分面记忆,只允许可观测的重复行为和具体变化 ### V77 - Thinking 独立缓存(内心独白与记忆解耦)`[feature/v62-emotion-engine]` - 🏗️ **Thinking 从感知流分离**(`game_tick.py`):Agent 决策的内心独白(`妹妹思考:...`)不再写入 `write_perception()`,改为写入瞬时的 `sister_thinking_cache`(最近 5 条,跨日清空)。感知流只保留 `妹妹行为:{desc}` 事实描述 - 🆕 **`sister_thinking_cache` 字段**(`models.py`):GameState 新增 `List[str]` 字段,存储 Agent 最近的推理文本。不进入 L1 提取、L2 摘要、L3 分面、向量索引——彻底隔离于记忆管线 - 🔧 **Agent Prompt 注入**(`sister_agent.py`):最近 3 条 thinking 以 `- 刚才在想:...` 注入当前状态区,给 Agent 短期思维连续性,但不污染长期记忆 - 🔧 **链式决策也分离**(`game_tick.py`):`chain_next` 连续决策的 thinking 同样写入 cache 而非感知 - 📐 **设计原则**:内心独白是瞬时推理过程,不是可观测事实。V73 已在消费端压缩(`_compress_perception` 去除思考段落),V77 在源头彻底分离——thinking 永远不进入感知流 ### V76 - 架构收敛期(感知纯化+记忆精简+死代码清理)`[feature/v62-emotion-engine]` - 🏗️ **V76 感知流彻底纯化**:修复 4 处残留解释性感知——`game_tick.py` 的"好像在忙""有点失落"情绪词、`handlers.py` 的叙事关键词推断中断意图、`routes.py` 的"无视了我的请求"。全部改为客观事实描述("等了X分钟,没有收到回应""哥哥打断了当前的活动") - 🔧 **V78 L2 摘要去评价**(`memory.py`):DAILY_SUMMARY_PROMPT 的 `[评价]: 林瑶当下的心情评价` → `[变化]: 客观变化`。防止"情绪残影"——PAD 已恢复但长期记忆中残留"很委屈"导致 LLM 重新激活旧情绪 - 🗑️ **V79 死代码清理**:删除 `summarize_incremental()` 函数及其 `INCREMENTAL_SUMMARY_PROMPT`(无调用点,L1 每 10 条感知提取已替代);删除 `daily_summary_count` 字段及 3 处重置代码(`models.py` + `game_tick.py` + `time_engine.py` + `handlers.py`) ### V75 - 感知流净化(记忆防污染)`[feature/v62-emotion-engine]` - 🏗️ **确立三层边界原则**:State is for simulation. Memory is for facts. LLM is for meaning. 写入 CLAUDE.md 第 6 条红线 - 🐛 **删除 5 处模板感知写入**(`handlers.py`):ask_brother 回应处理中的固定模板("哥哥答应去陪陪我""被拒绝了""哥哥不给吃...小气鬼"等)全部移除。这些模板忽略玩家实际回答,产生假记忆进入长期感知流,导致 LLM 基于错误记忆做决策 - 📐 **感知流规范**:`write_perception()` 只允许写入可观测事实(谁说了什么、谁做了什么、看到了什么)。状态分类(commitment/rejection/timeout)仅驱动数值结算,不进入感知流。聊天流本身已生成正确的事实感知 - 🔧 **保留状态转换和数值结算**:`fulfill_pending_interaction()`、`clear_pending_interaction()`、好感/怀疑调整、做饭场景的 `set_agent_action(WAITING)` 均保留——这些是模拟层,不是记忆层 ### V74 - 关系质感层(好感-行为解耦)`[feature/v62-emotion-engine]` - 🆕 **关系阶段心理描述**(`sister_agent.py`):好感标签从"四字行为标签"(如`黏人占有`)改为2-3句心理感受描述,7个阶段(陌生/熟悉/亲近/依赖/恋人未满/深度依恋/亲密无间)。描述只写"怎么想、怎么感受",不写"会做什么"——行为由 LLM 结合情绪+需求+环境自然涌现 - 🆕 **`## 关系状态` 独立段落**(`sister_agent.py`):Agent prompt 新增关系状态区块,展示关系阶段名 + 心理描述,与性格特点、情绪状态、需求系统四层正交 - 🔧 **删除所有好感阈值行为规则**:移除性格特点中的`好感>50想待着`/`好感<30保持距离`、决策规则中的`好感>50倾向waiting`、spend_free_time 技能中的`好感>50→等哥哥`。好感只影响关系质感,不再直接规定行为 - 📐 **设计原则**:好感提供关系的"温度",性格决定表达方式,PAD情绪决定此刻状态,需求决定驱动力——四层互不干涉,LLM 自由组合决策 ### V73.1 - ask_brother 超时弹框修复 + 室内移动状态修复 `[feature/v62-emotion-engine]` - 🐛 **ask_brother 超时弹框不消失**(`game_tick.py` + `scheduler.py` + `useGameStore.ts`):后端触发"失落"或"挂机过期"时,推送 `dismiss_interaction` WebSocket 事件,前端收到后自动关闭弹框并解除 UI 锁定 - 🐛 **室内移动保留旧活动状态**(`agent_tools.py`):go_to 室内移动时 `activity=sister.activity` → `activity=SisterActivity.RESTING`,从客厅去厨房不再保留"看电视"状态。chain_next 机制让 agent 立即在新位置选择合适活动 ### V73 - Agent Prompt 精炼优化 `[feature/v62-emotion-engine]` - 🔧 **感知历史压缩**(`sister_agent.py`):15条完整独白 → 8条行为摘要,去除`妹妹思考:`段落只保留`妹妹行为:`,非思考类感知截断80字。Token 减少约 60-70%,减少行为复读循环 - 🔧 **性格特点去重**(`sister_agent.py`):删除4条与决策规则/技能SOP重复的操作指令(饥饿优先/精力睡觉/熬夜惩罚),性格特点只保留人格特质描写 - 🔧 **决策规则条件化注入**(`sister_agent.py`):13条静态规则 → 6条核心始终注入 + 7条按当前状态动态注入(打工仅当缺钱且有精力、同房间才提示ask_brother、社交/娱乐需求>50才注入对应规则) - 🔧 **技能注入精简**(`agent_skills.py`):移除始终注入的 handle_promise 和 react_to_touch(承诺处理太罕见、触碰由叙事prompt处理),只保留 spend_free_time 作为核心兜底技能 - 🔧 **位置限制去重**(`sister_agent.py`):删除"每次只做一个行动"重复说明(已在工具使用段覆盖) ### V72 - 意图驱动上下文装载系统 `[feature/v62-emotion-engine]` - 🆕 **ContextIntentAnalyzer**(`app/services/intent.py`):小模型(Qwen3.6-35B-A3B, 3B 活跃参数)分类玩家消息意图,5 类多标签 - `photo`(拍照/自拍)→ 装载 SELFIE - `food`(食物/做饭/冰箱)→ 装载 FRIDGE_FOOD + FOOD_REQUEST + COOKING_SKILL - `item`(物品/送礼)→ 装载 RESOURCE_STATUS + USE_ITEM - `activity`(一起做事)→ 装载 SHARED_ACTIVITY - `general`(闲聊/默认)→ 无额外装载 - 降级策略:分类失败返回 `intents=None`,各路径回退为全量注入(兼容现有行为) - 🆕 **SMS 意图装载(Phase 1)**(`app/utils.py` + `app/api/routes.py`):`build_sms_prompt` 新增 `intents` 参数,按意图条件注入自拍指令和冰箱/背包食物列表 - 解决短信提到食物时 prompt 缺少冰箱物品的问题 - 闲聊时不再注入无关的拍照能力(减少 token 浪费和幻觉风险) - 🆕 **主交互意图-注意力叠加(Phase 2)**(`engines/attention_engine.py` + `app/api/handlers.py`): - `apply_intent_overrides(frame, required_sections)` 纯函数:将被注意力抑制但玩家意图需要的模块从 SUPPRESS 提升为 FULL,不改原帧 - 叠加规则:**意图只做加法**——注意力(V71)按妹妹活动过滤,意图(V72)按玩家消息补充。注意力说 SUPPRESS 但意图说需要 → FULL;反向不降级 - 典型场景:妹妹在看书(SELFIE=SUPPRESS),玩家说"发张自拍" → intent=photo → SELFIE 提升为 FULL - 🔧 **复用现有基础设施**:共用 `EventCategory.RETRIEVAL`(无限流)、同模型同网关通道、与 V63 IntentAnalyzer 同架构模式 - ✅ 新增 `test_context_intent.py`(29 个测试覆盖意图分类/降级/Section映射/SMS条件注入/意图-注意力叠加) ### V71.3 - 修复记忆提取死代码 `[feature/v62-emotion-engine]` - 🐛 **记忆系统从未触发**(`app/helpers.py`):V43 的感知计数器代码被错误放在 `format_time_ago()` 函数的 `return` 语句之后,导致**永远不会执行**。记忆提取(L1 摘要 + 原子记忆)从未被触发,working_memory 始终为空 - 🔧 修复:将计数器递增和提取触发代码移回 `write_perception()` 函数末尾,每 10 条感知自动触发一次 `extract_from_perceptions` ### V71.2 - 妹妹吃饭修复 `[feature/v62-emotion-engine]` - 🐛 **eat_food 模糊匹配增强**(`agent_tools.py`):Agent 用中文名(如"奶茶")调用 eat_food 时,新增 template.name 比对,不再只查 custom_name - 🐛 **eat_food 失败不再伪装成功**(`sister_agent.py`):eat_food 匹配失败时不再设 `activity="eating"`,改为"想吃东西但没找到合适的食物" - 🐛 **行动工具失败可重试**(`sister_agent.py`):`round_has_action` 只计成功的行动工具,失败时 Agent 能看到错误并重试 - 🔧 **eat_food 描述修正**(`agent_tools.py`):移除虚假的"必须先 go_to Kitchen"要求(代码实际允许任何室内位置吃冰箱食物) - 🔧 **室内移动链式决策**(`agent_tools.py` + `sister_agent.py`):go_to 室内目标后 `chain_next=True`,Agent 可立即执行后续动作(如去厨房→吃东西),不再原地发呆 2 分钟 ### V71.1 - 玩家挂机检测 `[feature/v62-emotion-engine]` - 🐛 **挂机时妹妹不再傻等**:玩家在线但挂机(标签页切走或长时间不操作)时,Agent 不再发起 `ask_brother` 请求,妹妹自主解决需求(吃饭、休息等) - 🆕 **前端挂机检测**(`useGameStore.ts`):重写死代码 `_setupVisibilityListener()` 为完整挂机检测系统 - 无用户操作 30 分钟 → 标记挂机 - 标签页隐藏 3 分钟 → 标记挂机 - 返回操作 → 立即恢复活跃 + 状态同步 - 活动事件 5 秒防抖,避免频繁重置 - 🆕 **WebSocket 状态消息**(`ws.ts` + `game.ts` + `ws_manager.py`):前端发送 `player_status: idle/active`,后端 `ConnectionManager` 新增 `is_idle` 字段存储状态 - 🔧 **Agent 感知挂机**(`sister_agent.py` + `agent_tools.py`):挂机时从工具列表移除 `ask_brother`,Prompt 中标注"哥哥在忙自己的事" - 🔧 **等待自动清除**(`game_tick.py`):挂机时立即清除 `pending_interaction`(不扣警惕值),写感知"哥哥好像在忙,自己想办法" ### V71 - 注意力焦点系统 `[feature/v62-emotion-engine]` - 🆕 **注意力引擎**(`engines/attention_engine.py` 新建):~350 行纯函数引擎,模拟真实人的注意力模式 - **FocusProfile**:19 种活动各自的注意力配置(做饭不想自拍、睡觉屏蔽大部分信息、游戏极度专注等) - **需求侵入**:hunger/energy/social/fun/health 超阈值时"打断"注意力,注入自然语言描写(如"肚子咕咕叫"),并挤出低优先级段落 - **注意力漂移**:同一活动持续 40 分钟后注意力开始飘散,SUPPRESS 段落恢复为 BRIEF;arousal 高时不易走神 - **记忆触发扩展**:根据活动/地点/时间/情绪/漂移生成额外检索词(最多 3 个),追加到 V63 向量召回 - **安全分类**:10 个 PROTECTED 段落(角色核心、格式等)始终 FULL;6 个 SAFETY_OVERRIDE 段落(生病、锁门等)触发时不受压制 - 🔧 **Prompt 段落过滤**(`app/utils.py`):`build_contextual_prompt()` 新增 `attention_frame` 参数,18 个可调制段落被 `_should_inject()/_is_brief()` 包裹 - BRIEF 模式:冰箱→物品计数、资源→计数、记忆→最近 2 条、历史→最近 5 条、行动同步→一行摘要 - 侵入注入点:生理/情绪段之后插入"注意力干扰"段落 - 🔧 **handlers.py 集成**:`handle_llm_response()` 在 V63 检索后调用 `compute_attention()`,用 extra_recall_queries 做额外检索,传 frame 给 prompt 构建 - 🔧 **角色片段接通**(`engines/character_profile.py`):BACKGROUND_FRAGMENT(深夜+好感>45)和 MONEY_FRAGMENT(商店/低资金)按条件注入 - ✅ 新增 `test_v71_attention.py`(43 个测试覆盖 FocusProfile/ComputeAttention/NeedIntrusions/MemoryTrigger/AttentionDrift/AttentionFrame/IntrusiveThought) ### V70.1 - 热修复 + dismiss_interaction `[feature/v62-emotion-engine]` - 🐛 **action_type 未定义崩溃**(`routes.py`):V70 游戏拦截层引入的早返回路径跳过了 `action_type` 定义,改用更早定义的 `preliminary_action_type` - 🐛 **eat_food 无法匹配做菜产物**(`agent_tools.py`):Agent 传 `item_id="tomato_egg_fried_rice"` 但实际 item_id 均为 `"cooked_dish"`,新增 custom_name 模糊匹配兜底 - 🔧 **技能 SOP 示例修正**(`agent_skills.py`):`SKILL_SATISFY_HUNGER` 示例从 `eat_food(item_id=...)` 改为 `eat_food(uid=...)`,引导 LLM 优先使用精确 uid - 🆕 **dismiss_interaction 弹框拒绝**(`routes.py` + 前端双端):关闭妹妹请求弹窗累计 3 次视为主动拒绝,注入 `disappointment` PAD 冲力 + 感知记录 - 🔧 **React 导入修复**(`SisterRequestModal.tsx`):`import gameApi` → `import { api as gameApi }`,修复白屏 ### V70 - 文字小游戏系统(二十问 / 接龙 / 真心话大冒险 / 文字画)`[feature/v62-emotion-engine]` - 🆕 **MiniGameSession 模型**(`models.py`):新增 `MiniGameSession(BaseModel)` 含 game_type、turn、score、game_data、history 等字段;GameState 新增 `active_game_session: Optional[Dict]` - 🆕 **游戏引擎**(`engines/mini_games.py` 新建):~400 行,4 种游戏完整实现 - **二十问**:硬编码 28 词库(分类+词语),玩家猜词,Python 关键词匹配判定,20 轮限制 - **词语接龙**:首字接尾字规则,重复检测,Python 确定性验证,妹妹回合由 LLM 生成词语后提取 - **真心话大冒险**:10 道真心话 + 8 道大冒险题库,玩家/妹妹轮流选择 - **文字画**(你画我猜文字版):25 词库,妹妹描述玩家猜,关键词匹配判定 - 🆕 **关键词发起**(`mini_games.py`):`detect_game_type()` 识别"二十问/接龙/真心话/你画我猜/玩游戏"等关键词,支持"random"随机选择 - 🆕 **游戏模式拦截**(`routes.py`):interact 端点新增两层拦截 — 游戏进行中拦截(处理玩家输入→游戏引擎→LLM 叙事)+ 游戏发起拦截(关键词检测→创建会话→开场白) - 🆕 **确定性裁判**(无 LLM 裁判):所有游戏验证由 Python 硬编码完成(首字匹配、关键词匹配、重复检测),不消耗额外 LLM 配额 - 🆕 **结算奖励**(`mini_games.py`):妹妹赢 — 好感+3、game_win 情绪;玩家赢 — 好感+2、game_lose 情绪;平局 — 好感+2、game_fun 情绪;放弃 — 好感+0.5;所有完成游戏 fun_need-15、social_need-10 - 🆕 **PAD 情绪冲力**(`emotion_rules.py`):`game_win`(0.3,0.25,0.15)、`game_lose`(-0.1,0.15,-0.05)、`game_fun`(0.2,0.2,0.1) - 🆕 **Agent 技能**(`agent_skills.py`):`SKILL_PROPOSE_GAME` — 好感 ≥ 40 且同房间且 fun_need > 60 且无活跃游戏时注入,妹妹可通过 `ask_brother(intent="play_game")` 邀请 - 🔧 **数据模型**(`models.py`):新增 `TOGETHER_GAME` 枚举值 - 🔧 **可中断**(`constants.py`):`together_game` 加入 `INTERRUPTIBLE_ACTIVITIES` - 🔧 **共同活动代谢**(`schedule.py`):TOGETHER_GAME 社交 -4/hr、娱乐 -3/hr;聊天好感 ×2.0 / 警惕 ×0.3 - 🏗️ **Tick 超时**(`game_tick.py`):Phase D.5 检查游戏过期 + 玩家离开房间 → 自动结算;游戏中跳过妹妹决策 - 📝 **Prompt 注入**(`utils.py`、`sister_agent.py`):TOGETHER_GAME 活动情境描述;Agent prompt 显示当前游戏状态 - 🖥️ **前端适配**(4 文件):helpers.js/ts + game.js + useGameStore.ts 添加 `together_game: '一起玩游戏'` - ✅ 新增 `test_v68_mini_games.py`(49 个测试覆盖模型/创建/关键词/四种游戏逻辑/结算/情绪/技能注入/工具枚举/Manager/Schedule) ### V69 - 妹妹学做饭(烹饪技能系统)`[feature/v62-emotion-engine]` - 🆕 **烹饪技能字段**(`models.py`):SisterState 新增 `cooking_skill: float`(0-100),默认 0,旧存档自动兼容 - 🆕 **技能增长公式**(`sister_state_manager.py`):`base_gain × quality_mult × diminish`,难度越高增长越多,成功×1.5/失败×0.5,高技能递减(`1 - skill/120`) - 🆕 **技能门槛**(`sister_state_manager.py`):none=0, easy=20, medium=50, hard=80,低于门槛禁止做该难度的菜 - 🆕 **execute_sister_cook()**(`sister_state_manager.py`):完整妹妹做饭流程 — 位置验证→门槛检查→质量掷骰→食材消耗→食物产出→技能增长→情绪冲力 - 🆕 **质量掷骰技能修改器**(`action.py`):`_roll_quality()` 新增 `skill` 参数,技能 0-100 映射 success +0~15% / fail -0~10%,默认 0 保持玩家烹饪不变 - 🆕 **观看增长**(`routes.py`):妹妹在场时看哥哥做饭 → cooking_skill +0.5/次 - 🆕 **PAD 情绪冲力**(`emotion_rules.py`):`cook_success_self`(0.35,0.25,0.2) 做饭成功自豪、`cook_fail_self`(-0.15,0.1,-0.1) 失败失落 - 🆕 **Agent 工具**(`agent_tools.py`):新增 `cook_meal` 工具 schema + handler,参数 ingredient_uids + heat_level - 🔧 **check_fridge 扩展**(`agent_tools.py`):返回结果新增 `ingredients` 列表 + `ingredient_count`,妹妹可以看到冰箱里的食材 - 🔧 **check_own_status 扩展**(`agent_tools.py`):返回结果新增 `cooking_skill` 字段 - 🔧 **工具注册**(`agent_tools.py`):`cook_meal` 加入 `SISTER_TOOL_HANDLERS`、`_LOCATION_TOOL_SET["Kitchen"]`、`ACTION_TOOLS` - 🧠 **Agent 技能**(`agent_skills.py`):新增 `SKILL_COOK_MEAL`,cooking_skill ≥ 20 且在厨房且饥饿 > 50 时注入 - 📝 **Prompt 注入**(`sister_agent.py`、`utils.py`):Agent prompt 显示烹饪技能值;叙事 prompt 在 skill ≥ 20 时注入技能等级描述 - ✅ 新增 `test_v69_cooking_skill.py`(22 个测试覆盖字段/掷骰/门槛/增长公式/情绪/技能注入/工具注册) ### V67 - 共同活动系统(一起看电视 + 一起吃饭)`[feature/v62-emotion-engine]` - 🆕 **共同活动状态管理**(`sister_state_manager.py`):新增 `start_shared_activity()` 方法,验证同房间后设置活动、减少社交(-8)/娱乐(-6)需求 - 🆕 **一起看电视**(`routes.py`):玩家通过关键词"一起看电视"或 `action_type="join_tv"` 发起,验证妹妹正在看电视且同房间,双方进入 30 分钟共同观看状态 - 🆕 **一起吃饭**(`routes.py`):做饭完成时妹妹在场且可打断 + 怀疑度 < 50 → 自动触发 20 分钟共餐 - 🆕 **共同活动代谢增强**(`schedule.py`):社交需求 -4/hr(vs 同房间 -2/hr);娱乐需求 TOGETHER_TV -3/hr、TOGETHER_EATING -1/hr - 🆕 **互动奖励**(`schedule.py`):TOGETHER_TV 聊天好感 ×2.0 / 警惕 ×0.3;TOGETHER_EATING 聊天好感 ×1.8 / 警惕 ×0.3 - 🆕 **PAD 情绪冲力**(`emotion_rules.py`):`shared_tv`(0.2, 0.1, 0.05) 和 `shared_eat`(0.25, 0.1, 0.1) 两个新事件 - 🆕 **Agent 技能**(`agent_skills.py`):`SKILL_PROPOSE_SHARED` — 好感 ≥ 45 且同房间且 fun_need > 50 时注入,妹妹可通过 `ask_brother(intent="watch_tv_together")` 邀请看电视 - 📝 **LLM 情境注入**(`utils.py`):`build_contextual_prompt()` 在共同活动期间注入情境描述(评论节目/聊日常/厨房氛围) - 🔧 **数据模型**(`models.py`):新增 `TOGETHER_EATING` 枚举值 - 🔧 **可中断**(`constants.py`):`together_eat` 加入 `INTERRUPTIBLE_ACTIVITIES` - 🖥️ **前端适配**(4 文件):helpers.js/ts + game.js + useGameStore.ts 添加 `together_eat: '一起吃饭'` 显示名 - ✅ 新增 `test_v67_shared_activities.py`(18 个测试覆盖状态管理/情绪冲力/互动奖励/代谢/技能注入/工具枚举) ### V66 - 多维需求系统 + OCC 社会情绪扩展 `[feature/v62-emotion-engine]` - 🆕 **社交/娱乐需求**(`models.py`):SisterState 新增 `social_need`、`fun_need`(0=满足, 100=极度渴望),默认 30.0,旧存档自动兼容 - 🆕 **需求代谢**(`schedule.py`):社交需求(独处+5/hr、同房间-2/hr、外出+3/hr、睡眠+1/hr);娱乐需求(闲坐+4/hr、等待+6/hr、睡眠不变) - 🆕 **OCC 社会情绪**(`emotion_rules.py`):6 种新事件类型→PAD 冲力映射(嫉妒/感激/失落/内疚/自豪/思念),做饭→感激、打工→自豪、送礼→感激 - 🆕 **需求→情绪桥接**(`engines/needs_bridge.py` 新建):需求>50 时每 tick 注入微量 PAD 冲力(孤独/无聊),强度与需求值成正比,PAD 弹簧阻尼自然平衡 - 🏗️ **Tick 集成**(`game_tick.py`):Phase B.7 需求→情绪桥接调用 - 🔧 **动作触发需求变化**(`agent_tools.py`、`routes.py`):聊天 social-5/fun-2、发短信 social-5、写便利贴 social-3、找哥哥说话 social-8/fun-3、娱乐活动 fun-6 - 🧠 **Agent 认知层**(`sister_agent.py`、`agent_skills.py`):Agent prompt 注入需求值 + 决策规则 12-13(社交>60 找人说话、娱乐>60 找乐子);2 个新技能 SOP(SKILL_SATISFY_SOCIAL、SKILL_FIND_FUN) - 📝 **叙事 Prompt 注入**(`utils.py`、`emotion_prompt.py`):状态行显示需求值;心理提示(落寞/想说话/无聊/叹气);行为指导(社交渴望/寻求刺激) - ✅ 新增 `test_v66_needs_occ.py`(28 个测试覆盖 Manager 方法/需求衰减/NeedsBridge/OCC 事件/动作触发/默认值) ### V65 - 睡眠交互改造 + 欲望引擎 `[feature/v62-emotion-engine]` - 🆕 **欲望引擎**(`engines/desire_engine.py` 新建):独立于 PAD 的欲望计算系统 - 正反馈增益:`factor = 1 + 2×(desire/100)²`,欲望越高收益越大(最高 300%) - 钟形衰减:`decay = max_rate × 4×d×(1-d)`,中间值衰减最快,两端趋零 - 睡眠触摸欲望:仅敏感部位(胸/腰/臀/腿根)和末端部位(腿/脚)产生欲望,头/发/脸/肩不产生 - 🆕 **累积唤醒进度**(`schedule.py`):取代单次判定的唤醒机制 - 每次触摸/互动累加 `wake_progress`,达到阈值触发阶段转换(deep→light:0.35, light→drowsy:0.25, drowsy→awake:0.15) - 转换后进度重置,自然衰减 -0.02/tick - 精简唤醒动作:移除 whisper/call,仅保留 pat(0.12)/shake(0.20) - 🔒 **睡眠聊天拦截**(`routes.py`):深睡/浅睡时聊天在代码层直接拦截(不调 LLM),迷糊/将醒放行 - 🔒 **睡眠触摸覆写**(`routes.py`):深/浅睡触摸绕过 accept/reject,仅加欲望不加好感/警惕;迷糊态好感×0.5、欲望×0.8 - 🔧 **插拔式欲望效果**(`config.py`):`ENABLE_DESIRE_EFFECTS` 开关(默认 OFF),V65 始终计算欲望增减,V66 通过此开关启用欲望对玩法的影响 - 🏗️ **Tick 集成**(`game_tick.py`):Phase B.6 欲望钟形衰减 + 唤醒进度自然衰减 - ✅ 新增 `test_v65_desire_wake.py`(40 个测试覆盖增益/衰减/睡眠触摸/累积唤醒/Manager 方法) ### V64.1 - 热修复:JSON 解析 + TTS 阻塞 + 位置活动校验 `[feature/v62-emotion-engine]` - 🔒 **JSON 解析管道重排**(`handlers.py`):qwen3 模型偶发漏掉字符串值开头引号(`"text": (听到...`),重排处理顺序为:提取 ```json 块 → 补引号 regex → `fix_json_string_values`,解决原有顺序导致的字符串边界误判 - 🔒 **TTS 不再阻塞事件循环**(`tts_service.py`、`config.py`):`is_available()` 不再触发同步模型加载;新增 `ENABLE_TTS` 环境变量(默认关闭),防止 `from_pretrained()` 阻塞 async 事件循环导致 WS 响应无法发出 - 🔒 **位置-活动交叉校验**(`schedule.py`、`utils.py`):`validate_sister_change` 新增 `LOCATION_ACTIVITIES` 映射表,拒绝不合法组合(如卧室+外出);prompt 明确告知 LLM 每个位置允许的活动 - 🩹 **移除未实现角色"小雨"引用**(`character_profile.py`):SMS prompt 不再提及未实现的 NPC - 🩹 **意图分析切换阿里云通道**(`intent.py`):从讯飞 MaaS 切换到 DashScope(`qwen3.6-35b-a3b`),延迟从 2-7s 降至 ~0.6s,支持 `enable_thinking=False` ### V64 - 分面记忆合并系统 `[feature/v62-emotion-engine]` - 🆕 **分面记忆数据模型**(`models.py`):`GameState` 新增 `faceted_memories: Dict[str, str]`,key 格式 `{character}/{facet}`(如 `player/preferences`) - 🆕 **6 大记忆分面**(`memory.py`):`MEMORY_FACETS` 定义 preferences(偏好)、experiences(经历)、promises(承诺)、milestones(里程碑)、patterns(相处模式)、self(自我认知) - 🆕 **分面合并函数**(`memory.py`):`consolidate_to_facets()` 单次 LLM 调用将 L2 日报合并到各分面,支持增量更新(`null` = 无变化) - 🆕 **分面格式化**(`utils.py`):`format_faceted_memories()` 输出结构化段落,`format_memories()` 优先读 `faceted_memories`,为空时降级读 `core_memories` - 🆕 **向量同步**(`retrieval.py`):分面合并后 upsert 到 ChromaDB,doc_id 格式 `facet_{character}_{facet}`,稳态 L3 向量数 = 分面数(6 条) - 🆕 **召回适配**(`utils.py`):`format_retrieved_memories()` 支持分面 metadata 标签,输出 `[偏好]`/`[经历]` 等前缀 - 🏗️ **触发点接入**(`handlers.py`、`game_tick.py`):睡眠处理和跨天结算优先调用分面合并,失败降级为原有 `consolidate_memories` - ✅ 新增 `test_faceted_memory.py` 测试(序列化/格式化/向后兼容) ### V63 - 记忆检索系统 `[feature/v62-emotion-engine]` - 🆕 **意图分析器**(`intent.py` 新建):轻量模型(qwen3.6-35b-a3b)判断玩家消息是否需要召回记忆,产出结构化查询词 - 🆕 **检索服务**(`retrieval.py` 新建):ChromaDB 三层向量索引(L1 工作记忆 / L2 日报 / L3 核心记忆),支持 embed + rerank 两阶段召回 - 🆕 **检索集成**(`handlers.py`):聊天处理流程新增意图分析 → 记忆召回 → 注入 prompt 链路 - 🆕 **检索格式化**(`utils.py`):`format_retrieved_memories()` 将召回的记忆片段格式化为 LLM 上下文 - 🔧 **可插拔设计**:`ENABLE_MEMORY_RETRIEVAL` 环境变量控制开关,关闭时零开销 - 🔧 **独立限流**(`llm_gateway.py`):新增 `RETRIEVAL` 事件类别,配额独立于其他类别 ### V62.5 - 自拍功能扩展(多渠道 + 服装姿势维度)+ Bug 修复 `[feature/v62-emotion-engine]` - 🆕 **自拍服装/姿势维度**(`image_gen.py`):`generate_selfie` 新增 `outfit`/`pose` 参数,prompt 融入服装与姿势描述 - 🆕 **短信发自拍**(`routes.py` + `utils.py`):短信渠道新增 `[SELFIE:场景:表情:服装:姿势]` 标记解析,妹妹可在短信中发自拍或旧照片 - 🆕 **便利贴附自拍**(`agent_tools.py` + `models.py`):`write_sticky_note` 工具支持 `include_photo`,`StickyNote` 新增 `image_url` 字段 - 🔧 **拍照精力阈值统一为 20**:聊天/短信/便利贴三个渠道的注入与执行阈值对齐(原聊天 40、短信 prompt 40,现统一 20) - 🔧 **TTS 开关**(`tts_service.py` + `.env.example`):新增 `TTS_ENABLED` 环境变量,低配设备可关闭跳过模型下载 - 🩹 **`clear_agent_action` 重置 activity**:清理过期 agent action 时同步将 activity 置为 RESTING,避免状态残留 - 🩹 **`_act_name` 优先自定义动作名**:`build_contextual_prompt` 优先用 `sister.action_description`,Agent 自定义动作名在叙事中生效 - 🐛 **修复过期食物删除失效**(`time_engine.py`):`_remove_expired_food` 误用 `remove_item_by_id(uid)` 把实例 uid 当成 item_id 模板匹配,导致过期食物实际删不掉。改为 `remove_item(uid)`,manager 改为必传参数 - 🩹 **修复短信自拍阈值不一致**:`build_sms_prompt` 注入阈值由 40 调整为 20 ### V62.4 - 叙事版好感度外显 `[feature/v62-emotion-engine]` - 🏗️ **叙事版情绪指导增强**(`emotion_prompt.py`):`_build_narrative()` 新增好感度外显参数,叙事 LLM 能根据好感度写出有层次感的情绪描写(高好感坦率表露、中好感不经意流露、低好感刻意隐藏) - ✅ 新增 3 个叙事版好感度测试(29 个翻译层测试全部通过) ### V62.3 - PAD 引擎参数调优 + 情绪行为翻译层 `[feature/v62-emotion-engine]` - 🔧 **引擎冲力注入重构**:冲力从"力"改为"速度脉冲 + 即时位置偏移"双注入,新增 `IMPULSE_VELOCITY_SCALE=0.8`、`IMPULSE_POSITION_SCALE=0.25`,单次事件即可产生可感知的 PAD 偏移 - 🔧 **弹簧/阻尼参数调优**:`spring_k` 0.15→0.3(更快回归基线)、`damping_c` 0.35→0.5(减少振荡),情绪恢复时间从 9 小时缩短至 2-5 小时 - 🔧 **mood 阈值下调**:`pad_to_mood` 的 content/happy 阈值从 0.5 降至 0.3,annoyed 从 -0.3 升至 -0.15,日常事件可触发 mood 变化 - 🆕 **PAD→行为翻译层**(`engines/emotion_prompt.py` 新建):将 PAD 数值翻译为自然语言行为指导注入 LLM prompt - 三维度各 5 档翻译规则(开心/微喜/平和/微闷/低落 等),比 mood 更细粒度 - 8 种心情组合行为指导(happy→"笑容藏不住"、angry→"语气很冲"等) - 健康状态行为指导(替换原有简陋的 `_health_hint`) - 好感度外显调节器(低好感藏情绪、高好感藏不住) - `for_narrative` 参数:Agent 版输出决策行为指导,叙事版只输出语气/表情描写指导 - 🏗️ **Agent Prompt 改造**(`sister_agent.py`):`_health_hint` 替换为完整的 `_emotion_guide`,LLM 现在能理解 PAD 数值对行为的影响 - 🏗️ **叙事 Prompt 改造**(`utils.py`):`build_contextual_prompt` 追加 `for_narrative=True` 的情绪语气指导,LLM 叙事时会根据 PAD 写出对应的表情/语气 - ✅ 303 个单元测试通过(新增 17 个翻译层测试) ### V62.2 - 连锁 Bug 修复 + EATING 单入口守护 + 菜名显示修复 `[feature/v62-emotion-engine]` - 🔒 **wake_up 清除 action_until**(P0-2):`wake_up()` 新增清除 `action_until_realtime` 和 `action_description`,修复唤醒后 action guard 持续阻止状态变更的连锁故障 - 🔒 **go_sleep Schema 对齐**(P0-1):Agent 工具 Schema 参数从 `waketime` 改为 `duration_min`,与 handler 实现对齐,消除参数不匹配 - 🔒 **EATING 代谢暂停**(P1-1):进食期间 hunger 不再减少(暂停代谢),饱腹恢复仅由 `consume_food()` 驱动,消除虚假饱腹 - 🔒 **narrative_override 禁止设 EATING**:`validate_sister_change` + `set_narrative_override` 双重拦截,LLM 无法再通过叙事覆盖凭空设置进食状态 - 🔒 **move_to 加 _guard_eating**:堵住 `move_to(activity=EATING)` 潜在旁路,EATING 状态现在只能通过 `set_agent_action` + 真实食物消耗设置 - 🛡️ **Phase B override 过期安全网**:代谢计算前增加 narrative_override 过期检查,防止被 action guard 阻断导致 override 永不过期 - 💊 **喂药修复**:赠送关键词列表加入"喂","喂药"不再被错误路由到"自己使用" - 🩹 **write_perception 作用域修复**:删除 handlers.py 中局部 import 覆盖全局 import,修复非 chat 分支的 UnboundLocalError - 🩹 **Schedule 加成走 Manager**:routes.py 中 suspicion/favorability/desire 的 Schedule 加成改用 `mgr.adjust_*()`,不再直接 setattr 绕过写保护 - ⏳ **ask_brother 重复弹框修复**:`fulfill_pending_interaction` 记录时间戳,`ask_brother` 新增 10 分钟冷却,哥哥答应后不会立刻再问 - 🍜 **家常剩饭彻底移除**:`leftover_meal` 重命名为 `cooked_dish`,所有食物显示优先用 `custom_name`(真实菜名),Agent/日志/叙事统一使用真实菜名 ### V62.1 - 睡眠系统重构 + Bug 修复 `[feature/v62-emotion-engine]` - 🏗️ **睡眠系统精简**:删除 `wake_time`、`planned_bedtime`、`planned_wake_time`、`bedtime_delay`、`sleep_plan_generated_today` 五个冗余字段 - 🏗️ **`sleep_start_time` 改为现实时间戳**:深度计算改为 `(剩余时间 / 总时长)` 的纯比值,彻底消除绝对分钟/日内分钟混用导致的跨午夜计算错误 - 🗑️ **删除 20:00 LLM 睡眠计划生成**:移除 `generate_sleep_plan_llm()` 及相关 Agent 技能/Prompt 注入,睡眠时长由 Agent 自主决定 - 🆕 **go_sleep 简化**:Agent 可传 `duration_min` 指定时长,未指定时根据精力/时间自动判断小睡(60-120分钟)或整晚(7-8小时),不再依赖计划入睡前6小时判断 - 🆕 **attempt_wake 阶段推进改为缩短 action_until_realtime**:唤醒不再修改 wake_time,而是把截止时间提前到上一阶段边界 - 🐛 **修复叫不醒 bug**:`get_sleep_depth` 因数据维度不一致返回 0,`attempt_wake` 的 depth=0 分支没有执行状态变更。两处均已修复 - 🐛 **修复 eat_food 位置死循环**:Agent 在客厅吃冰箱食物被拒但没有引导提示,新增工具描述位置限制 + 错误消息"请先 go_to Kitchen" - 🐛 **修复 eat_food 无法区分同类型食物**:参数从 `item_id`(模板ID)改为优先用 `uid`(唯一实例ID),两份番茄鸡蛋不再混淆 - 🐛 **修复烹饪成品显示名**:3 处代码用模板名"家常剩饭"而非自定义名"番茄鸡蛋炒饭",已全部改用 `inst.custom_name` - 🐛 **修复 LLM 思考模式**:网关层默认禁用 Qwen3.x 思考模式(`enable_thinking: False`),所有调用点统一生效,wake 叙事从 39s 降至数秒 - ✅ 277 个单元测试通过 ### V62 - 虚拟生命引擎升级 `[feature/v62-emotion-engine]` - 🆕 **PAD 弹簧阻尼情绪引擎**:三维情绪模型(愉悦度P/激活度A/支配度D),弹簧劲度+阻尼+逆势摩擦物理积分,每 Tick 自动推导 8 种心情标签(content/happy/shy/normal/annoyed/angry/scared/panic),性格基线决定自然回归点,`engines/emotion_engine.py` 新建 - 🆕 **事件情绪冲力规则引擎**:27 种事件类型→PAD 冲力向量映射(进食/社交/触碰/生理/关系/日常),动作执行自动触发对应情绪事件,`engines/emotion_rules.py` 新建 - 🆕 **记忆情绪门控**:L2+ 记忆写入门控 `Importance≥0.3 OR ΔPAD≥0.4`,L4 核心记忆门控 `Importance≥0.8 OR ΔPAD≥0.4`,记忆写入自动附加 PAD 快照,检索结果情绪加权 - 🆕 **健康控制层增强**:4 级健康状态(healthy/subhealthy/sick/critical),亚健康区间精力消耗+20%,6 种健康因素规则引擎(慢性饥饿/极度饥饿/透支/病愈/休息恢复/进食恢复) - 🆕 **关系期望值调节**:高好感→正面收益递减、负面冲击放大;低好感→正面收益放大、负面冲击递减。`adjust_favorability` 自动应用调节系数 - 🆕 **动态 SMS 响应延迟**:基于疲劳度和情绪激活度动态计算延迟时间,替代固定冷却 - 🆕 **主动分享概率**:基于情绪波动幅度和孤独度计算每次 Tick 妹妹主动发 SMS/便利贴的概率 - 🏗️ **models.py 扩展**:新增 `EmotionalState`、`EmotionalProfile`、`HealthLevel` 模型,`MemoryEntry` 新增 `pad_snapshot`/`pad_intensity`,`SisterState` 新增情绪相关字段 - 🏗️ **Agent Prompt 增强**:注入 PAD 三维情绪状态和健康等级提示,亚健康时注入"身体不适"暗示影响决策 - 🏗️ **叙事增强**:`build_contextual_prompt` 注入 PAD 数值和健康状态描述(脸色不好/病怏怏),mood 规则扩展为 8 种 - 🔧 **存档向后兼容**:旧存档加载时 `EmotionalState` 全默认值(全 0),`mood` 保持 "normal",不崩溃 - ✅ 281 个单元测试全部通过(新增 59 个 V62 专项测试) ### V61 - Agent 决策主权 + TTS 调优 `[feature/agent-architecture]` - 🏗️ **Agent 决策主权机制**:在线时 Agent (LLM) 全权决策,系统规则(workflow/fallback/override)不再干预;只有濒死状态例外。Agent 决定的行为不会被硬编码规则强制覆盖(如深夜强制睡觉覆盖正在吃东西) - ⚡ **TTS max_new_tokens 降至 256**:进一步缩短生成耗时 - 🧪 **TTS 性能测试套件**:`tests/test_tts_perf.py` — 5 个真实对话场景 + 截断对比 + max_tokens 分档对比 - 🧪 **LLM GPU 推理测试**:`tests/test_llm_gpu.py` — Qwen3.5-2B GGUF 模型推理 + GPU 利用率采样监控 - 🔧 **安装 llama-cpp-python (CUDA 12.4)**:支持本地 GGUF 模型推理 ### V60.1 - TTS 优化 + 环境配置补全 `[feature/agent-architecture]` - ⚡ **TTS 生成提速**:`max_new_tokens` 从 2048 降至 1024,`temperature` 0.9→0.7,`top_k` 50→30,缩短采样耗时 - ⚡ **音频格式 WAV→OGG Vorbis**:文件体积缩小约 10 倍(数百KB→数十KB),前端加载更快 - 🐛 **修复 React 前端 TTS 不播放**:WS 推送字段名 `audioUrl` 与前端读取 `audio_url` 不匹配,导致音频 URL 丢失 - 🐛 **修复 React 前端音频 404**:`vite.config.ts` 缺少 `/audio` 代理规则,请求未转发到后端 - 🐛 **修复 routes.py 启动报错**:缺少 `from typing import Optional` 导入 - 🔧 **好感度初始值**:从 40 调回 60,获得更自然的初始关系 - 🔧 **`.env.example` 补全**:从 3 个环境变量扩展到全部 10 个(含记忆提取、图片生成、TTS 相关配置) - 📊 **TTS 生成耗时日志**:记录生成时间(ms)、文件大小(KB)、文本长度(chars),便于性能观察 ### V60 - TTS 语音合成集成 `[feature/agent-architecture]` - 🆕 **聊天自动语音**:玩家与妹妹聊天时,后台异步生成语音,生成完毕自动播放 - 🆕 **对话文本智能提取**:自动剥离旁白/动作描写 `(...)`,只语音化妹妹说的话 - 🆕 **音频缓存**:`audio/{user_id}/` 目录按用户隔离,SHA256 去重,LRU 淘汰最近 100 条 - 🆕 **异步生成 + WS 推送**:文字立即返回,音频就绪后通过 WebSocket 推送 `audio.ready` - 🆕 **前端播放按钮**:DialogueBox 和 HistoryModal 每条消息旁显示 ▶ 按钮,支持手动重播 - 🆕 **本地 TTS 基础设施(V59)**:集成 Qwen3-TTS-12Hz-0.6B-CustomVoice,默认 Serena 温柔女声 - 🆕 **TTS API 端点**:`POST /api/tts`,接受文本+音色+情绪指令,返回 WAV 音频 - 🆕 **启动时模型检查**:首次启动自动通过 modelscope 下载模型到 `model/tts/`(不入 Git) - 🆕 **懒加载机制**:模型在首次 TTS 请求时加载到 GPU,启动不阻塞 - 🎭 **自然语言情绪控制**:支持 `instruct` 参数控制语音风格("用撒娇的语气说"等) - 📦 **依赖更新**:requirements.txt 新增 qwen-tts、modelscope、soundfile - 🔧 **React/Vue 双前端同步**:DialogueBox + HistoryModal 播放按钮、WS 自动播放 ### V59 - TTS 基础设施 `[feature/agent-architecture]` - 🆕 **本地 TTS 模型集成**:Qwen3-TTS-12Hz-0.6B-CustomVoice,Serena 默认音色 - 🆕 **TTS 服务**:`app/services/tts_service.py`,懒加载单例,启动时模型检查 - 🆕 **TTS API**:`POST /api/tts` 文本转语音端点 ### V58.2 - 日志系统重构 + 购物AP修复 `[feature/agent-architecture]` - 🏗️ **日志三文件分流**:server.log(游戏关键动作:Agent决策、LLM调用、状态变更)、metabolism.log(系统例行:tick调度、自动存档、代谢数值、HTTP/WS)、heartbeat.log(心跳),全部按日切换轮转 - 🎨 **日志中文化**:SisterStateMgr 关键字段翻译为中文(饥饿、精力、好感、可被打扰、入睡、醒来等),一眼看懂状态变化 - 🐛 **修复 WS 推送 sister_action 报错**:`_async_push` 中 `state` 变量在 `sister_action` 分支未定义,导致每次妹妹动作推送失败 - 🐛 **修复启动时 update_sister_state 重复调用**:`else` 分支和无条件调用各执行一次,导致启动日志重复 - 🐛 **修复便利店购物 AP 消耗过高**:前端 TripPlanner 购物步骤硬编码 2 AP → 1 AP(后端实际只扣移动的 1 AP) - ✅ 223 个单元测试全部通过 ### V58.1 - 离线托管重构 + 便利贴防幻觉 `[feature/agent-architecture]` - 🏗️ **离线 Workflow 全面接管生存决策**:移除离线 Agent 的 `run_sister_agent` 调用,生存(吃饭/睡觉/买食物/打工)完全由 Workflow 确定性引擎处理,Agent 仅用于社交(便利贴由 scheduler 独立调度) - 🏗️ **打工自救链路**:在家+没食物+没钱时,妹妹自动执行「去人才市场→发传单(赚100元)→去便利店→买2-3个食物→回家→吃」的完整工作流,不再死等 Agent - 🏗️ **预算规划购买**:兜底购买从"只买1个最便宜"升级为贪心算法,按价格升序根据预算买2-3个食物备荒 - 🐛 **修复便利贴幻觉**:便利贴 prompt 注入真实游戏状态(食物清单、妹妹感知、实际饥饿/精力),防止 LLM 凭空编造"冰箱留了布丁" - 🎨 **离线托管日志标记**:所有离线 Workflow 节点描述加 `(离线托管)` 前缀,日志中可区分在线/离线行为 - ✅ 223 个单元测试全部通过 ### V58 - Agent 渐进式披露 + Chat→Agent 握手协议 `[feature/agent-architecture]` - 🏗️ **Agent 渐进式披露**:按位置动态裁剪工具列表(20→9~14个)、按状态条件注入技能 SOP、Prompt 区块按需组装,降低约 50% 工具定义 Token 消耗 - 🏗️ **活动中断机制(Chat→Agent 握手)**:聊天 LLM 不再直接修改妹妹状态(废弃 `sister_use_item`),改为通过 `sister_interrupt` 信号清除活动截止时间 + 写感知,Agent 在下个 tick 接管执行实际动作。职责分离:叙事权属聊天 LLM,执行权属 Agent - 🐛 **修复 SisterStateManager UnboundLocalError**:handlers.py 两处局部 import 遮蔽模块级 import,导致 `sister_use_item` 处理崩溃 - ✅ 223 个单元测试全部通过 ### V57.2 - 感知格式优化 + 进食日志修复 `[feature/agent-architecture]` - 🎨 **感知格式重构**:妹妹感知拆分为「妹妹思考」和「妹妹行为」两行显示,阅读更清晰 - 🐛 **修复感知中英文混杂**:哥哥回应妹妹请求时,感知内容出现 "哥哥答应去做hungry" 的英文残留,现映射为中文(hungry→弄吃的、bored→找点乐子等) - 🐛 **修复重复感知**:当 pending_interaction 和 pending_sister_request 同时存在时,哥哥回应会写入两条几乎相同的感知,现去重 - 🎨 **进食日志统一**:格式从"林瑶吃了xxx"改为"正在吃xxx(饥饿-xx,精力+xx),约xx分钟",Agent 和 Workflow 两条路径一致 ### V57.1 - 睡眠死循环修复 + 深夜熬夜惩罚 `[feature/agent-architecture]` - 🐛 **修复 auto-wake 覆盖 Agent 睡眠计划**:`schedule.py` 代谢阶段 auto-wake 每 30s 检查精力≥阈值就重置 `action_until_realtime=0`,导致 Agent 反复调用 `go_sleep` 形成死循环。增加入睡至少 60 分钟才考虑自动唤醒的守卫 - 🆕 **深夜熬夜惩罚(23:00-5:00)**:不睡觉时精力消耗逐渐加速,23:00 为正常速率,线性增长到 5:00 达 300%。熬夜越久精力掉越快,自然驱动妹妹入睡 - 🔧 **Agent Prompt 睡眠规则优化**:将"深夜倾向睡觉"改为告知熬夜惩罚机制,精力高时做安静活动而非硬睡 7.5 小时 - ✅ 223 个单元测试全部通过 ### V57 - Agent 修复 + 目击重构 + 进食架构重设计 `[feature/agent-architecture]` - 🐛 **修复 Agent f-string 格式错误**:`sister_agent.py` Prompt 中 JSON 示例花括号未转义导致 Agent 100% 失败,所有决策退化到 workflow 硬编码。修复后 Agent 恢复正常 - 🔧 **目击系统重构**:删除泛目击 perception(routes.py 两处每操作触发),保留事件驱动目击(做饭/购物/行程回家)。妹妹在家时直接通过提示词感知哥哥位置(全图视野),外出时黑地图 - 🔧 **进食架构重设计**:workflow 管移动,Agent 管选择 - `_exec_eat_food` 改为 Agent(LLM)优先选食物 + 硬编码 fallback - `_exec_buy_food` 改为 Agent 优先选买什么 + 最便宜 fallback,支持多物品 - workflow `_exec_transit` 加精力扣除,遵循移动规范(家↔街 10、街↔店 5) - `_check_sister_auto_eat` 收紧为濒死安全网(hunger>95 且 energy<10),删除 5 处多余调用点 - 🆕 **新增 `run_sister_agent_for_food`**:离线食物决策入口,让 LLM 参与吃什么/买什么的选择 - ✅ 223 个单元测试全部通过 ### V56 - React 移动端前端 `[feature/agent-architecture]` - 🆕 **React 移动端前端**:新增 `frontend-react/` 目录,基于 React 18 + TypeScript + Zustand + Vite 的移动端前端,与原有 Vue 版功能对等 - 同样的 WebSocket 通信协议、玻璃拟态 UI 风格、竖屏 9:16 布局 - 所有 Modal 组件(商店、冰箱、烹饪、行程、记忆系统等)完整迁移 - 角色立绘 + 背景图层 + 屏幕特效(震动、红闪、黑屏) - 🐛 **修复 React StrictMode 双挂载导致 WS 连接报错**:`disconnect()` 优雅处理 CONNECTING 状态,`initWebSocket` 防止 handler 重复注册 - 🐛 **修复 React `{0 && ...}` 渲染 bug**:妹妹视角 cost 为 0 时不会渲染出多余的 "0" ### V55.1 - Prompt 细节优化 `[feature/agent-architecture]` - 🔧 **妹妹感知记录带完整日期**:`format_recent_perceptions` 保留完整 game_time(如 `1日 8:00`),不再截断为仅时间 - 🔧 **持有物品过滤食材**:Prompt 中【持有物品】列表过滤掉 INGREDIENT 类型,妹妹视角只显示成品食物和道具 - 🔧 **妹妹位置具体化**:`她就在这里` → `她在{具体地名}`,LLM 能明确知道妹妹所在房间 - 🗑️ **移除近期行动记录注入**:Prompt 中不再动态注入 `recent_actions`,减少无效上下文 ### V55 - Prompt 净化 + 对话上下文修复 + 死循环修复 `[feature/agent-architecture]` - 🔧 **冰箱静态冲突修复**:移除"冰箱通常是空的"静态文本,改为中性描述"冰箱里的食物取决于哥哥买回来什么",与动态注入不再矛盾 - 🔧 **V22 sister_use_item 只列 FOOD**:妹妹不做菜,V22 物品列表过滤掉 INGREDIENT 类型,避免 LLM 让妹妹啃生米 - 🔧 **sister_use_item 类型校验前置**:先校验 action 与物品类型匹配再删除物品,防止食材被白白吞掉 - 🔧 **对话上下文改用妹妹感知**:废弃经常为空的 `working_memory`(依赖 LLM 提取),改为直接用最近 20 条 `sister_perceptions` 作为对话历史注入 prompt - 🐛 **修复"找吃的"死循环**:移除 Kitchen 进入时强制 `LOOKING_FOOD` 状态,由 Agent 自主决定厨房行为,`_check_sister_auto_eat` 仍作为 hunger>70 的安全网 - 🗑️ **暂停欲望模板注入**:欲望维度文案与警惕值文案存在交叉冲突(警惕0+欲望1=既放松又防备),暂不注入 prompt,待后续加组合仲裁层 - 🗑️ **暂停里程碑系统**:注释掉里程碑注入和判定,避免 LLM 自行判断 milestone 字段;待后续后端硬判定 - 🗑️ **清理废弃字段**:JSON schema 删除 `sister_proposal`(已禁用)和 `milestone` 字段,减少 token 浪费和幻觉风险 - ✅ 157 个单元测试全部通过 ### V54 - 状态变更集中化(GameState 写保护) `[feature/agent-architecture]` - 🔒 **GameState 写保护**:`__setattr__` 扩展到 GameState,6 个核心字段(`favorability`/`suspicion`/`desire`/`game_over`/`items`/`inventory`)禁止直接赋值,必须通过 SisterStateManager - 🔧 **全库 68+ 处违规迁移**:handlers.py、routes.py、game_tick.py、scheduler.py、action.py、agent_tools.py、sister_workflow.py、offline_survival.py、trip.py、main.py 中所有直接写入改为 Manager 方法 - ✅ **SisterStateManager 新方法**:`adjust_desire()`、`remove_item_by_id()`、`consume_food()`(统一三条食物消费路径) - 🐛 **修复 action.py leftover_meal 双重 append bug** - 🧪 **测试辅助**:新增 `_force_game_state_attr()` 绕过 GameState 写保护(仅限测试 fixture) - 📝 **CLAUDE.md 全量更新**:root/app/app/api/app/services/engines/frontend/tests 7 个目录的 CLAUDE.md 同步更新写保护规则 - ✅ 157 个单元测试全部通过 ### V53.1 - 长期养成适配 + LLM 调试日志 + 记忆弹窗修复 `[feature/agent-architecture]` - 🗑️ **移除快速攻略版专属功能**:删除 `get_phase_instruction`(按天数划分阶段氛围)、`build_final_report_prompt`(暑假结束告别信)、`build_bad_end_prompt`(坏结局总结)、`get_force_sleep_text`(强制睡眠)、`get_game_over_text`(死亡结局文本)、`handle_summer_end`/`handle_bad_end`/`handle_game_over_check` 三个 handler 及对应路由调用。长期养成模式不再有 60 天期限和结局 - 🔍 **LLM 调试日志**:`LLMGateway` 新增 `_log_request`/`_log_response`,每次 LLM 调用在 DEBUG 级别完整打印 prompt(所有 role 的完整 content)和响应(content 或 tool_calls),方便调优 prompt 和检查 LLM 输出 - 🐛 **修复记忆弹窗数据不刷新**:打开记忆系统弹窗时自动调用 `syncState()` 拉取最新数据。之前"动态"tab 中的妹妹视角、近期动作等数据只在 new_day 或 WS 重连时才更新 ### V53 - LLM 调用统一网关 `[feature/agent-architecture]` - 🔒 **LLM 调用统一网关(`app/llm_gateway.py`)**:所有 19 个 LLM 调用点统一收口到 `LLMGateway`,像 `SisterStateManager` 保护状态写入一样保护 LLM 调用。调用方不再自行管理限流,网关内部按事件类型自动管理 - 📊 **4 类事件分级限流**: - `PLAYER_INTERACTIVE`(玩家互动/短信/唤醒):不限流 - `AGENT_DECISION`(妹妹自主决策):15分钟/5次 - `SYSTEM_BACKGROUND`(睡眠计划/自动叙事/离线便签):15分钟/5次 - `MEMORY`(记忆提取/压缩):15分钟/10次,独立 client - 🐛 **修复睡眠计划死循环**:旧版 `sync_state` 直接写 `planned_bedtime` 触发写保护 → `sleep_plan_generated_today` 永远为 False → 每 30 秒重试生成 → 12 分钟白烧 ~20 次 LLM API。现已修复 - 🐛 **修复 `set_bedtime` 签名不匹配**:`game_tick.py` 调用缺少 `wake_time` 参数 - 🛡️ **防呆设计**:新增 LLM 调用必须走网关,即使新增代码忘记检查限流也会被网关拦截并记日志 - ✅ 138 个单元测试全部通过 ### V52.2 - WS 状态同步修复 + 多样化购物 `[feature/agent-architecture]` - 🐛 **修复妹妹状态显示不同步**:`stats_update` 和 `sister.action` 推送新增 `action_description` 字段;前端 WS 处理器同步接收并更新该字段,解决"街道·看电视"短暂闪现后需刷新才显示"街道·去街道中"的问题 - 🐛 **`move_to` 原子更新**:`go_to` 工具和 transit arrival 的 `move_to` 调用现在带 `activity=SisterActivity.OUTSIDE` 参数,确保地址和活动状态同步变更 - 🛒 **多样化购物**:`buy_food` 工具新增 `items` 数组参数,支持一次买多样不同食物(如两个肉包+一份关东煮);保留 `item_id+qty` 单件兼容 - 💰 **购物预算容错**:钱不够时返回详细的可负担清单,引导 LLM 减量重试而非直接失败 - 📝 **Agent 提示词更新**:新增多样化购物引导和 `items` 参数示例 - ✅ 138 个单元测试全部通过 ### V52.1 - 交互修复 + 跟随移除 + 视角修正 `[feature/agent-architecture]` - 💬 **妹妹主动对话改为输入框**:`SisterRequestModal` 从预设按钮("给她"/"收起来")改为文本输入框,玩家自由回复,LLM 通过 `brother_response` 判断承诺/拒绝 - 💬 **WS 推送自动弹窗**:游戏 tick 推送 `pending_interaction` 事件时,前端自动弹出输入框等待玩家回应 - 🗑️ **移除跟随/跟屁虫系统**:删除 `following_player`/`clingy_mode` 字段、`set_following()`/`set_clingy()` 方法、移动跟随逻辑、prompt 中的跟屁虫/提议指引 - 🗑️ **移除提议系统**:删除 `sister_proposal` 处理(LLM 弹"好,走吧"按钮)、叙事中的"一起去"自动检测提案 - 🐛 **修复 pending 反复弹窗**:哥哥承诺后 `pending_interaction` 未 fulfill,每 30 秒 tick 重新推送弹窗 - 🐛 **修复旁白编造哥哥行为**:auto-narrative 改为第一人称视角——妹妹同位置才能看到哥哥状态(做家务中/做饭中/出行中),不同位置仅知道"出门了"或无信息 - 🎮 **初始 AP 改为 5**:新游戏/重置时行动力从 10 降为 5,恢复逻辑同步调整 - ✅ 138 个单元测试全部通过 ### V52 - 服务端 Game Tick 架构重构 `[feature/agent-architecture]` - 🏗️ **服务端驱动 Game Tick**:所有游戏逻辑(代谢、妹妹 agent、工作流、pending_interaction、日跨事件)从 sync 端点提取到独立 `game_tick()` 函数,APScheduler 每 30 秒自动运行,独立于前端 - 🏗️ **在线/离线分流**:WS 在线 → sister agent (LLM) 主力决策;WS 离线 → workflow (确定性) 主力 + 每 3h agent - 📡 **WS 实时推送**:tick 产生事件通过 `asyncio.run_coroutine_threadsafe` 桥接推送(`sister.action`、`stats.update`、`sister.pending_interaction`、`narrative` 等) - 🧹 **sync 瘦身**:`/api/sync` 改为纯状态查询,仅用于初始加载/重连 - 🧹 **WS 端点简化**:删除 `_bg_sync` 后台协程,WS 变为纯消息路由 - 🗑️ **清理 Director 残留**:routes.py / handlers.py / time_engine.py 中 `Director` 引用全部移除 - 🗑️ **清理旧 scheduler job**:删除 `_update_all_sister_schedules`、`_check_all_sister_auto_eat`、`_check_all_neglect`,由 game_tick 统一替代 - 🐛 **零花钱写保护修复**:`give_pocket_money` 直接赋值改为 `SisterStateManager.adjust_pocket_money()` - 🐛 **移除睡眠不足/熬夜扣健康**:改为精力 < 20 时 -2/hr - 🐛 **深夜 reset 不再强制睡觉**:精力 ≥ 30 玩手机 - 🎮 **前端 pending_interaction 监听**:妹妹主动说话(`ask_brother`)通过 WS 推送,弹 dialogue box - ✅ 138 个单元测试全部通过 ### V51.2 - 健康扣减重构 + sync 崩溃修复 `[feature/agent-architecture]` - 🩺 **移除睡眠不足/熬夜扣健康**:睡眠时长和就寝时间不再惩罚健康,精力已是唯一睡眠控制维度 - 🩺 **新增精力透支扣健康**:精力 < 20 且非睡觉时,健康 -2/hr(与饥饿过高同等惩罚) - 🐛 **深夜 reset 不再强制睡觉**:精力 ≥ 30 时玩手机而非强制 SLEEPING,避免初始化触发"0小时睡眠"误判 - 🐛 **sync UnboundLocalError 修复**:`sync_state` 内 1552 行局部 import `SisterStateManager` 导致整个函数将其视为局部变量,1414 行提前引用崩溃,每次 sync 都失败、妹妹永远不动。删除多余 import(顶部已有全局 import) ### V51.1 - 写保护遗漏修复 + 系统 LLM 限流 `[feature/agent-architecture]` - 🐛 **写保护遗漏修复**:routes.py(sleep plan / active_workflow / dying / pending_destination / shared food)、scheduler.py(active_workflow)、agent_tools.py(pending_destination)、action.py(dying recovery)共 ~15 处遗漏的直接赋值改为 Manager 调用 - ⏱️ **系统 LLM 15 分钟限流**:滑动窗口机制,15 分钟内系统 LLM 调用不超过 5 次(妹妹 agent、睡眠计划、自动叙事、离线 agent、离线便利贴),防止 bug 导致的无限 LLM 调用浪费算力 - ✅ 玩家聊天/SMS/唤醒/图片不受限流影响,仍走 `daily_llm_calls` 日限额 - ✅ 138 个单元测试全部通过 ### V51 - SisterState 写保护 + EATING 食物校验 `[feature/agent-architecture]` - 🔒 **SisterState 写保护**:`__setattr__` + `_managed_write` 实例标志,外部代码只能读取妹妹状态,写入必须通过 SisterStateManager - 🔒 **`@_mutation` 装饰器**:Manager 方法执行前自动设置写标志,执行后清除,运行时拦截所有绕过 Manager 的直接赋值 - 🍽️ **EATING 食物校验**:`set_activity(EATING)` 必须传 `food_id`,验证物品存在 + 类型为 FOOD + 位置限制(冰箱食物只能在 Kitchen 吃,背包食物不能在 Bathroom 吃) - 🔧 **全面 Manager 化**:~60 处直接 `sister.xxx = ...` 赋值跨 8 个文件改为 Manager 方法调用(schedule.py、time_engine.py、action.py、offline_survival.py、routes.py、handlers.py、agent_tools.py、sister_workflow.py) - 🧪 **`_force_sister_attr` 测试辅助**:测试用例通过 `object.__setattr__` 绕过写保护进行 fixture 初始化 - ✅ 138 个单元测试全部通过 ### V50 - 项目目录重组 `[feature/agent-architecture]` - 🏗️ **Java 风格包结构**:根目录仅保留 `main.py` 入口,所有业务代码归入 `app/` 包 - 🏗️ **三个子包**:`app/api/`(路由+处理+WebSocket)、`app/auth/`(认证+会话+账户)、`app/services/`(物品+记忆+存档+持久化+调度) - 🗑️ **删除死代码**:`events.py` + `engines/director.py`(互为唯一引用,无活跃调用者) - 🧹 **清理垃圾文件**:调试用 state*.json、autosave.json.bak、1.png、根目录 node_modules/ - 📁 **日志集中**:`logs/` 目录,server.log 不再散落在根目录 - 📁 **文档归档**:PRODUCT.md、ASSETS_GUIDE.md 移入 docs/ - ✅ 138 个单元测试全部通过 ### V49 - 吃饭修复 + 同步崩溃修复 + 购物感知 `[feature/agent-architecture]` - 🐛 **Agent 假吃修复**:妹妹用 `start_activity(eating)` 假装吃东西(不消耗食物/不减饥饿),改为必须走 `eat_food` 工具,prompt 加明确指引"饿了必须用 eat_food" - 🐛 **Workflow 饥饿度符号修复**:`_exec_eat_food` 调用 `adjust_hunger(+值)` 导致吃东西反而越吃越饿,改为 `adjust_hunger(-值)` - 🐛 **sync_state 崩溃修复**:函数内部局部 `from ... import SisterStateManager` 导致 Python UnboundLocalError,每次 sync 都崩溃(代谢、agent 决策全跳过),移除重复 import - 🐛 **购物感知缺失**:行程完成/直接购买时不写 `write_perception`,妹妹不知道哥哥买了什么;改为妹妹在场时写感知 - 🐛 **WS token 过期跳转修复**:`websocket.accept()` 移到 auth check 之前,确保 close frame code 4001 正确到达客户端触发登录页跳转 - 🐛 **_auto_eat_from_fridge 直接改值修复**:改用 SisterStateManager.adjust_hunger/adjust_health/adjust_energy - ✅ `start_activity` 禁止 eating/cooking 活动(返回错误提示引导使用正确工具) ### V48 - Sister Workflow Engine `[feature/agent-architecture]` - ✅ **确定性多步工作流引擎**:`engines/sister_workflow.py`,替代散落的 `check_sister_needs_override` + `decide_sister_action_llm` - ✅ **5 级优先级**:dying > tired+hungry > tired > hungry > default(go home) - ✅ **WorkflowNode 模型**:action, location, duration_min, desc, pending_destination, completed - ✅ **真实时间节点**:每个节点有 duration,必须等真实时间过期才执行下一节点 - ✅ **Online/Offline 双模式**:Online = dying workflow + agent 主力 + workflow 降级兜底;Offline = scheduler 用 workflow + 每 3h 调 agent - ✅ **所有状态变更通过 SisterStateManager** - ✅ **18 个单元测试**覆盖 WorkflowNode、select_workflow(10 场景)、execute_node(4 动作) - ✅ `check_sister_needs_override` 和 `decide_sister_action_llm` 标记 deprecated ### V47.2 - 中心街道 transit 修复 + 活动中文映射补全 `[feature/agent-architecture]` - ✅ **中心街道模型修复**:override/legacy 路径赶路回家时 `location` 不再直接跳到目标地,transit 期间保持 `"Street"`,到期后通过 `pending_destination` 到达,与 agent 路径一致 - ✅ 修复赶路日志语义矛盾("客厅·急忙往家赶"→ 应显示"街道·急忙往家赶") - ✅ 修复赶路完成后瞬间触发睡觉(transit 到位后下一轮 override 再触发) - ✅ 补全 3 处活动中文映射缺失(`sister_agent.py`、`utils.py`、`schedule.py` 的 `bench_rest`/`cooking`/`together_*`) ### V47.1 - 休息恢复修复 + 曲线恢复机制 `[feature/agent-architecture]` - ✅ **RESTING 精力恢复**:在家休息恢复精力(base 8/hr),之前休息时精力反而在掉(-4/hr 死循环) - ✅ **曲线恢复机制**:以 50 精力为基线,精力越低恢复越快(RESTING/BENCH_REST),接近满时自然减速(最低 20%) - ✅ 修复户外精力低时强制 RESTING + 描述"在家休息"(实际在街上),改为 BENCH_REST + 正确描述 - ✅ 修复前端活动状态显示英文 `bench_rest`(补充 store/game.js 缺失的中文映射) ### V47 - 主动沟通 + 纯净做饭 `[feature/agent-architecture]` - ✅ **PendingInteraction 主动沟通系统**:妹妹饿了/无聊时主动向哥哥提出请求(`ask_brother` 工具),等待哥哥回应 - ✅ **超时自解决**:请求有等待时限(饥饿程度越急越短),超时妹妹自己想办法(买零食、找事做) - ✅ **LLM 判断哥哥回复**:哥哥聊天内容由 LLM 判断为"承诺"或"敷衍",不再硬编码关键词匹配 - ✅ **做饭等待绑定**:哥哥答应做饭且确实在做饭 → 妹妹 `action_until_realtime` 绑定到哥哥的 `busy_until_realtime`,不再自己跑掉 - ✅ **纯净做饭**:移除做饭时妹妹自动吃/尝的旧逻辑,做饭只产出物品,吃是妹妹的主动动作(`eat_food` 工具) - ✅ 新增 16 个单元测试覆盖 PendingInteraction 全链路(模型、管理器、工具、边界),总测试数 120 个 ### V46 - 街道座椅休息 + 户外/厨房行为简化 `[feature/agent-architecture]` - ✅ 街道座椅休息:新增 `BENCH_REST` 活动,妹妹在街上精力低时座椅休息恢复精力(8/hr),不再被强制赶回家 - ✅ 街上行为简化:只保留"座椅休息"和"出行中"两种状态,不再有发呆/玩手机等闲置活动 - ✅ 厨房行为限制:厨房不允许做休闲活动(玩手机/看电视/看书/听音乐),只用于做饭 - ✅ Override 修复:妹妹在户外精力低 → 触发座椅休息(30-60分钟)而非赶回家睡觉 - ✅ 妹妹 agent prompt 更新:街上累了可以座椅休息,厨房不能休闲 - ✅ 修复 SisterStateManager UnboundLocalError(sync 重复计算代谢导致属性异常扣除) ### V45 - 目击系统 + 做饭修复 `[feature/agent-architecture]` - ✅ 妹妹目击系统(V45):`SisterState.last_seen_brother` 追踪妹妹上次看到哥哥的位置/时间/活动 - ✅ 妹妹 agent prompt 注入哥哥位置:同位置显示"同在客厅",不同位置显示"上次30分钟前在厨房看到他(他在做饭)" - ✅ `check_player_location` 工具改造:同位置返回真实位置,不同位置仅返回上次目击信息 - ✅ 相遇触发:哥哥移动到妹妹位置 / 妹妹 go_to 到哥哥位置 → 写感知 + 更新目击记录 - ✅ 做饭成品延迟创建:食材立刻消耗,成品在做完(busy 状态结束)后才创建,不再提前出现 - ✅ 做饭成品显示真实菜名:`ItemInstance` 新增 `custom_name`/`custom_description`/`custom_effects` 字段,做鸡腿面就显示"鸡腿面(⭐⭐⭐ 美味)" - ✅ 修复 `engines/action.py` 缺失 `ItemInstance` import 导致做饭 NameError ### V44 - 自动化测试框架 `[feature/agent-architecture]` - ✅ pytest 测试体系(120 个单元测试,0.4s 跑完) - ✅ ACTION_MATRIX 数值校验(每个动作的时间/精力/饥饿/警惕/好感/收入与 CLAUDE.md 文档一致) - ✅ 动作检测关键词测试(睡觉/聊天/家务/做饭/触碰/打工/移动等,含边界用例如"睡回笼觉"≠睡觉) - ✅ 代谢公式测试(警惕 -2/hr 衰减、睡眠 ≥6h 惩罚 -20、离线 48h 封顶、AP 恢复) - ✅ SisterStateManager 守卫测试(agent action 活跃时阻止位置/活动变更、force=True 绕过、MutationBlocked 异常) - ✅ 状态转换测试(enter_sleep/wake_up/enter_sick/recover_from_sick 原子操作) - ✅ 数值 clamp 测试(adjust_hunger/energy/health 的 0-100 边界) - ✅ 物品系统测试(过期判定、UID 生成、存储位置、配方匹配接口) - ✅ Fixture 系统(fresh_state 游戏状态工厂、关系预设、妹妹位置预设、时间 mock) - ✅ 测试隔离设计:不调 LLM、不启动服务器、不碰存档、不依赖真实时间 - ✅ `tests/README.md` 完整测试指南 ### V43.3 - 离线代谢修复 + 白天睡觉时长修复 `[feature/agent-architecture]` - ✅ 修复离线补算跳过妹妹代谢:`apply_offline_catchup` 新增 `_update_sister_needs` 调用,服务器重启不再丢失睡眠精力恢复 - ✅ 修复 `go_sleep` nap 标志读取错误:`action_result["result"].get("nap")` 替代 `action_result.get("nap")`,白天小睡正确返回 60-120 分钟而非硬编码 420 分钟 - ✅ 移除 `check_sister_needs_override` 的饥饿覆盖(空 EATING 不消耗食物不降饥饿,由 agent `eat_food` 工具和 `_check_sister_auto_eat` 处理) ### V43.2 - 冰箱保质期修复 `[feature/agent-architecture]` - ✅ 修复冰箱保质期叠加 bug:取出→放入重复操作会无限延长保质期 - ✅ `ItemInstance` 新增 `shelf_life` 字段,存储原始保质期天数 - ✅ `calc_fridge_expiry` 改用 `bought_date + shelf_life + bonus` 计算,不再基于当前剩余天数,彻底消除重复放入的叠加问题 - ✅ 行程/妹妹购物自动放冰箱时也正确调用 `calc_fridge_expiry` 延长保质期 ### V43.1 - 小睡修复 + 行程状态同步 `[feature/agent-architecture]` - ✅ `go_sleep` 区分小睡和正式睡眠:当前时间 < planned_bedtime → 小睡60-120分钟;>= planned_bedtime → 正式睡到 waketime - ✅ Agent prompt 注入 planned_bedtime + 小睡引导("精力低就小睡,别一觉到天亮") - ✅ 妹妹在睡觉时跳过睡眠规划生成(不再产生无意义的"今晚20:30睡"计划) - ✅ 修复行程结束后前端状态不同步:WS `stats.update` 推送新增 `on_trip` 字段,前端同步 - ✅ 行程结束自动便利贴:妹妹不在场(睡觉/不在客厅)时,自动在冰箱贴便利贴告知行程内容 ### V43 - 记忆系统统一到感知流 `[feature/agent-architecture]` - ✅ L1/L2/L3 + 原子记忆全部从 `sister_perceptions`(妹妹主观感知流)提取,不再从 `conversation_log` 提取 - ✅ 每新增10条感知触发一次提取(L1 叙事摘要 + 原子事实),0点强制提取剩余 - ✅ 妹妹 agent prompt 注入 L1/L2/L3 + 原子记忆(最近15条感知 + 长期记忆) - ✅ `record_action()` 不再写 `working_memory`,`working_memory` 完全由感知流提取管理 - ✅ 移除 `interaction_counter` 触发路径(旧的每5次对话提取) - ✅ `memory.py` `summarize_day()` / `summarize_incremental()` 适配新 working_memory 格式 ### V42 - 日程表移除 + 吃饭幻觉修复 + 行动力恢复 `[feature/agent-architecture]` - ✅ 移除 BASE_SCHEDULE / HIGH_FAVOR_SCHEDULE 日程表(V9 遗留),妹妹行为完全由 LLM Agent 驱动 - ✅ 修复"空气吃饭":12:00/19:00 硬编码 EATING 导致无食物也减饥饿,现在吃饭只走 agent `eat_food` 工具(有食物校验) - ✅ 修复行动力不恢复:`recover_action_points()` 加入 `/api/sync`(每30秒+上线时调用),离线时间正确恢复 AP - ✅ 修复行程 AP 不一致:前端市场购物 `apCost` 1→2(移动+购买),移除后端自动插入 Street 中转步骤(前端 `buildSteps` 已处理) - ✅ 修复前端打工数据不同步:ALL_JOBS 收入/时长与后端 `JOB_INCOME`/`JOB_DURATION` 对齐 - ✅ 修复服务器重启踢登录:WS `onclose` 不再把 `1006`(连接中断)当认证失败清 token,改为自动重连+状态同步 - ✅ 妹妹 Agent prompt 注入玩家常在线时段(`player_activity_hours` top3),Agent 会倾向在哥哥在线时保持醒着 - ✅ `get_sister_fallback_action` 按位置选活动(室外:听音乐/玩手机/发呆;室内:+看书/看电视),不做复杂行为 - ✅ 移除 `adapt_schedule_to_player`(改日程表条目→不再适用),玩家习惯适应改由 Agent prompt 和睡眠计划 LLM 承担 ### V41.2 - 短信/Transit/Session 修复 `[feature/agent-architecture]` - ✅ SMS 降级到 interact 时补全 `state=game_state` 参数(修复 Pydantic ValidationError) - ✅ 行程中发短信不再被拦截(`on_trip` 时跳过同位置降级) - ✅ Override 路径遵循 V40.3 中心街道模型(户外回家先到 Street,不直接跳室内) - ✅ `apply_sister_action` 修复 `activity=outside` + indoor location 的 findability 矛盾 - ✅ `persistence.delete()` 补全 `user_id` 参数(修复 reset 500 错误) - ✅ 服务器重启不再覆盖 agent 设置的 activity(移除存档加载时的 `_adapt_initial_state_to_time`) - ✅ JWT session 重启自动恢复(合法 token 首次请求自动回写 `_active_sessions`) - ✅ Sister Agent LLM `max_tokens` 500→1000 - ✅ 聊天 perception 妹妹回复截断 100→200 字符 ### V41 - 现实日期过期 + 行程余额平衡 `[feature/agent-architecture]` - ✅ ItemInstance 过期系统改用真实日历日期(`bought_date`/`expires_date` ISO格式,替代旧的相对天数 `bought_day`/`expires_day`) - ✅ 冰箱保鲜基于真实日期计算(remaining + bonus days from today) - ✅ 过期检查基于 `date.today()`(`_remove_expired_food`、`is_expired` 属性) - ✅ API 响应返回 `bought_date`/`expires_date` 字段 - ✅ 行程规划 running balance:打工收入可支付后续购物(前端 `maxTempQty` + 后端验证逐步骤检查余额) - ✅ 行程完成自动回到客厅(`state.location = "LivingRoom"`,V28 哥哥不出门) ### V40.3 - 中心街道模型 + 背包改版 + SMS降级 `[feature/agent-architecture]` - ✅ 中心街道模型:哥哥和妹妹移动过程中 location = Street(途中),步骤完成后到达目标地点 - ✅ 妹妹 transit 系统:`pending_destination` 记录目的地,sync 到期自动到达,到家时才放冰箱 - ✅ 哥哥行程位置更新:`update_trip_location()` 每30秒根据步骤进度更新 `state.location` - ✅ 背包改版:食物无操作按钮(妹妹自己吃),药品显示"喂药"按钮 - ✅ SMS 自动降级:妹妹在身边时不再硬拒,自动转发为正常聊天 ### V40.2 - 背包食物/药品全部给妹妹 + 全量状态WS推送 `[feature/agent-architecture]` - ✅ 背包食物和药品按钮从"使用"改为"给妹妹",效果直接加到妹妹身上 - ✅ 后端食物路径统一走 `_handle_gift_sister`:妹妹睡觉/濒死/不在身边时阻止 - ✅ WS `stats.update` 推送:每30秒同步周期推送全量状态(妹妹属性、时钟、好感、警惕、玩家状态),前端无需刷新 ### V40.1 - 感知完整性 + Agent 推理可观测 `[feature/agent-architecture]` - ✅ 聊天/SMS 感知完整记录:哥哥说了什么 + 妹妹回了什么(之前只截30字) - ✅ 妹妹感知条数从 8 条增加到 15 条 - ✅ Agent 推理捕获:LLM 决策前先说一句想法,记录到 `recent_actions` 和 `sister_perceptions` - ✅ Schedule 活动变更也写 perception(之前只记位置变化,不记活动变化如看手机→听音乐) - ✅ 修复 trip planner 打工步骤背景图 404(`bg_work_flyer` → `bg_street_talentmarket`) ### V40 - 行为守卫 + 濒死系统 + 妹妹动作推送 `[feature/agent-architecture]` - ✅ 行为不可中断:所有行为设了 duration 后必须等 `action_until_realtime` 到期才能切换 - ✅ 睡觉守卫:`go_sleep` 设置 `action_until_realtime`,只有哥哥主动唤醒(`/api/sister/wake`)可提前结束 - ✅ 精力<10 出门限制:妹妹精力不足不允许出门(便利店最少要10精力) - ✅ 濒死状态:energy≤5 + hunger≥90 + health<20 → 妹妹进入 DYING,卧床不动,代谢冻结 - ✅ 大还丹(¥100):药店级恢复,+20 精力/健康/饱腹,濒死和睡眠中也可服用 - ✅ 进食限制:睡觉/濒死不能吃食物,但可以吃药(大还丹、提神饮料等) - ✅ 妹妹动作即时推送:妹妹自主行为通过 WebSocket 推送到前端聊天栏显示 - ✅ 修复 `auto_save` 缺少 `user_id` 参数导致存档失效 ### V39 - 离线生存系统 + 离线便利贴 `[feature/agent-architecture]` - ✅ 离线妹妹生存 workflow(硬编码:饿了吃→没的买→没钱打工→累了睡觉,不走 LLM) - ✅ 在线/离线自动切换(在线走 LLM Agent,离线走 workflow,无缝切换) - ✅ 离线便利贴(LLM 生成,6-12h 冷却,优先回复哥哥留言,拉活利器) - ✅ 新文件 `engines/offline_survival.py`(生存 workflow + 便利贴生成) - ✅ Scheduler 多租户在线检查(`_is_online()` 基于 WS 连接状态) ### V38 - 多租户架构 + JWT 认证 + 管理后台 `[feature/agent-architecture]` - ✅ 多租户 GameState(`Dict[str, GameState]` 按 user_id 隔离) - ✅ JWT 认证(HS256,7天有效期,jti 互踢机制,第二登录自动踢掉第一) - ✅ 账号管理(JSON 文件存储,bcrypt 密码哈希,默认3个测试账号) - ✅ 管理员后台(Web 页面,添加/删除账号、重置密码、踢下线、在线状态) - ✅ 登录页(玻璃拟态风格,自动登录检测) - ✅ 按用户文件持久化(`saves/{user_id}/autosave.json`) - ✅ 全部 API 端点 + WebSocket 路由加 JWT 鉴权 - ✅ 前端 portrait 竖屏约束(桌面端 9:16 比例) - ✅ 首次进入立即同步状态(先连 WS 再渲染) ### V37 - WebSocket 全面通信改造 `[feature/agent-architecture]` - ✅ 后端 WebSocket 端点(`/ws`,ConnectionManager + MessageRouter,心跳 ping/pong) - ✅ 34 个 WS method handlers(覆盖全部 HTTP API 功能,JSON-RPC 风格协议) - ✅ 后台 30s sync 推送(妹妹自主行动/状态变化/自动叙事通过 WS 即时推送,替代前端轮询) - ✅ 前端 GameWebSocket 客户端(`ws.js`,自动重连指数退避、心跳保活、请求/响应匹配、断线消息队列) - ✅ 前端 API 层全面重写(`game.js` 从 `uni.request` 改为 `ws.request()`) - ✅ Store 改造(`startPolling` → `initWebSocket`,推送事件监听:sister.action/sister.sms/toast/narrative) - ✅ HTTP 端点保留不删除(deprecated),确保向后兼容 - 🔲 LLM 流式推送(待完成:`handle_llm_response` 需改为 async,LLM `stream=True` 逐 chunk 推送,前端打字机效果) - 🔲 推送差异化(待完成:当前推送全量 GameState,后续优化为 diff) - 🔲 小程序适配(待完成:`ws.js` 传输层从 `new WebSocket()` 换成 `uni.connectSocket`) ### V36.4 - 提示词位置上下文 + 出行修复 + UX打磨 `[feature/agent-architecture]` - 🔧 交互提示词注入妹妹位置上下文(不同位置/忙碌/可交互三态描述) - 🔧 修复行程规划中打工任务筛选和预算计算 bug - 🔧 便利贴交互体验优化 ### V36.3 - 场景图 + 菜市场营业时间 + Agent路径修复 `[feature/agent-architecture]` - 🎨 便利店/人才市场/菜市场独立场景背景图(AI生成,PNG→JPG,日间+夜间) - ✅ 菜市场营业时间约束(6:00-18:00,夜间关门不可访问) - API端点(/api/market/list、/api/market/buy)加营业时间校验 - 玩家移动(action.py)18:00-6:00 禁止前往菜市场 - 妹妹日程(schedule.py)validate_sister_change 拦截夜间菜市场 - 妹妹Agent(agent_tools.py)go_to 工具加营业时间校验 - 行程规划(trip.py)提交时检查菜市场步骤 - LLM提示词明确菜市场营业时间 - 前端 TripPlanner 夜间菜市场按钮灰显"已关门" - 前端背景夜间菜市场 fallback 到街道 - 🔧 修复妹妹 go_sleep 不校验位置的 bug(从外面直接传送到卧室睡觉) - agent_tools: go_sleep 加位置校验,外面需先回家 - sister_agent: go_sleep 失败时正确处理(chain_next 触发回家决策) ### V36.2 - Bug修复 `[feature/agent-architecture]` - 🔧 行程规划打工选择按累计打工次数过滤(之前显示全部工作,与后端解锁条件不一致) - 🔧 行程预算考虑打工收入(running balance 检查:打工赚的钱可用于后续购物,如 ¥100 + 打工 ¥50 → 可花 ¥150) - 🔧 便利贴改名("冰箱便利贴"→"便利贴"),外出中点击弹 toast 提示而非无响应 ### V36.1 - Bug修复 `[feature/agent-architecture]` - 🔧 修复 tool handler 和 routes.py 重复写 log_action 导致近期动作重复(统一由 routes.py 写入,带 reason) - 🔧 tool handler 返回值补充 desc 字段(buy_food、eat_food、start_work),保证 routes.py 日志内容完整 ### V36 - 妹妹主观感知系统 `[feature/agent-architecture]` - ✅ SisterPerception 模型(替代 SisterActionLog,5种感知来源:self/witnessed/chat/sms/sticky_note) - ✅ 妹妹 agent prompt 从 recent_actions(上帝视角)改为 sister_perceptions(主观感知流) - ✅ 实时感知追踪(哥哥做家务/做饭时妹妹在场才算看到,player_busy_perceived 标记) - ✅ 做饭改为真实等待(菜谱 time_cost 分钟 1:1 等同,15-65分钟) - ✅ 未被感知的忙碌行为自动写便利贴(信息补漏) - ✅ write_perception 统一写入 + 3天自动清理 - ✅ 前端 MemoryModal 妹妹视角 tab 改为显示感知流 ### V35.1 - Agent 行为可观测性 + 睡眠计划修复 `[feature/agent-architecture]` - 🔧 Agent 行动工具新增 `reason` 字段(`go_to` / `start_activity` / `go_sleep`),LLM 必须说明每次行动的原因 - 🔧 行为日志携带理由(如 `林瑶去了便利店——想买点零食吃` 替代原来的 `林瑶去了便利店`) - 🔧 修复睡眠计划生成时间早于当前时间的 bug(23:30 生成"决定23:00睡") - 🔧 睡眠计划 prompt 明确提醒"bedtime 不能早于现在" - 🔧 睡眠计划 bedtime 下限改为 `max(当前时间 + 15分钟, 原值)` ### V35 - 便利贴系统 + UI统一 `[feature/agent-architecture]` - ✅ 双向便利贴系统(哥哥和妹妹通过冰箱便利贴异步交流,替代旧V29单向往返便签) - ✅ 自动便利贴生成(做家务/做饭妹妹不在场时自动写,打工必写,跳过LLM直接返回系统文本) - ✅ 妹妹 Agent 便利贴工具(`check_sticky_notes` 读未读+好感加成,`write_sticky_note` 写/回复便利贴) - ✅ 哥哥手动留言(POST `/api/sticky-notes`,支持回复指定便利贴) - ✅ 便利贴 FIFO 容量(最多10条,按时间淘汰) - ✅ 玻璃拟态 StickyNoteModal(哥哥蓝色/妹妹粉色,未读红点,回复输入框) - ✅ 背包 Modal UI 统一(从蓝色渐变改为玻璃拟态风格,和其他 Modal 一致) - ✅ 移动房间不再记录日志(`recent_actions` 不再记录切换房间,不在对话框显示移动提示) ### V34 - 妹妹自主出门系统 `[feature/agent-architecture]` - ✅ `buy_food` 重构:去掉固定3阶段流水线(出门→买→回家),改为在便利店当场购买,支持多件购买(qty参数),物品放入背包 - ✅ `eat_food` 允许在外吃:不再限制只能在家吃,外面从背包取食物吃,使用 `dining_time_cost` 设置进食耗时 - ✅ `list_jobs` 新工具:妹妹在劳务市场查看可选工作(工资/时长/精力消耗/解锁条件) - ✅ `start_work` 新工具:妹妹可以自己去劳务市场打工,工资进零花钱,消耗精力和时间 - ✅ `go_to` 改造:移动不再是瞬移,设置真实时间(家↔街15分钟、街↔店10分钟),新增 `Street_TalentMarket` - ✅ 连锁决策机制:户外行动完成后立即触发下一轮 agent 决策(最多8轮),实现"出门→买→吃→打工→回家"多步规划 - ✅ Prompt 更新:自理决策指南、位置工具规则、出门流程示例 - ✅ 移除旧代码:sync 端点的3阶段购物行程管理、pending_deliveries 兜底配送 ### V33 - 图片生成系统(虚拟试穿 + 自拍) `[feature/agent-architecture]` - ✅ Qwen Image Edit API 封装(`engines/image_gen.py`,换装/自拍两种模式) - ✅ 虚拟试穿:哥哥上传衣服照片 → 妹妹参考立绘 + 衣服图 → Qwen 生成穿着效果图 - ✅ 自拍生成:哥哥说"拍张照" → 基于妹妹立绘 + 场景/表情 → 生成自拍风格图片 - ✅ 图片上传接口(`POST /api/image/upload`,multipart,10MB 限制) - ✅ 换装/自拍接口(`POST /api/image/tryon`、`POST /api/image/selfie`,含妹妹 LLM 文字反应) - ✅ 每日限额 10 张,跨天重置(`daily_image_count`) - ✅ 生成图片本地存储(`generated_images/`),静态文件服务 - ✅ 前端图片上传按钮(📷,`uni.chooseImage` → 上传 → 换装) - ✅ 对话框内图片显示 + 点击放大(`DialogueBox.vue` 全屏 overlay) - ✅ 图片 URL 跟随 `store.dialogue` 生命周期(新对话自动清除旧图) ### V32 - 数值平衡 `[feature/agent-architecture]` - 🔧 打工收入上调(发传单 ¥50→¥100、便利店 ¥140→¥180、家教 ¥200→¥250、工地不变 ¥350) - 🔧 妹妹饥饿速率下调(每小时消耗降低 20%,减少频繁觅食打断) - 🔧 修复妹妹购买食物不扣钱的 bug - 🔧 修复背包 UI 显示溢出(长物品名截断) - 🔧 修复妹妹离家出走后卡在外面不回来 ### V31 - 角色侧写系统 + 承诺系统重构 `[feature/agent-architecture]` #### 角色侧写系统 - ✅ 角色侧写文档(`docs/DESIGN_character_profile.md`)— 林瑶完整人格、背景、习惯、语言风格 - ✅ 角色数据模块(`engines/character_profile.py`)— 分层 prompt 片段生成 - ✅ 核心身份注入(每次 LLM 调用:嘴硬心软+外观反差+家庭背景+经济状况) - ✅ 场景触发注入(玩手机→爱好习惯、深夜→脆弱面、下雨→听雨情绪、出门→社交面、Day30+→依赖感) - ✅ 语言风格注入(口头禅+懒音+毒舌+嘴硬式关心,所有好感等级) - ✅ 短信角色提示(SMS prompt 注入精简版性格描述) - ✅ Sister Agent 性格增强(Agent 决策 prompt 加入人格细节) - ✅ 背景设定更新(高中毕业不上大学、经济紧张、去掉暑假框架、时间跟现实走) #### 承诺系统重构 - ✅ 提取路径重构(主 LLM 内联提取 + 记忆模型兜底,替代独立 memory_v2 提取) - ✅ 兑现奖励(好感 +3~8,按承诺重要性分级) - ✅ 过期惩罚(自然过期 -2/天,LLM 主动催促后再过期 -5/天) - ✅ 妹妹催促行为(Agent 决策时检查过期承诺,低好感生闷气、高好感发短信提醒) ### V30.1 - Bug修复 `[feature/agent-architecture]` - 🔧 修复原子记忆"林瑶林瑶"重复(schedule.py 写入记忆时 log 已含"林瑶"前缀又追加了一次) - 🔧 行程购物支持数量加减(TripPlannerModal 商品选择器从开关式改为 +/- 步进器,可买多件同商品) - 🔧 外出中 UI 隔离(ActionDock 出行中只显示「行程」和「背包」按钮,隐藏家务/喂食等) - 🔧 移动弹窗新增「街道」选项(始终禁用,标注"行程外出",明确只能通过行程到达) - 🔧 外出中禁止移动(后端 /api/move 检查 on_trip 状态,前端 MoveModal 全部禁用 + 提示) - 🔧 出行按钮直达行程面板(ActionDock 的「行程」按钮直接打开 TripPlannerModal) ### V30 - 架构治理 `[feature/agent-architecture]` - ✅ SisterStateManager 集中状态管理(所有妹妹状态变更通过统一接口,禁止外部直接赋值) - ✅ MutationBlocked 守卫系统(agent action 活跃时自动阻止 schedule/外部修改位置和活动) - ✅ 修复核心 Bug:妹妹外出时做家务导致妹妹被瞬间传送回家(schedule 不再覆盖活跃的 agent 行为) - ✅ ScheduleEngine 重构(update_sister_state / apply_narrative_override / on_player_action 全部走 manager) - ✅ agent_tools 重构(go_to / start_activity / go_sleep / lock_door / eat_food 全部走 manager) - ✅ time_engine 重构(enter_sick / recover_from_sick / unlock_door / metabolism 走 manager) - ✅ handlers / routes / scheduler 重构(所有直接赋值替换为 manager 方法调用) - ✅ 全量日志(`[SisterMgr]` 前缀,所有状态变更可追踪) - ✅ CLAUDE.md 架构约束文档更新 ### V29 - 养成陪伴感升级 `[feature/agent-architecture]` #### V29.3 - Sister Agent 响应式状态机重构 - ✅ 单次行动决策(每次 sync 调用 LLM → 一次信息查询 → 一次行动工具 → 结束,不再多行动连调) - ✅ 位置工具限制(eat_food 仅限家中,buy_food 仅限街上,go_to 室内外移动消耗 10 精力/15 分钟) - ✅ `pending_deliveries` 待送达系统(购买物品先进入携带状态,到家后自动放进冰箱) - ✅ `list_available_food` 工具(妹妹在街上时可查看便利店商品列表和价格) - ✅ `log_action()` 统一时间戳(所有 `recent_actions.append` 替换为带 `[HH:MM]` 前缀的 `log_action()`) - ✅ `go_to` 活动修正(去街外设 `activity=outside`,回家设 `activity=resting`,不再继承旧活动) - ✅ `buy_food` 描述区分位置(街上"在便利店买了" vs 出门"出门去买"),时长从 30 分钟缩短为 10 分钟 - ✅ 日志格式优化(去双重括号:`林瑶去了街道(-10精力),15分钟`) - ✅ `MemoryModal` Day 引用全部移除(source_time / game_time / created_time 替代 Day 编号) - ✅ `satisfy_hunger` 技能改为分步执行(每次 sync 做一步:check → go_to → buy → go_home → eat) - ✅ `_check_sister_auto_eat` 改为安全网(精力 < 30 才触发,Agent 主导进食) #### V29.2 - 全局"Day 0"修复 + 承诺时间简化 - ✅ 全局"Day X"时间格式统一为真实日期(涉及 13+ 文件:sister_agent/agent_tools/schedule/routes/memory_v2/memory/time_engine/models/MemoryModal 等) - ✅ 承诺时间记录简化(移除 `deadline` 字段和硬编码推断逻辑,只记录 `created_time`,LLM 自行判断是否逾期) - ✅ `check_overdue_promises()` 改为 no-op(不再自动扣好感,LLM 决策时根据 created_time 自行推断) - ✅ 辅助函数 `format_real_datetime()` / `format_real_datetime_full()` 统一时间格式化 #### V29.1 - 做家务实时等待 + UI 优化 - ✅ 做家务改为实时等待(60分钟真实时间,不消耗虚拟时间,聊天仍可用) - ✅ 玩家忙碌状态系统(`busy_until_realtime` / `busy_action`,忙碌中可聊天,其他操作被阻止) - ✅ 移动文本改为"切换房间"(去掉"耗时X分钟"描述,更符合不出门设定) - ✅ 前端时钟显示真实日期(`4月29日 15:09` 格式,替代 `Day 0 - 15:9`) - ✅ StatPanel 布局重构(妹妹状态全宽,玩家行动力/金钱移到天气栏,忙碌/行程徽章) #### V29.0 - 基础架构 - ✅ 真实时间戳系统(`datetime.now()` 替代虚拟 Day N HH:MM,所有记录统一 yyyy年mm月dd日 hh:mm:ss) - ✅ LLM 时间感知修复(`format_history()` 加 `[HH:MM]` 前缀,LLM 不再脑补"凌晨两点做家务") - ✅ working_memory 带真实时间戳(写入时记录 `timestamp`,prompt 注入时展示) - ✅ 妹妹行为防覆盖(`action_until_realtime` 守卫,BASE_SCHEDULE 不再覆盖 LLM Agent 安排的活动) - ✅ 记忆系统去冗余(移除 `daily_cache`,`conversation_log` 成为唯一数据源) - ✅ 妹妹活动名中文化(`desc` 统一中文,不再出现"开始phone"等混合语言) - ✅ 妹妹活动防重复(prompt 注入"不要重复当前活动"规则,避免连续数小时做同一件事) - 🔧 提示词基调优化(日常降温,减少30天实时养成中的表演疲劳) - 🔧 死亡→生病卧床(健康归零不再直接结束,改为生病护理玩法) - 🔧 防流失保底(离线超过48小时代谢封顶,防止意外死亡) - 🔧 妹妹主动短信(妹妹在外时主动给哥哥发消息,双向通信) - 🔧 冰箱便签系统(妹妹在家活动后留便签,异步陪伴感) - 🔧 零花钱系统(给妹妹独立消费能力,增加经济策略维度) - 🔧 Sister Agent 多轮工具调用(info tools → action tools,修复妹妹行为卡在"看书30分钟") - 🔧 Agent 架构:Sister Agent(LLM tool-calling 驱动妹妹自主决策),Player Agent 已移除 - ✅ 文档归档(`doc/` 旧规格文档迁移到 `docs/archive/`) ### V28 - 哥哥不出门 + 短信系统 + 卧室合并 (2026-04-27) `[feature/realtime-clock]` - ✅ 哥哥不能出门(移动只限室内4个地点) - ✅ 短信系统(妹妹外出时,输入框自动切换为短信模式,¥2/条 + 1AP) - ✅ 短信绿色按钮(InputArea动态切换:聊天/短信) - ✅ 短信LLM回复(旁白 + 简短口语化短信,结合当前活动) - ✅ 去掉哥哥睡觉(观察者模式,不需要睡觉) - ✅ 卧室合并(哥哥卧室 + 妹妹卧室 → 统一"卧室") - ✅ 行程购物修复(catalog模式,在家也能浏览商品列表) ### V27 - 妹妹睡眠系统改进 (2026-04-27) `[feature/realtime-clock]` - ✅ 健康影响精力上限(health=50→上限75)、恢复速率(15*health/100/hr)、消耗速率(5*(1+(100-health)/200)/hr) - ✅ 熬夜惩罚(1:00后睡→恢复打7折) - ✅ 睡眠不足惩罚(<6小时→每小时扣3健康,起床时检查) - ✅ 每天20:00 LLM睡眠计划(decide bedtime/waketime + 第一人称理由) - ✅ 精力影响入睡(>60推迟1小时,>40推迟30分钟) - ✅ 聊天推迟入睡(sister_delay_sleep字段,上限120分钟) - ✅ 白天小睡(6:00-22:00精力<20→睡1-2小时,不触发惩罚) - ✅ 22:00未睡理由(精力高→"不困",精力低→"还行") - ✅ 精力条变色(蓝色→黄色→红色) - ✅ 去掉疲劳作为决策维度(保留字段,不再用于判断) - ✅ 前端同步新字段(planned_bedtime/planned_wake_time/bedtime_delay) ### V26 - 养成模式重构 (2026-04-26) `[feature/realtime-clock]` - ✅ PlayerState 重构:移除玩家精力/饱腹/健康,改为行动力+金钱(养成模式) - ✅ 行动力系统(上限10,每小时恢复1,离线批量恢复) - ✅ 行程规划系统(engines/trip.py,多站路线一次性提交,真实时间等待完成) - ✅ 行程中锁定操作(interact/move 在行程中禁止) - ✅ 妹妹行为日志(SisterActionLog,所有自主行为记录,3天自动清理) - ✅ 做饭产出2份(体现性价比) - ✅ 妹妹偏好选食(基于V25记忆查询偏好,不爱吃的可能放坏) - ✅ 承诺追踪(LLM 根据 created_time 自行判断逾期并影响好感,不再硬编码自动扣分) - ✅ 只有妹妹会死(玩家行动力用完/不吃饭不会结束) - ✅ 前端适配(StatPanel 行动力条、行程状态查看器、MemoryModal 新tab) - ✅ 移动端行动点扣除、行程锁定、中文物品名称、行程进度UI ### V25 - 原子记忆系统 (2026-04-26) `[feature/realtime-clock]` - ✅ 原子事实记忆(MemoryEntry:text/category/tags/about/importance,7类分类) - ✅ 记忆提取引擎(每5次交互调用 LLM 提取,qwen3.5-plus 模型) - ✅ 优化提取提示词(防代词规则、重要性打分基准、隐性信息捕捉、已有记忆去重) - ✅ 关键词+标签检索(search_memories,时间衰减评分) - ✅ 承诺追踪(PromiseEntry 生命周期,LLM 判断兑现) - ✅ 记忆驱动妹妹行为(妹妹决策时检索偏好记忆和待兑现承诺) - ✅ 持久化存储(memory_store.json,自动保存/加载) - ✅ 记忆淘汰(保护承诺/高重要性/新记忆,重要性+时间衰减评分淘汰) - ✅ 截断 JSON 恢复(处理 LLM 输出被截断的情况) ### V24 - 实时时钟与妹妹自主行为 (2026-04-24) `[feature/realtime-clock]` - ✅ RealtimeClock 时钟系统,游戏时间与现实同步 - ✅ LLM 驱动妹妹自主行为(每1-5分钟决策一次,行为持续30分钟-3小时) - ✅ 妹妹行为状态机(action_until_realtime 精确到秒的截止时间) - ✅ 硬编码生存优先级(精力低→睡觉,饥饿高→吃/买,深夜→睡觉) - ✅ LLM 决策兜底(发呆事件30-60分钟填充) - ✅ 妹妹自主从冰箱吃东西(优先吃快过期的) - ✅ 睡眠立绘系统(多阶段深度:深睡/浅睡/迷糊/将醒) - ✅ 玩家作息学习(记录活跃时段,妹妹适应等哥哥) - ✅ 商店 UI 修复与角色立绘更新 ### V23 - 多阶段唤醒系统 (2026-04-24) `[feature/realtime-clock]` - ✅ 妹妹睡眠深度计算(0.0-1.0,基于入睡时长) - ✅ 4阶段睡眠(深睡→浅睡→迷糊→将醒) - ✅ 不同触碰方式的唤醒力度(聊天0.25,摸腰0.65) - ✅ 好感度加成唤醒(好感>60额外+0.15) - ✅ 唤醒反应(成功→上一阶段,微弱刺激→身体反应但不醒) ### V22 - 警惕平衡与锁门系统 (2026-04-23) - ✅ 警惕值衰减加速(自然2.0/hr、睡眠-20、聊天-2、家务-12、做饭-8) - ✅ 妹妹锁门系统(警惕≥40回卧室自动锁门,时间/离开/低警惕自动解锁) - ✅ 坏结局系统(好感<20且警惕>80,LLM生成总结信) - ✅ LLM触发的锁门(sister_lock_door JSON字段) - ✅ LLM驱动物品使用追踪(sister_use_item,妹妹独立吃东西自动扣库存) - ✅ 物品实例精准移除(uid定位,过期食物独立槽位) - ✅ 行动日志细化(区分独自/一起,打工/购物详细记录) - ✅ 玻璃拟态UI(MarketModal/ShopModal毛玻璃风格重构) - ✅ 购买无闪烁(本地更新can_afford,不重新加载列表) - ✅ 按钮动作架构(显式action_type绕过文本检测) ### V21 - 9区触碰系统 (2026-04-20) - ✅ 9个身体部位触碰区域(头发/脸/肩膀/胸/腰/臀/大腿/小腿/脚) - ✅ 每个部位独立的 favor/desire/suspicion 数值响应 - ✅ 4类区域分类(安全/暧昧/敏感/特殊) - ✅ 接受/拒绝阈值系统(基于好感+欲望双维度) - ✅ 前端立绘触摸区域映射(Y轴比例定位) - ✅ LLM 触碰部位上下文注入 - ✅ 新角色立绘与UI中文化 ### V20 - 冰箱与食物系统 (2026-04-17) - ✅ 冰箱保鲜(延长保质期50%) - ✅ 食物过期与中毒机制(过期1天→30%拉肚子,2-3天→健康损伤,4天+→致命) - ✅ 物品实例系统(ItemInstance,uid+独立过期追踪+存储位置) - ✅ 背包容量管理(默认12格,每件物品占不同空间) - ✅ 妹妹随机起床时间(8:00-10:00) - ✅ 妹妹自主从冰箱取食(优先吃快过期的) ### V19 - 行动日志与前端迁移 (2026-04-16) - ✅ 结构化近期行动记录(recent_actions) - ✅ LLM可感知玩家最近做过的事 - ✅ uni-app 前端上线(Vue 3 + Pinia,替代旧 HTML 前端) ### V18 - 时间纵深系统 (2026-04-15) - ✅ 4个时间阶段氛围(初夏试探1-10天/盛夏惯性11-25天/漫长发酵26-42天/夏末倒计时43+天) - ✅ AI告别信(暑假结束时林瑶以第一人称写信) - ✅ AI坏结局总结(关系破裂时生成回顾) ### V17 - 关系里程碑系统 (2026-04-15) - ✅ 牵手/拥抱/亲吻/同床/亲密 5个里程碑 - ✅ 门槛系统(好感+欲望+警惕三维条件) - ✅ 未达成里程碑禁止叙事中出现(严格提示词约束) ### V16 - 行为冲突防护 (2026-04-14) - ✅ 防止高好感时使用低好感行为描写 - ✅ Prompt行为系统重构 - ✅ 代码模块化重构(handlers.py 独立) ### V15.2 - 欲望系统与妹妹购物 (2026-04-12) - ✅ 6级身体接受度(排斥→羞涩→暧昧→渴望→迷恋→沉溺) - ✅ 妹妹主动购物请求(SisterShoppingModal) - ✅ 妹妹要吃的请求(SisterRequestModal) - ✅ 跟屁虫模式(高好感黏着玩家) ### V15.1 - 妹妹跟随模式 (2026-04-11) - ✅ 妹妹跟随玩家移动(following_player) - ✅ 跟随时禁止LLM覆盖位置 ### V15 - 妹妹提议与联合行动 (2026-04-11) - ✅ 妹妹主动邀请一起做事(sister_proposal) - ✅ 接受/拒绝UI(ProposalModal) - ✅ 拒绝妹妹提议→警惕+3 ### V14 - 烹饪系统 (2026-04-11) - ✅ 菜市场买食材(MarketModal,食材带保质期) - ✅ 食谱匹配(10+菜品,含模糊匹配) - ✅ 火候/辣度/品质/份量 - ✅ 3步烹饪向导(CookingModal) ### V13.1 - 街道子场景 (2026-04-11) - ✅ 街道拆分为4个子地点(街道/便利店/人才市场/菜市场) - ✅ 移动逻辑适配(室内/室外不同耗时) ### V13 - 叙事驱动妹妹状态 (2026-04-10) - ✅ LLM可改变妹妹位置/活动(sister_change JSON字段,后端验证) - ✅ 妹妹提议系统(sister_proposal) - ✅ 叙事覆盖窗口(最长120分钟) ### V12.1 - 深夜系统 (2026-04-10) - ✅ 深夜三阶段(22点困倦/23点就寝/0-6点深夜) - ✅ 礼物叙事增强 - ✅ 时间计算 bug 修复 ### V12 - 双轴行为系统 (2026-04-09) - ✅ 好感度7级基调(陌生人→同居人→熟人→朋友→暧昧→恋人未满→亲密无间) - ✅ 警惕5级防御覆盖(松弛→不安→抵触→排斥→敌对) - ✅ 特殊组合(内心冲突:好感>60+警惕50-80,心碎:好感>80+警惕>80) - ✅ 动态提示词系统(build_contextual_prompt) ### V11.4 - Bug修复与数值平衡 (2026-03-16) - ✅ 修复睡眠天数计算差一错误 - ✅ 修复暴饮暴食惩罚双重应用 - ✅ 修复礼物失败时警惕值不生效 - ✅ 代谢率与文档对齐 - ✅ UI修复与Prompt优化 ### V11.2 - 增量摘要优化 (2026-03-05) - ✅ 每5条对话触发增量摘要(减少LLM调用) - ✅ 永久对话日志(conversation_log,支持全流程回放) ### V11 - 动态提示系统 (2026-02-25) - ✅ 上下文感知提示生成(build_contextual_prompt替代静态SYSTEM_PROMPT) - ✅ Token使用减少67% - ✅ UI一致性修复 ### V11.1 - UI一致性修复 (2026-02-25) - ✅ UI一致性修复与体验优化 ### V10 - 存档系统 (2026-02-24) - ✅ 5个存档槽位,自定义名称 - ✅ 快照+历史双存储 - ✅ 存档摘要(天数/位置/好感/金钱) ### V9 - 妹妹日程系统 (2026-02-24) - ✅ SisterState独立状态(位置、活动、心情、疲劳、饥饿) - ✅ 时间表驱动作息(BASE_SCHEDULE + HIGH_FAVOR_SCHEDULE) - ✅ 交互限制与加成(不同活动影响聊天效果) - ✅ 天气影响(下雨取消外出,高温午后休息) ### V8.2 - 时态修正与过程描述 (2026-01-07) - ✅ 修复时态错位(睡觉后的叙事改为醒来场景而非入睡场景) - ✅ 长时间动作跳过过程直接展示结果(≥30分钟的动作) ### V8.1 - 防幻觉系统 (2026-01-06) - ✅ 资源感知机制(禁止LLM捏造不存在的物品/食物) - ✅ 叙事一致性约束 ### V8 - 自动美术生成 (2026-01-06) - ✅ Pillow自动生成背景图(按地点+时间+天气) - ✅ 角色立绘占位图 - ✅ 开发者风格UI资源(色调区分、网格辅助线、状态标签) ### V7.2 - 场景化UI (2026-01-06) - ✅ 场景动作栏(ActionDock,根据地点动态渲染按钮) - ✅ 长时间动作叙事控制(切断LLM调用,硬编码静态文本) - ✅ 烹饪系统初步实装 - ✅ 事件触发约束(时间窗口+前置状态检查) ### V7.1 - 交互体验修复 (2026-01-05) - ✅ 饮食惩罚机制修正 - ✅ 移动消耗合理化 - ✅ 商店时空限制(只能在外面买) - ✅ UI交互简化 ### V7 - 导演系统 (2026-01-05) - ✅ 天气系统(晴/阴/雨/高温,影响活动和叙事氛围) - ✅ 随机事件(EVENT_LIBRARY,概率/条件触发) - ✅ 阻塞事件(突发危机锁定常规操作) - ✅ 环境状态模型(Environment) ### V6.2 - UI与数值平衡 (2026-01-05) - ✅ 双面板UI(左侧玩家生存/右侧妹妹状态) - ✅ 数值平衡推演(初始资金500,生存压力曲线) - ✅ 饥饿/精力/健康阈值体系 ### V6.1 - 统一饮食系统 (2026-01-05) - ✅ 废除V4抽象eat_meal动作 - ✅ 所有进食统一为"获取物品→使用物品" - ✅ 外卖/堂食=即买即用,囤货=买→背包→后用 ### V6 - 经济系统 (2026-01-05) - ✅ 物品数据库(食品/礼物/关键道具) - ✅ 便利店商店系统(SHOP_INVENTORY) - ✅ 4种打工(发传单/便利店/家教/工地) - ✅ 礼物系统(赠送改变好感/警惕) ### V5 - 记忆系统 (2026-01-04) - ✅ 三层记忆架构(L1工作记忆/L2每日摘要/L3核心记忆) - ✅ 自动摘要(睡觉时L1→L2) - ✅ 记忆沉淀(每3天L2→L3) - ✅ 代码模块化重构(engines/目录拆分) ### V4 - 硬编码平衡 (2026-01-04) - ✅ 行动矩阵系统(ACTION_MATRIX,所有数值Python硬编码) - ✅ 数值与叙事分离(LLM只生成文字,不做计算) - ✅ 动作检测系统(detect_action_type) - ✅ 4维属性系统(好感/欲望/理智/警惕) - ✅ 正式API流程(预检查→计算→推时间→LLM→返回) ### V3 - 社交逻辑与性格校准 (2026-01-03) - ✅ 性格基调重定("松弛但冷漠"替代"内向敏感") - ✅ 警惕值自然衰减(每小时-2、正常聊天-3、睡觉-20) - ✅ 警惕区间系统(松弛0-20/不安21-50/抵触51-80/敌对81-100) - ✅ 表情映射(低警惕=无聊脸,高警惕=惊恐脸) ### V2 - 生存模拟 (2026-01-03) - ✅ 细粒度时钟系统(Day:Hour:Minute 格式,替代粗略时段) - ✅ 生理代谢系统(精力/饱腹/健康,每小时消耗) - ✅ 强制睡眠机制(精力≤0自动昏睡12小时) - ✅ 猝死判定(精力≤0且健康<20时概率触发) - ✅ 主动睡眠(玩家选择4-12小时) ### V1 - 项目奠基 (2026-01-02) - ✅ 核心游戏概念(LLM驱动的恋爱/生存模拟) - ✅ 行为边界管控系统(4级行为判定:正常→越界→拒绝→Bad End) - ✅ 4维属性设计(好感/欲望/理智/警惕) - ✅ Bad End定义(报警/自杀/离家出走/叫父母) - ✅ 技术栈确立(FastAPI + HTML/JS + OpenAI Compatible API) - ✅ 视觉反馈系统(屏幕震动、红闪、黑屏) --- < V55 以前的版本历史见 [git log](https://github.com/anthropics/SummerToYao/commits/main)