mirror of
https://github.com/motajs/template.git
synced 2026-09-13 02:08:50 +08:00
docs(01): create phase 1 plans
This commit is contained in:
parent
6f06b68656
commit
41cdd18761
@ -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)
|
||||
|
||||
@ -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)+ 执行器实现 + 删除旧 ITrigger(D-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**: 数据端 L0–L3 接口实现完成,数据层各系统可用并可在 Node 环境独立运行回放验证
|
||||
**Depends on**: Phase 2
|
||||
**Requirements**: DATA-01
|
||||
**Success Criteria** (what must be TRUE):
|
||||
|
||||
1. 用户设计的 L0–L3 数据层接口全部落地,地图/角色/敌人/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
|
||||
|
||||
@ -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%
|
||||
|
||||
253
.planning/phases/01-event/01-01-PLAN.md
Normal file
253
.planning/phases/01-event/01-01-PLAN.md
Normal 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-02),GameEvent.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 读写 events(ReadonlyMap<number,string>)而非 triggers(IMapBlockSaveBase.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.ts(EventTrigger 枚举 + IGameEvent/IReadonlyGameEvent,只读)
|
||||
- packages-user/data-common/src/event/event.ts(GameEvent 现状)
|
||||
- packages-user/data-common/src/store/eventStore.ts(GameEventStore 现状)
|
||||
- packages-user/data-common/src/store/index.ts(桶导出现状:缺 mapStore)
|
||||
- packages-user/data-base/src/map/types.ts(IReadonlyEventView/ILayerEventView/ITileBase/IReadonlyTileBase/IMapLayer.event/getPointEvent/IMapBlockSaveBase.events,只读)
|
||||
- packages-user/data-base/src/map/tile.ts(MapTileBase 现状: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。私有成员必须加 jsDoc(code.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.ts(IMapBlockSaveBase.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.ts(Task 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 3,core.ts/moverImpl/client-modules/tileStore.getTrigger 属跨计划/阶段外残差,不计入本任务失败</fails_when>
|
||||
</verify>
|
||||
<done>StaticTile/DynamicTile 存读档写 `events` Map(dirty 门控),不再引用 `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.ts(IMapLayer.event/getPointEvent/transferToDynamic/transferToStatic 的 keepEvent 参数,只读)
|
||||
- packages-user/data-common/src/store/types.ts(IMapRawData.events 的 Record<z, Record<pos, Record<priority, id>>> 结构,只读)
|
||||
- packages-user/data-base/src/map/mapLayer.ts(Task 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 与执行器装配一并落地。
|
||||
253
.planning/phases/01-event/01-02-PLAN.md
Normal file
253
.planning/phases/01-event/01-02-PLAN.md
Normal 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 实现 IGameEventSystem:executor + store + useStore() 完整装配,不出现只有 { executor } 的残缺装配"
|
||||
- "IStateSystem 不再含 triggerRegistry/triggerCollector,保留已有 eventSystem(IGameEventSystem)成员(D-13)"
|
||||
- "CoreState 实例化 GameEventStore 赋值 this.eventStore,装配完整 eventSystem(含 executor/store/useStore)并赋值 this.eventSystem"
|
||||
- "data-system/src/trigger/ 目录与 data-state/src/content/triggers.ts(ChangeFloorTrigger)已删除"
|
||||
- "@user/data-system 桶导出 event 执行器与事件系统(export * from './event')"
|
||||
- "logger.json 新增错误码 171 文案,执行器取回未知事件 id 时 logger.warn(171, id) 而非 throw(D-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 + GameEventSystem(implement 已有接口,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.ts(EventExecuteMode/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.ts(IReadonlyGameEvent.execute(param, env): Promise<R>,只读)
|
||||
- packages-user/data-common/src/store/types.ts(IGameEventStore.getEvent,只读)
|
||||
- packages-user/data-base/src/map/types.ts(IBlockEventParam/IBlockEventEnv/BlockEventType,只读)
|
||||
- packages-user/data-base/src/types.ts(IStateBase/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 禁用 getter,mode/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 2,moverImpl 引用旧 trigger、client-modules、tileStore.getTrigger 属跨计划/阶段外残差,不计入本任务失败</fails_when>
|
||||
</verify>
|
||||
<done>EventExecutor 实现 IGameEventExecutor(execute 返回 Promise<R>,await 每个事件并按 mode 合并返回值,mode 为普通字段由 setMode 赋值),GameEventSystem 实现 IGameEventSystem(executor+store+useStore,store 为普通字段由 useStore 赋值),`@user/data-system` 桶导出 event,错误码 171 文案可用。</done>
|
||||
</task>
|
||||
|
||||
<task type="auto">
|
||||
<name>删除旧 ITrigger 体系 + CoreState 装配完整 eventSystem(executor+store+useStore)(D-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.ts(IStateSystem 现状:triggerRegistry/triggerCollector + eventSystem)
|
||||
- packages-user/data-system/src/index.ts(Task 1 已切 trigger→event)
|
||||
- packages-user/data-system/src/event/types.ts 与 event/system.ts、event/executor.ts(Task 1 产出)
|
||||
- packages-user/data-state/src/core.ts(现状 L0/L2 初始化区)
|
||||
- packages-user/data-common/src/store/eventStore.ts(GameEventStore,只读)
|
||||
- packages-user/data-common/src/types.ts(IDataCommon.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,实现 IGameEventExecutor;execute 返回 Promise<R>,mode 为普通字段由 setMode 赋值)
|
||||
- `data-system/src/event/system.ts`(GameEventSystem,实现 IGameEventSystem;store 为普通字段由 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 中找到)——文案在本计划落地。
|
||||
195
.planning/phases/01-event/01-03-PLAN.md
Normal file
195
.planning/phases/01-event/01-03-PLAN.md
Normal 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.raw,commonTrigger 用 collector)
|
||||
- packages-user/data-base/src/hero/types.ts(IHeroMoveTopImpl 的 enter/leave/hit/cannotEnter 契约,只读)
|
||||
- packages-user/data-base/src/map/types.ts(ILayerLocation.static.raw(),只读)
|
||||
- packages-user/data-common/src/event/types.ts(EventTrigger 枚举:OnEnter/OnLeave/OnTouch 注释,只读)
|
||||
- packages-user/data-system/src/event/types.ts(IGameEventExecutor.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 已落地文案。
|
||||
440
.planning/phases/01-event/01-PATTERNS.md
Normal file
440
.planning/phases/01-event/01-PATTERNS.md
Normal 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 初始化 region,core.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/cannotEnter,moverImpl.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 或复用无关 code;params 为字符串
|
||||
|
||||
// 非空判断:对象 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` 失败)。
|
||||
|
||||
### Hooks(Hookable/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
|
||||
@ -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`。
|
||||
|
||||
Loading…
Reference in New Issue
Block a user