docs(01): create phase 1 plans

This commit is contained in:
unanmed 2026-09-07 22:08:35 +08:00
parent 6f06b68656
commit 41cdd18761
8 changed files with 1187 additions and 10 deletions

View File

@ -10,7 +10,7 @@ Requirements for the engine's completion. Each maps to roadmap phases.
### 事件系统Event
- [ ] **EVT-01**: 引擎提供事件系统的数据/序列化接口,支持 blockly 式低代码定义(编辑器在外部项目)
- [ ] **EVT-02**: 事件系统能驱动简单场景的事件流程(如踩踏触发、对话、开门
- [ ] **EVT-02**: 事件系统能驱动简单场景的事件流程(踩踏触发链路;对话、开门依赖 A2 内建函数清单,延后到收尾工作
- [ ] **EVT-03**: 事件系统定位为初学者抽象,仅覆盖简单场景,不为复杂场景追求通用表达
### 寻路Pathfinding

View File

@ -7,6 +7,7 @@
## Phases
**Phase Numbering:**
- Integer phases (1, 2, 3): Planned milestone work
- Decimal phases (2.1, 2.2): Urgent insertions (marked with INSERTED)
@ -22,70 +23,100 @@ Decimal phases appear between their surrounding integers in numeric order.
## Phase Details
### Phase 1: 事件系统
**Goal**: 引擎能以 blockly 式低代码定义事件,并驱动简单场景的事件流程
**Depends on**: Nothing (first phase)
**Requirements**: EVT-01, EVT-02, EVT-03
**Success Criteria** (what must be TRUE):
1. 开发者能通过数据/序列化接口定义事件blockly 式低代码可序列化为引擎事件数据,编辑器在外部项目)
2. 引擎能执行踩踏触发事件(角色踩上地板触发对应事件)
3. 引擎能执行对话事件与开门事件,打通简单场景事件全链路
3. 引擎能执行踩踏触发事件驱动的事件执行链路(对话/开门依赖 A2 内建函数清单,延后到收尾工作)
4. 事件系统保持面向初学者的简单抽象,未引入复杂场景的通用表达能力
**Plans**: TBD
**Plans**: 3 plans
Plans:
**Wave 1**
- [ ] 01-01-PLAN.md — L0/L1 事件数据契约落地 + 图块/点位事件视图 + 存读档迁移
**Wave 2** *(blocked on Wave 1 completion)*
- [ ] 01-02-PLAN.md — 执行器接口用户确认checkpoint+ 执行器实现 + 删除旧 ITriggerD-13+ CoreState 装配
**Wave 3** *(blocked on Wave 2 completion)*
- [ ] 01-03-PLAN.md — moverImpl 踩踏触发重写为事件执行 + 阶段验收
### Phase 2: 寻路系统
**Goal**: 引擎支持自动寻路,移动端点击地图即可触发移动
**Depends on**: Phase 1
**Requirements**: PATH-01, PATH-02
**Success Criteria** (what must be TRUE):
1. 角色能在地图上自动寻路移动到指定格
2. 移动端点击地图上的可达格时,角色自动寻路移动到该格
3. 寻路正确避开不可通行格(碰撞/障碍/墙体)
**Plans**: TBD
**UI hint**: yes
### Phase 3: 数据端完成
**Goal**: 数据端 L0L3 接口实现完成,数据层各系统可用并可在 Node 环境独立运行回放验证
**Depends on**: Phase 2
**Requirements**: DATA-01
**Success Criteria** (what must be TRUE):
1. 用户设计的 L0L3 数据层接口全部落地,地图/角色/敌人/flag/战斗/触发器/存档/回放各系统可用
2. 数据端可在 Node 环境独立运行回放验证,无 DOM 依赖
3. 数据端与渲染端保持双端分离,渲染相关代码经 `r()`/`rf()` 门控或走 hook渲染端不向数据端推送更新
4. 接口设计由用户主导AI 仅负责实现
**Plans**: TBD
### Phase 4: 渲染适配与双布局
**Goal**: 渲染端通过新数据层接口驱动,并同时支持移动端与桌面端布局
**Depends on**: Phase 3
**Requirements**: REND-01, REND-02
**Success Criteria** (what must be TRUE):
1. 用新数据层接口加载一张地图后,已重构的渲染端能正确渲染该地图场景
2. 桌面端布局下,地图、角色、界面元素正常显示并可操作
3. 移动端(窄屏)布局下,同一场景正常显示且可操作
4. 数据端与渲染端保持双端分离——数据端无 DOM仍可在 Node 环境跑回放验证
**Plans**: TBD
**UI hint**: yes
### Phase 5: Legacy 移植
**Goal**: 删除被新接口覆盖的 legacy 系统,迁移仍需要的内容
**Depends on**: Phases 1-4
**Requirements**: LEGACY-01, LEGACY-02
**Success Criteria** (what must be TRUE):
1. 被新接口覆盖的 legacy 系统已删除,代码中无残留引用
2. 仍需要的 legacy 内容已迁移到新接口
3. 仅当无新接口覆盖时才新增接口,且接口设计经用户 review
4. 移植后引擎仍能完整跑通一部魔塔,无功能回归
**Plans**: TBD
### Phase 6: 单元测试
**Goal**: 为核心系统(数据层等)补齐单元测试
**Depends on**: Phases 1-5
**Requirements**: TEST-01
**Success Criteria** (what must be TRUE):
1. 核心数据层系统有单元测试覆盖
2. 测试覆盖关键行为(战斗伤害、触发器、寻路、事件等)
3. 测试在本地可运行且全部通过
4. 测试由 AI 编写,经用户 review 后提交
**Plans**: TBD
## Progress

View File

@ -2,16 +2,16 @@
gsd_state_version: "1.0"
current_phase: 1
current_phase_name: 事件系统
status: planning
status: executing
stopped_at: Phase 1 context gathered
last_updated: "2026-09-07T10:26:54.629Z"
last_updated: "2026-09-07T14:06:31.386Z"
last_activity: 2026-09-07
last_activity_desc: Roadmap revised (6 phases, 11 requirements mapped)
state_head: 9cfe7ebea827cc13fbc2cac6d1440aa96db36a80
state_head: 6f06b686560de391f0c76bf0b0e72658a94e3e37
progress:
total_phases: 6
completed_phases: 0
total_plans: 0
total_plans: 3
completed_plans: 0
percent: 0
---
@ -27,9 +27,9 @@ See: .planning/PROJECT.md (updated 2026-09-07)
## Current Position
Phase: 1 of 6 (事件系统)
Phase: 1 (事件系统) — READY TO EXECUTE
Plan: 0 of TBD in current phase
Status: Ready to plan
Status: Ready to execute
Last activity: 2026-09-07 — Roadmap revised (6 phases, 11 requirements mapped)
Progress: [░░░░░░░░░░] 0%

View File

@ -0,0 +1,253 @@
---
phase: 01-event
plan: 01
type: execute
wave: 1
depends_on: []
files_modified:
- packages-user/data-common/src/event/event.ts
- packages-user/data-common/src/store/index.ts
- packages-user/data-base/src/map/eventView.ts
- packages-user/data-base/src/map/tile.ts
- packages-user/data-base/src/map/staticTile.ts
- packages-user/data-base/src/map/dynamicTile.ts
- packages-user/data-base/src/map/mapLayer.ts
- packages-user/data-base/src/map/mapState.ts
autonomous: true
requirements: [EVT-01, EVT-03]
estimate:
tokens: 70000
raw_tokens: 35000
tasks: 3
confidence: low
must_haves:
truths:
- "开发者能通过 IGameEvent 数据接口定义事件EventTrigger 枚举D-02GameEvent.compile() 编译一次后写回 compiled 缓存D-07"
- "GameEventStore 与 MapStore 均可通过桶导出 import { GameEventStore, MapStore } from '@user/data-common' 访问;事件存储走 id 引用、不进存档D-11"
- "图块实例通过 tileEvent() 返回 ILayerEventView通过 pointEvent() 返回所在位置的 ILayerEventView 或 null对象+地图格混合绑定D-01/D-03/D-04"
- "ILayerEventView 的 set/delete/clear/get/markPure/dirty 全部可用set 遇到重复优先级走 logger.warn(136, ...)D-06/D-12"
- "StaticTile/DynamicTile 的 saveState/loadState 读写 eventsReadonlyMap<number,string>)而非 triggersIMapBlockSaveBase.events图块默认事件同样走 eventStore id 引用D-05"
- "MapLayer.event(x,y)/getPointEvent(x,y) 返回点事件视图点事件不随图块移动D-03"
- "MapState.fromRaw 从 raw.events 装配点位事件(不再读 blockData/trigger"
artifacts:
- packages-user/data-base/src/map/eventView.ts
- "store/index.ts 的 mapStore 桶导出"
key_links:
- "MapTileBase.tileEvent()/pointEvent() 连接 LayerEventView 与 IMapLayer.event(x,y)"
- "StaticTile/DynamicTile.saveState/loadState 连接 tileEvent().dirty()/get()/set() 与 IMapBlockSaveBase.events"
- "MapState.fromRaw 连接 IMapRawData.events 与 layer 静态图块 tileEvent().set()"
---
<objective>
把用户已设计的 L0/L1 事件数据接口落地为实现L0 的事件编译缓存与存储桶导出L1 的图块/点位事件视图、存读档迁移、点位事件装配。本计划不触碰 L2 事件执行器执行器接口已由用户设计完成Plan 02 直接实现)。
Purpose: 打通「事件定义 → 编译 → 图块/点位绑定 → 存读档」的 L0→L1 垂直切片,为 Plan 02 的执行器(实现用户已设计的 IGameEventExecutor/IGameEventSystem与 Plan 03 的踩踏触发重写提供已落地的数据层骨架。
Output: `GameEvent.compile` 缓存回写、`store/index.ts` 导出 mapStore、`LayerEventView` 实现、`MapTileBase.tileEvent/pointEvent`、`StaticTile/DynamicTile` 存读档 `events`、`MapLayer.event/getPointEvent` + `syncStaticEvent`、`MapState.fromRaw` 从 `raw.events` 装配。
</objective>
<execution_context>
@C:/Users/book/.config/opencode/gsd-core/workflows/execute-plan.md
@C:/Users/book/.config/opencode/gsd-core/templates/summary.md
</execution_context>
<context>
@.planning/PROJECT.md
@.planning/ROADMAP.md
@.planning/STATE.md
@.planning/phases/01-event/01-CONTEXT.md
@.planning/phases/01-event/01-RESEARCH.md
@.planning/phases/01-event/01-PATTERNS.md
# 已设计接口(只读,禁止修改)
@packages-user/data-common/src/event/types.ts
@packages-user/data-common/src/event/event.ts
@packages-user/data-common/src/store/types.ts
@packages-user/data-common/src/store/eventStore.ts
@packages-user/data-common/src/store/mapStore.ts
@packages-user/data-base/src/map/types.ts
@packages-user/data-system/src/event/types.ts
# 本计划要修改/新建的实现文件
@packages-user/data-base/src/map/tile.ts
@packages-user/data-base/src/map/staticTile.ts
@packages-user/data-base/src/map/dynamicTile.ts
@packages-user/data-base/src/map/mapLayer.ts
@packages-user/data-base/src/map/mapState.ts
@packages-user/data-common/src/store/index.ts
@packages-user/data-common/src/event/event.ts
</context>
<tasks>
<task type="tracer">
<name>端到端切片:事件「定义→编译→图块/点位绑定」路径跑通L0 编译缓存 + L1 事件视图)</name>
<files>
packages-user/data-common/src/event/event.ts
packages-user/data-common/src/store/index.ts
packages-user/data-base/src/map/eventView.ts
packages-user/data-base/src/map/tile.ts
packages-user/data-base/src/map/mapLayer.ts
</files>
<read_first>
- packages-user/data-common/src/event/types.tsEventTrigger 枚举 + IGameEvent/IReadonlyGameEvent只读
- packages-user/data-common/src/event/event.tsGameEvent 现状)
- packages-user/data-common/src/store/eventStore.tsGameEventStore 现状)
- packages-user/data-common/src/store/index.ts桶导出现状缺 mapStore
- packages-user/data-base/src/map/types.tsIReadonlyEventView/ILayerEventView/ITileBase/IReadonlyTileBase/IMapLayer.event/getPointEvent/IMapBlockSaveBase.events只读
- packages-user/data-base/src/map/tile.tsMapTileBase 现状triggers/block 待清)
- packages-user/data-base/src/map/mapLayer.ts现状syncStaticTrigger/createDynamic/transferTo*
- .planning/phases/01-event/01-PATTERNS.md 的 Pattern 2/5/9 与「错误处理」段logger 数字错误码、isNil、getOrInsertComputed
</read_first>
<action>
实现一条「事件定义到图块/点位绑定」的最小可运行路径,逐层如下(只实现已设计接口,不改 types.ts
L0 — `packages-user/data-common/src/event/event.ts`:把 `GameEvent.compile()` 由「直接返回 `this.interpreter.compile(this.rawEvent)`」改为「先 `this.compiled = this.interpreter.compile(this.rawEvent)``return this.compiled`」,使 `execute()``if (this.compiled)` 分支真正命中缓存setRaw 已正确置 `this.compiled = null`,勿改)。
L0 — `packages-user/data-common/src/store/index.ts`:补一行桶导出 `export * from './mapStore';`(当前缺 mapStore导致 core.ts 只能相对路径绕过)。保持现有 eventStore/itemStore/tileStore/types 导出不变。
L1 — `packages-user/data-base/src/map/eventView.ts`(新建):实现 `class LayerEventView implements ILayerEventView`,内部持有 `private readonly store: Map<number, string>` 与一个 `private reference: Map<number, string> | null`dirty 基准D-12。方法`get()` 返回 `ReadonlyMap``set(priority, event)` 遇 `store.has(priority)``logger.warn(136, priority.toString())` 后覆盖;`delete`/`clear` 直接操作 store`markPure()` 把当前 store 内容拷贝进 reference`dirty()` 在 reference 为 null 时对空 store 判断,否则逐项比对 store 与 reference。私有成员必须加 jsDoccode.md 规则 8
L1 — `packages-user/data-base/src/map/tile.ts`:删除 `triggers: Set<number> | null` 成员及 `addTrigger`/`deleteTrigger`/`clearTrigger`/`useEmptyTrigger`,新增 `private tileEvents: LayerEventView`(构造器初始化 `new LayerEventView()` 并以空 Map `markPure()`),实现 `tileEvent(): ILayerEventView` 返回该视图,实现 `pointEvent(): ILayerEventView | null` 委托 `this.layer.event(this.locator.x, this.locator.y)`。移除引用未定义类型 `IMapPointRawData``block()` 方法与对应 import该类型已不在 @user/data-common见「Blockers」。`MapTileBase` 现需 `import { LayerEventView } from './eventView';``implements ITileBase` 应补齐 `tileEvent`/`pointEvent`。
L1 — `packages-user/data-base/src/map/mapLayer.ts`:新增 `event(x, y): ILayerEventView | null``getPointEvent(x, y): ReadonlyMap<number, string> | null`。点事件视图需要一份图层级存储点事件不随图块移动D-03新增私有成员 `private readonly pointEvents: Map<number, Map<number, LayerEventView>>`(外层 key = y内层 key = x`event(x, y)` 惰性创建并返回 `ILayerEventView``getOrInsertComputed` 惯例);`getPointEvent` 返回 `event(x, y)?.get() ?? null`。私有成员加 jsDoc。
约束:禁止出现 `as` 关键字、禁止 `value === undefined`(用 `isNil`)、公共方法必须先在接口定义再 implements、对象成员用接口类型、所有私有成员加 jsDoc。
</action>
<acceptance_criteria>
- `grep -c "mapStore" packages-user/data-common/src/store/index.ts` 命中 `export * from './mapStore'`
- `grep -n "triggers" packages-user/data-base/src/map/tile.ts``Set<number>` 命中
- `grep -n "tileEvent\|pointEvent" packages-user/data-base/src/map/tile.ts` 各至少命中一次
- `grep -n "event(x: number, y: number)" packages-user/data-base/src/map/mapLayer.ts` 命中一次
- `grep -n "getPointEvent(x: number, y: number)" packages-user/data-base/src/map/mapLayer.ts` 命中一次
</acceptance_criteria>
<verify>
<automated>pnpm check:type 2>&1 | Select-String -Pattern "eventView\.ts|map/tile\.ts|map/mapLayer\.ts|event/event\.ts|store/index\.ts"</automated>
<fails_when>命中以上 L0/L1 文件的任何 TS 错误有输出即失败。staticTile/dynamicTile/mapState 的 triggers 迁移在 Task 2/3 处理core.ts 缺失 eventStore/eventSystem、moverImpl 引用旧 trigger、client-modules、tileStore.getTrigger 等属跨计划/阶段外残差,不计入本任务失败</fails_when>
</verify>
<done>一条事件从 IGameEvent 定义、经 compile 缓存、经 tileEvent/pointEvent 视图绑定到图块/点位的路径,类型层面成立;旧 triggers 成员已从 MapTileBase 移除。</done>
</task>
<task type="auto">
<name>图块事件存读档StaticTile/DynamicTile 从 triggers 迁移到 events</name>
<files>
packages-user/data-base/src/map/staticTile.ts
packages-user/data-base/src/map/dynamicTile.ts
</files>
<read_first>
- packages-user/data-base/src/map/types.tsIMapBlockSaveBase.events / IStaticBlockSave / IDynamicBlockSave只读
- packages-user/data-base/src/map/staticTile.ts现状
- packages-user/data-base/src/map/dynamicTile.ts现状
- packages-user/data-base/src/map/tile.tsTask 1 改造后的 tileEvent/pointEvent
</read_first>
<action>
把两个类的存读档字段从旧 `triggers` 迁到 `events``IMapBlockSaveBase.events?: ReadonlyMap<number, string>`),结构保持不变、仅换字段与存取方式:
`staticTile.ts``shouldSave()` 由 `return !!this.triggers` 改为 `return this.tileEvent().dirty();``saveState()` 由 `save.triggers = this.triggers` 改为「当 `this.tileEvent().dirty()` 为 true 时 `save.events = this.tileEvent().get()`」;`loadState(save)` 由 `save.triggers.forEach(v => this.addTrigger(v))` 改为「读 `save.events`:无则 `this.tileEvent().clear()`,有则遍历 `[priority, id]` 调用 `this.tileEvent().set(priority, id)`,读完 `this.tileEvent().markPure()`」。
`dynamicTile.ts``saveState()` 同样把 `save.triggers = this.triggers` 改为 dirty 时 `save.events = this.tileEvent().get()``loadState(save)` 同上迁移到 `tileEvent().set/clear/markPure`
注意:`this.triggers` / `useEmptyTrigger` / `addTrigger` / `clearTrigger` 已在 Task 1 从 `MapTileBase` 移除,本任务不得再引用它们;`save` 对象字面量继续用接口类型 `IStaticBlockSave`/`IDynamicBlockSave`(其 `events` 为可选只读字段)。禁用 `as`、私有成员加 jsDoc。
</action>
<acceptance_criteria>
- `grep -n "triggers" packages-user/data-base/src/map/staticTile.ts packages-user/data-base/src/map/dynamicTile.ts` 无命中
- `grep -n "\.events" packages-user/data-base/src/map/staticTile.ts packages-user/data-base/src/map/dynamicTile.ts` 各至少命中一次
- `grep -n "tileEvent()" packages-user/data-base/src/map/staticTile.ts packages-user/data-base/src/map/dynamicTile.ts` 各至少命中一次
</acceptance_criteria>
<verify>
<automated>pnpm check:type 2>&1 | Select-String -Pattern "map/staticTile\.ts|map/dynamicTile\.ts"</automated>
<fails_when>命中 staticTile.ts/dynamicTile.ts 的任何 TS 错误有输出即失败。mapState 的迁移在 Task 3core.ts/moverImpl/client-modules/tileStore.getTrigger 属跨计划/阶段外残差,不计入本任务失败</fails_when>
</verify>
<done>StaticTile/DynamicTile 存读档写 `events` Mapdirty 门控),不再引用 `triggers` 或已删除的 trigger 方法。</done>
</task>
<task type="auto">
<name>点位事件装配与图层事件同步MapLayer 静态/动态转换 + MapState.fromRaw</name>
<files>
packages-user/data-base/src/map/mapLayer.ts
packages-user/data-base/src/map/mapState.ts
</files>
<read_first>
- packages-user/data-base/src/map/types.tsIMapLayer.event/getPointEvent/transferToDynamic/transferToStatic 的 keepEvent 参数,只读)
- packages-user/data-common/src/store/types.tsIMapRawData.events 的 Record<z, Record<pos, Record<priority, id>>> 结构,只读)
- packages-user/data-base/src/map/mapLayer.tsTask 1 已加 event/getPointEvent本任务补 syncStaticEvent/transferTo*
- packages-user/data-base/src/map/mapState.ts现状fromRaw 中 raw.blockData/data.trigger/addTrigger
</read_first>
<action>
`mapLayer.ts`(在 Task 1 已加 event/getPointEvent 基础上继续):
1. `createDynamic(num, x, y)`:把 `location.static.triggers` 的复制改为「从 `location.static.tileEvent()` 视图读取 id Map逐项 `tile.tileEvent().set(priority, id)`」。
2. `syncStaticTrigger(tile, keepTrigger)` 重命名为 `syncStaticEvent(tile, keepEvent)``keepEvent` 为 true 时把 `tile.tileEvent().get()` 逐项 `staticTile.tileEvent().set(priority, id)`,否则 `staticTile.tileEvent().clear()``transferToStatic`/`transferToStaticIfSafe` 同步改用 `keepEvent` 参数(参数名按 map/types.ts 已定义的 `keepEvent`,替换原 `keepTrigger`)。
3. `transferToDynamic(x, y, keepEvent)``staticTile.clearTrigger()` 改为 `staticTile.tileEvent().clear()``tile.clearTrigger()` 改为 `tile.tileEvent().clear()`
`mapState.ts` `fromRaw`:把「读 `raw.blockData[z]` + `data.trigger` + `addTrigger`」整段替换为「读 `raw.events[z]`:对每层 z、每个 pos 索引、每个 `[priority, id]`,由 pos 索引算出 x/y`index % raw.width` / `Math.floor(index / raw.width)`),经 `layer.getLocationData(x, y)` 拿到静态图块,调用 `static.tileEvent().set(priority, id)`」。
禁用 `as`、`isNil` 判空、logger 数字错误码、私有成员 jsDoc。
</action>
<acceptance_criteria>
- `grep -n "syncStaticTrigger" packages-user/data-base/src/map/mapLayer.ts` 无命中(已改名 syncStaticEvent
- `grep -n "blockData\|\.trigger\b" packages-user/data-base/src/map/mapState.ts` 无命中
- `grep -n "raw\.events" packages-user/data-base/src/map/mapState.ts` 命中一次
- `grep -n "keepEvent" packages-user/data-base/src/map/mapLayer.ts` 命中
</acceptance_criteria>
<verify>
<automated>pnpm check:type 2>&1 | Select-String -Pattern "map/mapLayer\.ts|map/mapState\.ts"</automated>
<fails_when>命中 mapLayer.ts/mapState.ts 的任何 TS 错误有输出即失败。core.ts/moverImpl/client-modules/tileStore.getTrigger 属跨计划/阶段外残差,不计入本任务失败</fails_when>
</verify>
<done>点事件视图经 `IMapLayer.event/getPointEvent` 落地,静态/动态转换同步 `events``MapState.fromRaw` 从 `raw.events` 装配点位事件。</done>
</task>
</tasks>
<threat_model>
## Trust Boundaries
| Boundary | Description |
|----------|-------------|
| 地图原始数据 `IMapRawData.events` → 图块/点位事件绑定 | 优先级与事件 id 来自外部序列化,可能是未知/损坏的 id |
## STRIDE Threat Register
| Threat ID | Category | Component | Severity | Disposition | Mitigation Plan |
|-----------|----------|-----------|----------|-------------|----------------|
| T-01-01 | Tampering | `LayerEventView.set(priority, id)` 收到重复优先级 | low | mitigate | `logger.warn(136, priority.toString())` 后覆盖,不抛异常 |
| T-01-02 | Tampering | `MapState.fromRaw` 读取 `raw.events` 中损坏的 pos 索引/优先级 | low | mitigate | `getLocationData(x,y)` 越界返回 null 时跳过;优先级/坐标为数值,`isNaN` 时走 logger 错误码跳过 |
| T-01-SC | Tampering | npm/pip/cargo installs | high | mitigate | 本计划无新依赖安装;`anon-tokyo@0.0.0-alpha.0` 为已锁定既有依赖RESEARCH Package Legitimacy Audit 标记 SUS仅提示不阻断 |
</threat_model>
<verification>
本计划验证聚焦「本计划涉及文件类型成立 + 旧 triggers 字段在本计划涉及文件内清零」。整体阶段绿门(`check:type` + 旧 ITrigger 引用清零)在 Plan 03 统一验收;执行器装配在 Plan 02 处理。
</verification>
<success_criteria>
- `GameEvent.compile()` 写回 `compiled` 缓存;`store/index.ts` 导出 `mapStore`
- `LayerEventView` 实现 `ILayerEventView`(含 dirty/markPure
- `MapTileBase` 提供 `tileEvent()`/`pointEvent()`,移除 `triggers`/`block()`
- `StaticTile`/`DynamicTile` 存读档 `events``MapLayer.event/getPointEvent` + `syncStaticEvent` + `MapState.fromRaw` 落地
</success_criteria>
<output>
Create `.planning/phases/01-event/01-01-SUMMARY.md` when done
</output>
## Artifacts this phase produces
- `GameEvent.compile()` 缓存回写逻辑(修改 `data-common/src/event/event.ts`
- `store/index.ts` 新增 `mapStore` 桶导出
- `LayerEventView` 类(`data-base/src/map/eventView.ts`新建implements `ILayerEventView`
- `MapTileBase.tileEvent()` / `pointEvent()` 方法(`data-base/src/map/tile.ts`,替换 `triggers`/`addTrigger`/`clearTrigger`/`useEmptyTrigger`/`block`
- `StaticTile.shouldSave/saveState/loadState`、`DynamicTile.saveState/loadState` 迁移到 `events`
- `MapLayer.event(x,y)` / `getPointEvent(x,y)` + `syncStaticEvent`(替换 `syncStaticTrigger`
- `MapState.fromRaw``raw.events` 装配点事件
## Blockers / Deferred非本计划落地项
1. **执行器接口 `IGameEventExecutor`A3 / D-10**:已由用户设计完成(`data-system/src/event/types.ts` 中 `EventExecuteMode`/`IGameEventExecutor`/`IGameEventSystem`/`IGameEventBuiltinFunction` 均已定义Plan 02 直接实现,无需 checkpoint。本计划不导出/实现执行器,仅在本计划 context 中把它列为只读接口。
2. **`IMapPointRawData`A1**`tile.ts` 原 `block()` 引用的 `IMapPointRawData` 已不在 `@user/data-common`,本计划移除该残留方法与 import点事件的承载字段已在 `IMapRawData.events`(三级结构)落地,本计划实现。
3. **`ITileStore.getTrigger(num): number[]`**`store/types.ts:85` 接口仍声明 `getTrigger` 返回 `number[]`,但 `ITileRawData` 已改为 `events: Record<number, string>`,实现 `tileStore.ts:27-29``.trigger` 已失效。此为用户接口遗留,删除/改签名需用户决定,本计划不动 `store/types.ts``tileStore.ts`
4. **内建函数清单A2**:本阶段完全不落地(已移出本阶段,归入收尾工作),本计划不涉及。
5. **移动四钩子 → EventTrigger 映射A4**本计划不落地Plan 03
## 错误码
- 复用:`136`(重复优先级)。执行器的 `171`(事件 id 未找到)在 Plan 02 与执行器装配一并落地。

View File

@ -0,0 +1,253 @@
---
phase: 01-event
plan: 02
type: execute
wave: 2
depends_on: [01-01]
files_modified:
- packages-user/data-system/src/event/types.ts
- packages-user/data-system/src/event/executor.ts
- packages-user/data-system/src/event/system.ts
- packages-user/data-system/src/event/index.ts
- packages-user/data-system/src/index.ts
- packages-user/data-system/src/types.ts
- packages-user/data-state/src/core.ts
- packages/common/src/logger.json
files_deleted:
- packages-user/data-system/src/trigger/types.ts
- packages-user/data-system/src/trigger/trigger.ts
- packages-user/data-system/src/trigger/registry.ts
- packages-user/data-system/src/trigger/collector.ts
- packages-user/data-system/src/trigger/collection.ts
- packages-user/data-system/src/trigger/index.ts
- packages-user/data-state/src/content/triggers.ts
autonomous: true
requirements: [EVT-01, EVT-02, EVT-03]
estimate:
tokens: 50000
raw_tokens: 25000
tasks: 2
confidence: low
must_haves:
truths:
- "data-system/src/event/executor.ts 实现 IGameEventExecutor 接口execute(events: string[], param, env): Promise<R> 接收预先收集的事件 id 数组await 每个事件后按 EventExecuteMode 合并返回值D-10 专门对象execute 返回 Promise<R> 为用户批准的接口变更)"
- "data-system/src/event/system.ts 实现 IGameEventSystemexecutor + store + useStore() 完整装配,不出现只有 { executor } 的残缺装配"
- "IStateSystem 不再含 triggerRegistry/triggerCollector保留已有 eventSystemIGameEventSystem成员D-13"
- "CoreState 实例化 GameEventStore 赋值 this.eventStore装配完整 eventSystem含 executor/store/useStore并赋值 this.eventSystem"
- "data-system/src/trigger/ 目录与 data-state/src/content/triggers.tsChangeFloorTrigger已删除"
- "@user/data-system 桶导出 event 执行器与事件系统export * from './event'"
- "logger.json 新增错误码 171 文案,执行器取回未知事件 id 时 logger.warn(171, id) 而非 throwD-08"
artifacts:
- packages-user/data-system/src/event/types.ts
- packages-user/data-system/src/event/executor.ts
- packages-user/data-system/src/event/system.ts
- "IStateSystem 移除 trigger 成员data-system/src/types.ts"
- "删除清单files_deleted全部移除"
key_links:
- "IGameEventSystem.executor 连接 EventExecutor 实现与 CoreState.eventSystem 装配(含 store/useStore"
- "EventExecutor.execute 连接 storeRef().getEvent(id) 与 await GameEvent.execute(param, env),按 mode 合并返回值"
- "data-system/src/index.ts 桶导出连接 @user/data-system 消费方moverImpl/combat"
---
<objective>
实现 `IGameEventExecutor`/`IGameEventSystem` 接口D-10 专门对象)并接入 L2/L3 装配,同时删除旧 `ITrigger` 体系D-13。接口本身仅做一处用户批准的单行变更——`IGameEventExecutor.execute` 返回类型由 `R` 改为 `Promise<R>`(消除 sync-R vs async 事件的 tension其余接口成员一律不动。`mode`/`store` 两个 `readonly` 接口成员以普通类字段实现(`.agents/code.md` 禁用 getter仅经 `setMode`/`useStore` 变更。本计划不实现任何内建函数(对话/开门/道具/战斗)——内建函数已移出本阶段,归入收尾工作。
> **Scope note回应 scope_sanity** 本计划文件操作中7 个为机械目录删除(`data-system/src/trigger/` 整目录 6 文件 + `data-state/src/content/triggers.ts`),不含逻辑改写;另含 1 个用户批准的单行接口签名变更(`event/types.ts` 的 `execute``Promise<R>`),其余为常规实现/装配/桶导出改动。
Purpose: 完成「旧体系删除 + 新执行器接入」的切换,使 `@user/data-system` 只暴露事件执行器接口,`CoreState` 完整实现 `IStateSystem`(含 `eventStore``eventSystem`);这是 EVT-02踩踏触发链路由 Plan 03 的 moverImpl 收集+排序后调用 executor.execute与 EVT-03初学者抽象、无旧残留的执行器骨架。
Output: `EventExecutor`implement `IGameEventExecutor`execute 返回 `Promise<R>`)、`GameEventSystem`implement `IGameEventSystem`)、`data-system/src/event/types.ts` 单行签名变更execute → Promise<R>)、`data-system/src/event/index.ts` + `src/index.ts` 桶导出、`IStateSystem` 移除 trigger 成员、`CoreState` 装配 eventStore/eventSystem含 store/useStore、trigger/ 目录与 content/triggers.ts 删除、logger.json 171。
</objective>
<execution_context>
@C:/Users/book/.config/opencode/gsd-core/workflows/execute-plan.md
@C:/Users/book/.config/opencode/gsd-core/templates/summary.md
</execution_context>
<context>
@.planning/phases/01-event/01-CONTEXT.md
@.planning/phases/01-event/01-RESEARCH.md
@.planning/phases/01-event/01-PATTERNS.md
@.planning/phases/01-event/01-01-PLAN.md
@packages-user/data-system/src/types.ts
@packages-user/data-system/src/index.ts
@packages-user/data-system/src/event/types.ts
@packages-user/data-system/src/event/index.ts
@packages-user/data-system/src/trigger/types.ts
@packages-user/data-system/src/trigger/collector.ts
@packages-user/data-system/src/trigger/collection.ts
@packages-user/data-system/src/trigger/registry.ts
@packages-user/data-system/src/trigger/trigger.ts
@packages-user/data-state/src/content/triggers.ts
@packages-user/data-state/src/core.ts
@packages-user/data-common/src/event/types.ts
@packages-user/data-base/src/map/types.ts
@packages/common/src/logger.json
</context>
<tasks>
<task type="auto">
<name>实现 EventExecutor + GameEventSystemimplement 已有接口execute 返回 Promise<R>+ event 桶导出 + logger 错误码 171</name>
<files>
packages-user/data-system/src/event/types.ts
packages-user/data-system/src/event/executor.ts
packages-user/data-system/src/event/system.ts
packages-user/data-system/src/event/index.ts
packages-user/data-system/src/index.ts
packages/common/src/logger.json
</files>
<read_first>
- packages-user/data-system/src/event/types.tsEventExecuteMode/IGameEventBuiltinFunction/IGameEventExecutor/IGameEventSystem本计划仅把 IGameEventExecutor.execute 返回类型改为 Promise<R>(用户批准),其余成员只读、禁止修改)
- packages-user/data-system/src/event/index.ts现状仅 export * from './types'
- packages-user/data-system/src/index.ts现状combat/trigger/types
- packages-user/data-common/src/event/types.tsIReadonlyGameEvent.execute(param, env): Promise<R>,只读)
- packages-user/data-common/src/store/types.tsIGameEventStore.getEvent只读
- packages-user/data-base/src/map/types.tsIBlockEventParam/IBlockEventEnv/BlockEventType只读
- packages-user/data-base/src/types.tsIStateBase/IDataBaseExtended只读
- .planning/phases/01-event/01-PATTERNS.md Pattern 1/2 与「错误处理」段logger 数字错误码、isNil
</read_first>
<action>
实现 `IGameEventExecutor``IGameEventSystem` 接口。除下述用户批准的单行变更(`execute` 返回 `Promise<R>`)外,不得新增/修改其他公共接口签名。执行器接收「预先收集好的事件 id 数组」,不负责收集事件(收集 + D-06 排序是 Plan 03 moverImpl 的职责):
0. `packages-user/data-system/src/event/types.ts`(用户批准的单行接口变更):把 `IGameEventExecutor.execute` 的返回类型由 `R` 改为 `Promise<R>``execute<R = void>(events: string[], param: IBlockEventParam, env: IBlockEventEnv): R` → `...: Promise<R>`),其余接口成员(`mode`/`interpreter`/`setMode` 及整个 `IGameEventSystem`)一概不动。
1. `packages-user/data-system/src/event/executor.ts`(新建):实现 `class EventExecutor implements IGameEventExecutor`
- 构造器 `constructor(readonly interpreter: AnonTokyoInterpreter, private readonly storeRef: () => IGameEventStore | null)``interpreter` 为公共只读字段;`storeRef` 是惰性取 store 的回调,供 `execute` 解析事件 id避免与 `IGameEventSystem.useStore` 换 store 后脱节)。
- 用普通类字段满足接口的 `readonly mode``.agents/code.md` 禁用 getter不用 `get mode()``mode: EventExecuteMode = EventExecuteMode.ListReturn;`(普通非 readonly 字段即可满足 `readonly mode` 接口成员),`setMode(mode)` 直接赋值 `this.mode = mode`
- `async execute<R = void>(events: string[], param: IBlockEventParam, env: IBlockEventEnv): Promise<R>`:遍历 `events`,对每个 id 调 `storeRef()?.getEvent(id)`;返回 null 时 `logger.warn(171, id)` 后跳过D-08绝不 throw命中则 `await event.execute(param, env)` 取到实际返回值。按 `this.mode` 合并各事件 `await` 后的返回值:
- `ListReturn`:收集所有返回值组成数组作为整体返回值。
- `OrReduce`:顺序短路取或;遇非布尔值按短路运算符规则输出并 `logger.warn`
- `AndReduce`:顺序短路取与;非布尔值同上。
- `CutIfFalsy`:任意事件返回 falsy 立即停止,把前面返回值组成数组返回。
- 返回值:`execute` 为 `async`,最终 `return``this.mode` 合并后的结果(`ListReturn`/`CutIfFalsy` 返回数组,`OrReduce`/`AndReduce` 返回短路运算结果),类型满足 `Promise<R>`
- 约束:禁用 `as``isNil` 判空;公共方法先定义于接口再 implements私有成员加 jsDoc本阶段不实现任何内建函数。
2. `packages-user/data-system/src/event/system.ts`(新建):实现 `class GameEventSystem implements IGameEventSystem`
- `readonly state: IStateBase`(构造器注入,满足 `IDataBaseExtended`)。
- `readonly executor: IGameEventExecutor`:构造时 `new EventExecutor(interpreter, () => this.store)``interpreter` 用 `new AnonTokyoInterpreter({ builtInFunctions: [], globalFunctions: [] })`(内建函数本阶段留空骨架)。
- 用普通类字段满足接口的 `readonly store``.agents/code.md` 禁用 getter不用 `get store()``store: IGameEventStore | null;`(普通非 readonly 字段即可满足 `readonly store` 接口成员);`useStore(store)` 直接赋值 `this.store = store`executor 的 `storeRef` 闭包随之读到新 store。构造时 `this.store = state.eventStore`
- 约束同上。
3. `packages-user/data-system/src/event/index.ts`:追加 `export * from './executor';``export * from './system';`(保留 `export * from './types';`)。
4. `packages-user/data-system/src/index.ts`:把 `export * from './trigger';` 替换为 `export * from './event';`(保留 `export * from './combat';``export * from './types';`)。
5. `packages/common/src/logger.json`:在 `warn` 段追加 `"171": "Event id '$1' not found in event store, event will be skipped."`(英文文案,沿用现有风格)。
</action>
<acceptance_criteria>
- `grep -n "class EventExecutor" packages-user/data-system/src/event/executor.ts` 命中
- `grep -n "class GameEventSystem" packages-user/data-system/src/event/system.ts` 命中
- `grep -n "Promise<R>" packages-user/data-system/src/event/types.ts` 命中execute 返回 Promise<R>,用户批准的单行接口变更)
- `grep -n "execute<R" packages-user/data-system/src/event/executor.ts` 命中(签名为 `events: string[]`,返回 `Promise<R>`
- `grep -n "get mode\|get store" packages-user/data-system/src/event/executor.ts packages-user/data-system/src/event/system.ts` 无命中code.md 禁用 gettermode/store 用普通字段)
- `grep -n "export \* from './executor'" packages-user/data-system/src/event/index.ts` 命中
- `grep -n "export \* from './system'" packages-user/data-system/src/event/index.ts` 命中
- `grep -n "export \* from './event'" packages-user/data-system/src/index.ts` 命中
- `grep -n "171" packages/common/src/logger.json` 命中一次warn 段)
- `Test-Path packages-user/data-system/src/event/builtin.ts` 返回 False本阶段不实现内建函数
</acceptance_criteria>
<verify>
<automated>pnpm check:type 2>&1 | Select-String -Pattern "event/types\.ts|event/executor\.ts|event/system\.ts|event/index\.ts|data-system/src/index\.ts"</automated>
<fails_when>命中 event/types.ts、event/executor.ts、event/system.ts、event/index.ts、data-system/src/index.ts 的任何 TS 错误有输出即失败。IStateSystem/core.ts 的 trigger 成员删除在 Task 2moverImpl 引用旧 trigger、client-modules、tileStore.getTrigger 属跨计划/阶段外残差,不计入本任务失败</fails_when>
</verify>
<done>EventExecutor 实现 IGameEventExecutorexecute 返回 Promise<R>await 每个事件并按 mode 合并返回值mode 为普通字段由 setMode 赋值GameEventSystem 实现 IGameEventSystemexecutor+store+useStorestore 为普通字段由 useStore 赋值),`@user/data-system` 桶导出 event错误码 171 文案可用。</done>
</task>
<task type="auto">
<name>删除旧 ITrigger 体系 + CoreState 装配完整 eventSystemexecutor+store+useStoreD-13</name>
<files>
packages-user/data-system/src/types.ts
packages-user/data-state/src/core.ts
</files>
<read_first>
- packages-user/data-system/src/types.tsIStateSystem 现状triggerRegistry/triggerCollector + eventSystem
- packages-user/data-system/src/index.tsTask 1 已切 trigger→event
- packages-user/data-system/src/event/types.ts 与 event/system.ts、event/executor.tsTask 1 产出)
- packages-user/data-state/src/core.ts现状 L0/L2 初始化区)
- packages-user/data-common/src/store/eventStore.tsGameEventStore只读
- packages-user/data-common/src/types.tsIDataCommon.eventStore只读
- packages-user/data-state/src/hero/moverImpl.ts确认其 import 的 TriggerType/ITriggerCollector/ITriggerHandler 将在本计划移除后失效Plan 03 重写)
</read_first>
<action>
1. 删除 `packages-user/data-system/src/trigger/` 整个目录types.ts/trigger.ts/registry.ts/collector.ts/collection.ts/index.ts共 6 文件),删除 `packages-user/data-state/src/content/triggers.ts`ChangeFloorTrigger 及其自引用的 TriggerType 枚举)。这些文件中的 `TriggerType`Enter/Leave/Hit/CannotEnter 与 ChangeFloor均为旧体系D-13 要求一并删除。
2. `packages-user/data-system/src/types.ts``IStateSystem` 删除 `triggerRegistry: ITriggerRegistry``triggerCollector: ITriggerCollector` 两个成员及其 `import { ITriggerCollector, ITriggerRegistry } from './trigger';`。保留已有 `eventSystem: IGameEventSystem`(其 import 已存在)。
3. `packages-user/data-state/src/core.ts`
- L0 区新增 `const eventStore = new GameEventStore(); this.eventStore = eventStore;` 并补 `import { GameEventStore } from '@user/data-common';`(同时把第 79 行 `import { MapStore } from '../../data-common/src/store/mapStore'` 改为桶导出 `import { MapStore } from '@user/data-common';`Plan 01 已补 mapStore 桶导出)。
- L2 区删除 `ITriggerCollector/ITriggerRegistry/TriggerCollector/TriggerRegistry` import第 42-46 行)、`triggerRegistry`/`triggerCollector` 成员声明(第 98-99 行)、装配段(第 202-207 行),改为 `const eventSystem = new GameEventSystem(this); this.eventSystem = eventSystem;` 并补 `import { GameEventSystem } from '@user/data-system';`。注意装配的是完整 `IGameEventSystem`(含 executor/interpreter/mode/setMode 与 store/useStore绝不装配 `{ executor }` 单独对象。
约束:禁用 `as`;删除后 `grep``packages-user/data-system``data-state/src/{core.ts,content}` 内不得残留旧符号;`EventTrigger`(新枚举)与 `BlockEventType`(新枚举)不受影响。
</action>
<acceptance_criteria>
- `grep -rn "ITrigger\|TriggerRegistry\|TriggerCollector\|TriggerCollection\|BaseTrigger\|TriggerType" packages-user/data-system packages-user/data-state/src/core.ts packages-user/data-state/src/content 2>$null` 无命中(注意 `EventTrigger`/`BlockEventType` 不属匹配目标)
- `Test-Path packages-user/data-system/src/trigger` 返回 False
- `Test-Path packages-user/data-state/src/content/triggers.ts` 返回 False
- `grep -n "eventStore" packages-user/data-state/src/core.ts` 命中
- `grep -n "eventSystem" packages-user/data-system/src/types.ts packages-user/data-state/src/core.ts` 各命中
- `grep -n "GameEventSystem" packages-user/data-state/src/core.ts` 命中(装配完整系统,而非 `{ executor }`
</acceptance_criteria>
<verify>
<automated>pnpm check:type 2>&1 | Select-String -Pattern "data-system/src/types\.ts|data-state/src/core\.ts"</automated>
<fails_when>命中 data-system/types.ts、data-state/core.ts 的任何 TS 错误有输出即失败。moverImpl 引用旧 trigger、client-modules、tileStore.getTrigger 属跨计划/阶段外残差,不计入本任务失败</fails_when>
</verify>
<done>旧 ITrigger 体系删除完毕,`@user/data-system` 只暴露 event 执行器与事件系统,`CoreState` 装配 `eventStore` + 完整 `eventSystem`executor+store+useStore并完整实现 IStateSystem。</done>
</task>
</tasks>
<threat_model>
## Trust Boundaries
| Boundary | Description |
|----------|-------------|
| 外部 blockly 编辑器 → `Statement[]` → AnonTokyoInterpreter | 事件数据为不可信输入,执行器须容忍未知 id 与非法参数 |
## STRIDE Threat Register
| Threat ID | Category | Component | Severity | Disposition | Mitigation Plan |
|-----------|----------|-----------|----------|-------------|----------------|
| T-02-01 | Tampering | `EventExecutor``getEvent(id)` 返回 null 的未知 id | low | mitigate | `logger.warn(171, id)` 跳过,绝不 throw 中断游戏 |
| T-02-02 | DoS | 恶意/损坏事件引用被删除的旧 trigger 概念 | low | accept | D-13 删除后旧体系无残留,无对应执行面 |
| T-02-SC | Tampering | npm/pip/cargo installs | high | mitigate | 本计划无新依赖;`anon-tokyo` 为已锁定 SUS 依赖,仅提示不阻断 |
</threat_model>
<verification>
本计划切换后,`check:type` 的预期残差仅剩 `moverImpl.ts`(仍 import 已删除的旧 trigger 符号Plan 03 重写与阶段外残差client-modules、tileStore.getTrigger。完整 eventSystem 装配、IStateSystem、CoreState、删除项在 `check:type` 中应无新增错误。
</verification>
<success_criteria>
- 旧 ITrigger 体系trigger/ 6 文件 + content/triggers.ts全部删除无残留引用
- `EventExecutor` 实现 `IGameEventExecutor``execute` 返回 `Promise<R>``await` 每个事件并按 mode 合并返回值,`mode` 为普通字段由 `setMode` 赋值);`GameEventSystem` 实现 `IGameEventSystem`executor + store + useStore`store` 为普通字段)
- `IStateSystem` 只含 `eventSystem`(无 triggerRegistry/triggerCollector
- `CoreState` 装配 `eventStore` + 完整 `eventSystem`,完整实现 IStateSystem
- logger.json 171 就位;无内建函数实现(内建函数移出本阶段)
</success_criteria>
<output>
Create `.planning/phases/01-event/01-02-SUMMARY.md` when done
</output>
## Artifacts this phase produces
- 删除:`data-system/src/trigger/`6 文件)、`data-state/src/content/triggers.ts`ChangeFloorTrigger + ChangeFloor TriggerType
- `data-system/src/event/types.ts` 单行变更:`IGameEventExecutor.execute` 返回 `Promise<R>`(用户批准)
- `data-system/src/event/executor.ts`EventExecutor实现 IGameEventExecutorexecute 返回 Promise<R>mode 为普通字段由 setMode 赋值)
- `data-system/src/event/system.ts`GameEventSystem实现 IGameEventSystemstore 为普通字段由 useStore 赋值)
- `data-system/src/event/index.ts` 追加 executor/system 导出;`src/index.ts` 桶导出 trigger→event
- `IStateSystem` 移除 trigger 成员(保留 eventSystem
- `CoreState.eventStore` + `CoreState.eventSystem` 装配(删除 TriggerRegistry/TriggerCollector装配完整 eventSystem
- `logger.json` 错误码 171
## Blockers / Deferred
1. **内建函数清单A2用户 deferred**:对话/开门/道具/战斗的具体 `BuiltInFunction` 清单与命名已移出本阶段,归入收尾工作。本计划不实现、不新建 builtin 文件;`AnonTokyoInterpreter` 以空 `LanguageFeature`builtInFunctions/globalFunctions 均空)构造。
2. **执行器 sync-R vs 异步事件(已解决)**`IGameEventExecutor.execute` 已按用户批准改为返回 `Promise<R>`(本计划在 `event/types.ts` 落地该单行签名变更),执行器 `async execute` `await` 每个事件并按 mode 合并返回值。原「sync-R 声明 vs async 事件」的 tension 已消除。
3. **moverImpl.ts 暂时编译失败**:删除旧 trigger 导出后 `moverImpl.ts` 的 import 失效属预期过渡态Plan 03 重写修复(重写后负责收集 + D-06 排序并调用 executor.execute
## 错误码
- 新增:`171`(事件 id 未在 eventStore 中找到)——文案在本计划落地。

View File

@ -0,0 +1,195 @@
---
phase: 01-event
plan: 03
type: execute
wave: 3
depends_on: [01-02]
files_modified:
- packages-user/data-state/src/hero/moverImpl.ts
autonomous: true
requirements: [EVT-02, EVT-03]
estimate:
tokens: 50000
raw_tokens: 25000
tasks: 2
confidence: low
must_haves:
truths:
- "英雄移动的踩踏触发从旧 TriggerCollector/TriggerType 改为moverImpl 收集点事件+图块事件、按 D-06先点后块、各自优先级降序排序后 await state.eventSystem.executor.execute(events[], param, env)D-06/D-08"
- "enter 映射 EventTrigger.OnEnter、leave 映射 EventTrigger.OnLeave、hit 映射 EventTrigger.OnTouch映射依据 OnTouch 注释,触碰触发,直接走入不触发)"
- "moverImpl.ts 不再 import 任何 @user/data-system 的旧 trigger 符号ITriggerCollector/ITriggerHandler/TriggerType也不再访问 ILayerLocation.raw改用 location.static.raw()"
- "全仓 packages-user 内 ITrigger/TriggerRegistry/TriggerCollector/TriggerCollection/BaseTrigger/TriggerType 引用清零EventTrigger/BlockEventType 为合法新符号,除外)"
- "pnpm check:type本阶段涉及文件无报错、pnpm check:circular、pnpm lint:user 全绿(阶段外残差另记)"
artifacts:
- packages-user/data-state/src/hero/moverImpl.ts
key_links:
- "DefaultHeroMoveTopImpl.commonTrigger 收集点事件+图块事件并按 D-06 排序后,连接 state.eventSystem.executor.execute(events[], param, env)"
- "EventTrigger.OnEnter/OnLeave/OnTouch 连接移动四钩子 enter/leave/hit"
---
<objective>
把英雄移动的踩踏触发从旧触发器收集器重写为moverImpl 收集点事件+图块事件并按 D-06 排序后,委托新事件执行器的 `execute(events[], param, env)` 执行,同时同步修复 moverImpl 对已变更 `ILayerLocation``raw` → `static.raw()`)的引用,完成事件系统与移动流程的对接,随后执行阶段级验收(旧体系引用清零 + 门禁)。
Purpose: EVT-02「事件系统能驱动踩踏触发链路」的关键集成点重写后 `moverImpl` 只负责「收集 → D-06 排序 → 委托执行器」返回值合并语义EventExecuteMode集中在执行器Plan 02 已落地),同时让 `check:type` 中 moverImpl 相关报错清零。内建函数(对话/开门/道具/战斗)已移出本阶段,不在本计划范围。
Output: `moverImpl.ts` 重写(含 raw 迁移 + 收集/排序/委托 execute+ 阶段级验证结论。
</objective>
<execution_context>
@C:/Users/book/.config/opencode/gsd-core/workflows/execute-plan.md
@C:/Users/book/.config/opencode/gsd-core/templates/summary.md
</execution_context>
<context>
@.planning/phases/01-event/01-CONTEXT.md
@.planning/phases/01-event/01-RESEARCH.md
@.planning/phases/01-event/01-PATTERNS.md
@.planning/phases/01-event/01-01-PLAN.md
@.planning/phases/01-event/01-02-PLAN.md
@.planning/phases/01-event/01-02-SUMMARY.md
@packages-user/data-state/src/hero/moverImpl.ts
@packages-user/data-base/src/hero/types.ts
@packages-user/data-base/src/map/types.ts
@packages-user/data-common/src/event/types.ts
@packages-user/data-system/src/event/types.ts
</context>
<tasks>
<task type="auto">
<name>moverImpl 重写raw 迁移 + 踩踏触发委托事件执行器enter/leave/hit → EventTrigger</name>
<files>
packages-user/data-state/src/hero/moverImpl.ts
</files>
<read_first>
- packages-user/data-state/src/hero/moverImpl.ts现状canPass/shouldHit 用 location.rawcommonTrigger 用 collector
- packages-user/data-base/src/hero/types.tsIHeroMoveTopImpl 的 enter/leave/hit/cannotEnter 契约,只读)
- packages-user/data-base/src/map/types.tsILayerLocation.static.raw(),只读)
- packages-user/data-common/src/event/types.tsEventTrigger 枚举OnEnter/OnLeave/OnTouch 注释,只读)
- packages-user/data-system/src/event/types.tsIGameEventExecutor.execute(events: string[], param, env): Promise<R> 签名 + EventExecuteMode只读
</read_first>
<action>
重写 `packages-user/data-state/src/hero/moverImpl.ts`,分两部分:
A. 修复已变更的 `ILayerLocation` 引用(`canPass`/`shouldHit`
- `canPass``curr.raw.pass.outPass`/`next.raw.pass.inPass` 改为 `curr?.static.raw()?.pass.outPass`/`next?.static.raw()?.pass.inPass``ILayerLocation` 已无 `raw`,改经 `static.raw()``ITileRawData`raw() 可能为 null`isNil`/可选链判空后默认 true 可通行)。外层 `if (curr && curr.raw)` 相应改为对 `static.raw()` 结果判空。
- `shouldHit``next.raw.eventPass` 改为 `next?.static.raw()?.eventPass`raw 为 null 时返回 false
B. 触发器行为改为「收集 + D-06 排序 → 委托执行器 execute」
1. 顶部 import删除 `ITriggerCollector/ITriggerHandler/TriggerType`(来自 `@user/data-system`);改为从 `@user/data-common` 引入 `EventTrigger`,从 `@user/data-base` 引入 `IBlockEventParam/IBlockEventEnv/BlockEventType`,从 `@user/data-system` 引入 `IGameEventExecutor`,从 `@motajs/common` 引入 `ITileLocator``state` 类型仍是 `IStateSystem`
2. 构造器:把 `private readonly collector: ITriggerCollector``this.collector = state.triggerCollector` 改为 `private readonly executor: IGameEventExecutor``this.executor = state.eventSystem.executor`
3. `commonTrigger` 改为签名 `commonTrigger(trigger: EventTrigger, handler: IHeroMoveTopHandler, heroLoc: ITileLocator, x: number, y: number): Promise<void>`方法体D-06先点事件、后图块事件各自 priority 降序):
- `if (isNil(handler.floorId)) return Promise.resolve();``const map = this.maps.getMap(handler.floorId); if (!map) return Promise.resolve(); const event = map.eventLayer; if (!event) return Promise.resolve();`
- 收集点事件:`const point = event.getPointEvent(x, y);``ReadonlyMap<number,string> | null`)。
- 收集图块事件:`const loc = event.getLocationData(x, y);``const tileEvents = loc?.static.tileEvent().get();``ReadonlyMap<number,string>``loc` 为 null 时按空处理)。
- 排序合并:把 `point``tileEvents` 各自的 `[priority, id]` 条目按 priority 降序排成 id 数组,先点后块拼成 `const sortedIds: string[]`D-06
- 构造 `const param: IBlockEventParam = { custom: {} };``const env: IBlockEventEnv = { type: BlockEventType.TileEvent, trigger, heroLocator: heroLoc, triggerLocator: { x, y }, tile: loc?.static ?? null, layer: event, map };`
- `await this.executor.execute<void>(sortedIds, param, env);`(执行器 `execute` 返回 `Promise<R>`,直接 `await`D-09 长事件 await。不再构造 `ITriggerHandler` 对象。
4. 四个钩子改为映射映射为规划期最佳解读A4 已延后确认见「Blockers」
- `enter(handler)``this.commonTrigger(EventTrigger.OnEnter, handler, handler.nextLoc, handler.nextLoc.x, handler.nextLoc.y)`
- `leave(handler)``this.commonTrigger(EventTrigger.OnLeave, handler, handler.currLoc, handler.currLoc.x, handler.currLoc.y)`
- `hit(handler)``this.commonTrigger(EventTrigger.OnTouch, handler, handler.currLoc, handler.nextLoc.x, handler.nextLoc.y)`
- `cannotEnter(handler)``return Promise.resolve();`(新 EventTrigger 无 CannotEnter 对应A4 已延后,先空实现并加 jsDoc 注明)
约束:禁用 `as``isNil`/可选链判空;公共方法不新增(接口契约未变);保留 4 钩子的 async 签名与返回类型。
</action>
<acceptance_criteria>
- `grep -n "TriggerType\|ITriggerCollector\|ITriggerHandler\|triggerCollector" packages-user/data-state/src/hero/moverImpl.ts` 无命中
- `grep -n "\.raw\b" packages-user/data-state/src/hero/moverImpl.ts` 无命中(已改 static.raw()
- `grep -n "EventTrigger.OnEnter\|EventTrigger.OnLeave\|EventTrigger.OnTouch" packages-user/data-state/src/hero/moverImpl.ts` 各命中一次
- `grep -n "getPointEvent\|tileEvent()\|executor.execute" packages-user/data-state/src/hero/moverImpl.ts` 命中(收集点事件/图块事件并委托 execute
- `grep -n "eventSystem\|executor" packages-user/data-state/src/hero/moverImpl.ts` 命中
- `pnpm check:type` 中无 moverImpl.ts 报错
</acceptance_criteria>
<verify>
<automated>pnpm check:type 2>&1 | Select-String -Pattern "hero/moverImpl\.ts"</automated>
<fails_when>命中 hero/moverImpl.ts 的任何 TS 错误有输出即失败。client-modules、tileStore.getTrigger 属阶段外残差,不计入本任务失败</fails_when>
</verify>
<done>moverImpl 三个移动钩子enter/leave/hit收集点事件+图块事件并按 D-06 排序后委托 executor.execute踩踏触发走 OnEnter/OnLeave/OnTouch不再引用旧 trigger 符号或 ILayerLocation.raw。</done>
</task>
<task type="auto">
<name>阶段验收:旧体系引用清零 + 门禁</name>
<files>
packages-user/data-state/src/hero/moverImpl.ts
</files>
<read_first>
- .planning/phases/01-event/01-RESEARCH.md 的「Validation Architecture → Phase gate」
- .planning/phases/01-event/01-01-PLAN.md 与 01-02-PLAN.md 的 must_haves
</read_first>
<action>
执行阶段级验收(本任务无源码改动,只跑门禁并记录结论):
1. 旧体系引用清零:`grep -rn "ITrigger\|TriggerRegistry\|TriggerCollector\|TriggerCollection\|BaseTrigger" packages-user` 应为空;`grep -rn "\bTriggerType\b" packages-user` 应为空(旧枚举已全删)。注意:`EventTrigger`新枚举data-common/src/event/types.ts`BlockEventType`data-base/src/map/types.ts是合法符号不得被误判为残留。
2. `pnpm check:type`本阶段涉及文件eventView/tile/staticTile/dynamicTile/mapLayer/mapState/event/executor/system/types/index/core/moverImpl应无报错阶段外残差client-modules 的 HeroMover/degradeFace/FaceDirection 等、tileStore.getTrigger记入 SUMMARY 的 blockers注明「非本阶段引入」。
3. `pnpm check:circular`无新增循环依赖event 执行器只依赖 IStateBase不可反向
4. `pnpm lint:user`packages-user 无 lint 错误eventView/executor/moverImpl 等新文件符合 code.md
把四项结论(命令 + 通过/失败 + 残差)写进 01-03-SUMMARY.md。若 check:type 出现非 mover 残留的既有错误,不静默忽略——在 SUMMARY 的 blockers 记录并注明「非本阶段引入」。
</action>
<acceptance_criteria>
- 旧 trigger 符号 grep 三组命令输出均为空
- 本阶段涉及文件在 `pnpm check:type` 中无报错(阶段外残差记录在案)
- `pnpm check:circular` 退出码 0
- `pnpm lint:user` 退出码 0
</acceptance_criteria>
<verify>
<automated>pnpm check:circular; if ($?) { pnpm lint:user }</automated>
<fails_when>任一命令非零退出check:type 的全仓退出码因阶段外 client-modules/tileStore 残差可能非零,故以「本阶段文件无报错」为准,见 Task 1 的 verify</fails_when>
</verify>
<done>阶段验收通过:旧 ITrigger 体系无残留,事件系统骨架类型(本阶段文件)/循环依赖/lint 门禁通过,阶段外残差已在 SUMMARY 记录。</done>
</task>
</tasks>
<threat_model>
## Trust Boundaries
| Boundary | Description |
|----------|-------------|
| 英雄移动钩子 → 事件执行器 → `Statement[]` 执行 | 移动触发的图块/点事件数据为不可信输入,执行器须容忍未知 id 与非法参数 |
## STRIDE Threat Register
| Threat ID | Category | Component | Severity | Disposition | Mitigation Plan |
|-----------|----------|-----------|----------|-------------|----------------|
| T-03-01 | Tampering | `moverImpl` 委托执行器触发含未知事件 id 的图块 | low | mitigate | 执行器 `getEvent` 为 null 时 `logger.warn(171)` 跳过,不阻断移动 |
| T-03-02 | DoS | 踩踏触发执行长事件阻塞移动循环 | low | accept | D-09 长事件 `await` 为既定语义,属设计范围,不序列化执行状态 |
| T-03-SC | Tampering | npm/pip/cargo installs | high | mitigate | 本计划无新依赖安装;`anon-tokyo` 已锁定SUS仅提示 |
</threat_model>
<verification>
阶段门禁:`grep` 旧 trigger 符号三组命令均为空;`pnpm check:circular` + `pnpm lint:user` 全绿;`check:type` 以「本阶段文件无报错」为准(阶段外 client-modules/tileStore 残差记录)。
</verification>
<success_criteria>
- 踩踏触发enter/leave/hit经事件执行器驱动
- 旧 ITrigger 体系在全仓无残留引用
- moverImpl 不再访问 ILayerLocation.raw
- 阶段门禁通过(本阶段文件类型成立 + circular + lint阶段可进入 review/transition
</success_criteria>
<output>
Create `.planning/phases/01-event/01-03-SUMMARY.md` when done
</output>
## Artifacts this phase produces
- `moverImpl.ts` 踩踏触发重写:`commonTrigger(EventTrigger, handler, heroLoc, x, y)` 收集点事件+图块事件、按 D-06 排序、委托 `executor.execute(events[], param, env)` + `raw``static.raw()` 迁移
- 阶段验收结论(旧体系引用清零 + 门禁),记录于 01-03-SUMMARY.md
## Blockers / Deferred
1. **移动四钩子 → EventTrigger 映射A4已延后**`enter→OnEnter`/`leave→OnLeave`/`hit→OnTouch` 为规划期最佳解读(依据 OnTouch 注释),`cannotEnter` 无 EventTrigger 对应空实现。OnTouch/OnEnter 边界与 cannotEnter 去留须用户后续确认。
2. **内建函数(对话/开门/道具/战斗A2已移出本阶段**EVT-02 的「对话/开门」端到端验证不在本阶段,归入收尾工作。
3. **执行器 sync-R vs 异步事件(已解决)**`execute` 已按用户批准改为返回 `Promise<R>`Plan 02 在 `event/types.ts` 落地签名变更moverImpl 以 `execute<void>` 消费并 `await`D-09 长事件 await
4. **执行器 env 的 `type`/heroLocator 精确值**`type` 暂统一为 `BlockEventType.TileEvent`(踩踏触发以图块为触发位置);`heroLocator` 已按 enter/hit=nextLoc、leave=currLoc 传入,`triggerLocator={x,y}`。接入 hero.location 后如需区分点事件类型再核对(不阻塞骨架)。
## 错误码
- 复用 `171`(执行器未找到事件 id——Plan 02 已落地文案。

View File

@ -0,0 +1,440 @@
# Phase 1: 事件系统 (event) - Pattern Map
**Mapped:** 2026-09-07
**Files analyzed:** 18 (create + modify + delete)
**Analogs found:** 18 / 18 (all have a codebase analog; no true "no-analog" files)
## File Classification
> 说明接口层L0/L1已由用户设计完成`EventTrigger`/`IGameEvent`/`GameEventStore`/`ITileBase.events`/`ILayerEventView`/`IBlockEvent*` 等)。本阶段是「落地已设计接口 + 删除旧 `ITrigger` 体系」。因此多数改造文件是**自我重构**(同一文件内 `triggers``events`),其 analog 即其自身。
| New/Modified File | Role | Data Flow | Closest Analog | Match Quality |
|-------------------|------|-----------|----------------|---------------|
| `data-system/src/event/types.ts` (新建) | service (接口) | event-driven | `data-system/src/trigger/types.ts` | exact |
| `data-system/src/event/executor.ts` (新建) | service | event-driven | `data-system/src/trigger/collector.ts` + `collection.ts` | exact |
| `data-system/src/event/index.ts` (新建) | config (桶导出) | — | `data-system/src/trigger/index.ts` | exact |
| `data-system/src/event/builtin.ts` (新建) | service (内建函数) | event-driven | `data-state/src/content/triggers.ts` (`ChangeFloorTrigger`) | role-match |
| `data-base/src/map/eventView.ts` (新建) | model (Map 视图 + dirty) | CRUD | `packages/common/src/dirtyTracker.ts` + `tile.ts` triggers 方法 | role-match |
| `data-common/src/store/index.ts` (改) | config (桶导出) | — | 自身 + `data-common/src/index.ts` | exact |
| `data-common/src/event/event.ts` (改) | model | transform | 自身 (`GameEvent.compile`) | exact |
| `data-common/src/store/tileStore.ts` (改) | store | CRUD | `store/itemStore.ts` | exact |
| `data-base/src/map/tile.ts` (改) | model | CRUD | 自身 (`MapTileBase` triggers 方法) | exact |
| `data-base/src/map/staticTile.ts` (改) | model | CRUD (存读档) | 自身 (`saveState`/`loadState`) | exact |
| `data-base/src/map/dynamicTile.ts` (改) | model | CRUD (存读档) | 自身 (`saveState`/`loadState`) | exact |
| `data-base/src/map/mapLayer.ts` (改) | model/service | CRUD (存读档) | 自身 (`syncStaticTrigger` + `compareWith`) | exact |
| `data-system/src/index.ts` (改) | config (桶导出) | — | 自身 | exact |
| `data-system/src/types.ts` (改) | config (接口) | — | 自身 (`IStateSystem`) | exact |
| `data-state/src/core.ts` (改) | provider (装配) | — | 自身 (L2 初始化 region) | exact |
| `data-state/src/hero/moverImpl.ts` (改) | controller | event-driven | 自身 (`commonTrigger`) | exact |
| `data-system/src/trigger/*` (删, 6 文件) | — | — | — (删除) | n/a |
| `data-state/src/content/triggers.ts` (删) | — | — | — (删除) | n/a |
---
## Pattern Assignments
### 1. `data-system/src/event/types.ts` (新建, service 接口, event-driven)
**Analog:** `data-system/src/trigger/types.ts`
这是「专门事件执行器」的接口层D-10。必须遵循项目「接口先行 + `implements`」的刚性约定code.md 规则 14/15/16公共方法必须先定义在接口、类成员必须 `readonly`、对象成员用接口类型不用类类型)。
**导入与依赖方向**trigger/types.ts:1-7 为样板):
```typescript
// 事件执行器依赖 L1 IStateBase与旧 ITriggerHandler 同向
import { ITileLocator } from '@motajs/common';
import { IGameMap, IMapLayer, IStateBase, IDataBaseExtended } from '@user/data-base';
import { EventTrigger } from '@user/data-common'; // 注意:事件相关类型来自 L0不是本层
```
**接口命名与结构样板**trigger/types.ts:20-29 `ITriggerHandler`
```typescript
// 事件执行上下文 —— 替换旧 ITriggerHandler。字段用 readonly。
export interface ITriggerHandler {
readonly state: IStateBase;
readonly layer?: IGameMap;
readonly mapLayer?: IMapLayer;
readonly locator?: ITileLocator;
}
```
**before 类返回语义的关键参照**——「执行器如何表达 `OnBeforeBattle`/`OnBeforeOpenDoor`/`OnBeforeChangeFloor` 返回 `R` 决定是否继续」已经在 combat 层有现成样板直接照抄其返回约定combat/types.ts:767-790 `ICombatScript`
```typescript
// combat/types.ts:772-779 —— before 返回 boolean 表示「是否继续」的既定语义
before(
info: IEnemyDamageInfo<TEnemy, THero>,
handler: ICombatFlowHandler<TEnemy, THero>
): Promise<boolean>;
// 注释约定:返回 false 会立刻停止后续执行并放弃本次战斗
```
**错误/缺省处理**:执行器接口方法若拿不到 `eventStore` 中对应 id 的事件,按项目惯例 `logger.warn(code)` 后跳过(不抛异常)。参照 `eventStore.ts``getEvent` 返回 `null`,调用侧做空判。
---
### 2. `data-system/src/event/executor.ts` (新建, service, event-driven)
**Analog:** `data-system/src/trigger/collector.ts`(收集+排序) + `trigger/collection.ts`(异步派发)
D-06/D-10 要求「先点事件、后图块事件各自按优先级降序before 返回值集中解析」。旧 `TriggerCollector.collect` 就是这套排序/去重的唯一现成实现,直接沿用其结构。
**收集与优先级排序样板**trigger/collector.ts:15-107——核心逻辑是「优先级降序 + 重复优先级 warn」
```typescript
// trigger/collector.ts:54-79动态图块 > 2 的通用排序,含重复优先级警告)
const usedPriority = new Set<number>();
const duplicate = new Set<number>();
// ... 收集时
if (usedPriority.has(trigger.priority)) {
duplicate.add(trigger.priority);
}
usedPriority.add(trigger.priority);
// ... 排序
triggers.sort((a, b) => b.priority - a.priority);
if (duplicate.size > 0) {
logger.warn(136, [...duplicate].join(',')); // 136 = duplicate trigger priority
}
```
**异步顺序派发样板**trigger/collection.ts:20-27——事件执行是 `await` 顺序执行D-09 长事件直接 await不并行
```typescript
async trigger(condition: TriggerType, handler: ITriggerHandler): Promise<void> {
for (const trigger of this.triggerList) {
await this.dispatch(trigger, condition, handler);
}
}
```
**核心差异点(执行器 vs 旧 collector**
- 旧 collector 输入 `ITriggerCollection`,新执行器输入「点事件 `ReadonlyMap<number,string>` + 图块事件 `ReadonlyMap<number,string>`」的 id`state.eventStore.getEvent(id)` 取出 `IReadonlyGameEvent`,再 `event.execute(param, env)`D-04/D-07/D-08
- `param` = `IBlockEventParam``{ custom: Record<string, any> }`map/types.ts:35-38`env` = `IBlockEventEnv`map/types.ts:40-55`type`/`trigger`/`heroLocator`/`triggerLocator`/`tile`/`layer`/`map`)。
- before 类触发器(`EventTrigger.OnBeforeBattle/OnBeforeOpenDoor/OnBeforeChangeFloor`)的 `execute` 返回 `R`boolean由执行器统一解析后决定是否继续对应动作。
**执行器持有解释器**:参照 `EnemyContext` 的「attach + constructor(state)」装配风格combat/context.ts:103执行器构造器注入 `state`,内部从 `state``eventStore`。若执行器负责编译,则持有 `AnonTokyoInterpreter` 实例anon-tokyo `.d.ts:19-29`)。
---
### 3. `data-system/src/event/index.ts` (新建, 桶导出)
**Analog:** `data-system/src/trigger/index.ts`5 行)与 `combat/index.ts`5 行)
```typescript
// trigger/index.ts:1-5 样板
export * from './builtin';
export * from './executor';
export * from './types';
```
---
### 4. `data-system/src/event/builtin.ts` (新建, 内建函数, event-driven)
**Analog:** `data-state/src/content/triggers.ts``ChangeFloorTrigger`,领域动作实现)
内建函数是 `BuiltInFunction[]`anon-tokyo `.d.ts:61-65`),对应「对话 / 开门 / 道具 / 战斗」等游戏动作EVT-02。旧的 `ChangeFloorTrigger` 是唯一现成的「领域动作」实现,其 `trigger(handler)` 方法content/triggers.ts:140-161展示了如何从 `handler``layer`/`locator`/`mapLayer` 并驱动游戏状态(`hero.changeFloor`)。
**内建函数签名样板**anon-tokyo `.d.ts:61-65` + `LanguageFeature` 153-156
```typescript
export declare interface BuiltInFunction {
name: string;
save?: boolean;
func: (parameters: Record<string, any>, env: Record<string, any>) => any;
}
export declare interface LanguageFeature {
builtInFunctions: BuiltInFunction[];
globalFunctions: [name: string, Block][];
}
```
**参数校验惯例**内建函数参数来自外部编辑器不可信输入RESEARCH Security Domain V5。用 `isNil`lodash-es+ `logger.warn(code)` 缺省跳过,**绝不抛异常中断**(参照 content/triggers.ts:141-150 的 `if (!handler.layer || !handler.locator) { logger.warn(164); return; }` 模式)。
> ⚠️ A2 待定:具体内建函数清单/命名由用户主导blockly 编辑器约定)。清单未定前,此文件仅含最小集(如 `openDoor`),其余留空。
---
### 5. `data-base/src/map/eventView.ts` (新建, ILayerEventView 实现, CRUD+dirty)
**Analog:** `packages/common/src/dirtyTracker.ts``PrivateMapDirtyTracker`+ `tile.ts`triggers 增删改方法)
`IReadonlyEventView` / `ILayerEventView`map/types.ts:83-119已定义但无实现。核心是「priority → eventId 的 Map」+ `dirty()`/`markPure()`。dirty 语义D-12初始 dirty=false变动且与基准不同则 true`PrivateMapDirtyTracker` 完全同构。
**dirty 追踪样板**dirtyTracker.ts:92-133 `PrivateMapDirtyTracker`
```typescript
// mark() 返回一个 symbol 快照dirtySince(mark) 比较 dirtyFlag 判断是否变化
private dirtyFlag: number = 0;
protected dirty(data: T): void {
this.dirtyFlag++;
this.markMap.set(data, this.dirtyFlag);
}
```
**增删改方法样板**tile.ts:47-65`addTrigger`/`deleteTrigger`/`clearTrigger`,新实现直接改名/换类型为 `set`/`delete`/`clear` 对应 `ILayerEventView`
```typescript
clearTrigger(): void { this.triggers = null; }
addTrigger(trigger: number): void {
if (!this.triggers) this.triggers = new Set();
this.triggers.add(trigger);
}
// → 新接口为 set(priority, eventId) / delete(priority) / clear() / markPure()
```
> 注:`ILayerEventView` 用 `set(priority, event)` 带「优先级冲突则覆盖并 warn」语义map/types.ts:96-102冲突警告沿用 `logger.warn(136, ...)`duplicate priority惯例。
---
### 6. `data-common/src/store/index.ts` (改, 桶导出)
**Analog:** 自身 + `data-common/src/index.ts`
当前内容store/index.ts:1-4已含 `eventStore` 导出,**缺失 `mapStore`**`core.ts:79` 只能写相对路径 `'../../data-common/src/store/mapStore'` 绕过桶导出——RESEARCH Pitfall 2
```typescript
// 需补为(参照 data-common/src/index.ts:1-7 的完整桶导出风格)
export * from './eventStore';
export * from './itemStore';
export * from './mapStore'; // ← 缺失,补上
export * from './tileStore';
export * from './types';
```
---
### 7. `data-common/src/event/event.ts` (改, compile 缓存回写)
**Analog:** 自身 (`GameEvent.compile`)
RESEARCH Pitfall 4`compile()` 返回编译结果但未写回 `this.compiled`,导致 `execute()` 每次走 compile 分支。修复样板:
```typescript
// event/event.ts:24-26 现状
compile(): AnonTokyoExecutable | null {
return this.interpreter.compile(this.rawEvent);
}
// → 改为写回缓存this.compiled 是 readonly 接口成员,但类内可写):
compile(): AnonTokyoExecutable | null {
this.compiled = this.interpreter.compile(this.rawEvent);
return this.compiled;
}
```
`setRaw`event.ts:45-48已正确置 `this.compiled = null`,缓存回写后即可生效。
---
### 8. `data-common/src/store/tileStore.ts` (改, 类型对齐)
**Analog:** `store/itemStore.ts``getData`/`getCategory` 的 `??` 缺省取值模式)
当前 `tileStore.ts:27-29``getTrigger` 返回 `dataMap.get(num)?.trigger ?? -1`,但 `ITileRawData` 已把 `trigger: number[]` 改为 `events: Record<number, string>`store/types.ts:53`ITileStore.getTrigger` 签名声明返回 `number[]`store/types.ts:85——实现与接口脱节属「落地已设计接口」必改项。
**getter 缺省样板**itemStore.ts:20-22
```typescript
getCategory(num: number): ItemCategory {
return this.dataMap.get(num)?.category ?? ItemCategory.Unknown;
}
```
---
### 9. `data-base/src/map/tile.ts` (改, MapTileBase triggers → events)
**Analog:** 自身(`MapTileBase` 现有 `triggers`/`addTrigger`/`clearTrigger`/`useEmptyTrigger`tile.ts:18,47-65
`ITileBase` 接口map/types.ts:148-170已定义 `tileEvent(): ILayerEventView``pointEvent(): ILayerEventView | null`,但 `MapTileBase` 未实现(`implements ITileBase` 会报缺成员)。改造方向:
- `triggers: Set<number> | null` → 图块事件视图(`ILayerEventView`,含 priority→id Map + dirty 标记)
- `addTrigger`/`deleteTrigger`/`clearTrigger`/`useEmptyTrigger` → 委托给 `tileEvent()` 视图的 `set`/`delete`/`clear`
- 新增 `tileEvent()`/`pointEvent()` 实现pointEvent 来自 `layer.event(x, y)`map/types.ts:573
私有成员必须加 jsDoc 注释code.md 规则 8
---
### 10/11. `data-base/src/map/staticTile.ts` / `dynamicTile.ts` (改, 存读档 events)
**Analog:** 自身(现有 `saveState`/`loadState`/`shouldSave` 结构)
`IStaticBlockSave`/`IDynamicBlockSave` 继承 `IMapBlockSaveBase.events?: ReadonlyMap<number, string>`map/types.ts:71-74但实现仍写 `save.triggers = this.triggers`staticTile.ts:41-43、dynamicTile.ts:105-107。改造样板保持现有结构只换字段类型与存取方式
```typescript
// staticTile.ts:34-58 现状triggers改 events 后结构不变
shouldSave(): boolean { return !!this.triggers; } // → 图块事件视图 dirty() 或非空判断
saveState(): Readonly<IStaticBlockSave> {
const save: IStaticBlockSave = {};
if (this.triggers) save.triggers = this.triggers; // → save.events = 视图.get() 的 ReadonlyMap
return save;
}
loadState(save: Readonly<IStaticBlockSave>): void {
// triggers.size === 0 → useEmptyTrigger(),否则逐条 addTrigger
// → events 为空 Map → clear(),否则逐条 set(priority, id)
}
```
> dirty 标记D-12语义图块事件视图初始以「原始图块 `ITileRawData.events`」为基准(`markPure()`),存档时仅当 `dirty()` 为 true 才写入 `events`。参照 `MapLayer.saveStatics`mapLayer.ts:580-589`tile.shouldSave()` 决定是否写存档的门控。
---
### 12. `data-base/src/map/mapLayer.ts` (改, 事件视图 + 点事件)
**Analog:** 自身(`syncStaticTrigger`、`compareWith`、`event(x,y)`/`getPointEvent` 待实现)
接口 `IMapLayer.event(x,y): ILayerEventView | null`map/types.ts:573`getPointEvent`581已声明未实现。改造点
- `syncStaticTrigger`mapLayer.ts:118-134`triggers` 复制 → 图块事件视图的 `set`/`clear`(对应 `transferToDynamic`/`transferToStatic`/`transferToStaticIfSafe` 的 `keepEvent` 参数map/types.ts:409-435 已定义)
- `createDynamic`mapLayer.ts:314-329`location.static.triggers` 复制 → 复制图块事件视图
- 新增 `event(x,y)` / `getPointEvent(x,y)`:点事件存于 `IMapRawData.events`store/types.ts:310`Record<z, Record<pos, Record<priority, id>>>`不随图块移动D-03
**脏标记对照**mapLayer.ts:481-503 `dirty()`/`markDirty`/`compareWith`/`isEqualToRef`):点事件的 dirty 应与图层 dirty 协同RESEARCH A1沿用 `layerDirty` + `refArray` 对比模式。
---
### 13/14. `data-system/src/index.ts` / `types.ts` (改, 桶导出 + IStateSystem)
**Analog:** 自身
- `index.ts`data-system/index.ts:1-4`export * from './trigger'` → `export * from './event'`
- `types.ts`data-system/types.ts:6-13`IStateSystem` 删除 `triggerRegistry`/`triggerCollector` 两个成员,替换为事件执行器成员(如 `readonly eventExecutor: IEventExecutor`,接口名由用户定/待确认)
```typescript
// data-system/types.ts:6-13 现状
export interface IStateSystem extends IStateBase {
readonly enemyContext: IEnemyContext<IEnemyAttr, IHeroAttr>;
readonly triggerRegistry: ITriggerRegistry; // ← 删
readonly triggerCollector: ITriggerCollector; // ← 删
// ← 增:事件执行器成员
}
```
---
### 15. `data-state/src/core.ts` (改, 装配)
**Analog:** 自身L2 初始化 regioncore.ts:184-209
- 删 `TriggerRegistry`/`TriggerCollector` 装配core.ts:203-207
- L0 增 `eventStore` 实例化:`const eventStore = new GameEventStore(); this.eventStore = eventStore;``IDataCommon.eventStore` 已声明types.ts:54`GameEventStore` 已实现store/eventStore.ts:5-28
- 修 `import { MapStore } from '../../data-common/src/store/mapStore'`core.ts:79为桶导出 `@user/data-common`
- L2 增事件执行器装配(构造器注入 `this`),参照 combat 的 `EnemyContext`/`DamageSystem` 装配风格core.ts:187-200
**装配样板**core.ts:202-207 现状 → 替换):
```typescript
// 触发器注册与收集器(待删)
const triggerRegistry = new TriggerRegistry(this);
const triggerCollector = new TriggerCollector();
triggerCollector.attachRegistry(triggerRegistry);
// → 事件执行器(新)
const eventExecutor = new EventExecutor(this); // 类名/接口名待定
```
---
### 16. `data-state/src/hero/moverImpl.ts` (改, 踩踏触发重写)
**Analog:** 自身(`commonTrigger` + enter/leave/hit/cannotEntermoverImpl.ts:141-184
`commonTrigger` 统一「收集 → 执行」流程moverImpl.ts:141-164是执行器委托的唯一入口。重写方向
- `this.collector`moverImpl.ts:19,23 `state.triggerCollector`)→ `state.eventExecutor`(或直接访问 `state.eventStore`
- `triggers.trigger(type, handler)` → 委托执行器,`type`(旧 `TriggerType.Enter/Leave/Hit/CannotEnter`)→ `EventTrigger.OnEnter/OnLeave/OnTouch`映射待用户确认RESEARCH A4
**委托样板**moverImpl.ts:166-184
```typescript
async enter(handler: IHeroMoveTopHandler): Promise<void> {
const { x, y } = handler.nextLoc;
return this.commonTrigger(TriggerType.Enter, handler, x, y); // → OnEnter
}
async hit(handler: IHeroMoveTopHandler): Promise<void> {
const { x, y } = handler.nextLoc;
return this.commonTrigger(TriggerType.Hit, handler, x, y); // → OnTouch待确认
}
```
---
### 17/18. 删除 `data-system/src/trigger/*``data-state/src/content/triggers.ts`
纯代码删除D-13。`content/triggers.ts` 的 `ChangeFloorTrigger`(楼层切换)语义改由 `OnBeforeChangeFloor`/`OnAfterChangeFloor` 事件承载,其 `getFloorTarget`/`getPosTarget` 逻辑content/triggers.ts:47-134可作为新「切换楼层」内建函数builtin.ts的实现参照而非直接丢弃。
---
## Shared Patterns
### 分层与依赖方向(本项目核心架构)
**Source:** `core.ts` 的 L0/L1/L2/L3 分区注释core.ts:82-103+ `data-base/types.ts`/`data-system/types.ts`
- **L0 `@user/data-common`**:无依赖的序列化契约与 store`event/types.ts`、`store/eventStore.ts`)。事件类型只依赖 `anon-tokyo`
- **L1 `@user/data-base`**:可存档数据(`ITileBase.events`、`IMapBlockSaveBase.events`、`ILayerEventView``IDataBaseExtended.state: IStateBase`。
- **L2 `@user/data-system`**:游戏逻辑系统(事件执行器),`IStateSystem extends IStateBase`(含 `IStateSystemExtended.state: IStateSystem`)。
- **L3 `@user/data-state`**:装配(`core.ts`)与顶层集成(`moverImpl`)。
依赖**只向下**event 执行器L2依赖 `IStateBase`L1绝不可反向。`check:circular` 是硬门禁。
### 接口先行 + `implements` + `readonly`code.md 规则 14/15/16刚性
**Source:** 全部接口文件(`store/types.ts`、`map/types.ts`、`trigger/types.ts`
**Apply to:** 所有新建/修改文件
```typescript
// 公共方法必须在 interface 定义class 用 implements 实现
// 接口成员一律 readonly对象成员用接口类型不用类类型
export interface IGameEventStore {
addEvent(id: string, event: IReadonlyGameEvent<...>): void; // 接口签名
getEvent<P, E, R>(id: string): IReadonlyGameEvent<P, E, R> | null;
}
// class GameEventStore implements IGameEventStore { ... }
```
### 错误处理:`logger` 数字错误码绝不抛异常中断code.md 规则 12/13
**Source:** `packages/common/src/logger.ts:112-171` + `logger.json`
**Apply to:** 所有文件
```typescript
import { logger } from '@motajs/common';
import { isNil } from 'lodash-es';
// warn可恢复继续运行logger.warn(code, ...params)
// error严重记录但继续logger.error(code, ...params)
// 每个错误码唯一,不得用 0 或复用无关 codeparams 为字符串
// 非空判断:对象 if (!obj);字面量 if (isNil(value)) —— 禁止 if (value === undefined)
```
本阶段新增/复用的事件相关错误码logger.json 已知占用):`132` registry 重复、`135` 缺 registry、`136` 重复优先级、`170` eventStore 重复 id。执行器「event id 未找到」需分配**新** code。
### Map 工具方法 `getOrInsert` / `getOrInsertComputed`(全局 Map 原型扩展)
**Source:** 用法遍布 `mapLayer.ts:82-83,598`、`combat/context.ts:138,159,405`
**Apply to:** 执行器收集/排序、事件视图内部映射
```typescript
const xMap = this.tilePosMap.getOrInsertComputed(y, () => new Map());
const list = blocks.getOrInsert(index, []);
```
### 桶导出 `index.ts`
**Source:** `data-common/src/index.ts`、`data-system/src/index.ts`
**Apply to:** 所有新模块
每个模块目录有 `index.ts``export * from './xxx'`;改动文件导出时同步改 `index.ts`(否则 `check:type` 失败)。
### HooksHookable/HookController
**Source:** `packages/common/src/hook.ts` + `gameMap.ts`/`mapLayer.ts` 的 `forEachHook`
**Apply to:** 若执行器/内建函数需对外通知(对话/开门动画),沿用 `Hookable<H>` + `createController` 模式gameMap.ts:213-217若不需要则不用D-09 长事件直接 await无需挂起状态
### 存读档 `ISaveableContent` + `SaveCompression`
**Source:** `mapLayer.ts:694-810`saveState/loadState 三分支 No/Low/HighCompression
**Apply to:** `staticTile`/`dynamicTile`/`mapLayer` 的 events 存档(事件本体不进存档,只存 id Map
---
## No Analog Found
无。本阶段是「落地已设计接口 + 删除旧体系」的 refactor所有文件的模式都能在代码库中找到同构参照。唯二的**语义待用户确认**项(非「无 analog」而是接口未定如下planner 应标注为待定而非自行拍板code.md 规则 3「歧义提问」
| 文件 | 待定项 | 依据 |
|------|--------|------|
| `data-system/src/event/builtin.ts` | 内建函数清单/命名(对话/开门/道具/战斗的块名与参数结构) | RESEARCH A2 / Open Question 2 |
| `data-base/src/map/mapLayer.ts` (点事件) | 点事件承载字段(`IMapPointRawData` 尚不存在,`IMapRawData.events` 已有但未落地) | RESEARCH A1 / Open Question 1 |
| `data-state/src/hero/moverImpl.ts` | 移动四钩子 → `EventTrigger` 映射(`cannotEnter` 是否有对应、`OnTouch` 与 `OnEnter` 边界) | RESEARCH A4 / Open Question 3 |
## Metadata
**Analog search scope:** `packages-user/{data-common,data-base,data-system,data-state}`, `packages/common`, `node_modules/anon-tokyo/dist/index.d.ts`
**Files scanned:** ~40事件/存储/地图/触发器/战斗/核心装配/移动/日志/dirty-tracker/解释器类型)
**Pattern extraction date:** 2026-09-07

View File

@ -329,7 +329,12 @@ export class GameEventStore implements IGameEventStore {
**Note:** A1/A2/A4 是「接口设计由用户主导」下 AI 无法自行拍板的点,应在计划阶段前由用户确认或明确标注为待定。它们不阻塞「删除旧体系 + 落地已设计接口」的骨架工作,但阻塞 EVT-02 对话/开门的端到端验证。
## Open Questions
## Open Questions (RESOLVED)
> 以下三个开放问题已在规划阶段前由用户决策2026-09-07
> - **A1 已解决**:点事件承载字段已在接口中落地(`IMapRawData.events` 三级结构、`IMapBlockSaveBase.events` 可选、`ITileRawData` 旧 `trigger` 已替换为 `events`)。
> - **A2 已延后**Anon Tokyo 内建函数清单(对话/开门/道具/战斗)延后到「收尾工作」,本阶段不实现,`builtInFunctions` 留空骨架。
> - **A4 已延后**:不处理旧 `ITrigger` 移动四钩子的兼容映射,本阶段直接删除旧体系,不实现 `cannotEnter` 对应触发。
1. **点事件的承载字段A1**
- What we know: D-03 区分点事件(不随图块移动)与图块事件;图块事件已设计在 `ITileBase.events``IMapPointRawData` 目前仅有 `trigger?: number[]``changeFloor`