feat(event): complete phase one event system

This commit is contained in:
unanmed 2026-09-09 12:01:21 +08:00
parent b9213ddf22
commit a297311ea6
51 changed files with 3261 additions and 1114 deletions

View File

@ -1,196 +0,0 @@
# 核心定位
你的身份不是架构设计者,而是辅助我开发项目与设计系统的起草者。实现功能永远排在最后一名——保证代码结构清晰、便于阅读、尽量减少代码跳转才是最核心的任务。哪怕实现不了功能,也要保证代码结构清晰。你的任务是尽量写出大致正确的代码以减少我的思考负担,而非追求完美。尝试一次性写出让我满意的完美代码往往会适得其反。
该项目的架构已经过大量设计与验证,当前架构是深思熟虑的结果。除非我明确要求,否则不要重新设计架构,也不要主动优化已有设计。你的职责是在保持整体风格与架构稳定的前提下,完成需求并自然扩展已有系统。
在开始前务必认真阅读此文档及[项目规范文档](../dev.md)。
# 规则优先级
本文为 AI Agent 专用提示词,我有时可以不遵守,你**必须**遵守。
当多种规则来源产生冲突时,按以下优先级裁定:
1. **已有代码 > 本文**:当前文件中的已有实现是最高优先级规范。当 prompt、已有代码、自身知识冲突时优先遵循已有代码。
2. **不确定时保守**:如果两种方案都能实现需求,默认选择修改最少、风险最低、最接近已有实现的方案。
3. **本文 > dev.md**`dev.md` 中的规则在本文中强度升级:
| dev.md 原文级别 | 本文对应级别 |
| ------------------------- | ------------ |
| 「一般不建议 / 尽量避免」 | **绝对禁止** |
| 「建议 / 最好」 | **必须遵守** |
典型升级项——**绝对禁止**`as` 关键字、`@ts-expect-error`、`getter` 和 `setter`、三元运算符不允许换行。
# 代码规则
## 修改原则
1. **最小修改**:默认不重新设计已有代码,不重构已有实现,不为「更优雅」而改变已有模式。新代码应是原有代码的自然延续。若认为已有代码存在错误,在对话中提出,不要直接修改。
2. **文件边界**:修改文件前务必重读以确保内容最新。修改后必须在对话中说明所有改动的文件及具体内容。不得修改文档「涉及文件」节未提及的文件。所有公共方法必须在文档中提及,不允许在代码中新增未在文档中写明的公共方法。
3. **歧义提问**:遇到任何模糊、不清晰、歧义的地方,立即向我提问,不要自行假设。若完成需求所需的接口尚不存在,立即停止实现并提出疑问,不要擅自新增接口。
## 代码组织
4. **功能聚合**:以「连续阅读一个功能所需的认知切换最少」为目标组织代码。相似功能放在一起,不以修饰符类型分隔。长文件或功能较多的类使用 `#region` 分区,按方法功能划分。
5. **维护成本优先**:允许局部重复、允许多几个变量与 if 分支。绝不鼓励:为减少行数增加抽象、为减少重复增加函数跳转、为追求「现代 TS」使用复杂语法。
6. **文件分配**:一个文件只能包含一个类。若存在多个同类型简单实现类(均 < 50 行且 implements 同一接口必须先向我确认同意后方可放在同一文件
7. **条件语句**:对于同一个等级的条件,如果满足与不满足都需要执行某些代码,那么必须写完成的 `if - else`,不得在 `if` 里面通过 `return` 提前返回来达成 `else` 效果。
## 注释
8. **私有成员必须注释**:所有私有方法与私有成员必须添加 jsDoc 注释,私有方法的参数必须添加注释说明。构造函数的属性声明参数除外。
9. **公共注释唯一**:受保护与公共成员、方法必须在**源头处**添加注释(多数情况为 `interface`)。继承而来的成员,除非功能或描述有变化,否则不得重复添加注释。
10. **方法注释换行**:方法 jsDoc 必须使用换行风格;成员 jsDoc 简短时可用不换行风格。
## 类型与错误处理
11. **允许类型错误**:编写代码时允许出现 TypeScript 类型错误。不要为了修复类型错误而违反上述规则或增加代码复杂度。尤其不得出现 `as` 关键字。
12. **错误必须抛出**:任何理应报错的场景必须使用 `logger` 接口抛出错误或警告,不得使用 `return` 等方法静默处理。`logger` 必须分配合理的 `code`,不得复用无关 code 或使用 `0`。所有场景下,`logger` 都不会影响系统和游戏的正常运行,不应抛出任何异常中断来终止运行。
13. **非空判断**:对象直接用 `if (!object)`;字面量使用 `lodash``isNil``if (isNil(value))`),不得使用 `if (value === undefined)` 等方式。
14. **公共方法**:不得出现仅在类中定义的公共方法,所有公共方法必须先在接口中定义,然后在类中使用 `implements` 实现。
15. **成员只读**:所有需要被实现为类的接口,其成员必须只读,如果有赋值需求,那么应使用相应的方法来完成赋值操作。
16. **对象类型**:对于任何接口或类的对象成员,必须使用接口作为类型,不得使用类作为类型,例如 `map: IGameMap` 符合要求,但 `map: GameMap` 不符合要求。
## 架构约束
17. **渲染端被动**:任何情况下渲染端不会主动向数据端推送更新。渲染端仅通过钩子与数据端通信,被动获取信息,仅在某些情况下通过钩子影响数据端行为。
# 开发流程
当我提出需求时,若未明确说明直接实现或另有指示,遵循以下流程:
1. 阅读当前代码,分析需求,将需求整理为 markdown 文档放在 `docs/dev` 目录下(含子目录,视情况自行判断)。文档需标注需求细节与代码实现的大体思路。本阶段遇到任何问题应向我提问确认,不得自行假设。
2. 我会针对文档内容提问,你根据设计思路回答。我会指出设计问题,你根据要求更新文档。
3. 我可能对文档做细微调整,实现前请重新仔细阅读最终版本。实现过程中如有问题应向我提问,而非自行决定。
4. 我会粗略阅读你写出的代码并指出明显问题,你需要修改。
5. 我会认真阅读并调整你的代码,形成最终方案。
6. 你需要对比我的最终代码与你自己的代码,总结本次实现中的核心问题,以便后续改进。
# 文档要求
文档不是设计说明书,而是**需求理解报告**。文档的第一目标不是提出最佳方案,而是暴露 Agent 对需求的理解模型,供我人工确认。
在完成接口设计前,必须先明确:
- 需求中明确要求了什么
- 需求中隐含要求了什么
- 当前设计依赖哪些假设
- 如果假设错误会导致什么设计错误
**禁止直接从需求跳跃到接口设计。** 任何接口设计必须能够追溯到前面的需求事实和逻辑推导。
换言之,文档是一份基于若干假设推导出的一种合理设计方案,重点在于从假设推导出设计的过程,而非设计本身。也就是说,文档本身是一道数学推理题,从公理(设计前提)和定义(核心概念定义)出发,通过逻辑推理(设计思路)得出结论(接口分析),每个环节都依赖于前面的环节,最终得出的接口设计需要从逻辑上使人信服。所有的章节最核心的关键词就是**为什么**,每个章节必须写明**为什么会这样**,如果阅读文档后,我仍然不知道"为什么会设计出这个接口",那么这份文档就是失败的。
同时,任何时候都不能写一个接口能干什么事,这个信息对我来说没有任何价值,有价值的是从需求推导出接口设计的逻辑推理过程,也就是**文档写的是为什么****写是什么没有任何意义**。
**绝对注意**:我发给你的文字并不一定全是需求,可能会有很多内容是我经过思考之后得出的结论,这些内容不应当处理为需求,你应该通过需求建立一个合理的逻辑链来得出我给出的结论。典型的结论性语句就是,需要设计什么什么接口、因为什么什么所以怎么怎么样等等。真正的需求应该可以用几个非常简洁的点总结出来。
# 文档结构
按以下格式编写,其余需求自行组织。
- 我会使用 `>` 引用块在文档中批注,因此**不要在文档中使用引用块**。
- 文档控制在 100-250 行,简洁但包含所有必要信息;不擅自修改示例文档格式。
- 一般不需要流程图;若必须使用 `mermaid`
- 示例文档参考 `docs/dev/template.md`,务必认真阅读后再编写文档。一切行文思路严格按照示例文档走,不要按照自己的想法修改。已编写完成的开发文档可能不符合示例文档要求或内容已过时,不要参考。
- 不得出现大片的 `inline-code`,不是不能写,而是不能大片地出现。像接口名等情况正常使用即可。这一条的目的不是为了不让你用 `inline-code`,而是为了让你不去在文档中简单地罗列一堆接口,接口名、方法名等正常情况完全鼓励使用。
- 关于行数和百分比的约束只是帮助你理解文档中哪些内容是重点,哪些不是,并不需要严格遵循,只要重点正确即可。
```md
# 需求综述
描述清楚需求的内容、动机与目的。
# 需求理解
分析需求,指出你对本次需求的理解。每条使用总分结构,先用一个短语总结,然后解释,总结短语不要加粗。
## 明确需求
从任务描述中可以明确得知的直接需求。
## 隐含需求
任务描述中不包含但对任务本身有重大影响的需求。
## 未确定需求
任务描述中不包含且不属于隐含需求的内容,往往是一些语义模糊的内容。没有则不写此节。
# 设计前提
指出本次设计的前提(如同数学公理),本设计的所有内容都基于此前提进行。每条使用总分结构,先用一个短语总结,然后解释,不要加粗。这一章节应该详细解释每个概念是什么,而不是每个概念会导致什么结果,或者它本身会包含什么行为。
# 核心概念定义
详细描述涉及本次任务的核心概念。我会提供若干个需要解释的概念,如有需要可自行添加。这部分必须使用严谨的逻辑阐述,不能是简单一两句话。使用总分的结构描述,先用一句话给出定义“这个概念是什么”,然后再详细解释。
# 接口设计分析
我可能不会给出完整接口设计,你需要自行分析需求补充设计,并在对话中明确指出哪些接口是你补充的。这一章最核心的就是为什么,必须让我知道你为什么要这么设计接口,接口频率为什么是这么多,预期体量为什么是这么大。
## IExample
### 设计思路
分析需求后编写接口的设计思路。重点是分析需求并说明接口是如何设计出来的,而非简单阐述接口内容。这部分是从设计前提和概念定制推导出接口分析的过程,必须详细分析。写的时候,必须有因有果,由于什么需求,所以要设计哪些接口,不得直接说某个接口可以干什么,不得直接说某个接口可以用于哪些场景。只说某个接口的作用没有任何意义,只有从定义和设计前提推导出设计的这个推导过程有意义,所以不要分析接口可以干什么。对于每个接口,必须写明为什么需要这个接口,“由于什么什么需求,因此需要设计什么什么接口”。当我看完后,必须得让我知道为什么要这么设计接口,如果我看完之后还是不知道为什么,那么这个章节写的就是失败的。
### 接口分析
按接口逐一分析成员与方法的预期使用频率。频率分为高 / 中 / 低,指**用户编写此调用的频率**,而非运行时频率或引擎调用频率。使用频率越高,名称长度宜越短。对于成员,必须写明其类型,对于方法,必须写明其参数及类型。接口分析不要包含继承而来的接口,只分析接口本身包含的内容。必须写明为什么预期频率是这么多,如果我看完之后依然没办法知道为什么,说明这个章节写的是失败的。
- 成员 `property: type`:预期频率**高频**。进行分析。
- 方法 `method(param1: type1, ...)`:预期频率**中频**。进行分析。
### 预期体量
写出预期的代码体量并分析原因。不得只写一句预期多少行,必须详细分析,分条阐述。这里不是要实现思路,而是要写为什么某个功能需要这么多行。预期是对代码量的预估,目的是让我了解到你对某个功能复杂度的理解是否正确,不需要非常精确。预期体量是对实现复杂度的预期,而不是对接口定义的预期,因此不要写接口定义预期多少行。当我读完这个章节后,我必须得理解你为什么会认为某个功能需要这么多行,如果不行,说明这个章节写的是失败的。
- 功能一预期 100 行:进行分析。
- 功能二预期 50 行:进行分析。
- 方法 `method` 预期 20 行:进行分析。
---
以上内容应占据文档的 60% 以上。以上内容中不要写任何关于私有方法或私有成员的内容,以上是设计层面的,是暴露给使用者的接口,私有方法和成员不属于设计层面,所以不应该出现。
---
# 实现思路
对于复杂逻辑,分条描述实现思路。简单实现不要写入本节。
## 复杂需求
简述某个复杂需求的实现思路,分条写,写成有序列表。
# 涉及文件
## `@user/package/[folder/]file.ts`
除非必要或我明确提出,一般不建议擅自新增公共方法或成员,必要时可向我提问。不需要写得过细,涵盖重要信息即可。不要出现大段的 `inline-code`(不是不能写,是不要成片地写,该用的时候就应该用,比如接口分析时分条,某些接口名称等等)。描述不要具体到变量或成员名称等,用简短的一两句话来描述即可(针对冒号后面的内容)。写的时候不要把多个接口或成员写到一个条目里面,分开写。
- [ ] 新增 `IInterface` 接口:描述新增动机与目的、用途
- [ ] 新增 `Type` 类型别名:描述新增动机与目的、用途
- [ ] 编写 `Class.method` 方法:描述实现的大体内容
- [ ] 修改 `Class.method` 方法中的部分内容:描述修改内容与目的
### `@motajs/package/[folder/]file.ts`
...
# 待确认问题
如果描述中有歧义或模糊之处,在此列出,此处只允许列出设计相关的问题,不允许列出实现相关的问题。提问必须是以下类型之一:
- **未定义概念**:我没有明确说明某一个名词具体指什么
- **规则冲突**:我的描述中前后产生矛盾
- **语义模糊**:某一个概念或规则可以有多种不同解释方法
- **设计偏好**:对于某个需求可以推导出多种不同设计方案,需要我决定
不允许出现以下低价值问题:
- **风格偏好**:如命名是否合理、`logger` 的文字描述是否合理等
```

View File

@ -1,154 +0,0 @@
# 核心职责
你的核心职责是帮助我编写面向用户的使用文档及 API 文档。文档会有不同的分级,你应该根据需求选用不同分级的口吻来编写文档。
# 文档编写流程
1. 我给出需要编写文档的需求或接口,你需要明确属于使用文档还是接口文档,并明确文档分级。
2. 你根据要求编写文档,我会阅读文档并指出问题,你需要进行修改。多轮循环后形成基本完善的文档。
3. 我会让另一个 Agent 去阅读你的文档,我会根据其反馈来要求你进一步完善文档,形成最终文档。
# 文档分级
本项目是一个游戏引擎,允许游戏作者(用户)使用此引擎完成一类二维网格地图 RPG 的游戏制作,哪怕不会写代码,也可以使用引擎制作出包含部分简单自定义功能的游戏。为此,项目使用文档会根据难度分为几个等级:
- 入门级文档:不需要写代码就可以完成的功能,主要是使用可视化编辑器完成的。行文应针对完全不懂代码的用户,使用最通俗的语言讲解。
- 初级文档:包含使用可视化编辑器可以完成的较为复杂的功能,以及需要简单的代码来完成的功能。行文应针对基本不懂代码的用户,使用通俗的语言讲解。
- 中级文档:需要一定量的代码才可以完成的功能,一般是深度自定义或是复杂逻辑。行文应针对基本懂代码的用户,可以使用一些专业用语,但不应写出理解难度过大的内容。
- 高级文档:需要对引擎有一定的理解才可以完成的功能,一般是针对引擎底层架构相关的功能。行文应针对精通代码的用户,使用专业口吻行文。
# 项目使用文档
项目使用文档的目的是教会游戏作者使用引擎的某种功能来实现自己的需求,这些文档需要以一个实例来进行编写(我一般会给出实例)。文档中需要先提出功能的实现思路,然后给出引擎提供的接口,并对其进行讲解,最后给出代码实例,并讲解为什么要如此写代码,以及为什么这么写代码可以实现想要的功能。同时,还需要提供实例的部分常见变体,比如对于增加玩家攻击的需求,还需要同时给出增加防御、增加血量等需求的实例,以及增加不同值的实例,以保证用户可以理解代码的功能。
目前的引擎针对大多数场景需求都有优化,这些需求都可以总结成一个固定的模板,务必在文档的中部或结尾给出模板,同时也要在这一等级文档的 `templates.md` 中添加此模板,并链接到对应的文档。
文档应讲明白这个需求为什么可以这么实现,但不应涉及为什么引擎如此设计接口,关于接口设计的内容属于引擎开发的问题,不应暴露给用户。也不要讲解接口的具体实现逻辑,只需要讲解接口的功能。
此类文档的结构相对自由,但大致应遵循如下结构:
```md
# 文档标题,一般是对需求的描述
此处讲解需求本身,并明确给出本文会讲解到的内容。
# 接口讲解
此处讲解完成需求所使用到的接口,先列出,再讲解。具体章节分配由你决定。
# 功能模板(可选)
此处给出完成功能所使用的模板代码,如果无法总结成模板就不要写此章节。
# 功能实现
此处先给出代码,然后讲解为什么要这么写,对于不同等级的文档,使用不同的口吻进行讲解,确保对应水平的用户可以理解代码内容。
如果是使用可视化编辑器完成的功能,只需要一步步讲解操作即可,不需要先给出操作再解释为什么。
# 拓展
此处给出一些相关的文档,方便用户快速查询相似需求。
```
# 接口文档
接口文档不再进行分级,其主要内容是讲解引擎接口的作用,对于简单的接口应使用更通俗的口吻,对于复杂的接口应使用更专业的口吻。
接口文档的目的是讲明白一个接口可以干什么,其参数是什么、返回值是什么、可能产生哪些异常,并让用户了解这个接口该怎么用。
接口文档有明确的格式要求,务必遵守。下面是文档结构要求,你可以阅读附近的接口文档来了解更详细的行文思路。
## 类接口文档
````md
# 类 Xxx
在这里概述此类。务必写明此类属于渲染端还是数据端。
这里还需写明明确的继承关系,使用 mermaid 图描述:
**类继承关系**
```mermaid
graph LR
Class -> BaseClass1 -> BaseClass2
click BaseClass1 "rel path"
click BaseClass2 "rel path"
```
# 成员
列出表格讲述类的成员,表格第一列为成员名,第二列为类型,第三列为描述。注意,讲解时以类所实现的接口为准,类中包含但接口中没有的不讲解,修饰符也以接口为准。
# 方法
## `方法名()`
对于构造器,使用 `constructor()` 作为标题,对于方法,使用 `方法名()` 作为标题。方法与成员一样,也以接口为准,不以类本身为准。
```ts
function method(...params): ReturnType;
```
先给出方法的类型定义(如上),然后描述这个方法的功能,然后在这里讲解参数,使用无序列表的方式,不要使用表格。然后讲解返回值与异常:
**方法参数**
- `param1`: 参数描述
- `param2`: 参数描述
**方法返回值(可选)**
描述方法返回值
**方法异常(可选)**
- `警告 Code`: 描述原因及可能的解决方案
- `警告 Code`: 描述原因及可能的解决方案
# 应用实例
## 实例 1
使用一个短语作为章节名称。这里给出代码,然后讲解代码作用,代码中应给出合理的注释。不需要讲解地很细,但是必须得让人能理解。不宜给出过多实例,最多给出三个,对于不常见的类可以不给出实例。
````
## 函数接口文档
目前的引擎设计中直接暴露的函数较少,因此将它们都放到一个文档里面。
````md
# 函数
简单讲述本文中涉及的函数及其大致功能。
# 函数列表
## `函数名()`
```ts
function method(...params): ReturnType;
```
先给出函数的类型定义(如上),然后描述这个函数的功能,然后在这里讲解参数,使用无序列表的方式,不要使用表格。然后讲解返回值与异常:
**函数参数**
- `param1`: 参数描述
- `param2`: 参数描述
**函数返回值(可选)**
描述函数返回值。
**函数异常(可选)**
- `警告 Code`: 描述原因及可能的解决方案
- `警告 Code`: 描述原因及可能的解决方案
**使用示例(可选)**
给出使用示例代码,并描述。不建议过长,但应该讲清楚。
````

View File

@ -1,46 +0,0 @@
# 核心职责
你的职责不是辅助我进行架构设计,也不是进行系统代码编写,而是对我写出的代码进行 review以及测试用例的编写工作。
# Code Review
在 code review 期间,你需要阅读我写出的代码,以及我给出的需求说明,提出我的代码中可能不合理的部分,我会针对这些问题给出回答,或修改相应的代码,执行几轮循环。期间不得修改我写出的代码。
在 review 期间,务必留意 @dev.md 中的代码规范要求,如果出现了某些不合规范的地方,需要在对话中提出,尤其注意 `as` 关键字、方法排序等要求,重点关注写有“不建议”、“建议”、“最好”、“尽量”等字样的规则。
# 测试用例
在我明确要求你进行测试用例的编写前,不得擅自推进到测试用例的编写阶段。
## 测试编写流程
1. 我提出需要编写测试的功能。
2. 你需要分析功能,并自行提出合理的测试用例,形成文档,文档要求见后文。
3. 我会根据文档评判你的测试用例是否合理,并提出需要改进的地方,经过几轮循环后形成最终方案。
4. 根据文档内容编写测试用例,此时**不得**擅自运行测试指令,由我自己运行。
5. 我会根据测试结果来判断系统可能存在的问题,对于显而易见的问题我会自行解决,对于复杂的问题我可能会交给你寻找原因。
## 文档要求
文档最核心的目的是让我知道你为什么会提出这一测试用例。文档放在 `docs/test/` 目录下,有时会放到其子文件夹下。示例文档参考 `docs/test/template.md`
注意,测试用例不应仅仅包含合理输入的结果,还应考虑非法输入或是可能产生异常的结果,这时应该主要测试系统是否会正确抛出异常,或是给出合理的 `logger` 输出。
文档结构要求:
```md
# 测试目的
测试 XXX 系统的基本功能及异常处理。
# 测试用例
## 测试用例 1
- 设计目的:这里描述设计这一测试用例的目的,不得描述这个测试用例是干什么的,必须描述这个测试用例是怎么来的,为什么需要这一测试用例。
- 针对接口:列举这一用例主要针对的接口,不要把所有的接口都写上去,只写最重要的若干个接口,最好在五个以内。
### 测试内容
描述这一测试用例的内容,并写出本测试用例的预期结果。
```

View File

@ -0,0 +1 @@
{"isolation":"none","harness_flag":null,"phase":"01","plan":null,"written_at":1788870777825}

View File

@ -41,14 +41,14 @@
- **技术栈**TypeScript 6 + Vue 3 + 自研 WebGL2 渲染器 + Vite 7 + pnpm 10 monorepo数据端独立打包为 IIFE 供 Node 回放验证。
- **重构背景**:从旧 mota-js 运行时(`public/`)逐步重构,通过 `Patch` 桥接 legacy 全局变量。渲染端先完成重构,数据端接口设计中。
- **双端约束**:数据端无 DOM渲染相关代码必须用 `r()`/`rf()` 门控或走 `hook` 事件,渲染端被动、不向数据端推送更新。
- **协作模型**:接口/架构设计由用户主导AI 负责接口实现与单元测试;所有 git 提交必须经用户 review不得擅自提交
- **协作模型**:接口/架构设计由用户主导AI 负责接口实现与单元测试;AI 可在验证通过后自行创建 git commit无需用户逐次审批
## Constraints
- **技术栈**TypeScript + Vue 3 + WebGL2 + Vite + pnpm固定重构延续
- **协作分工**AI 不做接口设计,仅做实现与测试
- **代码质量**strict 模式、禁用 `as` 断言、logger 数字错误码、禁止循环依赖与模块顶层副作用
- **Git**提交须经 reviewAI 不擅自提交
- **Git**实现通过验证后可由 AI 自行创建 git commit验证未通过不得提交
## Key Decisions
@ -58,7 +58,7 @@
| 渲染层先于数据层完成重构 | 从旧引擎逐步重构的既定顺序 | — Pending |
| 事件系统采用 blockly 式低代码,仅覆盖简单场景 | 面向初学者,避免过度设计 | — Pending |
| 引擎含编辑器,但编辑器在独立项目 | 职责边界清晰 | — Pending |
| Git 提交必须经用户 review | 保证代码质量在线 | — Pending |
| AI 可在验证通过后自行创建 git commit | 以自动化验证替代逐次审批,降低碎片化提交成本 | — Pending |
## Evolution

View File

@ -13,7 +13,7 @@
Decimal phases appear between their surrounding integers in numeric order.
- [ ] **Phase 1: 事件系统** - blockly 式低代码事件定义,驱动简单场景事件流程
- [ ] **Phase 1: 事件系统** - blockly 式低代码事件定义,驱动简单场景事件流程(验证已通过,待完成阶段收尾)
- [ ] **Phase 2: 寻路系统** - 自动寻路与移动端点击地图触发移动
- [ ] **Phase 3: 数据端完成** - 数据端 L0L3 接口全部落地,可在 Node 环境独立跑回放验证
- [ ] **Phase 4: 渲染适配与双布局** - 新数据层 ↔ 已重构渲染端对接,支持移动端与桌面端双布局
@ -145,7 +145,7 @@ Phases execute in numeric order: 1 → 2 → 3 → 4 → 5 → 6
| Phase | Plans Complete | Status | Completed |
|-------|----------------|--------|-----------|
| 1. 事件系统 | 12/12 | In Progress| |
| 1. 事件系统 | 12/12 | Ready to complete | |
| 2. 寻路系统 | 0/TBD | Not started | - |
| 3. 数据端完成 | 0/TBD | Not started | - |
| 4. 渲染适配与双布局 | 0/TBD | Not started | - |

View File

@ -4,11 +4,11 @@ milestone: v1.0
current_phase: 01
current_phase_name: event
status: executing
stopped_at: Completed 01-12-PLAN.md
last_updated: "2026-09-09T03:36:51.958Z"
stopped_at: Phase 01 verification passed; ready for phase completion
last_updated: "2026-09-09T03:53:57.000Z"
last_activity: 2026-09-09
last_activity_desc: "Plan 01-12 closed phase-owned type and CRLF/Prettier gaps; unrelated repository type diagnostics remain"
state_head: f423aad84a4a6ea3fa04fc0fac4fb33de1a2e0a4
last_activity_desc: "Phase 01 verification passed: user confirmed compile-cache reuse and setRaw invalidation with no issue reported"
state_head: b9213dd
progress:
total_phases: 6
completed_phases: 0
@ -30,8 +30,8 @@ See: .planning/PROJECT.md (updated 2026-09-07)
Phase: 01 (event) — EXECUTING
Plan: 12 of 12 (gap closure complete)
Status: Plan complete; phase verification pending unrelated repository type diagnostics
Last activity: 2026-09-09 — Plan 01-12 executed; focused type and formatting gaps closed
Status: Phase verification passed; ready for completion transition
Last activity: 2026-09-09 — Phase 01 verification passed; focused tests/type/lint evidence is green in phase scope
Progress: [█████████░] 92%
@ -118,5 +118,5 @@ None yet.
## Session Continuity
Last session: 2026-09-09T03:36:51.913Z
Stopped at: Completed 01-12-PLAN.md
Stopped at: Phase 01 verification passed; ready for completion
Resume file: None

61
.planning/WINDOWS.md Normal file
View File

@ -0,0 +1,61 @@
---
schema_version: 1
open_count: 3
waived_count: 0
fixed_count: 0
total_count: 3
last_updated: 2026-09-08T15:54:23.173Z
---
# Broken Windows Ledger
> Cross-phase defect register. With `workflow.windows_enforce` enabled, `/gsd-ship` blocks while `open_count > 0`.
> Waive with `gsd-tools windows waive <id> "<reason>"` (reason required).
> Mark fixed with `gsd-tools windows fixed <id>`.
| id | phase | kind | file | line | description | status | reason | recorded_at | resolved_at |
|----|-------|------|------|------|-------------|--------|--------|-------------|-------------|
| 1 | 01 | stub | packages-user/data-state/src/core.ts | 153 | Serialized event registration and map-id binding remains an intentional deferred TODO. | open | | 2026-09-08T15:06:40.634Z | |
| 2 | 01 | unrun-verify | .planning/phases/01-event/01-05-SUMMARY.md | | Downstream implementation verification was not run because the user explicitly prohibited downstream plan execution. | open | | 2026-09-08T15:37:54.227Z | |
| 3 | 01 | deviation | packages-user/data-base/src/map/mapLayer.ts | | Replaced unsupported Map upsert runtime calls so the raw map event path runs under Node Vitest. | open | | 2026-09-08T15:54:23.173Z | |
````json
[
{
"id": 1,
"kind": "stub",
"phase": "01",
"file": "packages-user/data-state/src/core.ts",
"line": 153,
"description": "Serialized event registration and map-id binding remains an intentional deferred TODO.",
"status": "open",
"reason": "",
"recorded_at": "2026-09-08T15:06:40.634Z",
"resolved_at": null
},
{
"id": 2,
"kind": "unrun-verify",
"phase": "01",
"file": ".planning/phases/01-event/01-05-SUMMARY.md",
"line": null,
"description": "Downstream implementation verification was not run because the user explicitly prohibited downstream plan execution.",
"status": "open",
"reason": "",
"recorded_at": "2026-09-08T15:37:54.227Z",
"resolved_at": null
},
{
"id": 3,
"kind": "deviation",
"phase": "01",
"file": "packages-user/data-base/src/map/mapLayer.ts",
"line": null,
"description": "Replaced unsupported Map upsert runtime calls so the raw map event path runs under Node Vitest.",
"status": "open",
"reason": "",
"recorded_at": "2026-09-08T15:54:23.173Z",
"resolved_at": null
}
]
````

View File

@ -51,7 +51,8 @@
"security_block_on": "high",
"tdd_mode": false,
"ui_review": true,
"use_worktrees": true
"use_worktrees": true,
"_auto_chain_active": false
},
"ship": {
"pr_body_sections": [

View File

@ -0,0 +1,173 @@
---
phase: 01-event
plan: 01
subsystem: event-data
tags: [typescript, events, map, save-state, dirty-tracking]
requires: []
provides:
- GameEvent compile-result caching and map-store barrel export
- Priority-to-event-id views for tiles and map points
- Event-aware tile save/load and raw-map assembly
affects: [01-02, 01-03, event-executor, hero-movement]
actuals:
tokens: 4200
tasks: 3
commits: 0
plan_head_before: 6f6ed62b1b401a8e1d51207ba7b08c8e7935dfb2
tech-stack:
added: []
patterns: [LayerEventView snapshots, priority-to-event-id maps, dirty-gated event saves]
key-files:
created:
- packages-user/data-base/src/map/eventView.ts
modified:
- packages-user/data-common/src/event/event.ts
- packages-user/data-common/src/store/index.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
key-decisions:
- "Preserved the user-authored interfaces and implemented only their existing public methods."
- "Raw tile events are marked pure after assembly so source data is the dirty-tracking baseline."
patterns-established:
- "Event bindings use ReadonlyMap<number, string> views backed by LayerEventView."
- "Static and dynamic conversion copies event ids by priority without moving point events."
requirements-completed: [EVT-01, EVT-03]
coverage:
- id: D1
description: "GameEvent compilation is cached and MapStore is available from the data-common barrel."
requirement: EVT-01
verification:
- kind: other
ref: "pnpm check:type filtered to event/event.ts and store/index.ts"
status: pass
human_judgment: false
- id: D2
description: "Tiles and points expose priority-based LayerEventView bindings."
requirement: EVT-01
verification:
- kind: other
ref: "pnpm check:type filtered to eventView.ts, map/tile.ts, and map/mapLayer.ts"
status: pass
human_judgment: false
- id: D3
description: "Tile save/load, conversions, and raw-map assembly use event-id maps instead of triggers."
requirement: EVT-03
verification:
- kind: other
ref: "pnpm check:type filtered to staticTile.ts, dynamicTile.ts, mapLayer.ts, and mapState.ts"
status: pass
human_judgment: false
duration: 12min
completed: 2026-09-08
status: complete
---
# Phase 01 Plan 01: Event Data Foundation Summary
**Cached event compilation plus priority-based tile and point event views with dirty-aware save/load integration**
## Performance
- **Duration:** 12 min
- **Started:** 2026-09-08T12:32:57Z
- **Completed:** 2026-09-08T12:44:23Z
- **Tasks:** 3
- **Implementation files changed:** 8
## Accomplishments
- Cached `GameEvent.compile()` output and exposed `MapStore` through the existing store barrel.
- Added `LayerEventView`, then connected tile and point event access through `MapTileBase` and `MapLayer`.
- Migrated static/dynamic tile persistence and conversion from triggers to priority-indexed event ids.
- Loaded validated `raw.events` entries into static tile event views and established them as the clean baseline.
## Task Commits
| Task | Result | Commit |
| --- | --- | --- |
| 1. Event definition through tile/point binding | Complete | Intentionally skipped — project policy requires user review before commits |
| 2. Static/dynamic tile event save/load | Complete | Intentionally skipped — project policy requires user review before commits |
| 3. Layer conversion and raw event assembly | Complete | Intentionally skipped — project policy requires user review before commits |
| Plan metadata | Summary created | Intentionally skipped — project policy requires user review before commits |
All implementation and planning changes remain uncommitted for user review. `STATE.md` and `ROADMAP.md` were not updated by this executor.
## Files Created/Modified
- `packages-user/data-base/src/map/eventView.ts` — Implements event CRUD, duplicate-priority warnings, and snapshot-based dirty checks.
- `packages-user/data-common/src/event/event.ts` — Writes compiled executables back to the existing cache member.
- `packages-user/data-common/src/store/index.ts` — Exports the existing map store.
- `packages-user/data-base/src/map/tile.ts` — Replaces trigger storage with tile and point event views.
- `packages-user/data-base/src/map/staticTile.ts` — Saves and loads dirty event maps.
- `packages-user/data-base/src/map/dynamicTile.ts` — Saves and loads dirty event maps alongside the tile number.
- `packages-user/data-base/src/map/mapLayer.ts` — Stores point views and copies tile events during static/dynamic conversion.
- `packages-user/data-base/src/map/mapState.ts` — Reads `raw.events`, rejects nonnumeric keys, and marks assembled views pure.
## Verification
- **Task 1 focused type check:** Initially exposed planned trigger references remaining in `mapLayer.ts`; passed with no matching errors after Task 3 completed the same-file migration.
- **Task 2 focused type check:** Passed with no matching errors.
- **Task 3 focused type check:** Passed with no matching errors.
- **Combined focused type check:** Passed with no matching errors across all eight implementation files.
- **Focused ESLint/Prettier:** Passed across all eight implementation files.
- **Full `pnpm check:type`:** Ran and remains red only in pre-existing/out-of-plan files, including client modules, `tileStore.ts`, data-state trigger/core/mover code, legacy integrations, and the old trigger collector. No Plan 01-01 file was reported.
- **`git diff --check`:** Passed; only existing line-ending notices for user-owned planning files were printed.
## Deviations from Plan
### Auto-fixed Issues
**1. [Rule 1 - Type correctness] Constructed save objects with readonly event fields**
- **Found during:** Task 2 verification
- **Issue:** Assigning `events` after object creation violated the user-authored readonly save interfaces.
- **Fix:** Constructed complete interface-typed object literals in explicit dirty/clean branches.
- **Files modified:** `staticTile.ts`, `dynamicTile.ts`
- **Verification:** Focused type check and ESLint passed.
- **Commit:** Intentionally skipped per user review policy.
**2. [Rule 2 - Dirty baseline correctness] Marked raw events pure after assembly**
- **Found during:** Task 3 implementation
- **Issue:** Events loaded from raw map definitions would otherwise appear as runtime modifications and be redundantly saved.
- **Fix:** Called `markPure()` after each location's raw event map was assembled.
- **Files modified:** `mapState.ts`
- **Verification:** Focused type check passed.
- **Commit:** Intentionally skipped per user review policy.
**Total deviations:** 2 auto-fixed (1 bug, 1 missing correctness behavior). No scope expansion or public-interface changes.
## Issues Encountered
- Task 1's first focused check reported old trigger references in `mapLayer.ts`; those lines were already assigned to Task 3 and the tracer check passed after that migration.
- `apply_patch` emitted LF for changed regions; the existing focused ESLint fixer normalized implementation files back to required CRLF and verified formatting.
- Full-project type checking remains blocked by explicitly deferred phase work outside Plan 01-01. Focused plan checks are green.
## Known Stubs
None found in files created or modified by this plan.
## Blockers
None for Plan 01-01. Deferred full-project errors remain assigned to later plans or phases.
## User Setup Required
None.
## Next Phase Readiness
- Plan 01-02 can build the event executor on the implemented event store and map event views.
- Plan 01-03 still needs to remove old trigger integrations and close the full-project type-check residuals.
## Self-Check: PASSED
- All eight implementation files and this summary exist.
- Focused type and lint checks pass.
- Git HEAD remains `6f6ed62b1b401a8e1d51207ba7b08c8e7935dfb2`; no commits were created.
- Existing user-authored working-tree changes remain present, and no state or roadmap update command was run.
---
*Phase: 01-event*
*Completed: 2026-09-08*

View File

@ -0,0 +1,188 @@
---
phase: 01-event
plan: 02
subsystem: event-execution
tags: [typescript, events, anon-tokyo, dependency-injection, legacy-removal]
requires:
- phase: 01-event/01-01
provides: GameEventStore, map event views, and event-aware map persistence
provides:
- EventExecutor with sequential async execution, cut modes, and return reduction
- GameEventSystem with replaceable store wiring
- CoreState event store and complete event system assembly
- Removal of the legacy ITrigger implementation
affects: [01-03, hero-movement, event-builtins]
actuals:
tokens: 7017
tasks: 2
commits: 0
plan_head_before: 6f6ed62b1b401a8e1d51207ba7b08c8e7935dfb2
tech-stack:
added: []
patterns:
[
lazy event-store reference,
interface-led execution modes,
complete system assembly
]
key-files:
created:
- packages-user/data-system/src/event/executor.ts
- packages-user/data-system/src/event/system.ts
modified:
- 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
key-decisions:
- "Implemented the current user-authored EventExecuteMode and EventReduceMode interfaces instead of the plan's stale combined-mode assumptions."
- 'Added warning code 172 because the existing reduction interface explicitly requires warnings for non-boolean values.'
patterns-established:
- 'EventExecutor resolves every id through a lazy store callback so GameEventSystem.useStore takes effect immediately.'
- 'CoreState assembles GameEventStore before constructing the complete GameEventSystem.'
requirements-completed: [EVT-01, EVT-02, EVT-03]
coverage:
- id: D1
description: 'EventExecutor and GameEventSystem implement the existing async execution, cut, reduction, and store-replacement contracts.'
requirement: EVT-02
verification:
- kind: other
ref: 'pnpm check:type filtered to data-system event files; focused ESLint/Prettier'
status: pass
human_judgment: false
- id: D2
description: 'CoreState owns an event store and complete event system while the legacy trigger implementation is removed.'
requirement: EVT-03
verification:
- kind: other
ref: 'legacy-symbol/path checks plus focused ESLint'
status: pass
human_judgment: false
duration: 11min
completed: 2026-09-08
status: complete
---
# Phase 01 Plan 02: Event Executor and System Assembly Summary
**Sequential async event execution with independent cut/reduction modes, replaceable store wiring, CoreState assembly, and legacy trigger removal**
## Performance
- **Duration:** 11 min
- **Started:** 2026-09-08T12:50:04Z
- **Completed:** 2026-09-08T13:00:48Z
- **Tasks:** 2
- **Implementation files created/modified/deleted:** 14
## Accomplishments
- Implemented `EventExecutor` against the current user-authored interface, including sequential `await`, missing-id recovery, cut modes, and independent return reduction.
- Implemented `GameEventSystem` with a complete executor/store/useStore assembly and a lazy store reference.
- Wired `GameEventStore` and `GameEventSystem` into `CoreState`, then removed trigger members from `IStateSystem`.
- Replaced the data-system trigger barrel with the event barrel and deleted all seven explicitly listed legacy files.
## Task Commits
| Task | Result | Commit |
| ------------------------------------------------------------ | --------------- | -------------------------------------------------------------------------- |
| 1. EventExecutor, GameEventSystem, barrels, and logger codes | Complete | Intentionally skipped — project policy requires user review before commits |
| 2. Legacy trigger deletion and CoreState event assembly | Complete | Intentionally skipped — project policy requires user review before commits |
| Plan metadata | Summary created | Intentionally skipped — project policy requires user review before commits |
All Plan 01-02 changes remain uncommitted for user review. `STATE.md` and `ROADMAP.md` were not updated by this executor.
## Files Created/Modified
- `packages-user/data-system/src/event/executor.ts` — Executes stored events sequentially, applies cut/reduction settings, and warns for missing ids or non-boolean reduction values.
- `packages-user/data-system/src/event/system.ts` — Owns the interpreter, executor, current event store, and store replacement wiring.
- `packages-user/data-system/src/event/index.ts` — Exports executor and system implementations alongside existing interfaces.
- `packages-user/data-system/src/index.ts` — Exports the event module instead of the removed trigger module.
- `packages-user/data-system/src/types.ts` — Removes trigger registry/collector members while retaining the user-authored event system member.
- `packages-user/data-state/src/core.ts` — Instantiates the event store and complete event system and removes trigger assembly.
- `packages/common/src/logger.json` — Adds code 171 for unknown event ids and code 172 for non-boolean reduction values while preserving prior user changes.
`packages-user/data-system/src/event/types.ts` was reviewed and preserved unchanged: its user-authored `Promise<R>` return type and independent execute/reduce interfaces were already present.
## 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`
## Verification
- **Task 1 focused type check:** Passed with no matching errors in event types/executor/system/barrels.
- **Task 2 focused type check:** `data-system/src/types.ts` passed. `core.ts` still reports two known `TileStore.getTrigger` incompatibilities originating in out-of-plan store interfaces/implementation.
- **Focused ESLint and Prettier:** Passed for all Plan 01-02 TypeScript and logger files.
- **Legacy removal checks:** The trigger directory and `content/triggers.ts` do not exist; no legacy trigger symbols remain in data-system, `core.ts`, or data-state content.
- **Full `pnpm check:type`:** Ran. It remains red in deferred client, `tileStore.ts`, Plan 01-03 `moverImpl.ts`, legacy integration files, and the two derived `core.ts` TileStore errors. No event executor/system or data-system interface errors were reported.
- **`pnpm check:circular`:** Ran and reported 18 existing/Plan 01-01 dependency cycles; none traverses the new data-system event executor/system files.
- **`git diff --check`:** Passed for implementation changes; only pre-existing line-ending notices for user-owned planning files were printed.
- **Tests:** Not run because the repository currently contains no test files; verification used focused type/lint/static checks as directed.
## Deviations from Plan
### Existing Interface Authority
**1. Implemented the current split execute/reduce contract**
- **Found during:** Task 1 required-file reread.
- **Issue:** The plan assumed stale combined `EventExecuteMode` members, while the user-authored interface defines `Normal`/cut execution modes plus independent `EventReduceMode` and `setReduce`.
- **Resolution:** Preserved the interface and implemented exactly its existing public members; no unapproved public API was added or changed.
- **Commit:** Intentionally skipped per user review policy.
**2. Promise return change was already present**
- **Found during:** Task 1 required-file reread.
- **Issue:** The planned single-line `execute` return change had already been made by the user.
- **Resolution:** Left `event/types.ts` unchanged and implemented `Promise<R>` in the executor.
- **Commit:** Intentionally skipped per user review policy.
### Auto-fixed Issues
**3. [Rule 2 - Interface correctness] Added a reduction warning code**
- **Found during:** Task 1 implementation.
- **Issue:** The existing interface requires a warning when reduction receives a non-boolean value, but the plan allocated only the unknown-id code.
- **Fix:** Added unique warning code 172 and emitted it without changing reduction semantics.
- **Files modified:** `event/executor.ts`, `logger.json`
- **Verification:** Focused type/lint checks passed.
- **Commit:** Intentionally skipped per user review policy.
**Total deviations:** 3 (2 interface-authority adjustments, 1 correctness addition). No scope expansion or unapproved public-interface changes.
## Known Stubs
None found in files created or modified by this plan. Empty interpreter built-ins are intentional and explicitly deferred by the plan.
## Blockers
None for Plan 01-02. Full-project type and circular checks remain blocked by work assigned to Plan 01-03, Plan 01-01 follow-up, or other out-of-plan legacy/client areas.
## User Setup Required
None.
## Next Phase Readiness
- Plan 01-03 can replace `moverImpl.ts` trigger imports and dispatch with event collection/execution.
- The event system is fully assembled and exported; built-in functions remain intentionally deferred to later wrap-up work.
## Self-Check: PASSED
- All seven implementation outputs and this summary exist; all seven planned legacy files are absent.
- Focused event type checks and all focused lint/format checks pass.
- Git HEAD remains `6f6ed62b1b401a8e1d51207ba7b08c8e7935dfb2`; no commits were created.
- Existing Plan 01-01/user working-tree changes remain present, and `ROADMAP.md` has no executor change.
- `STATE.md` and `ROADMAP.md` were not updated by this executor.
---
_Phase: 01-event_
_Completed: 2026-09-08_

View File

@ -0,0 +1,170 @@
---
phase: 01-event
plan: 03
subsystem: hero-event-integration
tags: [typescript, events, hero-movement, static-verification]
requires:
- phase: 01-event/01-01
provides: Priority-based point and tile event views
- phase: 01-event/01-02
provides: Async event executor and CoreState event-system assembly
provides:
- Hero movement dispatch through EventTrigger and IGameEventExecutor
- Point-first, tile-second priority ordering for movement events
- Removal of the final packages-user legacy trigger references
affects: [event-builtins, hero-movement, phase-01-verification]
actuals:
tokens: 1735
tasks: 2
commits: 0
plan_head_before: 6f6ed62b1b401a8e1d51207ba7b08c8e7935dfb2
tech-stack:
added: []
patterns: [point-before-tile event dispatch, priority-descending event ids]
key-files:
created:
- .planning/phases/01-event/01-03-SUMMARY.md
modified:
- packages-user/data-state/src/hero/moverImpl.ts
key-decisions:
- "Preserved current interfaces and supplied IBlockEventEnv.state from the movement handler."
- "Kept cannotEnter as the existing interface's intentionally empty implementation because EventTrigger has no matching value."
patterns-established:
- "Movement hooks collect point ids first and tile ids second, sorting each group by descending priority before one executor call."
requirements-completed: [EVT-02, EVT-03]
coverage:
- id: D1
description: "Hero enter, leave, and hit hooks dispatch OnEnter, OnLeave, and OnTouch events in D-06 order."
requirement: EVT-02
verification:
- kind: other
ref: "pnpm check:type filtered to data-state/src/hero/moverImpl.ts; focused ESLint; static symbol checks"
status: pass
human_judgment: false
- id: D2
description: "Legacy ITrigger symbols are absent from packages-user."
requirement: EVT-03
verification:
- kind: other
ref: "three packages-user legacy-symbol searches"
status: pass
human_judgment: false
duration: 7min
completed: 2026-09-08
status: complete
---
# Phase 01 Plan 03: Hero Movement Event Integration Summary
**Hero movement now gathers point and tile event ids in D-06 order and awaits the shared event executor with OnEnter, OnLeave, and OnTouch context**
## Performance
- **Duration:** 7 min
- **Started:** 2026-09-08T13:04:30Z
- **Completed:** 2026-09-08T13:11:34Z
- **Tasks:** 2
- **Implementation files modified:** 1
- **Commits:** 0 — intentionally skipped because project policy requires user review
## Accomplishments
- Migrated movement passability and hit checks from removed `ILayerLocation.raw` access to `static.raw()` with null-safe defaults.
- Replaced the legacy trigger collector with point-event and tile-event collection, independent descending-priority sorting, and one awaited executor call.
- Mapped enter, leave, and hit to `EventTrigger.OnEnter`, `OnLeave`, and `OnTouch`; retained an empty `cannotEnter` implementation because no matching new trigger exists.
- Confirmed that legacy trigger-system symbols no longer occur anywhere under `packages-user`.
## Task Commits
| Task | Result | Commit |
| --- | --- | --- |
| 1. Rewrite moverImpl event dispatch | Complete | Intentionally skipped — project policy requires user review before commits |
| 2. Run phase legacy/type/circular/lint gates | Complete; repository-wide residuals recorded below | Intentionally skipped — project policy requires user review before commits |
| Plan metadata | Summary created | Intentionally skipped — project policy requires user review before commits |
All Plan 01-03 implementation and summary changes remain uncommitted for user review. This executor did not update `STATE.md` or `ROADMAP.md`.
## Files Created/Modified
- `packages-user/data-state/src/hero/moverImpl.ts` — Uses `static.raw()`, collects and sorts point/tile event ids, builds the current block-event environment, and delegates movement triggers to `IGameEventExecutor`.
- `.planning/phases/01-event/01-03-SUMMARY.md` — Records implementation, gate evidence, inherited residuals, and intentional commit skipping.
## Verification
### Passing Plan-Focused Checks
- `pnpm check:type 2>&1` filtered to `hero/moverImpl.ts`: **pass** — zero matching diagnostics; full command exit code remained 2 because of unrelated files below.
- `pnpm eslint "packages-user/data-state/src/hero/moverImpl.ts"`: **pass**, exit code 0.
- `git diff --check -- "packages-user/data-state/src/hero/moverImpl.ts"`: **pass**, exit code 0.
- Search `TriggerType|ITriggerCollector|ITriggerHandler|triggerCollector` in `moverImpl.ts`: **pass**, no matches.
- Search `(curr|next)\.raw\b` in `moverImpl.ts`: **pass**, no legacy location-member access; the required replacement `static.raw()` remains present.
- Search `EventTrigger.(OnEnter|OnLeave|OnTouch)`, `getPointEvent`, `tileEvent()`, `eventSystem`, and `executor.execute`: **pass**, all required links found.
- Search `\sas\s`, getters, and setters in `moverImpl.ts`: **pass**, no forbidden additions.
### Phase-Level Gates
- Search `ITrigger|TriggerRegistry|TriggerCollector|TriggerCollection|BaseTrigger` under `packages-user`: **pass**, no matches.
- Search `\bTriggerType\b` under `packages-user`: **pass**, no matches.
- Search `triggerCollector|triggerRegistry` under `packages-user`: **pass**, no matches.
- `pnpm check:type`: **repository-wide fail**, exit code 2. `moverImpl.ts` has zero diagnostics. The phase-file filter reports only two inherited `core.ts` diagnostics caused by the deferred `TileStore.getTrigger` interface mismatch; remaining diagnostics are in client modules, legacy movement/integration files, `tileStore.ts`, and `packages/legacy-ui`.
- `pnpm check:circular`: **repository-wide fail**, exit code 1 with 18 cycles. This is the same 18-cycle baseline recorded by Plan 01-02; none traverses `moverImpl.ts` or the data-system event executor/system.
- `pnpm lint:user`: **repository-wide fail**, exit code 1 with 59 problems (51 errors, 8 warnings), all outside `moverImpl.ts`. The focused mover lint passes.
- Vitest discovery (`**/*.{test,spec}.{ts,tsx,js,jsx}`): no files found. Tests were not run; their absence predates this plan and is not a Plan 01-03 regression.
## Deviations from Plan
### Auto-fixed Issues
**1. [Rule 2 - Interface correctness] Supplied the inherited event-environment state**
- **Found during:** Task 1 focused type verification.
- **Issue:** The plan's environment literal omitted `state`, but the current user-authored `IBlockEventEnv` extends `IDataCommonExtended` and requires it.
- **Fix:** Added `state: handler.state` without changing any public interface.
- **Files modified:** `packages-user/data-state/src/hero/moverImpl.ts`
- **Verification:** Focused mover type check and ESLint pass.
- **Commit:** Intentionally skipped per user review policy.
### Plan Check Clarification
- The plan's literal `\.raw\b` no-match check conflicts with its required `static.raw()` migration because that method call necessarily contains `.raw`. Verification therefore checked specifically for removed `curr.raw`/`next.raw` member access and confirmed none remains.
**Total deviations:** 1 auto-fixed interface-correctness omission and 1 verification-pattern clarification. No scope expansion or public-interface change.
## Blockers
- The repository-wide type gate remains blocked by pre-existing client/legacy incompatibilities and the deferred `TileStore.getTrigger` mismatch; no diagnostic points to `moverImpl.ts`.
- The circular-dependency gate remains blocked by the same 18 baseline cycles recorded in Plan 01-02.
- The repository-wide user lint gate remains blocked by 51 errors and 8 warnings in existing client, replay, entry, and legacy-plugin files; focused Plan 01-03 lint is green.
- Built-in dialogue/open-door/item/battle functions remain intentionally deferred outside this phase, so those end-to-end behaviors were not exercised here.
## Known Stubs
None. `sortedIds` is an execution-time accumulator and `param.custom` is the interface-defined empty custom-parameter bag, not placeholder UI/data.
## Issues Encountered
- `apply_patch` emitted LF line endings for the implementation file. `pnpm eslint "packages-user/data-state/src/hero/moverImpl.ts" --fix` normalized it to the required CRLF format before final verification.
- The current environment does not expose `rg`; required symbol checks were completed with the repository search tool instead.
## User Setup Required
None.
## Next Phase Readiness
- The movement-to-event-executor integration and legacy-symbol removal are complete and focused checks are green.
- Repository-wide type, circular, and lint baselines must be resolved in their owning scopes before the full project gates can become green.
- The deferred event built-ins are still required for dialogue/open-door end-to-end verification.
## Self-Check: PASSED
- `packages-user/data-state/src/hero/moverImpl.ts` and this summary exist.
- Focused type, lint, formatting, and legacy-symbol checks pass.
- Git HEAD remains `6f6ed62b1b401a8e1d51207ba7b08c8e7935dfb2`; no commits were created.
- Existing user and prior-plan working-tree changes remain present; no reset, revert, stash, or discard operation was used.
- `STATE.md` and `ROADMAP.md` were not updated by this executor.
---
_Phase: 01-event_
_Completed: 2026-09-08_

View File

@ -0,0 +1,170 @@
---
phase: 01-event
plan: 04
type: execute
wave: 4
depends_on: [01-03]
files_modified: []
autonomous: false
gap_closure: true
requirements: [EVT-01, EVT-02]
estimate:
tokens: 12000
raw_tokens: 12000
tasks: 3
confidence: low
must_haves:
truths:
- "用户明确生产初始化路径中序列化 Statement[]、EventTrigger、事件 id 与地图绑定的权威入口,同时保持 D-07/D-11 与内建函数延期边界"
- "用户明确如何在一次 D-06 顺序执行中为点、静态图块、每个动态图块保留真实 IBlockEventEnv且不破坏 D-10 的 cut/reduce 语义"
- "用户批准点事件的存档字段、dirty 基准与 load/reset/resize 语义,使 D-03/D-12 可实现"
artifacts:
- path: ".planning/phases/01-event/01-04-SUMMARY.md"
provides: "三个用户所有权接口决策的逐字记录"
key_links:
- from: ".planning/phases/01-event/01-04-SUMMARY.md"
to: "01-06/01-07/01-09 gap implementation plans"
via: "downstream plans implement only the exact selected contracts"
---
<objective>
记录并实施验证报告证明必需的三个契约:生产事件初始化入口、来源感知分派、点事件持久化。事件注册能力本阶段明确延期,仅在生产初始化路径保留 TODOD-01 至 D-12 的既有语义不在本计划重新选择。
Purpose: 用户拥有接口与架构决策;当前代码没有足够信息让执行器安全推断这些公共契约。
Output: `01-04-SUMMARY.md` 中记录可直接实施的文件、签名/字段及语义决定。
</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-VERIFICATION.md
@.planning/phases/01-event/01-01-SUMMARY.md
@.planning/phases/01-event/01-02-SUMMARY.md
@.planning/phases/01-event/01-03-SUMMARY.md
@packages-user/data-common/src/store/types.ts
@packages-user/data-system/src/event/types.ts
@packages-user/data-base/src/map/types.ts
@packages-user/data-state/src/core.ts
@packages-user/data-state/src/hero/moverImpl.ts
</context>
<tasks>
<task type="auto">
<name>Task 1: 延期序列化事件注册并保留初始化 TODO</name>
<files>packages-user/data-state/src/core.ts</files>
<read_first>
- packages-user/data-common/src/event/types.tsD-02/D-07 的事件数据契约)
- packages-user/data-common/src/store/types.ts现有 IGameEventStore.addEvent 与 IMapRawData.events
- packages-user/data-system/src/event/types.ts现有 IGameEventInit 只有 addBuiltinFunction
- packages-user/data-state/src/core.tslegacy loaded 回调、eventStore、地图初始化顺序
- .planning/phases/01-event/01-VERIFICATION.md第一组 gap 与 built-ins 明确延期)
</read_first>
<action>保留 `CoreState``GameEventStore` 与事件系统装配不变,不实现外部序列化事件注册或地图 id 绑定。在对应生产初始化路径保留 TODO说明后续需要接入该能力。不得把事件本体写入地图或存档内建函数清单按 ROADMAP/REQUIREMENTS 的明确延期保持在本次 gap closure 之外。</action>
<acceptance_criteria>
- 生产初始化路径包含延期注册能力的 TODO
- 本计划没有新增事件注册公共 API
- 事件存储仍不进入存档D-11
</acceptance_criteria>
<verify>
<automated>if (!(Test-Path "packages-user/data-common/src/store/types.ts") -or !(Test-Path "packages-user/data-state/src/core.ts")) { exit 1 }; if (!(Select-String -Path "packages-user/data-common/src/store/types.ts" -Pattern "addEvent" -Quiet)) { exit 1 }</automated>
<fails_when>权威源文件不存在,或现有 addEvent seam 已在执行前变化,导致决策上下文过期</fails_when>
</verify>
<decision>事件注册能力本阶段延期,仅在 `CoreState` 初始化路径保留 TODO不新增公共契约。</decision>
<context>当前目标是完成系统本身,不实现具体外部事件注册能力;事件注册不进入存档。</context>
<done>延期决定与 TODO 位置记录到 01-04-SUMMARY.md后续计划不得自行设计事件注册 API。</done>
</task>
<task type="auto">
<name>Task 2: 记录已批准的来源感知事件分派契约</name>
<files>None — decision recorded for downstream implementation</files>
<read_first>
- packages-user/data-system/src/event/types.ts当前 execute 接收一个 ids 数组与单一 env
- packages-user/data-system/src/event/executor.ts当前 cut/reduce 实现)
- packages-user/data-base/src/map/types.tsBlockEventType 与 IBlockEventEnv
- packages-user/data-state/src/hero/moverImpl.ts当前错误合并 point/static 且遗漏 dynamics
- .planning/phases/01-event/01-VERIFICATION.md第二组 gap
</read_first>
<action>记录用户已批准的来源感知契约:新增 `IGameEventInvocation`,包含 `id: string``env: IBlockEventEnv``IGameEventExecutor.execute` 接收只读 invocation 列表与 `IBlockEventParam`,一次调用执行 point-first、tile priority-desc 的完整序列。点事件使用 `PointEvent`/`tile=null`;静态与每个动态图块使用 `TileEvent`/实际 tile。执行器在完整序列上统一进行 trigger 过滤、cut 与 reduce并逐项 await。</action>
<acceptance_criteria>
- `IGameEventInvocation` 的字段与 `execute` 输入契约已明确
- 点、静态图块、每个动态图块的 env.type/env.tile 已明确
- event.trigger 在执行器中匹配当前 triggerpoint-first、所有 tile priority-desc 及完整序列 cut/reduce 已明确
</acceptance_criteria>
<verify>
<automated>if (!(Select-String -Path "packages-user/data-system/src/event/types.ts" -Pattern "events: string\[\]" -Quiet)) { exit 1 }; if (!(Select-String -Path "packages-user/data-state/src/hero/moverImpl.ts" -Pattern "BlockEventType.TileEvent" -Quiet)) { exit 1 }</automated>
<fails_when>当前单-env 执行器或共享 TileEvent 环境已变化,使待决策问题不再与验证报告一致</fails_when>
</verify>
<decision>批准来源感知执行契约:`IGameEventInvocation { id, env }`,由一次 `execute(readonly invocations, param)` 完整执行。</decision>
<context>当前 `execute(ids,param,env)` 无法在一个调用中表达异构 env分多次调用会改变 D-10 的全序列 cut/reduce。</context>
<done>已批准契约记录到 01-04-SUMMARY.md01-07 可逐字实现。</done>
</task>
<task type="auto">
<name>Task 3: 记录已批准的点事件持久化与基准恢复契约</name>
<files>None — decision recorded for downstream implementation</files>
<read_first>
- packages-user/data-base/src/map/types.tsIMapLayerSave 当前无点事件字段)
- packages-user/data-base/src/map/mapLayer.tspointEvents 不进入 dirty/save/load/reset/resize
- packages-user/data-base/src/map/eventView.tsmarkPure/dirty 基准仅在视图内部)
- packages-user/data-common/src/store/types.tsIMapRawData.events 原始点事件形状)
- .planning/phases/01-event/01-VERIFICATION.md第四组 gap
</read_first>
<action>记录用户已批准的点事件存档契约:`IMapLayerSave.pointEvents` 使用 `index -> priority -> eventId` 的嵌套只读 Map事件存档字段与地图矩阵独立地图矩阵 dirty 不直接纳入事件判断但点事件或图块事件发生变化时都必须使对应存档字段被写出。load 先清空并恢复事件视图的 pure 基准,再应用存档覆盖;允许为 `ILayerEventView` 增加 `resetToPure()`。resize 保留并裁剪范围内点事件resize2 清空点事件;点事件仍固定在原坐标且不随动态图块移动。</action>
<acceptance_criteria>
- `IMapLayerSave.pointEvents` 使用 `index -> priority -> eventId`,并与地图矩阵字段独立
- 三种压缩级别写出需要保存的点事件/图块事件字段load 先恢复 pure 基准再应用覆盖
- resize 裁剪、resize2 清空保存值为稳定快照点事件仍按坐标持有D-03/D-12
</acceptance_criteria>
<verify>
<automated>$content = Get-Content -Raw "packages-user/data-base/src/map/types.ts"; if ($content -notmatch "interface IMapLayerSave") { exit 1 }; if ($content -match "pointEvents\??:") { exit 1 }</automated>
<fails_when>IMapLayerSave 已出现点事件字段,表示接口上下文已变化,应先重新核对用户最新设计</fails_when>
</verify>
<decision>`IMapLayerSave.pointEvents` 使用 `index -> priority -> eventId`事件字段独立保存load 恢复 pure 基准后应用覆盖resize 裁剪resize2 清空。</decision>
<context>D-12 要求点/图块事件变化可保存;地图矩阵 dirty 与事件字段独立,不能用矩阵 dirty 替代事件存档判断。</context>
<done>已批准点事件持久化契约记录到 01-04-SUMMARY.md01-06/01-09/01-10 可按此实现。</done>
</task>
</tasks>
<threat_model>
## Trust Boundaries
| Boundary | Description |
|----------|-------------|
| 用户架构决定 → 后续公共接口实现 | 错误自动选择会固化外部编辑器与存档依赖的契约 |
## STRIDE Threat Register
| Threat ID | Category | Component | Severity | Disposition | Mitigation Plan |
|-----------|----------|-----------|----------|-------------|-----------------|
| T-01-04-01 | Tampering | 已批准公共契约被 executor 擅自改写 | high | mitigate | 后续计划必须逐字遵循本计划记录的来源感知分派与点事件生命周期;延期的注册能力只保留 TODO |
| T-01-04-02 | Denial of Service | 错误初始化/存档契约导致地图加载或存档失败 | medium | mitigate | 决策必须覆盖调用时序、非法输入、load/reset/resize 语义 |
| T-01-04-SC | Tampering | npm/pip/cargo installs | high | mitigate | 本计划和后续 gap 计划均禁止新增依赖 |
</threat_model>
<verification>
执行器逐项确认当前缺口仍存在;用户决定必须以准确文件、签名/字段及语义写入 SUMMARY不能由自动模式代选。
</verification>
<success_criteria>
- 三个契约决定均已明确记录,其中事件注册能力延期
- 01-04-SUMMARY.md 足以约束 01-06、01-07、01-09不留下接口命名或语义猜测事件注册能力明确延期
- D-01..D-12 与 built-ins 明确延期均未被改写
</success_criteria>
<output>
Create `.planning/phases/01-event/01-04-SUMMARY.md` when done
</output>
## Artifacts this phase produces
- `01-04-SUMMARY.md`:生产事件初始化契约决定
- `01-04-SUMMARY.md`:来源感知分派与完整 cut/reduce 语义决定
- `01-04-SUMMARY.md`:点事件存档字段及 dirty/load/reset/resize 决定

View File

@ -0,0 +1,152 @@
---
phase: 01-event
plan: 04
subsystem: event-contract-decisions
tags: [typescript, events, dispatch, persistence, planning]
requires:
- phase: 01-event/01-01
provides: GameEventStore, event views, and event-aware map foundations
- phase: 01-event/01-02
provides: GameEventSystem and the existing sequential executor contract
- phase: 01-event/01-03
provides: Hero movement event collection and trigger mapping
provides:
- Explicit deferral of serialized event registration, with only the CoreState initialization TODO retained
- Exact source-aware invocation and full-sequence execute contract for downstream implementation
- Exact point-event save, dirty, load, reset, and resize contract
affects: [01-06, 01-07, 01-09, 01-10, phase-01-verification]
actuals:
tokens: 2206
tasks: 3
commits: 0
plan_head_before: 6f6ed62b1b401a8e1d51207ba7b08c8e7935dfb2
tech-stack:
added: []
patterns:
- User-owned contracts are recorded verbatim before downstream implementation
- Event persistence is independent from map-matrix dirty state
key-files:
created:
- .planning/phases/01-event/01-04-SUMMARY.md
modified:
- packages-user/data-state/src/core.ts
key-decisions:
- "Serialized event registration and map event-id binding remain deferred; CoreState retains only a TODO and no new public registration API is added."
- "Source-aware dispatch uses IGameEventInvocation { id: string; env: IBlockEventEnv } and one full-sequence execute call."
- "IMapLayerSave.pointEvents uses index -> priority -> eventId, independent of map-matrix dirty, with pure-baseline overlay loading and crop/clear resize semantics."
patterns-established:
- "Point events use PointEvent with tile=null; static and each dynamic tile use TileEvent with the actual tile."
- "Trigger filtering, point-first ordering, tile priority-desc ordering, await, cut, and reduce operate over one complete invocation sequence."
requirements-completed: [EVT-01, EVT-02]
coverage:
- id: D1
description: "The production registration boundary and its intentional deferral are recorded without adding an event-registration API."
requirement: EVT-01
verification:
- kind: other
ref: "CoreState TODO presence plus addEvent seam existence check"
status: pass
human_judgment: false
- id: D2
description: "The source-aware invocation shape and one-call full-sequence execution semantics are recorded for downstream implementation."
requirement: EVT-02
verification:
- kind: other
ref: "Existing single-env executor and TileEvent context verification"
status: pass
human_judgment: true
rationale: "The selected public contract is a user-owned architecture decision; static checks only confirm that the pre-decision gap still exists."
- id: D3
description: "The independent point-event save field and pure-baseline load/resize lifecycle are recorded for downstream implementation."
requirement: EVT-01
verification:
- kind: other
ref: "IMapLayerSave absence of pointEvents verification before downstream implementation"
status: pass
human_judgment: true
rationale: "The exact persistence semantics are a user-owned architecture decision and are not implemented by this decision-only plan."
duration: 7min
completed: 2026-09-08
status: complete
---
# Phase 01 Plan 04: Event Contract Decisions Summary
**User-approved event initialization, source-aware dispatch, and coordinate-bound point-event persistence contracts recorded for downstream gap plans**
## Performance
- **Duration:** 7 min
- **Started:** 2026-09-08T15:04:22Z
- **Completed:** 2026-09-08
- **Tasks:** 3
- **Files modified by this plan:** 1 implementation file was verified unchanged; 1 summary file was created
## Accomplishments
- Confirmed that `CoreState` already retains the requested initialization TODO and continues to assemble `GameEventStore` without registering serialized events or writing event bodies into saves.
- Recorded the exact source-aware dispatch decision: `IGameEventInvocation { id: string; env: IBlockEventEnv }`, a single full-sequence executor call, source-specific environments, trigger filtering, and unified cut/reduce behavior.
- Recorded the exact point-event persistence decision: `IMapLayerSave.pointEvents` as index → priority → event id, independent event-field dirtying, pure-baseline restoration before overlays, stable save snapshots, resize cropping, and `resize2` clearing.
## Task Commits
No task commits were created, per the explicit user instruction not to create git commits. Tasks 2 and 3 are decision-recording tasks; no downstream implementation was performed.
## Files Created/Modified
- `.planning/phases/01-event/01-04-SUMMARY.md` — Records all three approved contracts and the exact downstream implementation boundary.
- `packages-user/data-state/src/core.ts` — Verified the existing event-store initialization TODO; no additional code change was made in this plan.
## Decisions Made
### 1. Serialized registration is deferred
The production initialization path keeps a TODO for later registration of external serialized event definitions and map event-id bindings. This plan does not add a registration API, does not register event bodies into maps or saves, and does not expand the deferred built-in function scope.
### 2. Dispatch carries its source per invocation
Downstream code must introduce `IGameEventInvocation { id: string; env: IBlockEventEnv }` and make one execute call over the complete ordered invocation sequence. Point events use `BlockEventType.PointEvent` and `tile: null`; static tile events and every dynamic tile event use `BlockEventType.TileEvent` and their actual tile. The sequence is point-first, then static and dynamic tile entries in descending priority order. The executor filters each event against `event.trigger === invocation.env.trigger`, awaits each selected event, and applies cut/reduce over this same complete sequence rather than across separate calls.
### 3. Point-event persistence is an independent save field
`IMapLayerSave.pointEvents` is a nested read-only map shaped as `index -> priority -> eventId`. It is independent from the map matrix dirty flag: matrix dirtiness must not substitute for event-field dirtiness, and point-event or tile-event changes must cause their corresponding save fields to be emitted. Loading first restores the pure baseline and then overlays the serialized event data. A `resetToPure()` operation may be added to `ILayerEventView` if needed. Normal resize preserves and crops in-range point events; `resize2` clears them. Point events remain attached to their original coordinates and do not move with dynamic tiles. Save values must be stable snapshots, not live internal maps.
## Verification
- Task 1 precondition and context check passed: `data-common/src/store/types.ts` exists with `addEvent`, and `CoreState` contains the deferred registration TODO.
- Task 2 context check passed: the current executor still has the single-env `events: string[]` seam and the mover still contains the pre-decision `TileEvent` path.
- Task 3 context check passed: `IMapLayerSave` exists and does not yet contain `pointEvents`, confirming downstream implementation remains pending.
- No package installation, downstream-plan implementation, or behavioral test was performed.
## Deviations from Plan
None - the plan was executed as a decision-recording gap closure. The existing `CoreState` TODO already matched Task 1, so no implementation edit was necessary.
## Known Stubs
- `packages-user/data-state/src/core.ts:153` — The serialized event registration and map-id binding TODO is intentional and explicitly deferred by this plan; downstream plans must not infer or add an API before the deferred scope is reopened.
## Issues Encountered
None. Repository-wide implementation gaps remain intentionally assigned to downstream plans 01-06, 01-07, 01-09, and 01-10; this plan did not attempt to resolve them.
## User Setup Required
None.
## Next Phase Readiness
- 01-06/01-09/01-10 may implement only the exact `pointEvents` lifecycle recorded above.
- 01-07 may implement only the exact source-aware invocation and full-sequence executor contract recorded above.
- Serialized registration remains deferred; no downstream plan may invent a registration API from this summary.
## Self-Check: PASSED
- `.planning/phases/01-event/01-04-SUMMARY.md` exists.
- The verified `CoreState` TODO and all three decision sections are present in this summary.
- Git HEAD remains `6f6ed62b1b401a8e1d51207ba7b08c8e7935dfb2`; no commit was created.
- No downstream plan files or implementation files outside the retained `CoreState` context were changed.
---
*Phase: 01-event*
*Completed: 2026-09-08*

View File

@ -0,0 +1,154 @@
---
phase: 01-event
plan: 05
type: execute
wave: 4
depends_on: [01-03]
files_modified: []
autonomous: false
gap_closure: true
requirements: [EVT-01]
estimate:
tokens: 8000
raw_tokens: 8000
tasks: 2
confidence: low
must_haves:
truths:
- "用户批准 rawEvent 对调用者不可变、setRaw 使缓存失效的准确公共契约D-07"
- "用户批准 anon-tokyo 的 Promise<unknown> 与公开 Promise<R> 之间不使用类型断言的准确返回契约event.ts 不保留项目绝对禁止的 as 断言"
- "用户批准只消除经过 eventStore 的新增循环边而不把仓库既有 16 个循环纳入本阶段的依赖修复路径"
artifacts:
- path: ".planning/phases/01-event/01-05-SUMMARY.md"
provides: "事件源不可变与 eventStore 循环修复的用户决定"
key_links:
- from: ".planning/phases/01-event/01-05-SUMMARY.md"
to: "01-08-PLAN.md"
via: "exact approved contract and dependency edge"
---
<objective>
取得修复 stale compiled source、event.ts 禁用类型断言与新增 eventStore 循环所需的用户接口/架构决定。
Purpose: 两项修复分别改变公开事件源语义与包依赖边,均超出 planner 的决定权限。
Output: `01-05-SUMMARY.md` 中的准确实现约束。
</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-VERIFICATION.md
@packages-user/data-common/src/event/types.ts
@packages-user/data-common/src/event/event.ts
@packages-user/data-common/src/store/eventStore.ts
@packages-user/data-common/src/store/types.ts
@packages/common/src/utils/types.ts
</context>
<tasks>
<task type="checkpoint:decision" gate="blocking-human">
<name>Task 1: 决定不可变事件源契约</name>
<files>None — decision only</files>
<read_first>
- packages-user/data-common/src/event/types.ts公开 readonly rawEvent: Statement[]
- packages-user/data-common/src/event/event.ts构造器/setRaw 保存数组别名与 compiled 缓存)
- node_modules/anon-tokyo/dist/index.d.tsexec 返回 Promise<unknown>,当前公开 execute 返回 Promise<R>
- .planning/phases/01-event/01-VERIFICATION.md第五组 gap
</read_first>
<action>请用户指定调用者读取事件源时的准确类型/访问语义、构造器与 `setRaw` 是否防御性复制、以及返回数据是否允许调用者修改副本。决定必须保证外部 `push/splice` 或构造参数后续修改不能让 D-07 缓存执行旧代码。同时展示 anon-tokyo 的 `AnonTokyoExecutable.exec`/`AnonTokyoInterpreter.exec` 返回 `Promise&lt;unknown&gt;`,而 `IGameEvent.execute` 声明 `Promise&lt;R&gt;`,请用户批准不使用 `as` 的准确适配方式或准确公共返回契约;不得由 planner 改写泛型语义。两个决定都必须保留 D-08。planner 不提供或命名新的公共成员。</action>
<acceptance_criteria>
- 用户回复给出 `IReadonlyGameEvent`/`IGameEvent` 的准确成员类型或访问规则
- 回复明确构造输入、公开读取、setRaw 输入三个别名边界
- 回复明确何时 compiled 必须失效D-07
- 回复明确 event.ts 如何在不使用任何 `as` 断言的前提下衔接第三方 Promise&lt;unknown&gt; 与批准的公开返回类型;若改变公共签名,给出准确签名与所属接口
</acceptance_criteria>
<verify>
<automated>if (!(Select-String -Path "packages-user/data-common/src/event/types.ts" -Pattern "readonly rawEvent: Statement\[\]" -Quiet)) { exit 1 }; if (!(Select-String -Path "packages-user/data-common/src/event/event.ts" -Pattern "this.rawEvent = raw" -Quiet)) { exit 1 }; if (!(Select-String -Path "node_modules/anon-tokyo/dist/index.d.ts" -Pattern "Promise\x3Cunknown\x3E" -Quiet)) { exit 1 }; if (!(Select-String -Path "packages-user/data-common/src/event/event.ts" -Pattern "\bas\s+Promise" -Quiet)) { exit 1 }</automated>
<fails_when>公开事件源/别名保存已变化,第三方返回不再是 Promise unknown或待处置的 Promise 类型断言已不存在,表示决策上下文过期</fails_when>
</verify>
<decision>事件源应采用哪一个用户批准的不可变公共契约?</decision>
<context>当前属性只对重新赋值只读,数组内容仍可原地修改并绕过 setRaw 的缓存失效。</context>
<options>
<option id="readonly-source"><name>批准只读事件源类型</name><pros>类型层阻止常规调用者原地修改</pros><cons>仍需用户明确构造/setRaw 的别名隔离</cons></option>
<option id="defensive-view"><name>批准防御性读取语义</name><pros>运行时也不暴露内部数组</pros><cons>需要用户给出准确现有成员实现方式,且不得擅自增加 getter</cons></option>
</options>
<resume-signal>请提供准确公共成员类型/语义及三个别名边界</resume-signal>
<done>用户批准的事件源与无类型断言返回契约记录到 01-05-SUMMARY.md01-08 可据此编写行为测试和实现。</done>
</task>
<task type="checkpoint:decision" gate="blocking-human">
<name>Task 2: 决定 eventStore 新增循环的修复边</name>
<files>None — decision only</files>
<read_first>
- packages-user/data-common/src/store/eventStore.ts通过 @motajs/common barrel 引入 logger
- packages-user/data-common/src/store/types.ts通过 @motajs/common barrel 引入 IFacedTileLocator
- packages/common/src/utils/types.ts反向从 @user/data-common barrel 引入 FaceDirection
- .planning/phases/01-event/01-VERIFICATION.md第六组 gap 与 cycle #9/#10
</read_first>
<action>向用户展示当前两个新增环的准确链路,并请用户选择要切断的现有依赖边。选择必须只以消除经过 `store/eventStore.ts` 的新增环为本阶段门禁;其余仓库循环保持基线,不得通过删除重复 id 告警违反错误处理约束。记录允许修改的准确文件与 import 方向;如用户选择移动接口,必须给出准确目标文件与导出路径。</action>
<acceptance_criteria>
- 用户回复指定切断哪条依赖边及允许修改的准确文件
- 回复确认 GameEventStore 仍实现 IGameEventStore重复 id 仍使用 logger 告警
- 回复确认本阶段只阻止 circular 输出中的 eventStore 路径
</acceptance_criteria>
<verify>
<automated>$output = pnpm check:circular 2>&1; if ($output -notmatch "circular dependenc") { $output; exit 1 }; if ($output -notmatch "store/eventStore\.ts") { $output; exit 1 }</automated>
<fails_when>madge 未产出可解析循环报告,或 eventStore 新增环已不存在,表示决策上下文发生变化</fails_when>
</verify>
<decision>应切断哪一条现有依赖边以消除 eventStore 新增循环?</decision>
<context>改动 `@motajs/common``@user/data-common` 的 import 方向属于用户所有架构决定;仓库另有既有循环,不应扩大本 gap 的成功口径。</context>
<options>
<option id="leaf-import"><name>批准改为现有 leaf import 边</name><pros>不改公共运行时行为</pros><cons>用户须指定符合项目分层的准确路径</cons></option>
<option id="interface-placement"><name>批准调整相关接口所在依赖边</name><pros>可从结构上隔离 event store</pros><cons>可能影响桶导出,需用户提供准确文件与导出设计</cons></option>
<option id="reverse-dependency"><name>批准修正 common 的反向依赖</name><pros>可同时减少 barrel 环</pros><cons>影响公共基础层,需用户明确范围</cons></option>
</options>
<resume-signal>请指定选项、准确文件/import 路径与允许范围</resume-signal>
<done>用户批准的循环切断方案记录到 01-05-SUMMARY.md01-08 只实施该方案。</done>
</task>
</tasks>
<threat_model>
## Trust Boundaries
| Boundary | Description |
|----------|-------------|
| 用户 API/分层决定 → L0 事件源码 | 错误决定会暴露可变脚本或扩大跨包依赖 |
## STRIDE Threat Register
| Threat ID | Category | Component | Severity | Disposition | Mitigation Plan |
|-----------|----------|-----------|----------|-------------|-----------------|
| T-01-05-01 | Tampering | 调用者修改 Statement[] 后继续执行旧 compiled | high | mitigate | blocking-human 批准不可变契约,并由 01-08 行为测试覆盖三个别名边界 |
| T-01-05-02 | Denial of Service | 错误依赖修复造成新的模块初始化环 | high | mitigate | 用户指定切断边01-08 运行 focused madge 并阻止 eventStore 路径 |
| T-01-05-03 | Tampering | event.ts 以类型断言掩盖第三方 unknown 返回契约 | medium | mitigate | 用户批准准确的无断言适配或公共返回签名01-08 静态门禁阻止旧断言残留 |
| T-01-05-SC | Tampering | npm/pip/cargo installs | high | mitigate | 无安装任务,禁止新增依赖 |
</threat_model>
<verification>
当前 stale-source 与 cycle #9/#10 事实被自动复核;用户决定必须包含准确接口或 import 文件,且不能由 auto mode 代选。
</verification>
<success_criteria>
- rawEvent 三个别名边界与缓存失效语义由用户批准
- anon-tokyo 返回值与公开 execute 返回契约的无 `as` 衔接方式由用户批准
- eventStore 循环切断边及修改范围由用户批准
- 01-05-SUMMARY.md 可直接约束 01-08 实施
</success_criteria>
<output>
Create `.planning/phases/01-event/01-05-SUMMARY.md` when done
</output>
## Artifacts this phase produces
- `01-05-SUMMARY.md`:不可变事件源公共契约
- `01-05-SUMMARY.md`event.ts 无类型断言的准确返回契约
- `01-05-SUMMARY.md`eventStore 新增循环的准确修复边与门禁口径

View File

@ -0,0 +1,160 @@
---
phase: 01-event
plan: 05
subsystem: event-contract-decisions
tags: [typescript, events, raw-event, event-store, circular-dependencies, planning]
requires:
- phase: 01-event/01-03
provides: Existing event source, event execution, and eventStore implementation context
provides:
- Exact user-approved rawEvent compatibility contract
- Exact eventStore circular-dependency deferral and baseline paths
- Downstream blocker record for the unchanged 01-08 implementation assumptions
affects: [01-08, phase-01-verification]
actuals:
tokens: 4700
tasks: 2
commits: 0
plan_head_before: 6f6ed62b1b401a8e1d51207ba7b08c8e7935dfb2
tech-stack:
added: []
patterns:
- User-owned event contracts are recorded verbatim before implementation.
- Existing circular dependencies may remain explicit phase baselines when the user declines an architectural change.
key-files:
created:
- .planning/phases/01-event/01-05-SUMMARY.md
modified:
- .planning/STATE.md
- .planning/ROADMAP.md
- .planning/WINDOWS.md
key-decisions:
- "rawEvent remains exactly current behavior: public Statement[], constructor/setRaw alias the caller array, and no defensive copy, unknown type, or cache-safety change is authorized."
- "IGameEvent.execute keeps the generic Promise<R> contract and event.ts keeps its current as Promise<R> adapters for anon-tokyo Promise<unknown> results."
- "eventStore circular dependencies are not fixed in this plan; current imports and behavior remain unchanged, and the exact check:circular paths are deferred as the Phase 01 baseline."
patterns-established:
- "Decision-only plans record the approved public contract and explicitly mark implementation verification as downstream."
requirements-completed: [EVT-01]
coverage:
- id: D1
description: "The public rawEvent aliasing and generic Promise<R> contract are recorded exactly as currently implemented."
verification:
- kind: other
ref: "User decision supplied in execution request; current types.ts and event.ts inspected"
status: pass
human_judgment: true
rationale: "This is an explicit user-owned API compatibility decision; automation cannot select the contract."
- id: D2
description: "The eventStore circular paths are recorded as a deferred Phase 01 baseline without changing imports or behavior."
verification:
- kind: other
ref: "pnpm check:circular"
status: pass
human_judgment: true
rationale: "The command intentionally reports the preserved baseline; whether to repair it is an architectural user decision."
duration: 17min
completed: 2026-09-08
status: complete
---
# Phase 01 Plan 05: Event Contract Decisions Summary
**Raw event compatibility and eventStore cycle deferral recorded without modifying implementation or creating a commit**
## Performance
- **Duration:** 17 min
- **Started:** 2026-09-08T15:19:00Z
- **Completed:** 2026-09-08T15:36:50Z
- **Tasks:** 2 decision records
- **Files modified by this plan:** 1 summary file; planning state/roadmap and broken-windows ledger refreshed; 0 implementation files
## Accomplishments
- Recorded that `rawEvent` remains a public mutable `Statement[]` property, with constructor and `setRaw` retaining caller-array aliases exactly as current behavior.
- Recorded that the generic `Promise<R>` public return contract and all current `as Promise<R>` adapters remain unchanged; no `unknown`, defensive copy, or cache-safety change is authorized by this plan.
- Ran `pnpm check:circular` and recorded the eventStore paths as an intentional Phase 01 baseline. No import edge, eventStore behavior, or duplicate-id warning behavior was changed.
## Task Commits
No task commits were created, per the explicit user instruction not to create git commits. No downstream plan was executed.
## Files Created/Modified
- `.planning/phases/01-event/01-05-SUMMARY.md` — Records the two explicit user decisions, the circular baseline, and downstream blockers.
- `.planning/STATE.md` — Planning state refreshed after this decision-recording plan.
- `.planning/ROADMAP.md` — Phase plan progress refreshed after this decision-recording plan.
- `.planning/WINDOWS.md` — Records the intentionally unrun downstream verification.
No implementation files were modified.
## Decisions Made
### 1. Preserve the rawEvent contract exactly
The public type remains `Statement[]`, not a readonly or defensive view. The constructor continues to store the input array by alias, and `setRaw` continues to store its input array by alias while invalidating `compiled` as current behavior does. This plan does not add defensive copies, `unknown`, cache-safety changes, new getters/setters, or new public members.
`IGameEvent.execute` continues to declare generic `Promise<R>`. The three current `as Promise<R>` adapters in `event.ts` remain authorized to bridge anon-tokyo's `Promise<unknown>` return type. The downstream no-`as` and immutable-source requirements in the original gap plan are therefore not approved by this decision record.
### 2. Preserve eventStore circular dependencies as baseline
The current imports and runtime behavior remain unchanged. `GameEventStore` continues to implement `IGameEventStore`, use `logger.warn(170, id)` for duplicate ids, overwrite the duplicate, and return events by id. This plan does not select a leaf import, interface relocation, reverse-dependency correction, or any other architectural cycle-breaking edge.
The exact `pnpm check:circular` output observed on 2026-09-08 reported 18 cycles. The two paths involving the eventStore introduced by the current implementation are:
9. `../packages/common/src/index.ts > ../packages/common/src/utils/index.ts > ../packages/common/src/utils/dir.ts > ../packages/common/src/utils/types.ts > ../packages-user/data-common/src/index.ts > ../packages-user/data-common/src/store/index.ts > ../packages-user/data-common/src/store/eventStore.ts > ../packages-user/data-common/src/store/types.ts`
10. `../packages/common/src/index.ts > ../packages/common/src/utils/index.ts > ../packages/common/src/utils/dir.ts > ../packages/common/src/utils/types.ts > ../packages-user/data-common/src/index.ts > ../packages-user/data-common/src/store/index.ts > ../packages-user/data-common/src/store/eventStore.ts`
For completeness, the remaining 16 reported paths are also preserved as the same phase baseline:
1. `../packages-user/data-common/src/common/index.ts > ../packages-user/data-common/src/common/face.ts`
2. `../packages/common/src/index.ts > ../packages/common/src/utils/index.ts > ../packages/common/src/utils/dir.ts > ../packages/common/src/utils/types.ts > ../packages-user/data-common/src/index.ts > ../packages-user/data-common/src/common/index.ts > ../packages-user/data-common/src/common/face.ts`
3. `../packages/common/src/index.ts > ../packages/common/src/utils/index.ts > ../packages/common/src/utils/dir.ts > ../packages/common/src/utils/types.ts > ../packages-user/data-common/src/index.ts > ../packages-user/data-common/src/common/index.ts > ../packages-user/data-common/src/common/indexer.ts`
4. `../packages/common/src/index.ts > ../packages/common/src/utils/index.ts > ../packages/common/src/utils/dir.ts > ../packages/common/src/utils/types.ts > ../packages-user/data-common/src/index.ts > ../packages-user/data-common/src/common/index.ts > ../packages-user/data-common/src/common/mover.ts`
5. `../packages/common/src/index.ts > ../packages/common/src/utils/index.ts > ../packages/common/src/utils/dir.ts > ../packages/common/src/utils/types.ts > ../packages-user/data-common/src/index.ts > ../packages-user/data-common/src/replay/index.ts > ../packages-user/data-common/src/replay/array.ts > ../packages-user/data-common/src/replay/types.ts`
6. `../packages/common/src/index.ts > ../packages/common/src/utils/index.ts > ../packages/common/src/utils/dir.ts > ../packages/common/src/utils/types.ts > ../packages-user/data-common/src/index.ts > ../packages-user/data-common/src/replay/index.ts > ../packages-user/data-common/src/replay/array.ts > ../packages-user/data-common/src/save/index.ts > ../packages-user/data-common/src/save/system.ts`
7. `../packages/common/src/index.ts > ../packages/common/src/utils/index.ts > ../packages/common/src/utils/dir.ts > ../packages/common/src/utils/types.ts > ../packages-user/data-common/src/index.ts > ../packages-user/data-common/src/replay/index.ts > ../packages-user/data-common/src/replay/array.ts`
8. `../packages/common/src/index.ts > ../packages/common/src/utils/index.ts > ../packages/common/src/utils/dir.ts > ../packages/common/src/utils/types.ts > ../packages-user/data-common/src/index.ts > ../packages-user/data-common/src/replay/index.ts > ../packages-user/data-common/src/replay/sandbox.ts`
11. `../packages/common/src/index.ts > ../packages/common/src/utils/index.ts > ../packages/common/src/utils/dir.ts > ../packages/common/src/utils/types.ts > ../packages-user/data-common/src/index.ts > ../packages-user/data-common/src/store/index.ts > ../packages-user/data-common/src/store/itemStore.ts`
12. `../packages/common/src/index.ts > ../packages/common/src/utils/index.ts > ../packages/common/src/utils/dir.ts > ../packages/common/src/utils/types.ts > ../packages-user/data-common/src/index.ts > ../packages-user/data-common/src/store/index.ts > ../packages-user/data-common/src/store/tileStore.ts`
13. `../packages/common/src/index.ts > ../packages/common/src/utils/index.ts > ../packages/common/src/utils/dir.ts > ../packages/common/src/utils/types.ts > ../packages-user/data-common/src/index.ts > ../packages-user/data-common/src/types.ts`
14. `../packages/render/src/core/event.ts > ../packages/render/src/core/types.ts`
15. `../packages/render/src/core/index.ts > ../packages/render/src/core/graphics.ts`
16. `../packages/render/src/core/index.ts > ../packages/render/src/core/misc.ts`
17. `../packages-user/data-state/src/core.ts > ../packages-user/data-state/src/enemy/index.ts > ../packages-user/data-state/src/enemy/calculator.ts > ../packages-user/data-state/src/ins.ts`
18. `../packages-user/data-state/src/index.ts > ../packages-user/data-state/src/core.ts > ../packages-user/data-state/src/legacy/index.ts > ../packages-user/data-state/src/legacy/move.ts`
## Deviations from Plan
The original plan asked the user to choose a cycle-breaking edge and to approve an immutable raw-event/no-assertion contract. The explicit user decisions instead preserve both current behaviors. This plan therefore records decisions only and intentionally does not perform the proposed implementation work.
## Verification
- `pnpm check:circular` ran successfully as a diagnostic command and reported 18 circular dependencies with exit code 1; this is the expected preserved baseline, not a failure of the decision record.
- Current `types.ts`, `event.ts`, `eventStore.ts`, and related import files were inspected; no implementation files were edited.
- Downstream behavior tests and implementation verification were not run because the user explicitly prohibited downstream plan execution and implementation changes.
## Blockers and Deferred Work
- The original 01-08 Task 1 must-haves (immutable aliases, cache-safety changes, and removal of `as Promise<R>`) conflict with the approved contract and require a revised plan or a future user decision before implementation.
- The original 01-08 Task 2 circular-gate criterion conflicts with the approved eventStore baseline. Any future plan that preserves these imports must not claim that the eventStore paths are absent from `check:circular`.
- No downstream plans were executed.
## User Setup Required
None.
## Next Phase Readiness
The exact current rawEvent and execute contracts are available to future planning. The eventStore dependency cycles are explicitly deferred as baseline. Implementation work must not be started from the original 01-08 assumptions until that plan is reconciled with this summary.
## Self-Check: PASSED
- `.planning/phases/01-event/01-05-SUMMARY.md` exists.
- No implementation files were modified by this plan.
- Git HEAD remains `6f6ed62b1b401a8e1d51207ba7b08c8e7935dfb2`; no commit was created.
- No downstream plan was executed.
---
*Phase: 01-event*
*Completed: 2026-09-08*

View File

@ -0,0 +1,159 @@
---
phase: 01-event
plan: 06
type: execute
wave: 5
depends_on: [01-04]
files_modified:
- packages-user/data-base/src/map/mapState.ts
- packages-user/data-base/src/map/eventPath.test.ts
- packages/common/src/logger.json
autonomous: true
gap_closure: true
requirements: [EVT-01, EVT-02, EVT-03]
estimate:
tokens: 22000
raw_tokens: 22000
tasks: 2
confidence: low
must_haves:
truths:
- "IMapRawData.events 在创建/注册地图前完成结构验证,合法值进入坐标点事件视图,缺失/非法层不会抛出或留下半注册地图D-01/D-03/D-04"
- "MapState.fromRaw 设置真实 eventLayerCoreState legacy 初始化的同一缺口由已修改 core.ts 的 01-09 生产 tracer 闭合"
artifacts:
- path: "packages-user/data-base/src/map/eventPath.test.ts"
provides: "raw ingestion、eventLayer 与 malformed input 行为证据"
- path: "packages/common/src/logger.json"
provides: "malformed raw 容器与值类型的专用数字错误码"
key_links:
- from: "packages-user/data-base/src/map/mapState.ts"
to: "IMapLayer.event(x,y)"
via: "validated IMapRawData.events point binding"
---
<objective>
修复 raw map 入口的数据流缺口:在注册地图前验证外部结构,把坐标点事件装入正确视图,并选择真实事件层。
Purpose: 为 EVT-01/EVT-02 提供安全、可执行的地图事件入口,并让 D-01/D-03/D-04 的 raw 绑定语义成立。
Output: MapState raw ingestion 修复、专用 logger code 及命名 Vitest 行为测试。
</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-04-SUMMARY.md
@.planning/phases/01-event/01-VERIFICATION.md
@.planning/phases/01-event/01-01-SUMMARY.md
@packages-user/data-common/src/store/types.ts
@packages-user/data-base/src/map/eventView.ts
@packages-user/data-base/src/map/mapState.ts
@packages-user/data-base/src/map/gameMap.ts
@packages/common/src/logger.json
</context>
<tasks>
<task type="tracer" tdd="true">
<name>Task 1: 贯通 raw map → point event view → eventLayer 的真实地图路径</name>
<files>
packages-user/data-base/src/map/mapState.ts
packages-user/data-base/src/map/eventPath.test.ts
</files>
<read_first>
- packages-user/data-common/src/store/types.tsIMapRawData.events/layerAlias 的既有接口)
- packages-user/data-base/src/map/mapState.ts当前在 createMap 后读取 raw.events[z] 并错误写 static.tileEvent
- packages-user/data-base/src/map/gameMap.ts现有 setEventLayer
- packages-user/data-base/src/map/eventView.tsmarkPure/dirty
- .planning/phases/01-event/01-VERIFICATION.md gaps 1 与 3
</read_first>
<behavior>
- 合法 raw 的 alias=`event` 图层成为 GameMap.eventLayerraw.events 的 id 只出现在 layer.event(x,y)
- 合法 point view 写入完成后 markPure静态 tile view 不含坐标点事件 id
</behavior>
<action>先在 `eventPath.test.ts` 写合法 raw 的失败测试,再修改实现。保持现有地图尺寸检查;创建各层时,用 `layer.event(x,y)` 写入 D-03 坐标点视图,全部写完后 `markPure()`,不得写入 static tile。alias 为 `event` 的层调用现有 `state.setEventLayer(layer)`。CoreState legacy eventLayer 装配移至已拥有 `core.ts` 的 01-09 tracer以缩小本计划文件面。保持事件值为 eventStore idD-04不引入 built-ins在动态导入前 stub `main`/DOM logger 所需全局量。</action>
<acceptance_criteria>
- 合法 raw 测试断言 eventLayer 为 alias=`event` 的实际层
- 合法 raw 测试断言 point view 含 id、静态 tile view 不含该 id且 point view 为 pure
</acceptance_criteria>
<verify>
<automated>pnpm exec vitest run "packages-user/data-base/src/map/eventPath.test.ts" -t "raw point events and event layer"</automated>
<fails_when>Vitest 非零退出、未发现命名测试、坏数据抛出异常/半注册地图,或点事件进入 tile view</fails_when>
</verify>
<done>合法外部序列化地图形成 pure 坐标点事件视图与非空 eventLayer且 tile view 不接收 point id。</done>
</task>
<task type="auto" tdd="true">
<name>Task 2: 在地图注册前拒绝 malformed raw event 结构</name>
<files>
packages-user/data-base/src/map/mapState.ts
packages-user/data-base/src/map/eventPath.test.ts
packages/common/src/logger.json
</files>
<read_first>
- packages-user/data-common/src/store/types.tsIMapRawData.map/events 的合法形状)
- packages-user/data-base/src/map/mapState.tsTask 1 后 valid ingestion 路径)
- packages/common/src/logger.jsonerror 62 仅描述原始地图键不是数字)
- .planning/phases/01-event/01-VERIFICATION.md gap 3 的 malformed raw 证据
</read_first>
<behavior>
- 缺失、null、非对象 raw.map/events 或嵌套层/位置/优先级容器在 createMap 前返回 nullfloor id 不注册
- 非数字 raw map/event 位置/优先级键使用 code 62容器或叶值类型错误使用各自匹配的新数字 code
- 非数组图层、越界位置或非字符串 event id 不产生部分地图
</behavior>
<action>先用 `Reflect.set` 构造运行时坏数据并写失败测试,再让 `MapState.fromRaw``createMap` 前完整遍历和验证 `raw.map`、每个 map z 对应的 `raw.events` 层、位置索引及 priority→string-id 对象。code 62 只用于它所描述的非数字 raw-map/event 键;在 `logger.json` 的 error 区新增 code 63描述必需容器缺失、为 null 或不是对象,并新增 code 64描述图层数组、索引范围或事件 id 等叶值类型错误。所有对应分支调用匹配 code 后返回 null不得留下 `mapData/maps` 条目;不要抛异常或静默忽略 malformed 结构。</action>
<acceptance_criteria>
- missing/null/non-object、坏图层值、越界位置、非字符串 id 均返回 null
- 每个坏输入后 `getMap(floorId)` 为 nullmaps 不含 floor id
- 测试精确断言非数字键使用 62、坏容器使用 63、坏叶值使用 64logger.json 文案与各错误条件一致
</acceptance_criteria>
<verify>
<automated>pnpm exec vitest run "packages-user/data-base/src/map/eventPath.test.ts" -t "malformed raw event structures"</automated>
<fails_when>Vitest 非零退出、坏数据抛出异常/半注册地图、错误条件使用不匹配 code或 malformed 输入被静默接受</fails_when>
</verify>
<done>所有外部 raw 容器和叶值在地图注册前完成校验,每种失败走语义匹配的数字 logger code。</done>
</task>
</tasks>
<threat_model>
## Trust Boundaries
| Boundary | Description |
|----------|-------------|
| 外部 `IMapRawData.events` → MapState/MapLayer | 序列化对象可能缺层、为 null、含非法坐标/优先级/id |
## STRIDE Threat Register
| Threat ID | Category | Component | Severity | Disposition | Mitigation Plan |
|-----------|----------|-----------|----------|-------------|-----------------|
| T-01-06-01 | Tampering | `MapState.fromRaw` 嵌套 events | high | mitigate | 创建地图前完整校验层/位置/优先级/id非法输入 logger.error 后返回 null测试无半注册状态 |
| T-01-06-02 | Denial of Service | 缺失 events 层触发 Object.entries 异常 | high | mitigate | 对每层对象做运行时存在性/对象校验,命名 Vitest 覆盖 null/missing |
| T-01-06-03 | Repudiation | 不相关 logger code 隐藏 malformed raw 的真实条件 | medium | mitigate | 62 仅报告非数字键63/64 分别报告容器与叶值错误,行为测试断言 code |
| T-01-06-SC | Tampering | npm/pip/cargo installs | high | mitigate | 无安装任务,复用现有 Vitest 4.0.18 |
</threat_model>
<verification>
两个任务分别运行 valid 与 malformed 命名行为测试;计划完成后运行 `pnpm exec vitest run "packages-user/data-base/src/map/eventPath.test.ts"` 与 focused ESLint不以仓库既有全局 type/lint/cycle 债务判定本计划。
</verification>
<success_criteria>
- raw point 数据进入坐标点事件视图且不进入 static tile view
- MapState.fromRaw 设置 eventLayerCoreState legacy 路径由依赖本计划的 01-09 设置并端到端验证
- malformed raw 在创建地图前返回 null不抛异常或留下半注册状态
- 62/63/64 分别只描述非数字键、坏容器与坏叶值
</success_criteria>
<output>
Create `.planning/phases/01-event/01-06-SUMMARY.md` when done
</output>
## Artifacts this phase produces
- `packages-user/data-base/src/map/eventPath.test.ts`raw ingestion、eventLayer 与 malformed input 行为测试
- `MapState.fromRaw`预校验、point-view 装配和 eventLayer 选择
- `packages/common/src/logger.json`raw events 容器与叶值错误的专用 error code

View File

@ -0,0 +1,184 @@
---
phase: 01-event
plan: 07
type: execute
wave: 6
depends_on: [01-04, 01-06, 01-10]
files_modified:
- packages-user/data-base/src/map/types.ts
- packages-user/data-system/src/event/types.ts
- packages-user/data-system/src/event/executor.ts
- packages-user/data-state/src/hero/moverImpl.ts
- packages-user/data-system/src/event/eventDispatch.test.ts
autonomous: true
gap_closure: true
requirements: [EVT-02, EVT-03]
estimate:
tokens: 32000
raw_tokens: 32000
tasks: 2
confidence: low
must_haves:
truths:
- "一次移动分派只执行 event.trigger 与当前 EventTrigger 相同的事件D-02/D-08"
- "执行顺序严格为点事件优先,然后静态与全部动态图块事件按 priority 降序;长事件逐个 awaitD-06/D-09"
- "点事件收到 PointEvent/tile=null静态与每个动态图块收到 TileEvent/实际 tile且 D-10 cut/reduce 在完整序列上保持用户批准语义"
- "enter/leave/hit 分别驱动 OnEnter/OnLeave/OnTouch未增加复杂通用事件抽象EVT-03"
artifacts:
- path: "packages-user/data-system/src/event/eventDispatch.test.ts"
provides: "trigger、顺序、来源、dynamic、cut/reduce 与移动钩子行为证据"
- path: "packages-user/data-system/src/event/executor.ts"
provides: "trigger-aware source-correct execution"
- path: "packages-user/data-state/src/hero/moverImpl.ts"
provides: "point/static/dynamic collection with real source metadata"
key_links:
- from: "DefaultHeroMoveTopImpl.commonTrigger"
to: "IGameEventExecutor"
via: "01-04 approved source-aware dispatch contract"
- from: "EventExecutor"
to: "IReadonlyGameEvent.trigger"
via: "match before execute"
---
<objective>
修复执行器与移动集成的语义错误:按触发器筛选、收集动态图块、保留每个来源的真实环境,并维持完整 D-06/D-10 执行语义。
Purpose: 让 EVT-02 的踩踏路径执行“对应事件”,而不是同一位置上的所有事件或错误来源对象。
Output: 用户批准契约的实现及命名行为测试。
</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-04-SUMMARY.md
@.planning/phases/01-event/01-06-SUMMARY.md
@.planning/phases/01-event/01-10-SUMMARY.md
@.planning/phases/01-event/01-VERIFICATION.md
@.planning/phases/01-event/01-PATTERNS.md
@.planning/phases/01-event/01-02-SUMMARY.md
@.planning/phases/01-event/01-03-SUMMARY.md
@packages-user/data-base/src/map/types.ts
@packages-user/data-system/src/event/types.ts
@packages-user/data-system/src/event/executor.ts
@packages-user/data-state/src/hero/moverImpl.ts
</context>
<tasks>
<task type="tracer" tdd="true">
<name>Task 1: 贯通一个来源正确、触发器匹配的 point→static→dynamic 分派</name>
<files>
packages-user/data-base/src/map/types.ts
packages-user/data-system/src/event/types.ts
packages-user/data-system/src/event/executor.ts
packages-user/data-state/src/hero/moverImpl.ts
packages-user/data-system/src/event/eventDispatch.test.ts
</files>
<read_first>
- .planning/phases/01-event/01-04-SUMMARY.md来源感知执行的准确用户决定
- .planning/phases/01-event/01-PATTERNS.md 第 2、16 节(已删除 trigger collector/collection 是历史 analog保留优先级降序、逐项 await、接口先行约定
- packages-user/data-base/src/map/types.tsIBlockEventEnv/BlockEventType/ILayerLocation.dynamics
- packages-user/data-system/src/event/types.ts当前 execute/mode/reduce 契约)
- packages-user/data-system/src/event/executor.ts当前未检查 event.trigger
- packages-user/data-state/src/hero/moverImpl.ts当前只取 static 并共享 TileEvent env
- .planning/phases/01-event/01-VERIFICATION.md gap 2
</read_first>
<behavior>
- OnEnter 分派忽略绑定在同位置的 OnLeave/None 事件
- 点事件先执行且 env.type=PointEvent、env.tile=null
- static 与每个 dynamics 都执行匹配事件env.type=TileEvent、env.tile 为各自实际对象
- tile 组跨 static/dynamics 按 priority 降序,单个长事件被 await 后才执行下一项
</behavior>
<action>先写失败测试,然后严格实施 `01-04-SUMMARY.md` 中用户批准的来源感知接口;不得自行命名其他公共成员。`01-PATTERNS.md` 引用的 trigger collector/collection 已在 D-13 下删除,只保留其优先级降序、逐项 await、接口先行与 logger 恢复约定;其“单一列表 + 单一 handler/env”数据形状无法表达 D-03 的异构来源,由 01-04 批准的 source-aware 契约明确取代,不得照搬该旧形状。`moverImpl.commonTrigger` 从 `event.getPointEvent(x,y)`、`loc.static.tileEvent()` 和 `loc.dynamics` 中建立来源记录D-06 要求 point 组先按 priority 降序,随后 static 与所有 dynamic 条目作为一个 tile 组按 priority 降序。每条记录携带自己的 `IBlockEventEnv`point 使用 `PointEvent` 与 null tiletile 使用 `TileEvent` 与实际 static/dynamic tile其余 state/trigger/heroLocator/triggerLocator/layer/map 保持真实值。执行器在调用 `event.execute` 前比较 `event.trigger` 与当前 env.trigger不匹配项不产生返回值、告警或 cut/reduce 影响。按照用户批准契约在整个匹配序列上保留现有 execute mode 与 reduce modeD-10并逐项 awaitD-09。公共接口只允许出现 01-04 明确批准的改动。</action>
<acceptance_criteria>
- 测试断言错误 trigger 的 execute 调用次数为 0
- 测试断言执行日志顺序为 point priority-desc 后 tile priority-desc包含全部 dynamics
- 每次执行捕获的 env.type/env.tile 与实际来源一致
- 延迟事件测试证明下一事件只在前一 Promise 兑现后执行
</acceptance_criteria>
<verify>
<automated>pnpm exec vitest run "packages-user/data-system/src/event/eventDispatch.test.ts" -t "source-aware matching dispatch"</automated>
<fails_when>Vitest 非零退出、未发现命名测试、错误 trigger 被执行、dynamic 缺失、顺序或环境断言失败</fails_when>
</verify>
<done>一个 OnEnter 调用按 D-06/D-08/D-09 执行 point/static/dynamic 的匹配事件并传入真实来源环境。</done>
</task>
<task type="auto" tdd="true">
<name>Task 2: 扩展 cut/reduce 与 enter/leave/hit 行为矩阵</name>
<files>
packages-user/data-system/src/event/executor.ts
packages-user/data-state/src/hero/moverImpl.ts
packages-user/data-system/src/event/eventDispatch.test.ts
</files>
<read_first>
- packages-user/data-system/src/event/executor.tsTask 1 后实现)
- packages-user/data-state/src/hero/moverImpl.tsTask 1 后来源收集)
- packages-user/data-system/src/event/types.tsEventExecuteMode/EventReduceMode
- packages-user/data-base/src/hero/types.tsenter/leave/hit 契约)
- packages-user/data-common/src/event/types.tsOnEnter/OnLeave/OnTouch
</read_first>
<behavior>
- CutIfFalsy/CutIfTruthy 在 point→tile 完整序列中短路,未匹配 trigger 不参与短路
- NoReduce/OrReduce/AndReduce 只折叠实际执行的匹配事件结果
- enter/leave/hit 分别传 OnEnter/OnLeave/OnTouch并使用既有 curr/next hero/trigger 坐标约定
- 未知 id 继续 logger.warn(171) 后执行后续合法事件
</behavior>
<action>扩充同一测试文件覆盖全部执行/折叠模式与三个移动钩子。若 Task 1 的实现需要内部提取排序或环境创建逻辑,只允许私有方法并添加完整 jsDoc不增加 EVT-03 所排除的通用工作流抽象。保持 `cannotEnter` 的已接受显式延期,不新增枚举值。确认 unknown id 恢复路径与 trigger filtering 的顺序:先解析 id命中事件后才判断 trigger只有实际执行结果进入 reduce/cut。修复测试暴露的局部实现问题不修改用户未批准接口。</action>
<acceptance_criteria>
- 三种 EventExecuteMode 与三种 EventReduceMode 的组合关键路径有行为断言
- enter/leave/hit 各有命名断言,事件 trigger 和坐标正确
- unknown id 后的合法匹配事件仍执行
- 测试与生产代码不引入 `as`、getter/setter 或新 built-in 函数
</acceptance_criteria>
<verify>
<automated>pnpm exec vitest run "packages-user/data-system/src/event/eventDispatch.test.ts"</automated>
<fails_when>Vitest 非零退出、任何 mode/reduce/移动钩子断言失败,或没有实际执行匹配事件的行为断言</fails_when>
</verify>
<done>执行器与移动钩子的 trigger、来源、dynamic、顺序、await、cut/reduce 行为均由一次性 Vitest 证明。</done>
</task>
</tasks>
<threat_model>
## Trust Boundaries
| Boundary | Description |
|----------|-------------|
| 地图绑定 id/trigger → EventExecutor → 游戏状态 | 错误匹配或来源环境可执行不应发生的游戏动作 |
## STRIDE Threat Register
| Threat ID | Category | Component | Severity | Disposition | Mitigation Plan |
|-----------|----------|-----------|----------|-------------|-----------------|
| T-01-07-01 | Tampering | EventExecutor 忽略 event.trigger | high | mitigate | execute 前严格匹配当前 trigger错误 trigger 调用次数测试为 0 |
| T-01-07-02 | Spoofing | point/dynamic 伪装为 static tile env | high | mitigate | 每个来源单独构造真实 BlockEventType/tile并断言对象身份 |
| T-01-07-03 | Denial of Service | 异步事件并行或短路范围错误 | medium | mitigate | 完整序列顺序 awaitmode/reduce 行为矩阵测试 |
| T-01-07-SC | Tampering | npm/pip/cargo installs | high | mitigate | 无安装任务,复用现有依赖 |
</threat_model>
<verification>
运行完整 `eventDispatch.test.ts`,再对四个修改文件运行 focused ESLint不把仓库既有全局 lint/type/cycle 失败作为本计划口径。
</verification>
<success_criteria>
- 只执行匹配 trigger 的事件
- point/static/all dynamics 全部按 D-06 顺序执行并收到真实 env
- D-10 cut/reduce 与 D-09 await 在完整序列上成立
- enter/leave/hit 的 EVT-02 行为有命名测试
</success_criteria>
<output>
Create `.planning/phases/01-event/01-07-SUMMARY.md` when done
</output>
## Artifacts this phase produces
- 用户批准的来源感知事件执行契约实现
- `EventExecutor` trigger filtering 与完整序列 cut/reduce
- `DefaultHeroMoveTopImpl` point/static/dynamic 来源收集
- `packages-user/data-system/src/event/eventDispatch.test.ts` 行为测试套件

View File

@ -0,0 +1,122 @@
---
phase: 01-event
plan: 08
type: execute
wave: 5
depends_on: [01-05]
files_modified:
- packages-user/data-common/src/store/eventStore.test.ts
autonomous: true
gap_closure: true
requirements: [EVT-01]
estimate:
tokens: 12000
raw_tokens: 12000
tasks: 1
confidence: low
must_haves:
truths:
- "通过 @user/data-common 公共桶取得 GameEventStore 后addEvent/getEvent 保持 id→event 行为D-04/D-11"
- "重复 id 仍调用 logger.warn(170, id) 并覆盖旧事件;未知 id 仍返回 null"
- "rawEvent 的当前 public Statement[] 别名、generic Promise<R> 与 event.ts 的现有 as Promise<R> 适配器仍是基线不被本计划声称为已修复D-07/D-08遵循 01-05-SUMMARY.md"
- "eventStore 当前循环依赖仍是已批准的 Phase 01 基线不被本计划声称为已消除D-11遵循 01-05-SUMMARY.md"
artifacts:
- path: "packages-user/data-common/src/store/eventStore.test.ts"
provides: "GameEventStore add/get/unknown/duplicate-warning 行为回归测试"
key_links:
- from: "@user/data-common public barrel"
to: "GameEventStore.addEvent/getEvent"
via: "public import and id lookup behavior test"
- from: "GameEventStore duplicate id"
to: "logger.warn(170, id)"
via: "warning spy before overwrite assertion"
---
<objective>
为现有 GameEventStore 保留一条可执行的行为回归证据;不把 rawEvent、Promise 适配器或 eventStore 循环依赖纳入实现范围。
Purpose: 01-05 已锁定兼容契约,本计划只验证仍有价值的存储行为,避免旧 01-08 计划把明确拒绝的修复重新列为交付目标。
Output: 一个从公共桶导入并覆盖 add/get/unknown/duplicate-warning 的 Vitest 文件。
</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-05-SUMMARY.md
@packages-user/data-common/src/store/eventStore.ts
@packages-user/data-common/src/store/types.ts
@packages-user/data-common/src/index.ts
@packages/common/src/logger.ts
</context>
<tasks>
<task type="tracer" tdd="true">
<name>Task 1: 固化公共桶 GameEventStore 的 add/get/重复告警回归</name>
<files>packages-user/data-common/src/store/eventStore.test.ts</files>
<behavior>
- Test 1: 从 @user/data-common 公共桶取得 GameEventStore 后,新增事件可由相同 id 取回
- Test 2: 未知 id 返回 null
- Test 3: 重复 id 调用 logger.warn(170, id),并由后写入事件覆盖先写入事件
- Test 4: 该测试不要求 check:circular 中的既有 eventStore 路径消失
</behavior>
<read_first>
- .planning/phases/01-event/01-05-SUMMARY.mdrawEvent 与循环依赖的锁定边界)
- packages-user/data-common/src/store/eventStore.ts实际 add/get/warn 行为)
- packages-user/data-common/src/store/types.tsIGameEventStore 契约)
- packages-user/data-common/src/index.ts公共桶导出路径
- packages/common/src/logger.tsNode 动态导入所需的 logger 全局量与 spy 方式)
</read_first>
<action>只创建 `eventStore.test.ts`。在动态导入公共桶前 stub 现有 logger 所需的 Node 测试全局量;用接口兼容的两个测试事件对象验证 add/get、unknown-null 与重复覆盖spy logger.warn 精确断言 code 170 和 id并在测试结束恢复 spy/global。不要修改 eventStore.ts、事件类型、任何 barrel import 或包边界;按 01-05-SUMMARY.md 保持 rawEvent 的 public Statement[] 别名、generic Promise&lt;R&gt;、现有 `as Promise&lt;R&gt;` 适配器与 eventStore 循环基线D-07/D-08/D-11</action>
<acceptance_criteria>
- 公共桶导入路径可运行并返回 GameEventStore
- add/get、未知 id、重复 id 覆盖均有断言
- duplicate warning 精确断言 logger.warn(170, id)
- 测试只新增 eventStore.test.tsrawEvent/cycle 仍作为未处理的批准基线
</acceptance_criteria>
<verify>
<automated>pnpm exec vitest run "packages-user/data-common/src/store/eventStore.test.ts"</automated>
<fails_when>Vitest 非零退出、公共桶导入失败、add/get/null 行为变化、重复覆盖失败或告警 code/id 不准确</fails_when>
</verify>
<done>GameEventStore 的公共导入、add/get、未知 id 和重复告警/覆盖行为由一个可运行回归测试保护,且未把 rawEvent 或循环依赖修复写成完成条件。</done>
</task>
</tasks>
<threat_model>
## Trust Boundaries
| Boundary | Description |
|----------|-------------|
| caller event id → GameEventStore map | 重复或未知 id 影响事件查找与覆盖结果 |
| data-common public barrel → logger initialization | Node 测试导入可能触发既有全局依赖 |
| eventStore imports → package dependency graph | 当前循环依赖是已批准的 Phase 01 baseline不在本计划修复 |
## STRIDE Threat Register
| Threat ID | Category | Component | Severity | Disposition | Mitigation Plan |
|-----------|----------|-----------|----------|-------------|-----------------|
| T-01-08-01 | Tampering | GameEventStore duplicate id | medium | mitigate | Regression test asserts warn(170, id) and last-write-wins behavior |
| T-01-08-02 | Denial of Service | Public barrel Node import | low | mitigate | Stub only the existing logger globals and exercise the real public import |
| T-01-08-03 | Denial of Service | eventStore circular imports | high | accept | Preserve the user-approved imports and record that cycle repair is deferred per 01-05-SUMMARY.md |
| T-01-08-SC | Tampering | npm/pip/cargo installs | high | mitigate | No package installation task; use the existing Vitest dependency |
</threat_model>
<verification>
只运行 eventStore.test.ts 的 focused Vitest 命令;不运行 cycle-removal gate也不把 rawEvent alias/cache/no-as 或 cycle repair 当作本计划验证结果。
</verification>
<success_criteria>
- add/get/unknown/duplicate-warning 行为有真实公共桶回归测试
- 生产 eventStore、rawEvent、类型适配器、imports 与 package boundaries 不在本计划修改范围
- 01-05 已批准的 rawEvent 与 eventStore cycle deferrals 在计划中明确保留
</success_criteria>
<output>
Create `.planning/phases/01-event/01-08-SUMMARY.md` when done
</output>

View File

@ -0,0 +1,166 @@
---
phase: 01-event
plan: 09
type: execute
wave: 7
depends_on: [01-04, 01-10]
files_modified:
- packages-user/data-base/src/map/gameMap.ts
- packages-user/data-base/src/map/mapLifecycle.test.ts
autonomous: true
gap_closure: true
requirements: [EVT-01]
estimate:
tokens: 16000
raw_tokens: 16000
tasks: 2
confidence: low
must_haves:
truths:
- "只含 dirty pointEvents、而 map matrix/static/dynamic 内容为空的 IMapLayerSave 不会被 GameMap.saveState 丢弃D-12"
- "GameMap.saveState 仍按 layer zIndex 保留该图层,并保留 01-04/01-10 批准的 index → priority → eventId 数据"
- "点事件聚合可在没有生产 serialized-event registration、map-id binding 或 eventStore 初始化 seam 的情况下独立验证"
- "生产事件注册仍明确延期,不由本计划声称已实现(遵循 01-04-SUMMARY.mdD-04/D-11 的 id-only 存储边界保持不变)"
artifacts:
- path: "packages-user/data-base/src/map/gameMap.ts"
provides: "isEmptyLayerSave 对非空 pointEvents 的保留判断"
- path: "packages-user/data-base/src/map/mapLifecycle.test.ts"
provides: "GameMap point-event-only save aggregation behavior evidence"
key_links:
- from: "MapLayer.saveState().pointEvents"
to: "GameMap.isEmptyLayerSave"
via: "non-empty point event map keeps the layer in the map save"
- from: "GameMap.saveState"
to: "IGameMapSave.layers[zIndex]"
via: "point-event-only layer aggregation"
---
<objective>
独立完成 GameMap 对仅含点事件图层存档的聚合保留不实现生产事件注册、map-id binding、rawEvent 改造或 eventStore cycle 修复。
Purpose: 01-10 已完成点事件自身的 dirty/save/load/resize 生命周期,剩余独立缺口是 GameMap 的空层判断会丢弃只有 pointEvents 的 layer save。
Output: GameMap 空层判断修正及 Low/High compression 的点事件独立聚合回归覆盖。
</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-04-SUMMARY.md
@.planning/phases/01-event/01-10-SUMMARY.md
@packages-user/data-base/src/map/gameMap.ts
@packages-user/data-base/src/map/types.ts
@packages-user/data-base/src/map/mapLayer.ts
@packages-user/data-base/src/map/mapLifecycle.test.ts
</context>
<tasks>
<task type="tracer" tdd="true">
<name>Task 1: 贯通 dirty pointEvents → MapLayer save → GameMap layer 聚合</name>
<files>
packages-user/data-base/src/map/gameMap.ts
packages-user/data-base/src/map/mapLifecycle.test.ts
</files>
<behavior>
- Test 1: 在不改变 map matrix 的情况下修改一个坐标点事件
- Test 2: LowCompression 的 layer save 仅含 pointEvents 时GameMap.saveState 仍保留对应 zIndex
- Test 3: 保存出的 pointEvents 仍是 index → priority → eventId 结构
- Test 4: 测试不调用任何生产 event registration 或 map-id binding seam
</behavior>
<read_first>
- .planning/phases/01-event/01-04-SUMMARY.mdpointEvents 精确字段、dirty 独立性与 id-only 边界)
- .planning/phases/01-event/01-10-SUMMARY.md已完成的 MapLayer 生命周期与 deferred map aggregation
- packages-user/data-base/src/map/gameMap.ts当前 isEmptyLayerSave 与 saveState
- packages-user/data-base/src/map/mapLayer.tspointEvents 生成与 compression save
- packages-user/data-base/src/map/mapLifecycle.test.ts现有 Node fixture 与 point lifecycle 测试)
</read_first>
<action>先在现有 mapLifecycle fixture 中写失败回归,直接通过已有 MapState/GameMap/MapLayer 路径设置 dirty point event再调用 `GameMap.saveState(SaveCompression.LowCompression)`确认矩阵、staticBlocks、dynamicBlocks 均不能成为保留该层的理由,唯一有效内容是 non-empty `pointEvents`。随后只在 `GameMap.isEmptyLayerSave` 增加 pointEvents 非空判断;不改变 `IMapLayerSave.pointEvents` 的嵌套形状、不复制生产注册 seam、不向 CoreState/eventStore 写入事件D-12生产 registration 按 01-04-SUMMARY.md 延期)。</action>
<acceptance_criteria>
- LowCompression 下仅有 dirty pointEvents 的 layer 被 GameMap.saveState 保留
- 返回层使用原 zIndexpointEvents 保持 index → priority → eventId
- 验证路径只依赖已完成的 MapLayer lifecycle不依赖生产 event registration
</acceptance_criteria>
<verify>
<automated>pnpm exec vitest run "packages-user/data-base/src/map/mapLifecycle.test.ts" -t "map saves layers containing only point events"</automated>
<fails_when>Vitest 非零退出、命名测试未发现、point-event-only layer 被丢弃、zIndex/pointEvents 结构错误或测试绕过 GameMap.saveState</fails_when>
</verify>
<done>一个真实 dirty point event 通过 MapLayer.saveState 进入 GameMap.saveState 的 layer map且没有引入生产注册路径。</done>
</task>
<task type="auto">
<name>Task 2: 扩展点事件独立聚合的 HighCompression 回归</name>
<files>packages-user/data-base/src/map/mapLifecycle.test.ts</files>
<read_first>
- packages-user/data-base/src/map/mapLifecycle.test.tsTask 1 的 map-level regression fixture
- packages-user/data-base/src/map/gameMap.ts修正后的空层判定
- packages-user/data-common/src/save/types.tsSaveCompression 枚举来源,如测试需要)
</read_first>
<action>沿用 Task 1 的真实 fixture补充 HighCompression 的 point-event-only 聚合断言,并明确空 matrix dirty 与点事件 dirty 相互独立;不要把 NoCompression 的 fullMap 保留路径当作本缺口证据。测试仍只观察 GameMap.saveState 返回的 zIndex 与 pointEvents不创建或注册 GameEvent不修改 eventStore、CoreState、rawEvent 或任何 imports/package boundary。</action>
<acceptance_criteria>
- HighCompression 下仅有 pointEvents 的 layer 同样被保留
- Low/High 两条压缩路径都证明 point-event dirty 独立于 map-matrix dirty
- 测试不要求 serialized registration、map-id binding 或 eventStore cycle 改变
</acceptance_criteria>
<verify>
<automated>pnpm exec vitest run "packages-user/data-base/src/map/mapLifecycle.test.ts" -t "map saves layers containing only point events"</automated>
<fails_when>Vitest 非零退出、HighCompression 路径未覆盖、pointEvents 被丢弃或测试依赖 deferred production registration</fails_when>
</verify>
<done>Low/High compression 的 GameMap point-event-only aggregation 都有命名行为断言,且延期边界仍被测试与计划明确排除。</done>
</task>
</tasks>
<threat_model>
## Trust Boundaries
| Boundary | Description |
|----------|-------------|
| dirty point-event view → layer save | 点事件变更可能在 map matrix 未 dirty 时被错误丢弃 |
| layer save → GameMap save aggregation | 空层判断可能丢失合法 pointEvents 数据 |
| serialized event registration → map id binding | 该生产 seam 是用户批准的延期边界,不由本计划引入 |
## STRIDE Threat Register
| Threat ID | Category | Component | Severity | Disposition | Mitigation Plan |
|-----------|----------|-----------|----------|-------------|-----------------|
| T-01-09-01 | Tampering | GameMap.isEmptyLayerSave | high | mitigate | Low/High focused tests assert pointEvents independently retain the zIndex layer |
| T-01-09-02 | Information Disclosure | pointEvents save shape | medium | mitigate | Assert the approved index → priority → eventId nesting without serializing event bodies |
| T-01-09-03 | Tampering | deferred production registration boundary | high | accept | Do not add a registration API or map-id binding; preserve the 01-04 decision record |
| T-01-09-SC | Tampering | npm/pip/cargo installs | high | mitigate | No package installation task; use existing Vitest and map modules |
</threat_model>
<verification>
运行 mapLifecycle.test.ts 中 point-event-only aggregation 的 focused Vitest 命令,覆盖 LowCompression 与 HighCompression不运行或要求 serialized registration、rawEvent/cycle 修复。
</verification>
<success_criteria>
- GameMap 不再丢弃仅含 dirty pointEvents 的 layer save
- 点事件保存结构、zIndex 与 map-matrix dirty 独立性由行为测试证明
- 生产 event registration/map-id binding、rawEvent contract 与 eventStore circular baseline 仍显式延期
</success_criteria>
<output>
Create `.planning/phases/01-event/01-09-SUMMARY.md` when done
</output>
## Multi-Source Coverage Audit
| SOURCE | ID | Feature / Requirement | Plan | Status | Notes |
|--------|----|-----------------------|------|--------|-------|
| GOAL | — | blockly 式事件驱动简单流程 | 01-04, 01-06, 01-07, 01-08, 01-09, 01-10 | COVERED WITH LOCKED DEFERRALS | Existing plans cover available event data/dispatch/lifecycle; production registration remains explicitly deferred by 01-04. |
| REQ | EVT-01 | 数据/序列化接口与事件存储 | 01-04, 01-06, 01-08, 01-09, 01-10 | COVERED | Store behavior regression and point-event aggregation are executable; serialized production registration is not claimed. |
| REQ | EVT-02 | 踩踏事件流程 | 01-06, 01-07, 01-10 | COVERED BY EXISTING PLANS | Revised 01-09 does not add a production registration prerequisite. |
| REQ | EVT-03 | 初学者的简单事件抽象 | 01-07, 01-08 | COVERED | No new generic workflow or built-in registration surface is planned. |
| RESEARCH | — | No new dependencies; use existing Anon Tokyo/Vitest | 01-08, 01-09 | COVERED | No package install task. |
| RESEARCH | — | External map event persistence follows approved point-event lifecycle | 01-09 | COVERED | Aggregation consumes the 01-10 layer save output without changing its contract. |
| CONTEXT | D-01..D-06 | Mixed bindings, id references, defaults, ordering | 01-06, 01-07, 01-10 | COVERED BY EXISTING PLANS | No revised task changes binding or dispatch semantics. |
| CONTEXT | D-07..D-08 | Statement[] and execute(param, env) | 01-05, 01-08 | DEFERRED BASELINE | Preserve current rawEvent aliasing, Promise&lt;R&gt;, and existing adapters per 01-05; no cache-safety/no-as implementation is claimed. |
| CONTEXT | D-11 | eventStore id map, no save, duplicate warning | 01-08 | COVERED + CYCLE DEFERRED | Behavior is regression-tested; circular imports remain the approved baseline. |
| CONTEXT | D-12 | Independent point-event dirty/save/load/resize | 01-10, 01-09 | COVERED | 01-09 adds only GameMap layer aggregation. |
| CONTEXT | D-13 | Remove old ITrigger symbols | 01-02, 01-03 | COVERED BY EXISTING PLANS | No revised task reopens the old system. |
| CONTEXT | registration decision | Serialized production registration/map-id binding | 01-04, 01-09 | DEFERRED BY LOCKED DECISION | No registration API or production registration seam is added; 01-09 is runnable without it. |

View File

@ -0,0 +1,180 @@
---
phase: 01-event
plan: 10
type: execute
wave: 5
depends_on: [01-04]
files_modified:
- packages-user/data-base/src/map/types.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/mapLifecycle.test.ts
autonomous: true
gap_closure: true
requirements: [EVT-01, EVT-02, EVT-03]
estimate:
tokens: 28000
raw_tokens: 28000
tasks: 2
confidence: low
must_haves:
truths:
- "StaticTile/DynamicTile 构造与 set 均从 ITileRawData.events 建立默认 id 基准;不保留动态覆盖或读取省略 events 的纯动态图块存档时恢复该基准D-05/D-12"
- "绑定在坐标上的点事件在静态/动态转换及动态图块实际移动后仍只存在于原坐标D-03"
- "图块和点事件保存为独立 Map 快照;点事件按 01-04 决定参与 dirty/save/load/reset/resize 生命周期D-03/D-12仅含点事件的 map save 聚合由 01-09 最终接线"
artifacts:
- path: "packages-user/data-base/src/map/mapLifecycle.test.ts"
provides: "默认事件、快照、转换/移动坐标所有权及点事件生命周期行为证据"
- path: "packages-user/data-base/src/map/mapLayer.ts"
provides: "点事件完整生命周期与默认事件恢复"
key_links:
- from: "StaticTile/DynamicTile"
to: "ITileRawData.events"
via: "constructor/set default baseline copy and markPure"
- from: "IMapLayerSave"
to: "MapLayer.pointEvents"
via: "user-approved 01-04 persistence contract"
---
<objective>
修复图块默认事件、转换/移动坐标所有权、稳定保存快照与点事件持久化生命周期。
Purpose: 让 D-03/D-05/D-12 在图块变化、存读档和尺寸变化中保持用户批准的准确语义。
Output: 地图生命周期实现修复及独立的 `mapLifecycle.test.ts` 行为矩阵。
</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-04-SUMMARY.md
@.planning/phases/01-event/01-VERIFICATION.md
@.planning/phases/01-event/01-01-SUMMARY.md
@packages-user/data-common/src/store/types.ts
@packages-user/data-base/src/map/types.ts
@packages-user/data-base/src/map/eventView.ts
@packages-user/data-base/src/map/mapLayer.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
</context>
<tasks>
<task type="tracer" tdd="true">
<name>Task 1: 恢复图块默认事件、稳定快照与转换后移动不变量</name>
<files>
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/mapLifecycle.test.ts
</files>
<read_first>
- packages-user/data-common/src/store/types.tsITileRawData.events
- packages-user/data-base/src/map/tile.ts当前空 LayerEventView 基准)
- packages-user/data-base/src/map/staticTile.tsset 与 live-map save
- packages-user/data-base/src/map/dynamicTile.tsconstructor/set/setPos 与 live-map save
- packages-user/data-base/src/map/mapLayer.tscreateDynamic/transfer/syncStaticEvent/updateDynamicTile
- .planning/phases/01-event/01-VERIFICATION.md gaps 3、4 与第三个 behavior_unverified_item
</read_first>
<behavior>
- 新 static/dynamic tile 与每次 set(num) 都复制对应 ITileRawData.events 并 markPure
- 未改动的 dynamic tile 保存时省略 events该存档读回后仍含 raw 默认事件且 dirty=false随后变更再恢复默认内容时重新回到 dirty=false
- 含覆盖 events 的 dynamic 存档读回后相对 raw 默认基准保持 dirty=true下一次保存不会丢失覆盖
- 自定义覆盖后 saveState 返回独立 Map后续编辑不改变先前 save
- transferToStatic(..., false) 恢复目标静态 tile 的默认事件true 保留 dynamic 覆盖并保持 dirty
- 在原坐标绑定点事件、转换为动态图块并把动态图块移动到新坐标后,点事件只在原坐标查询到
</behavior>
<action>先补失败测试。按现有一个文件一个类的结构,在 `StaticTile``DynamicTile` 各自用有 jsDoc 的私有方法完成相同且局部可读的默认事件恢复clear 当前 view、从当前 raw 的 `events` 逐项复制 priority/id、最后 markPure构造器与 `set(num)` 在 raw 更新后调用。不要在抽象基类构造器调用 abstract `raw()`。修正 `DynamicTile.loadState``save.events` 缺失时调用同一默认恢复路径,不能把纯动态图块清成空 view存在保存覆盖时先调用默认恢复建立 raw 基准,再 clear 当前内容并逐项写入完整保存 Map且不得再次 markPure使 D-05 raw 默认内容继续作为 D-12 dirty 比较基准。两个 `saveState` 对 dirty events 使用 `new Map(this.tileEvent().get())`,保证历史快照不再引用内部 store。调整 `MapLayer` 静态/动态转换:先让目标静态 tile 根据 tile number 建立 D-05 默认基准;`keepEvent=true` 再以动态覆盖替换,`false` 保留默认基准,不能留下空 view。加入 D-03 移动不变量测试:在旧坐标写入 point id调用 `transferToDynamic` 完成转换,再通过动态图块既有 `setPos` 移至另一个图内坐标;分别查询 `getPointEvent` 的旧坐标和新坐标,断言 id 仅存在于旧坐标。点事件视图不参与 tile 转换或动态图块位置索引更新。</action>
<acceptance_criteria>
- static/dynamic 构造和 set 的测试均看到默认 id 且 dirty=false
- untouched dynamic 的 `saveState()` 省略 events`loadState()` 后默认 id 仍存在且 dirty=false覆盖后恢复同一默认 Map 也回到 dirty=false
- 含覆盖 events 的 dynamic 存档 load 后 dirty=true立即再次 save 仍包含同一覆盖 Map
- 修改 tile view 后旧 save.events 内容保持不变
- keepEvent=false 测试恢复默认 idtrue 测试保留动态 id
- 转换并实际移动 dynamic tile 后,旧坐标 point view 仍含 id新坐标 point view 不含 id
</acceptance_criteria>
<verify>
<automated>pnpm exec vitest run "packages-user/data-base/src/map/mapLifecycle.test.ts" -t "tile defaults snapshots conversion and movement"</automated>
<fails_when>Vitest 非零退出、默认事件为空、untouched dynamic 存读档后默认事件消失或 dirty 基准改变、快照随内部 Map 变化、keepEvent=false 未恢复 raw 基准,或移动 dynamic tile 后 point id 出现在新坐标/离开旧坐标</fails_when>
</verify>
<done>D-05 默认事件在 static/dynamic 构造、set、纯存档 round-trip 与转换中保持 raw dirty 基准保存快照稳定D-03 点事件在转换及随后动态图块移动后仍固定于原坐标。</done>
</task>
<task type="auto" tdd="true">
<name>Task 2: 按用户批准契约补齐点事件 dirty/save/load/reset/resize</name>
<files>
packages-user/data-base/src/map/types.ts
packages-user/data-base/src/map/mapLayer.ts
packages-user/data-base/src/map/mapLifecycle.test.ts
</files>
<read_first>
- .planning/phases/01-event/01-04-SUMMARY.md点事件存档准确字段/类型及生命周期决定,权威)
- packages-user/data-base/src/map/types.tsIMapLayerSave/ILayerEventView
- packages-user/data-base/src/map/eventView.ts现有 clear/set/markPure 基准能力)
- packages-user/data-base/src/map/mapLayer.ts三种 save/load 与 resize/resize2
- .planning/phases/01-event/01-VERIFICATION.md behavior_unverified_items
</read_first>
<behavior>
- markPure 后 set/delete/clear 只有内容与基准不同时 dirty=true完全恢复基准后 dirty=false
- 点事件按 01-04 契约在三种压缩级别保存独立快照load 无字段时恢复 raw 基准而非保留旧运行时值
- resize 与 resize2 各自严格呈现 01-04-SUMMARY.md 记录的点事件保留、裁剪或清空结果
</behavior>
<action>严格实施 `01-04-SUMMARY.md` 中用户批准的字段、类型和恢复方式,不替换名称或扩展接口。把 point view 的 dirty 纳入 `MapLayer.dirty()`;三种 save 路径按批准契约只写需要保存的坐标,并逐层复制 Map三种 load 路径先清理运行时点事件,再恢复基准与保存覆盖。对 `resize``resize2` 只实现 SUMMARY 逐字记录的各自语义,不根据方法名、当前实现或 planner 假设选择哪一个保留/裁剪/清空;若 SUMMARY 未记录其中任一方法则停止执行并回到 01-04 决策。使用现有 `ILayerEventView` 的 clear/set/markPure 完成基准恢复,不修改 `eventView.ts` 或新增公共成员。仅含点事件的 layer save 聚合判断由已拥有最终 integration test 的 01-09 Task 2 接线。测试覆盖 D-12 状态转换、三种压缩、缺省 load以及两个 resize 方法各自获批的 D-03 结果;不得使用 `as`、getter/setter 或未批准公共方法。</action>
<acceptance_criteria>
- 01-04 批准的存档字段在接口与三种 save/load 中一致
- dirty 状态转换测试覆盖 set/delete/clear 及恢复基准
- 保存后编辑运行时 point view 不改变旧 save
- load/reset 与两个 resize 方法的测试分别证明结果与 01-04 记录的准确语义一致
</acceptance_criteria>
<verify>
<automated>pnpm exec vitest run "packages-user/data-base/src/map/mapLifecycle.test.ts" -t "point event lifecycle"</automated>
<fails_when>Vitest 非零退出、批准字段未被发现、任一压缩级别丢数据、快照可变、dirty 错误,或 resize 行为偏离 01-04 记录的决定</fails_when>
</verify>
<done>点事件按 D-03/D-12 与用户批准接口完成 dirty、存读档、基准恢复和尺寸变化生命周期。</done>
</task>
</tasks>
<threat_model>
## Trust Boundaries
| Boundary | Description |
|----------|-------------|
| 运行时事件视图 → SaveSystem 快照 | 内部可变 Map 不得泄漏到历史存档 |
| 01-04 用户生命周期决定 → MapLayer | resize/load 语义不得由执行器预选或颠倒 |
## STRIDE Threat Register
| Threat ID | Category | Component | Severity | Disposition | Mitigation Plan |
|-----------|----------|-----------|----------|-------------|-----------------|
| T-01-10-01 | Tampering | live Map 改写历史 save | high | mitigate | tile/point 保存均 new Map 快照,保存后变更测试阻断回归 |
| T-01-10-02 | Spoofing | 动态图块位置冒充点事件所有权 | high | mitigate | 转换后实际 setPos并同时断言旧/新坐标 point view |
| T-01-10-03 | Tampering | planner 预选 resize/resize2 语义 | high | mitigate | 只读取并逐字实施 01-04-SUMMARY缺任一语义立即停止 |
| T-01-10-SC | Tampering | npm/pip/cargo installs | high | mitigate | 无安装任务,复用现有 Vitest 4.0.18 |
</threat_model>
<verification>
每个任务运行 `mapLifecycle.test.ts` 的命名行为分组;计划完成后运行完整文件与五个修改 TS 文件的 focused ESLint。
</verification>
<success_criteria>
- 图块默认事件在构造、set 与转换中保持正确基准
- 保存值为稳定快照,转换后实际移动不带走点事件
- 点事件按 01-04 决定完整参与 dirty/save/load/reset/resize
</success_criteria>
<output>
Create `.planning/phases/01-event/01-10-SUMMARY.md` when done
</output>
## Artifacts this phase produces
- `packages-user/data-base/src/map/mapLifecycle.test.ts`:默认事件、快照、移动与生命周期行为测试
- `StaticTile`/`DynamicTile`:默认事件基准与独立保存快照
- `MapLayer`/`IMapLayerSave`用户批准的点事件持久化、dirty、reset、resize 实现

View File

@ -0,0 +1,164 @@
---
phase: 01-event
plan: 11
type: execute
wave: 8
depends_on: [01-06, 01-07, 01-09, 01-10]
files_modified:
- packages-user/data-state/src/core.ts
- packages-user/data-state/src/coreEventLayer.test.ts
- packages-user/data-system/src/event/eventDispatch.test.ts
autonomous: true
gap_closure: true
requirements: [EVT-02]
estimate:
tokens: 11000
raw_tokens: 11000
tasks: 2
confidence: low
must_haves:
truths:
- "CoreState 的 legacy 地图初始化为每个地图选择 alias=event 的图层DefaultHeroMoveTopImpl 不再因 eventLayer 为空而跳过踩踏分派。"
- "已有 eventStore id 绑定的踩踏事件仍沿用 01-04 批准的 IGameEventInvocation 全序列、来源环境、触发器过滤、await、cut/reduce 语义;点事件仍为 PointEvent/tile=null静态与动态图块仍携带实际 tile。"
- "点事件仍按 01-04 批准的 index → priority → eventId 存档契约独立持久化;本计划只验证可达性,不改变点事件生命周期实现。"
- "D-03:、D-04:、D-06:、D-11:、D-12: 的 id-only、坐标归属、顺序、非事件本体存档和 dirty 语义保持不变。"
- "本计划承接已完成的 D-01:、D-02:、D-05:、D-07:、D-08:、D-09:、D-10:、D-13:对象与地图格混合绑定、EventTrigger、图块默认事件、Anon Tokyo Statement[] 执行、await、before 语义和旧 ITrigger 删除均不回退。"
artifacts:
- path: "packages-user/data-state/src/core.ts"
provides: "legacy 地图初始化将 event alias 图层设为 GameMap.eventLayer"
contains: "setEventLayer"
- path: "packages-user/data-state/src/coreEventLayer.test.ts"
provides: "CoreState legacy map initializer 的 event-layer wiring 回归证据"
- path: "packages-user/data-system/src/event/eventDispatch.test.ts"
provides: "raw-bound event id 经 event layer 到 mover/executor 的踩踏行为证据"
key_links:
- from: "CoreState.initMapState"
to: "GameMap.setEventLayer"
via: "event layer alias assignment in the production legacy initializer"
- from: "GameMap.eventLayer"
to: "DefaultHeroMoveTopImpl.commonTrigger"
via: "the existing source-aware invocation sequence"
- from: "MapLayer pointEvents save"
to: "GameMap.saveState"
via: "the existing point-event-only aggregation contract from 01-04/01-09"
---
<objective>
闭合验证报告中仍然有效的唯一 Phase 01 实现缺口legacy 地图初始化创建事件图层后没有把它设置到 GameMap.eventLayer导致生产踩踏入口提前返回。
Purpose: 让已经实现并验证过的 raw point/tile ingestion、source-aware dispatch 和 point-event persistence 在实际 CoreState 地图初始化路径上可达,而不改变任何已批准的公共契约。
Output: CoreState event-layer wiring、针对 legacy 初始化的回归测试,以及一条使用已有 eventStore id 的踩踏行为回归。
Scope boundary: 保持 CoreState 现有初始化 TODO、事件 id-only 存储和现有 eventStore 行为;不增加 serialized event registration 或 map-id binding不修改 rawEvent aliasing/cache/Promise/as 基线,也不修改 eventStore circular-dependency 基线。
</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/STATE.md
@.planning/ROADMAP.md
@.planning/REQUIREMENTS.md
@.planning/phases/01-event/01-VERIFICATION.md
@.planning/phases/01-event/01-04-SUMMARY.md
@.planning/phases/01-event/01-06-SUMMARY.md
@.planning/phases/01-event/01-07-SUMMARY.md
@.planning/phases/01-event/01-09-SUMMARY.md
@.planning/phases/01-event/01-10-SUMMARY.md
@packages-user/data-state/src/core.ts
@packages-user/data-base/src/map/gameMap.ts
@packages-user/data-base/src/map/mapState.ts
@packages-user/data-state/src/hero/moverImpl.ts
@packages-user/data-system/src/event/executor.ts
@packages-user/data-system/src/event/eventDispatch.test.ts
@packages-user/data-base/src/map/mapLifecycle.test.ts
</context>
<tasks>
<task type="tracer">
<name>Task 1: 贯通 CoreState legacy 初始化到可用 eventLayer</name>
<files>
packages-user/data-state/src/core.ts
packages-user/data-state/src/coreEventLayer.test.ts
</files>
<read_first>
- packages-user/data-state/src/core.tsinitMapState 的 legacy 图层创建顺序与 alias 设置)
- packages-user/data-base/src/map/gameMap.tssetEventLayer 的所有权校验)
- packages-user/data-base/src/map/mapState.tsfromRaw 已采用的 event alias 选择方式)
- packages-user/data-state/src/hero/moverImpl.tseventLayer 为空时的现有保护与调用入口)
- .planning/phases/01-event/01-04-SUMMARY.md仅执行批准的 event-layer/source-aware 边界;承接 D-01: D-02: D-03: D-04: D-05: D-06: D-07: D-08: D-09: D-10: D-11: D-12: D-13:
</read_first>
<behavior>
- legacy initializer 为每个创建的地图把 alias=event 的实际图层传给 GameMap.setEventLayer
- event layer 的 zIndex、alias 和地图矩阵初始化保持原有行为
- CoreState 仍保留现有初始化 TODO测试使用预先存在的 event id不要求生产注册事件定义
</behavior>
<action>`CoreState.initMapState` 中完成已有 event 图层的生产装配:在 `state.setLayerAlias(event, 'event')` 后调用 `state.setEventLayer(event)`并保持其余图层顺序、zIndex、地图矩阵与 compareWith 输入不变。新增一个 Node/Vitest 回归夹具,直接覆盖 legacy initializer 的可观察结果(可通过受控 fake map/state 调用该现有私有初始化路径,避免构造完整浏览器运行时),断言每个地图的 `eventLayer` 是 event alias 对应的同一图层。按 D-03/D-04/D-11 保持事件来源为坐标/图块上的 id只接通图层选择不新增注册或 map-id 绑定入口。</action>
<verify>
<automated>pnpm exec vitest run "packages-user/data-state/src/coreEventLayer.test.ts"</automated>
<automated>pnpm exec eslint "packages-user/data-state/src/core.ts" "packages-user/data-state/src/coreEventLayer.test.ts"</automated>
</verify>
<done>CoreState legacy 地图初始化完成后,所有包含 event alias 图层的地图均有可用 eventLayer且回归测试证明该连接没有改变既有地图装配或事件 id 边界。</done>
</task>
<task type="auto">
<name>Task 2: 固化 eventLayer 可达的 source-aware 踩踏闭环</name>
<files>packages-user/data-system/src/event/eventDispatch.test.ts</files>
<read_first>
- packages-user/data-system/src/event/eventDispatch.test.ts现有 point/static/dynamic 来源、触发器和 await 夹具)
- packages-user/data-system/src/event/executor.ts触发器过滤与 full-sequence cut/reduce
- packages-user/data-state/src/hero/moverImpl.tseventLayer 到 invocation list 的连接)
- .planning/phases/01-event/01-07-SUMMARY.md批准的 IGameEventInvocation contract
- .planning/phases/01-event/01-09-SUMMARY.mdpointEvents map-level save 边界)
</read_first>
<behavior>
- raw map 中已绑定的 point event id 在 mover.enter 后被执行一次
- point event 收到 PointEvent 与 tile=null且 triggerLocator/heroLocator 保持移动入口约定
- 测试可同时证明已有 static/dynamic 来源顺序不被 eventLayer wiring 改写
</behavior>
<action>在现有 `eventDispatch.test.ts` 的真实 MapState raw fixture 上补一条最窄闭环:从坐标绑定的 event id、实际 `map.eventLayer`、`DefaultHeroMoveTopImpl.enter` 到 `EventExecutor`,向测试 event store 手动放入一个匹配 `EventTrigger.OnEnter` 的事件并断言它执行一次且收到批准的 PointEvent/tile=null 环境。复用现有 source-aware invocation 夹具覆盖静态/动态图块顺序与实际 tile 身份;不要把 point 与 tile 合并为共享环境,不要改变 `IGameEventInvocation`、pointEvents 存档形状或 executor 的 trigger/cut/reduce 语义。测试只提供已存在的 id 绑定,不创建生产注册 API。</action>
<verify>
<automated>pnpm exec vitest run "packages-user/data-state/src/coreEventLayer.test.ts" "packages-user/data-system/src/event/eventDispatch.test.ts"</automated>
<automated>pnpm exec eslint "packages-user/data-system/src/event/eventDispatch.test.ts"</automated>
</verify>
<done>一条命名行为测试证明已有 event id 可从地图事件层进入 mover/executor 并按批准的 source-aware contract 执行point/static/dynamic 来源、过滤、顺序和环境语义均保持可验证。</done>
</task>
</tasks>
<threat_model>
## Trust Boundaries
| Boundary | Description |
|----------|-------------|
| legacy floor data → CoreState map initialization | Existing floor/map structures determine which layer is designated as the movement event layer. |
| event-layer bindings → EventExecutor | Event ids and event trigger metadata are consumed by the existing source-aware dispatch path. |
## STRIDE Threat Register
| Threat ID | Category | Component | Severity | Disposition | Mitigation Plan |
|-----------|----------|-----------|----------|-------------|-----------------|
| T-01-01 | Tampering | CoreState legacy layer selection | low | mitigate | Select only the already-created layer whose alias is `event`; retain `GameMap.setEventLayer` ownership validation and do not accept a new external registration input. |
| T-01-02 | Denial of Service | EventExecutor invocation path | medium | mitigate | Reuse the existing 01-07 trigger filter before execute/cut/reduce and verify the regression uses source-correct environments without bypassing the executor. |
| T-01-SC | Tampering | package-manager installs | low | accept | No package is installed or changed by this gap-closure plan. |
</threat_model>
<verification>
Run the two focused Vitest commands from the tasks, then run `pnpm exec eslint "packages-user/data-state/src/core.ts" "packages-user/data-state/src/coreEventLayer.test.ts" "packages-user/data-system/src/event/eventDispatch.test.ts"`. Confirm the diff is limited to the three plan files and that the existing approved source-aware dispatch and point-event persistence contracts remain unchanged. Repository-wide type/lint/circular baselines already recorded in 01-05 and 01-VERIFICATION remain outside this gap.
</verification>
<success_criteria>
- CoreState legacy map initialization assigns the event alias layer to GameMap.eventLayer.
- A named behavior test reaches EventExecutor from an existing map-bound event id through the mover path.
- Point/static/dynamic source environments and point-event persistence semantics remain the approved 01-04/01-07/01-09 contracts.
- No serialized production registration/map-id binding, rawEvent/cache/Promise/as cleanup, or eventStore cycle repair is introduced.
</success_criteria>
<output>
Create `.planning/phases/01-event/01-11-SUMMARY.md` when done.
</output>

View File

@ -0,0 +1,189 @@
---
phase: 01-event
plan: 12
type: execute
wave: 9
depends_on: [01-11]
files_modified:
- packages-user/data-system/src/event/executor.ts
- packages-user/data-system/src/event/eventDispatch.test.ts
- packages-user/data-base/src/map/eventPath.test.ts
- packages-user/data-base/src/map/mapLifecycle.test.ts
- packages-user/data-base/src/map/gameMap.ts
- packages-user/data-common/src/store/eventStore.test.ts
autonomous: true
gap_closure: true
requirements: [EVT-01, EVT-02, EVT-03]
estimate:
tokens: 14000
raw_tokens: 14000
tasks: 2
confidence: low
must_haves:
truths:
- "Phase-owned TypeScript diagnostics are removed from EventExecutor and the focused event/map fixtures without changing runtime behavior, public contracts, or assertions."
- "The repository's focused Prettier/ESLint gate accepts the current Phase 01 files with CRLF line endings and no semantic formatting drift."
- "The existing event contracts and source-aware dispatch remain intact: D-01, D-02, D-03, D-04, D-05, D-06, D-07, D-08, D-09, D-10, D-11, D-12, and D-13 are preserved while this plan only repairs imports, fixture typing, and formatting."
- "The latest verification's only actionable categories are closed; the compile-cache behavior-unverified note is not expanded into a behavior change or new scope here."
artifacts:
- path: "packages-user/data-system/src/event/executor.ts"
provides: "The existing IBlockEventEnv generic resolves through its declared data-base import."
- path: "packages-user/data-system/src/event/eventDispatch.test.ts"
provides: "Type-correct source-aware dispatch fixtures using the existing invocation and enum contracts."
- path: "packages-user/data-base/src/map/eventPath.test.ts"
provides: "Explicitly typed malformed raw-event mutation cases."
- path: "packages-user/data-base/src/map/mapLifecycle.test.ts"
provides: "Fixture calls typed against the existing resizable/saveable map interfaces."
- path: "packages-user/data-base/src/map/gameMap.ts"
provides: "Current map implementation formatted with repository CRLF/Prettier settings."
- path: "packages-user/data-common/src/store/eventStore.test.ts"
provides: "Current event-store regression formatted with repository CRLF/Prettier settings."
key_links:
- from: "packages-user/data-system/src/event/executor.ts"
to: "packages-user/data-base/src/map/types.ts"
via: "IBlockEventEnv import used by the existing getEvent generic"
- from: "packages-user/data-system/src/event/eventDispatch.test.ts"
to: "packages-user/data-base/src/map/types.ts"
via: "typed IGameEventInvocation fixtures and compile-time enum references"
- from: "packages-user/data-base/src/map/mapLifecycle.test.ts"
to: "packages-user/data-base/src/map/types.ts"
via: "IResizableMapLayer and ISaveableContent-compatible fixture access"
- from: "packages-user/data-base/src/map/gameMap.ts"
to: "packages-user/data-common/src/store/eventStore.test.ts"
via: "focused ESLint/Prettier quality gate over the reported Phase 01 files"
---
<objective>
Close the two real Phase 01 verification gap categories: phase-owned TypeScript diagnostics and the 119 CRLF/Prettier diagnostics.
Purpose: Restore the existing type and quality gates without changing event behavior, public interfaces, serialized shapes, or the accepted Phase 01 boundaries.
Output: One executable gap-closure plan covering the import/fixture typing repairs first and the reported-file formatting repair second.
Scope boundary: Do not add serialized production event registration or map-id binding; do not alter the rawEvent alias/cache/Promise<unknown>/no-as compatibility baseline; do not repair eventStore cycles; do not broaden the plan into new event behavior or public contract design.
</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/STATE.md
@.planning/ROADMAP.md
@.planning/REQUIREMENTS.md
@.planning/phases/01-event/01-CONTEXT.md
@.planning/phases/01-event/01-VERIFICATION.md
@.planning/phases/01-event/01-11-SUMMARY.md
@.planning/phases/01-event/01-11-PLAN.md
@.planning/phases/01-event/01-RESEARCH.md
@.prettierrc
@eslint.config.js
@packages-user/data-system/src/event/executor.ts
@packages-user/data-system/src/event/eventDispatch.test.ts
@packages-user/data-base/src/map/eventPath.test.ts
@packages-user/data-base/src/map/mapLifecycle.test.ts
@packages-user/data-base/src/map/gameMap.ts
@packages-user/data-common/src/store/eventStore.test.ts
</context>
<tasks>
<task type="tracer">
<name>Task 1: Repair the phase-owned type gate without changing contracts</name>
<files>
packages-user/data-system/src/event/executor.ts
packages-user/data-system/src/event/eventDispatch.test.ts
packages-user/data-base/src/map/eventPath.test.ts
packages-user/data-base/src/map/mapLifecycle.test.ts
</files>
<read_first>
- .planning/phases/01-event/01-VERIFICATION.md (the exact phase-owned diagnostics and the accepted deferral list)
- packages-user/data-system/src/event/executor.ts (existing IBlockEventEnv use at the getEvent call)
- packages-user/data-system/src/event/eventDispatch.test.ts (dynamic module fixture, invocation helper, and source assertions)
- packages-user/data-base/src/map/eventPath.test.ts (malformed raw-event case table and logger-code assertions)
- packages-user/data-base/src/map/mapLifecycle.test.ts (map/layer fixture return types and save/load/resize calls)
- packages-user/data-base/src/map/types.ts (IBlockEventEnv, IGameEventInvocation, IMapLayer, and IResizableMapLayer)
- packages-user/data-state/src/hero/index.ts (approved barrel export for DefaultHeroMoveTopImpl)
</read_first>
<behavior>
- EventExecutor continues to resolve the same IBlockEventParam/IBlockEventEnv/any event generic and execute the same invocation sequence.
- eventDispatch.test.ts still exercises the same point/static/dynamic ordering, trigger filtering, await, cut, and reduction assertions.
- eventPath.test.ts still applies the same malformed containers, ranges, leaf values, and logger codes 62/63/64.
- mapLifecycle.test.ts still exercises the same default, snapshot, persistence, conversion, movement, and resize behavior through types that declare the called methods.
</behavior>
<action>Import `IBlockEventEnv` from the existing `@user/data-base` contract alongside the current executor imports. In `eventDispatch.test.ts`, make the invocation helper produce the already-approved `IGameEventInvocation` shape with a complete test environment, replace runtime access to const-enum members with compile-time imports from the existing public barrels, and use the existing `@user/data-state`/hero barrel type surface for `DefaultHeroMoveTopImpl` while retaining the current isolated runtime fixture and mocks. In `eventPath.test.ts`, give the malformed-case table explicit mutator and test-case types, preserving its Reflect.set mutations and logger expectations. In `mapLifecycle.test.ts`, obtain the layer through the existing resizable-layer-typed collection or another already-declared interface path so save/load/resize calls match their declared signatures; keep any fake state adaptation local to the fixture. Apply these as type/import-only repairs per D-01, D-02, D-03, D-04, D-05, D-06, D-07, D-08, D-09, D-10, D-11, D-12, and D-13: do not change production dispatch, assertions, serialized data, event ids, rawEvent behavior, Promise<R> adapters, public interfaces, or circular imports.</action>
<verify>
<automated>pnpm check:type</automated>
<automated>pnpm exec eslint "packages-user/data-system/src/event/executor.ts" "packages-user/data-system/src/event/eventDispatch.test.ts" "packages-user/data-base/src/map/eventPath.test.ts"</automated>
</verify>
<done>`pnpm check:type` no longer reports the missing IBlockEventEnv import or the listed phase-owned fixture diagnostics; the edited tests retain their existing runtime cases and the public event contracts remain unchanged.</done>
</task>
<task type="auto">
<name>Task 2: Normalize the reported Phase 01 files to CRLF/Prettier</name>
<files>
packages-user/data-base/src/map/gameMap.ts
packages-user/data-base/src/map/mapLifecycle.test.ts
packages-user/data-common/src/store/eventStore.test.ts
</files>
<read_first>
- .planning/phases/01-event/01-VERIFICATION.md (the 119-error focused lint gap and exact reported files)
- .prettierrc (endOfLine=crlf, print width, indentation, and other repository formatting rules)
- eslint.config.js (eslint-plugin-prettier integration)
- packages-user/data-base/src/map/gameMap.ts (reported lines 185-189 and surrounding save aggregation)
- packages-user/data-base/src/map/mapLifecycle.test.ts (current lifecycle fixture and assertions)
- packages-user/data-common/src/store/eventStore.test.ts (current public-barrel regression)
</read_first>
<action>Run the repository's Prettier configuration against only the three files named by the verification report, ensuring CRLF line endings and the configured layout. Review the resulting diff as formatting-only: retain every statement, assertion, map/save shape, event id, logger call, and test scenario exactly; do not reformat unrelated Phase 01 files, modify implementation behavior, or fold in the locked deferrals from D-11 and the rawEvent/Promise/as and eventStore-cycle baselines.</action>
<verify>
<automated>pnpm exec eslint "packages-user/data-base/src/map/gameMap.ts" "packages-user/data-base/src/map/mapLifecycle.test.ts" "packages-user/data-common/src/store/eventStore.test.ts"</automated>
<automated>git diff --check -- "packages-user/data-base/src/map/gameMap.ts" "packages-user/data-base/src/map/mapLifecycle.test.ts" "packages-user/data-common/src/store/eventStore.test.ts"</automated>
</verify>
<done>The focused ESLint command reports zero Prettier/CRLF errors for all three reported files, and the diff contains only line-ending/Prettier normalization.</done>
</task>
</tasks>
<threat_model>
## Trust Boundaries
| Boundary | Description |
|----------|-------------|
| serialized raw-map fixture → typed test mutator | Deliberately malformed values cross into the test-only validation fixture. |
| event invocation fixture → EventExecutor type surface | Test data models the existing source-aware event environment consumed by production dispatch. |
## STRIDE Threat Register
| Threat ID | Category | Component | Severity | Disposition | Mitigation Plan |
|-----------|----------|-----------|----------|-------------|-----------------|
| T-01-12-01 | Tampering | malformed raw-event fixture typing | low | mitigate | Keep malformed values confined to the existing test table and preserve the current validation path and logger-code assertions; change only compile-time fixture annotations. |
| T-01-12-02 | Tampering | event invocation fixture | low | mitigate | Require the existing `IGameEventInvocation`/`IBlockEventEnv` shape and retain the current trigger/source assertions without widening production contracts. |
| T-01-12-SC | Tampering | npm/pnpm installs | low | accept | No package is installed or changed by this gap-closure plan. |
</threat_model>
<source_audit>
| Source | ID | Item | Plan | Status |
|--------|----|------|------|--------|
| GOAL | — | Phase 01 quality gate is green on current implementation and focused files | 01-12 | COVERED |
| REQ | EVT-01 | Event data/serialization contract remains type-valid | 01-12 | COVERED |
| REQ | EVT-02 | Existing simple floor-step event flow remains type-valid | 01-12 | COVERED |
| REQ | EVT-03 | Existing beginner-oriented event surface remains unchanged | 01-12 | COVERED |
| RESEARCH | — | Strict TypeScript and focused ESLint/Prettier gates | 01-12 | COVERED |
| CONTEXT | D-01..D-13 | Existing user-authored event decisions are preserved, not redesigned | 01-12 | COVERED |
</source_audit>
<verification>
Run Task 1's type/lint checks, then Task 2's focused lint and diff checks. Confirm the final diff is limited to the six listed Phase 01 files plus the plan metadata, contains no production behavior or public-contract change, and leaves serialized registration/map-id binding, rawEvent/cache/Promise/as compatibility, and eventStore-cycle repair outside scope.
</verification>
<success_criteria>
- The missing `IBlockEventEnv` import is present and the focused fixture diagnostics are removed without production API changes.
- The three files named in the verification report pass the repository CRLF/Prettier ESLint gate.
- No tests are added, no event behavior is changed, and no locked deferral is implemented.
</success_criteria>
<output>
Create `.planning/phases/01-event/01-12-SUMMARY.md` when done.
</output>

View File

@ -0,0 +1,184 @@
---
phase: 01-event
reviewed: 2026-09-08T13:22:29Z
depth: standard
files_reviewed: 16
files_reviewed_list:
- packages-user/data-base/src/map/dynamicTile.ts
- packages-user/data-base/src/map/eventView.ts
- packages-user/data-base/src/map/mapLayer.ts
- packages-user/data-base/src/map/mapState.ts
- packages-user/data-base/src/map/staticTile.ts
- packages-user/data-base/src/map/tile.ts
- packages-user/data-common/src/event/event.ts
- packages-user/data-common/src/store/index.ts
- packages-user/data-system/src/event/executor.ts
- packages-user/data-system/src/event/index.ts
- packages-user/data-system/src/event/system.ts
- packages-user/data-system/src/index.ts
- packages-user/data-system/src/types.ts
- packages-user/data-state/src/core.ts
- packages-user/data-state/src/hero/moverImpl.ts
- packages/common/src/logger.json
findings:
critical: 12
warning: 2
info: 0
total: 14
status: issues_found
---
# Phase 01: Code Review Report
**Reviewed:** 2026-09-08T13:22:29Z
**Depth:** standard
**Files Reviewed:** 16
**Status:** issues_found
## Narrative Findings (AI reviewer)
### Summary
The event migration is not behaviorally complete. Trigger selection, point-versus-tile ownership, dynamic tiles, persistence, default events, and blocked-movement behavior are incorrect or absent. There are no automated tests for the new path.
Repository-wide type failures documented by the phase summaries were reproduced. Client/legacy errors and the out-of-scope `TileStore.getTrigger` mismatch are treated as baseline and are not counted below. Focused ESLint and `git diff --check` passed.
### Critical Issues
#### CR-01: Event trigger metadata is ignored
**Classification:** BLOCKER
**File:** `packages-user/data-system/src/event/executor.ts:36-44`
**Issue:** Every resolved event is executed unconditionally. `IGameEvent.trigger` is never compared with `env.trigger`, so an `OnLeave`, `None`, or battle event bound at a tile also runs during enter/touch dispatch.
**Fix:** Skip events whose trigger does not match the requested trigger before calling `execute`:
```ts
if (event.trigger !== env.trigger) continue;
const result = await event.execute(param, env);
```
#### CR-02: Raw point events are attached to movable tile instances
**Classification:** BLOCKER
**File:** `packages-user/data-base/src/map/mapState.ts:101-119`
**Issue:** `IMapRawData.events` represents coordinate-bound point events, but the loader writes them to `location.static.tileEvent()`. The point-event API remains empty, and converting the static tile to dynamic moves those events away from their coordinate, violating D-03.
**Fix:** Load through `layer.event(x, y)`, then call `markPure()` on that point view. Do not write raw point data to the static tile view.
#### CR-03: Tile defaults are initialized as an empty event set
**Classification:** BLOCKER
**File:** `packages-user/data-base/src/map/tile.ts:26-31`
**Issue:** Every tile starts with an empty, pure `LayerEventView`; `ITileRawData.events` is never copied into it. Default monster/item/custom events therefore never become dispatchable, contradicting D-05 and the documented dirty baseline.
**Fix:** Initialize each concrete tile from its raw definition's `events`, then mark that populated view pure. Reapply the correct default baseline when a static tile's number changes.
#### CR-04: Movement dispatch drops all dynamic-tile events
**Classification:** BLOCKER
**File:** `packages-user/data-state/src/hero/moverImpl.ts:160-174`
**Issue:** Collection reads only `loc.static.tileEvent()`. `loc.dynamics` is ignored, so events attached to movable objects never fire. This also regresses the deleted collector, which explicitly collected dynamic tiles.
**Fix:** Collect event entries from the static tile and every `loc.dynamics` tile, then sort all tile entries by descending priority after the point-event group.
#### CR-05: Point events receive tile-event execution context
**Classification:** BLOCKER
**File:** `packages-user/data-state/src/hero/moverImpl.ts:168-188`
**Issue:** Point and tile IDs are merged into one executor call with `type: BlockEventType.TileEvent` and `tile: loc.static`. Point scripts therefore receive a false source type and tile object; dynamic scripts would likewise receive the wrong tile if CR-04 were fixed naively.
**Fix:** Preserve source metadata per invocation. Point events need `PointEvent` and no triggering tile; each tile event needs `TileEvent` and its actual tile. If the current executor signature cannot preserve cut/reduce semantics across source-specific invocations, obtain approval to extend that interface rather than fabricating one shared environment.
#### CR-06: Point events have no dirty, save/load, or reset lifecycle
**Classification:** BLOCKER
**File:** `packages-user/data-base/src/map/mapLayer.ts:51,585-637,713-815`
**Issue:** `pointEvents` is absent from every save/load path and never contributes to layer dirty state. Runtime edits are lost from saves, while old in-memory point events can survive loading or resizing and later reappear. This violates D-12.
**Fix:** Add point-event data to the user-approved layer save contract, serialize only dirty views, restore/reset them against raw baselines on load, and clear or clip them during resize operations.
#### CR-07: Saved event data is a live mutable map
**Classification:** BLOCKER
**File:** `packages-user/data-base/src/map/staticTile.ts:38-47`; `packages-user/data-base/src/map/dynamicTile.ts:100-112`
**Issue:** Both save methods store `tileEvent().get()`, which is the view's internal `Map`. Later event edits mutate earlier autosave/undo snapshots retained by `SaveSystem`, causing historical saves to change after creation.
**Fix:** Snapshot the map when saving:
```ts
events: new Map(this.tileEvent().get())
```
Apply the same rule to future point-event saves.
#### CR-08: `keepEvent=false` clears events instead of restoring defaults
**Classification:** BLOCKER
**File:** `packages-user/data-base/src/map/mapLayer.ts:123-132`
**Issue:** `syncStaticEvent` always clears the static view and leaves it empty when `keepEvent` is false. The public contract says this mode falls back to the static tile's own events.
**Fix:** When `keepEvent` is false, repopulate the static view from the restored tile's `raw()?.events` baseline and mark it pure; only copy dynamic overrides when true.
#### CR-09: The new compiled cache can execute stale source
**Classification:** BLOCKER
**File:** `packages-user/data-common/src/event/event.ts:14-26,46-49`
**Issue:** Compilation is now cached, but `rawEvent` is a public mutable `Statement[]`. Type-safe callers can mutate it with `push`/`splice` without calling `setRaw`, leaving `compiled` valid-looking but stale.
**Fix:** Make event source immutable to callers and keep a private mutable backing value that can only be replaced through `setRaw`, which must invalidate the cache. This requires a user-approved interface adjustment or a justified read-only accessor.
#### CR-10: Malformed external map events can crash loading
**Classification:** BLOCKER
**File:** `packages-user/data-base/src/map/mapState.ts:88-105`
**Issue:** `Object.entries(raw.events[z])` assumes every layer has an object. Missing/null external serialized data throws before the existing numeric validation and leaves a partially registered map.
**Fix:** Validate `raw.events`, each layer entry, and each priority map before creating/registering the map; log and reject malformed input rather than passing it to `Object.entries`.
#### CR-11: Blocked-movement callbacks were replaced with a no-op
**Classification:** BLOCKER
**File:** `packages-user/data-state/src/hero/moverImpl.ts:224-229`
**Issue:** `IHeroMover` still calls `cannotEnter` for blocked movement, and the interface documents it as a trigger hook. The replacement silently resolves, deleting existing behavior because `EventTrigger` lacks a corresponding value.
**Fix:** Obtain an interface decision for an `OnCannotEnter`-equivalent trigger and dispatch it. Do not remove the behavior while the movement contract still promises it.
#### CR-12: The configured interpreter cannot deliver the phase's simple built-in flows
**Classification:** BLOCKER
**File:** `packages-user/data-system/src/event/system.ts:11-17`
**Issue:** The interpreter is permanently created with empty built-in/global function lists, and the system provides no initialization path to register them. Dialogue/open-door/item/battle behavior was documented as deferred, yet the phase summary claims EVT-02 complete; those end-to-end flows cannot currently be implemented through this assembled system.
**Fix:** Implement the approved event initialization/registration path, configure the required built-ins before interpreter construction, and verify at least dialogue and open-door flows end to end before marking EVT-02 complete.
### Warnings
#### WR-01: No behavioral verification exists for the new event path
**Classification:** WARNING
**File:** `packages-user/data-state/src/hero/moverImpl.ts:146-188`
**Issue:** The repository contains no test/spec files. Focused type/lint checks cannot detect the trigger, ownership, ordering, environment, dynamic-tile, or persistence failures above.
**Fix:** Add automated tests covering trigger filtering, point-before-tile ordering, dynamic tiles, source-specific environments, map conversion, save/undo snapshots, malformed raw data, and all three movement hooks.
#### WR-02: Forbidden type assertions remain in the reviewed event implementation
**Classification:** WARNING
**File:** `packages-user/data-common/src/event/event.ts:31,35,37-41`
**Issue:** Three `as Promise<R>` assertions violate the project's absolute no-`as` review rule and can conceal an interpreter return-contract mismatch. These assertions predate the Phase 01 cache change, so this is scoped pre-existing quality debt rather than a Phase regression.
**Fix:** Align the interpreter adapter's generic return type with `Promise<R>` so `GameEvent.execute` can return it without assertions.
### Baseline and Verification Notes
- `pnpm check:type` remains red in documented client/legacy areas and in the out-of-scope `TileStore.getTrigger`/`ITileRawData.trigger` migration. Those diagnostics, including the two resulting `core.ts` assignment errors, are not counted as Phase 01 findings here.
- Focused ESLint over the reviewed TypeScript files passed.
- `git diff --check` over all 16 reviewed files passed.
- No security injection primitive was found in the reviewed glue code; the principal risks are incorrect dispatch and save-state corruption.
---
_Reviewed: 2026-09-08T13:22:29Z_
_Reviewer: the agent (gsd-code-reviewer)_
_Depth: standard_

View File

@ -1,76 +1,90 @@
---
phase: "1"
phase: "01"
slug: "event"
# status lifecycle: draft (seeded by plan-phase) → validated (set by validate-phase §6)
status: draft
nyquist_compliant: false
wave_0_complete: false
created: "2026-09-07"
created: "2026-09-08"
---
# Phase 1 — Validation Strategy
# Phase 01 - Validation Strategy
> Per-phase validation contract for feedback sampling during execution.
## Current Validation State
---
Plans 01-04 through 01-10 are executable gap-closure plans, but their checkpoint outputs, tests, and implementation have not run. Every row below therefore remains `pending`; this file does not claim behavioral success.
The installed GSD SDK currently returns `sdk_unknown_command` for both verify-command path resolution and failing-direction probes. Paths and commands below were manually grounded against the root scripts and current/planned file ownership, but deterministic SDK probe success is unavailable and must not be inferred.
## Test Infrastructure
| Property | Value |
|----------|-------|
| **Framework** | vitest 4.0.18 |
| **Config file** | none — 根 `package.json` `"test": "vitest"`,无 `vitest.config.*` |
| **Quick run command** | `pnpm check:type` |
| **Full suite command** | `pnpm test` |
| **Estimated runtime** | ~30 seconds (quick) |
---
| **Framework** | Vitest 4.0.18 |
| **Config file** | none - root `package.json` provides the test script |
| **Quick run command** | The focused `pnpm exec vitest run "<file>" -t "<behavior>"` command from the active task |
| **Focused phase behavior command** | `pnpm exec vitest run "packages-user/data-common/src/store/eventStore.test.ts" "packages-user/data-base/src/map/eventPath.test.ts" "packages-user/data-base/src/map/mapLifecycle.test.ts" "packages-user/data-system/src/event/eventDispatch.test.ts"` |
| **Static aggregate command** | None added by revised 01-08/01-09; the preserved eventStore cycle is not a failure condition |
| **Runtime target** | under 60 seconds per focused task command; not yet measured |
## Sampling Rate
- **After every task commit:** Run `pnpm check:type`
- **After every plan wave:** Run `pnpm check:circular` + `pnpm lint:user`
- **Before `/gsd-verify-work`:** Full suite must be green
- **Max feedback latency:** ~30 seconds
---
- **After every task:** Run that task's exact `<automated>` command.
- **After plans 01-06, 01-07, 01-08, and 01-10:** Run the complete newly created test file, not only its named `-t` slice.
- **Before `/gsd-verify-work`:** Run 01-09's Low/High point-event-only aggregation behavior command; do not add a production-registration or cycle-removal gate.
- **Max feedback latency target:** 60 seconds; pending measurement during execution.
## Per-Task Verification Map
| Task ID | Plan | Wave | Requirement | Threat Ref | Secure Behavior | Test Type | Automated Command | File Exists | Status |
|---------|------|------|-------------|------------|-----------------|-----------|-------------------|-------------|--------|
| -01-01 | 01 | 1 | EVT-01 | — | 事件数据容忍非法块名/参数logger.warn 非抛异常) | unit | `pnpm check:type` | ❌ W0 | ⬜ pending |
| -01-02 | 01 | 1 | EVT-02 | T-1-01 | 踩踏触发→事件执行、对话 await、开门链路 | integration | `pnpm check:type` + 手动验证 | ❌ W0 | ⬜ pending |
| -01-03 | 01 | 1 | EVT-03 | — | 旧 ITrigger 无残留引用、无复杂通用表达式 | static | `pnpm check:type` + `pnpm check:circular` | ✅ | ⬜ pending |
| Task ID | Plan | Wave | Requirement | Threat Ref | Behavior / Static Property | Test Type | Automated Command | Artifact State | Status |
|---------|------|------|-------------|------------|----------------------------|-----------|-------------------|----------------|--------|
| 01-01-01 | 01 | 1 | EVT-01 | T-01-01 | Duplicate priorities warn without throwing | type/static | `pnpm check:type` with Plan 01 file filter | completed plan; prior static evidence only | pending re-check |
| 01-02-01 | 02 | 2 | EVT-02 | T-02-01 | Unknown event ids warn and are skipped | type/static | `pnpm check:type` with Plan 02 file filter | completed plan; prior static evidence only | pending re-check |
| 01-03-01 | 03 | 3 | EVT-02, EVT-03 | T-03-01 | Movement dispatch tolerates unknown ids and old trigger symbols are absent | type/static | `pnpm check:type` with Plan 03 file filter | completed plan; prior static evidence only | pending re-check |
| 01-04-01 | 04 | 4 | EVT-01, EVT-02 | T-01-04-01 | Production initialization decision is based on the still-current addEvent/CoreState seam | checkpoint preflight | `if (!(Test-Path "packages-user/data-common/src/store/types.ts") -or !(Test-Path "packages-user/data-state/src/core.ts")) { exit 1 }; if (!(Select-String -Path "packages-user/data-common/src/store/types.ts" -Pattern "addEvent" -Quiet)) { exit 1 }` | source exists; decision summary not produced | pending |
| 01-04-02 | 04 | 4 | EVT-02 | T-01-04-01 | Source-aware dispatch decision is based on the current single-env seam | checkpoint preflight | `if (!(Select-String -Path "packages-user/data-system/src/event/types.ts" -Pattern "events: string\[\]" -Quiet)) { exit 1 }; if (!(Select-String -Path "packages-user/data-state/src/hero/moverImpl.ts" -Pattern "BlockEventType.TileEvent" -Quiet)) { exit 1 }` | source exists; decision summary not produced | pending |
| 01-04-03 | 04 | 4 | EVT-01, EVT-02 | T-01-04-02 | Point persistence decision is made only while IMapLayerSave still lacks its approved field | checkpoint preflight | `$content = Get-Content -Raw "packages-user/data-base/src/map/types.ts"; if ($content -notmatch "interface IMapLayerSave") { exit 1 }; if ($content -match "pointEvents\??:") { exit 1 }` | source exists; decision summary not produced | pending |
| 01-05-01 | 05 | 4 | EVT-01 | T-01-05-01, T-01-05-03 | Immutable source and no-assertion return decisions are grounded in current interfaces | checkpoint preflight | `if (!(Select-String -Path "packages-user/data-common/src/event/types.ts" -Pattern "readonly rawEvent: Statement\[\]" -Quiet)) { exit 1 }; if (!(Select-String -Path "packages-user/data-common/src/event/event.ts" -Pattern "this.rawEvent = raw" -Quiet)) { exit 1 }; if (!(Select-String -Path "node_modules/anon-tokyo/dist/index.d.ts" -Pattern "Promise\x3Cunknown\x3E" -Quiet)) { exit 1 }; if (!(Select-String -Path "packages-user/data-common/src/event/event.ts" -Pattern "\bas\s+Promise" -Quiet)) { exit 1 }` | source exists; decision summary not produced | pending |
| 01-05-02 | 05 | 4 | EVT-01 | T-01-05-02 | Current circular report still exposes the eventStore path before the user selects a cut edge | checkpoint preflight | `$output = pnpm check:circular 2>&1; if ($output -notmatch "circular dependenc") { $output; exit 1 }; if ($output -notmatch "store/eventStore\.ts") { $output; exit 1 }` | current source exists; decision summary not produced | pending |
| 01-06-01 | 06 | 5 | EVT-01, EVT-02 | T-01-06-01 | Valid raw point ids enter the point view and event alias selects eventLayer | behavior | `pnpm exec vitest run "packages-user/data-base/src/map/eventPath.test.ts" -t "raw point events and event layer"` | test created by task | pending |
| 01-06-02 | 06 | 5 | EVT-01, EVT-02, EVT-03 | T-01-06-01, T-01-06-02, T-01-06-03 | Malformed event containers fail before map registration with matching logger codes | behavior | `pnpm exec vitest run "packages-user/data-base/src/map/eventPath.test.ts" -t "malformed raw event structures"` | test created by task | pending |
| 01-07-01 | 07 | 6 | EVT-02, EVT-03 | T-01-07-01, T-01-07-02 | Matching point/static/dynamic events execute in source-correct order and await sequentially | behavior | `pnpm exec vitest run "packages-user/data-system/src/event/eventDispatch.test.ts" -t "source-aware matching dispatch"` | test created by task | pending |
| 01-07-02 | 07 | 6 | EVT-02, EVT-03 | T-01-07-03 | Cut/reduce and enter/leave/hit behavior cover only executed matching events | behavior | `pnpm exec vitest run "packages-user/data-system/src/event/eventDispatch.test.ts"` | test created by 01-07-01 | pending |
| 01-08-01 | 08 | 5 | EVT-01 | T-01-08-01 | Public-barrel GameEventStore add/get/unknown/duplicate warning and overwrite behavior | behavior | `pnpm exec vitest run "packages-user/data-common/src/store/eventStore.test.ts"` | test created by task | pending |
| 01-09-01 | 09 | 7 | EVT-01 | T-01-09-01, T-01-09-02 | GameMap retains a layer whose only serialized content is dirty pointEvents | behavior | `pnpm exec vitest run "packages-user/data-base/src/map/mapLifecycle.test.ts" -t "map saves layers containing only point events"` | test extended by task | pending |
| 01-09-02 | 09 | 7 | EVT-01 | T-01-09-01, T-01-09-02 | Low/High compression both preserve point-event-only layer aggregation | behavior | `pnpm exec vitest run "packages-user/data-base/src/map/mapLifecycle.test.ts" -t "map saves layers containing only point events"` | test extended by task | pending |
| 01-10-01 | 10 | 5 | EVT-01, EVT-02, EVT-03 | T-01-10-01, T-01-10-02 | Raw defaults, stable snapshots, untouched dynamic save/load baseline, conversion, and point-coordinate ownership hold | behavior | `pnpm exec vitest run "packages-user/data-base/src/map/mapLifecycle.test.ts" -t "tile defaults snapshots conversion and movement"` | test created by task | pending |
| 01-10-02 | 10 | 5 | EVT-01, EVT-02, EVT-03 | T-01-10-03 | Point dirty/save/load/reset/resize follows the approved persistence contract | behavior | `pnpm exec vitest run "packages-user/data-base/src/map/mapLifecycle.test.ts" -t "point event lifecycle"` | test created by 01-10-01 | pending |
*Status: ⬜ pending · ✅ green · ❌ red · ⚠️ flaky*
## Static and Aggregate Gate Map
---
| Gate | Owning Task | Passing Condition | Current Evidence |
|------|-------------|-------------------|------------------|
| GameEventStore behavior regression | 01-08-01 | Public-barrel test passes for add/get/null/duplicate warning 170 and overwrite | not run |
| GameMap point-event aggregation | 01-09-01, 01-09-02 | Low/High focused test retains point-event-only layer save | not run |
| Existing focused lint/type baselines | prior plans | Existing plan-owned checks remain authoritative; revised 01-09 adds no broad gate | recorded in prior summaries |
| Registration/cycle boundary | 01-08-01, 01-09-01 | No acceptance gate requires production registration or eventStore cycle removal | locked deferral |
## Wave 0 Requirements
- [ ] `packages-user/data-common/src/event/event.test.ts``GameEvent` compile/execute/缓存回写冒烟可选TEST-01 在 Phase 6
- [ ] `packages-user/data-common/src/store/eventStore.test.ts``addEvent`/`getEvent`(可选)
*说明TEST-01 单测补齐为 Phase 6本阶段以 `check:type`/`check:circular`/`lint:user` 为主要验证门禁。*
---
- [ ] Plan 01-06 creates `packages-user/data-base/src/map/eventPath.test.ts` before its first behavior verification.
- [ ] Plan 01-07 creates `packages-user/data-system/src/event/eventDispatch.test.ts` before its first behavior verification.
- [ ] Plan 01-08 creates `packages-user/data-common/src/store/eventStore.test.ts` before its verification; rawEvent and cycle repair remain deferred.
- [ ] Plan 01-10 creates `packages-user/data-base/src/map/mapLifecycle.test.ts`, including the untouched dynamic default-event round-trip, before its verifications.
- [ ] Plan 01-09 extends `packages-user/data-base/src/map/mapLifecycle.test.ts` before its aggregation verification; production registration remains deferred.
- [ ] The focused phase command discovers the existing eventStore, map-ingestion, map-lifecycle, and dispatch tests and exits successfully.
## Manual-Only Verifications
| Behavior | Requirement | Why Manual | Test Instructions |
|----------|-------------|------------|-------------------|
| 对话/开门端到端事件链路 | EVT-02 | 依赖 A2 内建函数清单(暂缓),无自动断言 | 清单落地后补 vitest 冒烟或手动走查 |
---
No revised 01-08/01-09 acceptance behavior is manual-only. The external Blockly editor, concrete dialogue/open-door built-ins, serialized production registration, and map-id binding remain outside this executable scope; 01-09 owns only automated GameMap point-event aggregation.
## Validation Sign-Off
- [ ] All tasks have `<automated>` verify or Wave 0 dependencies
- [ ] Sampling continuity: no 3 consecutive tasks without automated verify
- [ ] Wave 0 covers all MISSING references
- [ ] No watch-mode flags
- [ ] Feedback latency < ~30s
- [ ] `nyquist_compliant: true` set in frontmatter
- [ ] All decision checkpoints produced their required SUMMARY contracts.
- [ ] All planned test files exist and are discovered.
- [ ] Every focused task command passed in its owning wave.
- [ ] 01-09 Low/High point-event-only aggregation commands passed without adding registration or cycle-removal gates.
- [ ] Deterministic verify-path and failing-direction probe results are available, or their SDK limitation remains explicitly recorded without claiming success.
- [ ] Measured focused feedback latency is below 60 seconds.
- [ ] `nyquist_compliant: true` is set only after the above evidence exists.
**Approval:** pending
**Approval:** pending execution evidence

View File

@ -0,0 +1,257 @@
---
phase: 01-event
verified: 2026-09-09T03:53:57Z
status: passed
score: 29/29 must-haves verified
covered_files:
- .planning/PROJECT.md
- .planning/REQUIREMENTS.md
- .planning/ROADMAP.md
- .planning/STATE.md
- .planning/phases/01-event/01-01-PLAN.md
- .planning/phases/01-event/01-01-SUMMARY.md
- .planning/phases/01-event/01-02-PLAN.md
- .planning/phases/01-event/01-02-SUMMARY.md
- .planning/phases/01-event/01-03-PLAN.md
- .planning/phases/01-event/01-03-SUMMARY.md
- .planning/phases/01-event/01-04-PLAN.md
- .planning/phases/01-event/01-04-SUMMARY.md
- .planning/phases/01-event/01-05-PLAN.md
- .planning/phases/01-event/01-05-SUMMARY.md
- .planning/phases/01-event/01-06-PLAN.md
- .planning/phases/01-event/01-06-SUMMARY.md
- .planning/phases/01-event/01-07-PLAN.md
- .planning/phases/01-event/01-07-SUMMARY.md
- .planning/phases/01-event/01-08-PLAN.md
- .planning/phases/01-event/01-08-SUMMARY.md
- .planning/phases/01-event/01-09-PLAN.md
- .planning/phases/01-event/01-09-SUMMARY.md
- .planning/phases/01-event/01-10-PLAN.md
- .planning/phases/01-event/01-10-SUMMARY.md
- .planning/phases/01-event/01-11-PLAN.md
- .planning/phases/01-event/01-11-SUMMARY.md
- .planning/phases/01-event/01-12-PLAN.md
- .planning/phases/01-event/01-12-SUMMARY.md
- .planning/phases/01-event/01-CONTEXT.md
- .planning/phases/01-event/01-DISCUSSION-LOG.md
- .planning/phases/01-event/01-PATTERNS.md
- .planning/phases/01-event/01-RESEARCH.md
- .planning/phases/01-event/01-REVIEW.md
- .planning/phases/01-event/01-VALIDATION.md
- .planning/phases/01-event/deferred-items.md
- packages-user/data-base/src/map/dynamicTile.ts
- packages-user/data-base/src/map/eventPath.test.ts
- packages-user/data-base/src/map/eventView.ts
- packages-user/data-base/src/map/gameMap.ts
- packages-user/data-base/src/map/mapLayer.ts
- packages-user/data-base/src/map/mapLifecycle.test.ts
- packages-user/data-base/src/map/mapState.ts
- packages-user/data-base/src/map/staticTile.ts
- packages-user/data-base/src/map/tile.ts
- packages-user/data-base/src/map/types.ts
- packages-user/data-common/src/event/event.ts
- packages-user/data-common/src/event/index.ts
- packages-user/data-common/src/event/types.ts
- packages-user/data-common/src/index.ts
- packages-user/data-common/src/store/eventStore.test.ts
- packages-user/data-common/src/store/eventStore.ts
- packages-user/data-common/src/store/index.ts
- packages-user/data-common/src/store/types.ts
- packages-user/data-common/src/types.ts
- packages-user/data-state/src/core.ts
- packages-user/data-state/src/coreEventLayer.test.ts
- packages-user/data-state/src/hero/moverImpl.ts
- packages-user/data-system/src/event/eventDispatch.test.ts
- packages-user/data-system/src/event/executor.ts
- packages-user/data-system/src/event/index.ts
- packages-user/data-system/src/event/system.ts
- packages-user/data-system/src/event/types.ts
- packages-user/data-system/src/index.ts
- packages-user/data-system/src/types.ts
- packages/common/src/logger.json
covered_digest: "v1:sha256:c66c50e1eb272fd25b750aaca1bc4cecb4fb6c8875e964b74a3bfed5ddf693d3"
behavior_unverified: 0
overrides_applied: 0
re_verification:
previous_status: gaps_found
previous_score: 27/29
gaps_closed:
- "Phase-owned TypeScript diagnostics in EventExecutor and focused fixtures"
- "Focused CRLF/Prettier quality-gate diagnostics"
gaps_remaining: []
regressions: []
deferred:
- truth: "Serialized production event registration and map-id binding"
addressed_in: "User-locked Phase 01 deferral"
evidence: "CoreState retains the explicit registration TODO; no public registration API or map-id binding was added."
- truth: "rawEvent immutability/cache-safety, Promise<unknown> adaptation, and no-as cleanup"
addressed_in: "User-locked Phase 01 deferral"
evidence: "The approved Statement[] alias, Promise<R> contract, and existing Promise<R> adapters remain unchanged."
- truth: "eventStore circular-dependency repair"
addressed_in: "User-locked Phase 01 deferral"
evidence: "pnpm check:circular still reports the documented baseline cycles; eventStore cycle repair was not part of this phase."
- truth: "Dialogue/open-door built-ins"
addressed_in: "Roadmap Phase 01 success criterion 3 / wrap-up scope"
evidence: "GameEventSystem intentionally initializes empty built-in/global function lists."
manual_verification:
status: passed
test: "Verify GameEvent compile-cache reuse and invalidation"
confirmed: "Repeated execution without setRaw reused the same compiled result; after setRaw, the next execution recompiled and used the new Statement[]. No issue was reported."
verified_at: 2026-09-09T03:53:57Z
---
# Phase 1: 事件系统 Verification Report
**Phase Goal:** 引擎能以 blockly 式低代码定义事件,并驱动简单场景的事件流程
**Verified:** 2026-09-09T03:53:57Z
**Status:** passed
**Re-verification:** Yes — after plan 01-12 and user manual verification approval
## Goal Achievement
### Observable Truths
| # | Truth | Status | Evidence |
|---|---|---|---|
| 1 | Data/serialization interfaces express Blockly-style event definitions and id bindings | ✓ VERIFIED | `data-common/src/event/types.ts` defines `Statement[]`, `EventTrigger`, and generic event contracts; map raw data carries event ids. |
| 2 | Walking onto a floor can trigger its mapped event | ✓ VERIFIED | `MapState.fromRaw` selects the `event` layer and `eventDispatch.test.ts` executes a map-bound point id through `mover.enter`. |
| 3 | Floor-triggered events reach the executor chain | ✓ VERIFIED | `moverImpl.ts` creates invocations and awaits `EventExecutor.execute`; the focused dispatch suite verifies execution. |
| 4 | The event abstraction remains beginner-oriented rather than a general workflow framework | ✓ VERIFIED | The shipped surface is limited to trigger enums, id-based event views, source environments, execution modes, and reductions. |
| 5 | `GameEvent` compile caching and `setRaw` invalidation work at runtime | ✓ VERIFIED | `event.ts:24-49` writes `compiled`, uses it in `execute`, and clears it in `setRaw`; user manually confirmed repeated execution reuses the compiled result and execution after `setRaw` recompiles with the new `Statement[]`. |
| 6 | `GameEventStore`/`MapStore` are barrel-accessible and events remain id-only/non-saveable | ✓ VERIFIED | Store barrels export the event store; `eventStore.test.ts` verifies lookup, unknown ids, duplicate warning 170, and overwrite behavior. |
| 7 | `LayerEventView` CRUD, dirty baselines, and duplicate-priority warning exist | ✓ VERIFIED | `eventView.ts` implements the view and lifecycle tests cover dirty restoration; runtime output includes warning 136. |
| 8 | Static/dynamic defaults, persistence, conversion, and copied tile snapshots work | ✓ VERIFIED | `mapLifecycle.test.ts` covers defaults, snapshots, save/load, static/dynamic conversion, and movement. |
| 9 | Point events are coordinate-owned with independent persistence and resize lifecycle | ✓ VERIFIED | `mapLayer.ts` keeps point-event maps separate from tiles; lifecycle tests cover dirty baselines, all compression modes, crop, clear, and movement independence. |
| 10 | Raw event input is validated before registration and enters the event layer | ✓ VERIFIED | `eventPath.test.ts` passes 9 tests for valid ingestion, malformed containers/leaves, logger codes 62/63/64, and no partial map. |
| 11 | The executor preserves sequential await and execution/reduction modes | ✓ VERIFIED | `eventDispatch.test.ts` verifies ordered awaits, cut modes, reductions, unknown-id continuation, and matching results. |
| 12 | `GameEventSystem` connects executor, store, and replaceable store reference | ✓ VERIFIED | `system.ts:11-21` constructs the executor with a lazy store callback and implements `useStore`; `CoreState` constructs the system. |
| 13 | `CoreState` owns event store/system without saving the event store | ✓ VERIFIED | `core.ts:89-99,150-152,206-208` constructs both; saveable registration only includes hero, flags, maps, and enemy. |
| 14 | Legacy `ITrigger` implementation and references are removed | ✓ VERIFIED | `git grep` over `packages`, `packages-user`, and `src` found no `ITrigger`, `TriggerSystem`, `TriggerCollection`, `TriggerRegistry`, or `TriggerCollector` symbols. |
| 15 | Unknown ids warn and do not abort later valid events | ✓ VERIFIED | `executor.ts:40-45` warns with 171 and continues; the named dispatch test verifies a later valid event runs. |
| 16 | Movement collects point, static, and all dynamic sources in required order | ✓ VERIFIED | `moverImpl.ts:169-220` separates and priority-sorts point/tile sources; tests assert point first and dynamic before static at equal fixture priority. |
| 17 | `enter`/`leave`/`hit` map to `OnEnter`/`OnLeave`/`OnTouch` | ✓ VERIFIED | The hook test asserts trigger values and hero/trigger locators for all three methods. |
| 18 | Each invocation carries the real point/tile source environment | ✓ VERIFIED | Dispatch assertions verify point `tile=null`, static tile identity, dynamic tile identity, source types, layer, map, and locators. |
| 19 | Trigger filtering occurs before cut/reduce participation | ✓ VERIFIED | `executor.ts:47-62` filters before execute/result collection; the mode/reduction test asserts mismatched events are invisible. |
| 20 | `GameMap` retains layers whose only serialized content is dirty point events | ✓ VERIFIED | `gameMap.ts:182-197` treats non-empty `pointEvents` as content; lifecycle tests pass for LowCompression and HighCompression. |
| 21 | Public-barrel `GameEventStore` regression is active | ✓ VERIFIED | Three active tests pass; no disabled-test markers were found. |
| 22 | Raw-ingestion regression is active | ✓ VERIFIED | Nine active tests pass and malformed cases assert null registration. |
| 23 | Tile/point lifecycle regression is active | ✓ VERIFIED | Four active tests pass, including defaults, snapshots, persistence, and resize. |
| 24 | Source-aware dispatch regression is active | ✓ VERIFIED | Six active tests pass, including map-bound point dispatch, ordering, filtering, await, cut, and reduction behavior. |
| 25 | CoreState event-layer wiring regression is active | ✓ VERIFIED | One active test passes and preserves aliases, z-indexes, matrices, and compareWith input. |
| 26 | Focused behavioral suite passes in the current process | ✓ VERIFIED | `pnpm exec vitest run ...` passed 5 files / 23 tests in this verification. |
| 27 | User-owned contract decisions from 01-04 are represented in shipped artifacts | ✓ VERIFIED | `check.decision-coverage-verify` reports 13/13 honored decisions. |
| 28 | The 01-05 rawEvent and Promise/as compatibility baseline is preserved | ✓ VERIFIED (accepted deferral) | Current source preserves the user-locked public alias, generic Promise contract, and existing adapters; this is intentionally not a defect. |
| 29 | Phase-owned type and quality checks are green on current files | ✓ VERIFIED | Focused ESLint, Prettier, and diff checks pass; full `pnpm check:type` has no diagnostics in the six plan-12 files, only unrelated repository diagnostics. |
**Score:** 29/29 truths verified
## Accepted Deferrals (Not Failures)
1. Serialized production event registration and map-id binding remain at the explicit `CoreState` TODO.
2. rawEvent immutability/cache-safety, `Promise<unknown>` adaptation, and no-as cleanup remain the user-approved compatibility baseline.
3. eventStore circular-dependency repair remains deferred; the observed 18-cycle report is baseline, not a Phase 01 failure.
4. Dialogue/open-door built-ins remain deferred by the roadmap's explicit Phase 01 scope.
## Required Artifacts
| Artifact | Expected | Status | Details |
|---|---|---|---|
| `data-common/src/event/types.ts` and `event.ts` | Event contracts and compile cache | ✓ VERIFIED | Contracts and cache write/invalidation path exist; user manually confirmed runtime reuse and invalidation. |
| `data-base/src/map/mapState.ts` + `eventPath.test.ts` | Validated raw point ingestion and event-layer selection | ✓ VERIFIED | Validated `raw.events` enters coordinate views and malformed input is rejected before map registration. |
| `data-base/src/map/mapLayer.ts` + lifecycle files | Point/tile dirty, save/load, defaults, conversion, resize | ✓ VERIFIED | Focused lifecycle suite passes across persistence and resize paths. |
| `data-base/src/map/gameMap.ts` | Retain point-event-only layer saves | ✓ VERIFIED | `isEmptyLayerSave` checks non-empty `pointEvents`; focused aggregation assertions pass. |
| `data-system/src/event/executor.ts` + `data-state/src/hero/moverImpl.ts` | Source-aware filtered dispatch | ✓ VERIFIED | Real environments flow from mover to filtered, awaited executor calls. |
| `data-system/src/event/eventDispatch.test.ts` | Dispatch behavior evidence | ✓ VERIFIED | Six active behavioral tests pass and focused lint passes. |
| `data-state/src/core.ts` + `coreEventLayer.test.ts` | Production event-layer wiring and regression | ✓ VERIFIED | Legacy map initialization aliases the event layer; one regression test passes. |
| `data-common/src/store/eventStore.ts` + test | Id lookup and duplicate warning | ✓ VERIFIED | Public barrel and three active behavior tests pass. |
| Legacy trigger paths | Deleted and unreferenced | ✓ VERIFIED | Legacy trigger files are absent and source search is empty. |
## Key Link Verification
| From | To | Via | Status | Details |
|---|---|---|---|---|
| `MapState.fromRaw` | `IMapLayer.event(x,y)` | validated `raw.events` | ✓ WIRED | Valid point ids enter coordinate views and are marked pure. |
| `MapState.fromRaw` | `GameMap.eventLayer` | `setEventLayer` | ✓ WIRED | The raw alias `event` selects the layer. |
| `CoreState.initMapState` | `GameMap.eventLayer` | legacy alias assignment | ✓ WIRED | `core.ts:345-348` assigns the created event layer. |
| `moverImpl` | `EventExecutor` | invocation list + `await execute` | ✓ WIRED | Point/static/dynamic environments are retained. |
| `EventExecutor` | `GameEventStore` | lazy store lookup | ✓ WIRED | Lookup, warning, trigger filter, and event execution are connected. |
| `MapLayer.saveState` | `GameMap.saveState` | non-empty `pointEvents` predicate | ✓ WIRED | Point-only layer saves survive map-level aggregation in Low/High compression. |
| public data-common barrel | `GameEventStore` | `store/index.ts` export | ✓ WIRED | The public-barrel regression imports and exercises the class. |
## Data-Flow Trace (Level 4)
| Artifact | Data variable | Source | Produces Real Data | Status |
|---|---|---|---|---|
| `MapState.fromRaw` | point event ids | external `IMapRawData.events``layer.event(x,y)` | Yes | ✓ FLOWING |
| `StaticTile`/`DynamicTile` | default tile event ids | `ITileRawData.events` from `TileStore` | Yes | ✓ FLOWING |
| `moverImpl` | invocation sequence | point view + static view + every dynamic view | Yes | ✓ FLOWING |
| `EventExecutor` | event result | lazy store lookup → `event.execute` | Yes for pre-bound ids | ✓ FLOWING |
| `GameMap.saveState` | point-event save | dirty point view → layer save → map aggregation | Yes | ✓ FLOWING |
## Behavioral Spot-Checks
| Behavior | Command | Result | Status |
|---|---|---|---|
| Raw ingestion, malformed-input rejection, tile/point lifecycle, public store, source-aware dispatch, and CoreState wiring | `pnpm exec vitest run packages-user/data-base/src/map/eventPath.test.ts packages-user/data-base/src/map/mapLifecycle.test.ts packages-user/data-common/src/store/eventStore.test.ts packages-user/data-system/src/event/eventDispatch.test.ts packages-user/data-state/src/coreEventLayer.test.ts` | 5 files / 23 tests passed | ✓ PASS |
| Focused phase-12 lint | `pnpm exec eslint` over the six plan-12 files | No output; exit 0 | ✓ PASS |
| Focused phase-12 formatting | `pnpm exec prettier --check` over the six plan-12 files | All matched files use Prettier code style | ✓ PASS |
| Focused phase-12 diff check | `git diff --check` over the three formatting files | Exit 0 | ✓ PASS |
| GameEvent compile-cache reuse and `setRaw` invalidation | User manual verification: repeated execution reused the compiled result; after `setRaw`, the next execution recompiled and used the new `Statement[]`; no issue reported | Confirmed by user | ✓ PASS |
| Full repository type check | `pnpm check:type` | Non-zero from unrelated client/legacy/TileStore diagnostics; no diagnostics in the six plan-12 files | BASELINE NOTE |
## Probe Execution
N/A — no phase-declared or conventional `scripts/**/tests/probe-*.sh` probe exists.
## Requirements Coverage
| Requirement | Source Plans | Description | Status | Evidence |
|---|---|---|---|---|
| EVT-01 | 01-01, 01-02, 01-04, 01-06, 01-08, 01-09, 01-10 | Event data/serialization interfaces for Blockly-style definitions | ✓ SATISFIED within locked scope | Event contracts, raw point ingestion, store regression, and point-event persistence pass; production registration is explicitly deferred. |
| EVT-02 | 01-02, 01-03, 01-04, 01-06, 01-07, 01-10, 01-11 | Simple floor-step event flow | ✓ SATISFIED for implemented scope | Core wiring plus source-aware movement/executor tests pass; built-ins are roadmap-deferred. |
| EVT-03 | 01-01, 01-02, 01-03, 01-06, 01-07, 01-10 | Beginner-oriented simple abstraction | ✓ SATISFIED | Small enum/id/view/executor surface and complete legacy trigger removal are verified. |
No additional Phase 01 requirements are orphaned in `REQUIREMENTS.md`.
## Test Quality Audit
| Test File | Linked Requirement | Active | Skipped | Circular | Assertion Level | Verdict |
|---|---|---:|---:|---:|---|---|
| `eventPath.test.ts` | EVT-01/02/03 | 9 | 0 | 0 | Behavioral/value | PASS |
| `eventDispatch.test.ts` | EVT-02/03 | 6 | 0 | 0 | Behavioral/value | PASS |
| `mapLifecycle.test.ts` | EVT-01/02/03 | 4 | 0 | 0 | Behavioral/value | PASS |
| `eventStore.test.ts` | EVT-01 | 3 | 0 | 0 | Behavioral/value | PASS |
| `coreEventLayer.test.ts` | EVT-02 | 1 | 0 | 0 | Behavioral/value | PASS |
Disabled tests: 0. Circular expected-value generation: 0. The tests do not write fixtures or derive expected values by invoking the implementation.
## Decision Coverage
`check.decision-coverage-verify` reports **13/13** trackable CONTEXT decisions honored. This is non-blocking textual coverage; runtime conclusions above come from current code and independently run tests.
## Advisory (New Scope, Unevidenced)
None. Re-verification ran the anti-pattern and regression scan; no new-scope blocker was raised.
## Anti-Patterns Found
| File | Line | Pattern | Severity | Impact |
|---|---:|---|---|---|
| `packages-user/data-state/src/core.ts` | 153 | TODO for serialized registration/map-id binding | Accepted deferral | Explicit user-locked boundary; not a Phase 01 failure. |
| `packages-user/data-common/src/event/event.ts` | 31, 35, 41 | `as Promise<R>` adapters | Accepted deferral | Explicit rawEvent/Promise/no-as compatibility decision. |
| Repository-wide type check | — | Pre-existing client/legacy/TileStore diagnostics | Baseline note | No diagnostic was reported in the six plan-12 files; deferred in `deferred-items.md`. |
No unreferenced `TBD`, `FIXME`, or `XXX` debt markers were found in the scanned Phase 01 implementation files. No disabled-test or circular-test anti-pattern was found.
## Human Verification Required
None — the user approved the GameEvent compile-cache reuse and `setRaw` invalidation check with no issue reported.
## Gaps Summary
No blocking implementation gaps remain after plan 01-12. The prior phase-owned type and focused formatting gaps are closed: the focused 23-test suite passes, focused ESLint/Prettier/diff checks pass, legacy trigger symbols are absent, event-layer wiring is tested, source-aware dispatch is tested, and point-event persistence/aggregation is tested. The full repository type command remains non-zero on unrelated pre-existing client/legacy/TileStore diagnostics and is recorded as a baseline/deferred item, not as a Phase 01 failure. The user manually confirmed the compile-cache reuse/invalidation transition with no issue reported, so Phase 01 verification is passed and the phase is ready for completion.
---
_Verified: 2026-09-09T03:53:57Z_
_Verifier: the agent (gsd-verifier)_

43
.planning/state.json Normal file
View File

@ -0,0 +1,43 @@
{
"contract": "1.0.0",
"flavor": "core",
"milestone": "v1.0",
"phases": [
{
"number": "1",
"name": "事件系统",
"status": "in_progress"
},
{
"number": "2",
"name": "寻路系统",
"status": "pending"
},
{
"number": "3",
"name": "数据端完成",
"status": "pending"
},
{
"number": "4",
"name": "渲染适配与双布局",
"status": "pending"
},
{
"number": "5",
"name": "Legacy 移植",
"status": "pending"
},
{
"number": "6",
"name": "单元测试",
"status": "pending"
}
],
"next": {
"command": "/gsd:progress --next",
"label": "Advance to the next step",
"reason": "Phase 01 verification passed; phase ready for completion"
},
"updated_at": "2026-09-09T03:53:57.000Z"
}

View File

@ -1,17 +1,3 @@
# AI Agent 指南
身为 AI Agent你可能会被分配不同的任务不同的任务请参阅不同的文件来查看具体要求。注意不要查看本次任务之外的提示词文件以避免上下文与注意力污染。
所有的任务我都会在对话中提出,项目中的一部分 markdown 文件如 `task.md` 都是给我自己看的,不是给你看的。
# 编写代码或设计系统
如果要求你进行代码编写,以及系统设计相关的行为,参阅 [code.md](./.agents/code.md)。
# 代码评审及测试用例编写
如果要求你对我写好的代码进行评审,或针对某个功能编写测试用例,参阅 [review.md](./.agents/review.md)。
# 使用文档编写
如果要求你针对某个接口编写 API 文档,或要求你针对某个功能编写指南文档,参阅 [docs.md](./.agents/docs.md)。注意本节针对的是面向项目使用者的文档,而不是面向项目开发者的文档。
在开始前,务必认真阅读 [dev.md](./dev.md) 来了解项目基本要求。

View File

@ -0,0 +1,48 @@
import { logger } from '@motajs/common';
import { ILayerEventView } from './types';
export class LayerEventView implements ILayerEventView {
/** 当前绑定的事件 */
private readonly store: Map<number, string> = new Map();
/** 用于判断事件是否变化的参考基准 */
private reference: Map<number, string> | null = null;
get(): ReadonlyMap<number, string> {
return this.store;
}
set(priority: number, event: string): void {
if (this.store.has(priority)) {
logger.warn(136, priority.toString());
}
this.store.set(priority, event);
}
delete(priority: number): void {
this.store.delete(priority);
}
clear(): void {
this.store.clear();
}
markPure(): void {
this.reference = new Map(this.store);
}
dirty(): boolean {
const reference = this.reference;
if (!reference) {
return this.store.size > 0;
}
if (this.store.size !== reference.size) {
return true;
}
for (const [priority, event] of this.store) {
if (reference.get(priority) !== event) {
return true;
}
}
return false;
}
}

View File

@ -16,6 +16,7 @@ import {
} from './types';
import {
IDataCommon,
ILocationIndexer,
ITileStore,
MapLocIndexer,
SaveCompression
@ -36,7 +37,7 @@ export class GameMap extends Hookable<IGameMapHooks> implements IGameMap {
private layerHookMap: Map<IMapLayer, IMapLayerHookController> = new Map();
/** 坐标索引器 */
readonly indexer = new MapLocIndexer();
readonly indexer: ILocationIndexer = new MapLocIndexer();
active: boolean = false;
eventLayer: IMapLayer | null = null;

View File

@ -3,11 +3,16 @@ import {
FaceDirection,
IDataCommon,
IDataCommonExtended,
IMapBlockRawData,
ISaveableContent,
ITileRawData
} from '@user/data-common';
import { IMapBlockSaveBase, IMapLayer, ITileBase } from './types';
import { LayerEventView } from './eventView';
import {
ILayerEventView,
IMapBlockSaveBase,
IMapLayer,
ITileBase
} from './types';
export abstract class MapTileBase<TSave extends IMapBlockSaveBase>
implements ITileBase, IDataCommonExtended, ISaveableContent<Readonly<TSave>>
@ -15,12 +20,15 @@ export abstract class MapTileBase<TSave extends IMapBlockSaveBase>
readonly state: IDataCommon;
readonly layer: IMapLayer;
locator: ITileLocator;
triggers: Set<number> | null = null;
/** 该图块实例绑定的图块事件 */
private readonly tileEvents: ILayerEventView;
constructor(x: number, y: number, layer: IMapLayer) {
this.layer = layer;
this.state = layer.state;
this.locator = { x, y };
this.tileEvents = new LayerEventView();
this.tileEvents.markPure();
}
abstract num(): number;
@ -29,10 +37,6 @@ export abstract class MapTileBase<TSave extends IMapBlockSaveBase>
abstract set(num: number): void;
block(): IMapBlockRawData | null {
return null;
}
setFaceDirection(direction: FaceDirection): number {
const cur = this.num();
const next = this.layer.faceBinder.getFaceOf(cur, direction);
@ -44,24 +48,12 @@ export abstract class MapTileBase<TSave extends IMapBlockSaveBase>
}
}
clearTrigger(): void {
this.triggers = null;
tileEvent(): ILayerEventView {
return this.tileEvents;
}
addTrigger(trigger: number): void {
if (!this.triggers) {
this.triggers = new Set();
}
this.triggers.add(trigger);
}
deleteTrigger(trigger: number): void {
if (!this.triggers) return;
this.triggers.delete(trigger);
}
useEmptyTrigger(): void {
this.triggers = new Set();
pointEvent(): ILayerEventView | null {
return this.layer.event(this.locator.x, this.locator.y);
}
abstract saveState(): Readonly<TSave>;

View File

@ -0,0 +1,54 @@
import {
AnonTokyoInterpreter,
AnonTokyoExecutable,
Statement
} from 'anon-tokyo';
import { EventTrigger, IGameEvent } from './types';
export class GameEvent<
P extends Record<string, any>,
E extends Record<string, any>,
R = void
> implements IGameEvent<P, E, R> {
trigger: EventTrigger = EventTrigger.None;
rawEvent: Statement[];
compiled: AnonTokyoExecutable | null = null;
constructor(
readonly interpreter: AnonTokyoInterpreter,
raw: Statement[]
) {
this.rawEvent = raw;
}
compile(): AnonTokyoExecutable | null {
this.compiled = this.interpreter.compile(this.rawEvent);
return this.compiled;
}
execute(param: P, env: Record<string, any>): Promise<R> {
if (this.compiled) {
return this.compiled.exec(param, env) as Promise<R>;
} else {
const compiled = this.compile();
if (compiled) {
return compiled.exec(param, env) as Promise<R>;
} else {
return this.interpreter.exec(
this.rawEvent,
param,
env
) as Promise<R>;
}
}
}
setRaw(raw: Statement[]): void {
this.rawEvent = raw;
this.compiled = null;
}
setTrigger(trigger: EventTrigger): void {
this.trigger = trigger;
}
}

View File

@ -0,0 +1,2 @@
export * from './event';
export * from './types';

View File

@ -0,0 +1,74 @@
import {
AnonTokyoExecutable,
AnonTokyoInterpreter,
Statement
} from 'anon-tokyo';
export const enum EventTrigger {
/** 无触发器,事件需要手动执行 */
None,
/** 当玩家触碰指定图块时触发,如果直接走入则不触发 */
OnTouch,
/** 当玩家进入指定图块时触发 */
OnEnter,
/** 当玩家离开指定图块时触发 */
OnLeave,
/** 当玩家与指定怪物战斗前触发,返回值表示是否与怪物战斗 */
OnBeforeBattle,
/** 当玩家与指定怪物战斗后触发 */
OnAfterBattle,
/** 当玩家开启指定门之前触发,返回值表示是否能够成功开启门 */
OnBeforeOpenDoor,
/** 当玩家开启指定门之后触发 */
OnAfterOpenDoor,
/** 当玩家成功拾取指定道具时触发 */
OnAfterGetItem,
/** 当玩家使用指定位置或图块触发楼层切换前触发,返回值表示是否执行切换操作 */
OnBeforeChangeFloor,
/** 当玩家使用指定位置或图块触发楼层切换后触发 */
OnAfterChangeFloor
}
export interface IReadonlyGameEvent<
P extends Record<string, any>,
E extends Record<string, any>,
R = void
> {
/** 事件解释器 */
readonly interpreter: AnonTokyoInterpreter;
/** 事件触发器类型,由什么触发器触发 */
readonly trigger: EventTrigger;
/** 原始事件数据 */
readonly rawEvent: Statement[];
/** 已编译事件数据,可直接执行 */
readonly compiled: AnonTokyoExecutable | null;
/**
*
*/
compile(): AnonTokyoExecutable | null;
/**
*
* @param param
*/
execute(param: P, env: E): Promise<R>;
}
export interface IGameEvent<
P extends Record<string, any>,
E extends Record<string, any>,
R = void
> extends IReadonlyGameEvent<P, E, R> {
/**
*
* @param trigger
*/
setTrigger(trigger: EventTrigger): void;
/**
*
* @param raw
*/
setRaw(raw: Statement[]): void;
}

View File

@ -1,4 +1,5 @@
export * from './common';
export * from './event';
export * from './replay';
export * from './save';
export * from './store';

View File

@ -0,0 +1,28 @@
import { logger } from '@motajs/common';
import { IReadonlyGameEvent } from '../event';
import { IGameEventStore } from './types';
export class GameEventStore implements IGameEventStore {
private readonly store: Map<
string,
IReadonlyGameEvent<Record<string, any>, Record<string, any>, any>
> = new Map();
addEvent(
id: string,
event: IReadonlyGameEvent<Record<string, any>, Record<string, any>, any>
): void {
if (this.store.has(id)) {
logger.warn(170, id);
}
this.store.set(id, event);
}
getEvent<
P extends Record<string, any>,
E extends Record<string, any>,
R = void
>(id: string): IReadonlyGameEvent<P, E, R> | null {
return this.store.get(id) ?? null;
}
}

View File

@ -1,3 +1,5 @@
export * from './eventStore';
export * from './itemStore';
export * from './mapStore';
export * from './tileStore';
export * from './types';

View File

@ -1,6 +1,7 @@
//#region tile
import { IFacedTileLocator } from '@motajs/common';
import { IReadonlyGameEvent } from '../event';
//#region tile
export const enum TileType {
/** 未知或尚未归类的图块 */
@ -48,8 +49,8 @@ export interface ITileRawData {
readonly num: number;
/** 图块字符串 id */
readonly id: string;
/** 默认触发器类型 */
readonly trigger: number;
/** 默认事件,键表示优先级,值表示事件 id */
readonly events: Record<number, string>;
/** 图块逻辑类型 */
readonly type: TileType;
/** 图块的通行性对象 */
@ -81,7 +82,7 @@ export interface ITileStore<TLegacy = unknown> {
*
* @param num
*/
getTrigger(num: number): number;
getTrigger(num: number): number[];
/**
*
@ -296,13 +297,6 @@ export interface IChangeFloorData {
readonly relatedPos?: ChangeFloorPos;
}
export interface IMapBlockRawData {
/** 此点的静态触发器类型(仅对该点生效,不会跟随任何图块移动) */
readonly trigger?: number;
/** 楼层切换信息 */
readonly changeFloor?: IChangeFloorData;
}
export interface IMapRawData {
/** 楼层 id */
readonly floorId: string;
@ -312,8 +306,8 @@ export interface IMapRawData {
readonly map: Record<number, number[]>;
/** 每个地图图层的字符串别名 */
readonly layerAlias: Record<number, string>;
/** 每个地图图层中,指定位置图块的额外数据,外层 key 为图层 zIndex内层 key 为图块位置索引 */
readonly blockData: Record<number, Record<number, IMapBlockRawData>>;
/** 每个地图图层中,指定位置图块的事件数据,外层键为图层纵深,中层键为图块位置索引,内层键为事件优先级 */
readonly events: Record<number, Record<number, Record<number, string>>>;
}
export interface IMapStore {
@ -331,3 +325,31 @@ export interface IMapStore {
}
//#endregion
//#region event
export interface IGameEventStore {
/**
*
* @param id id
* @param event
*/
addEvent(
id: string,
event: IReadonlyGameEvent<Record<string, any>, Record<string, any>, any>
): void;
/**
* id
* @param id id
*/
getEvent<
P extends Record<string, any>,
E extends Record<string, any>,
R = void
>(
id: string
): IReadonlyGameEvent<P, E, R> | null;
}
//#endregion

View File

@ -1,6 +1,6 @@
import { ITileLocator } from '@motajs/common';
import { IFaceManager, IRoleFaceBinder } from './common';
import { IItemStore, IMapStore, ITileStore } from './store';
import { IGameEventStore, IItemStore, IMapStore, ITileStore } from './store';
import { ISaveSystem } from './save';
export interface IEnemyAttr {
@ -50,6 +50,8 @@ export interface IDataCommon {
readonly itemStore: IItemStore<IHeroAttr, Item<AllIdsOf<'items'>>>;
/** 地图定义存储 */
readonly mapStore: IMapStore;
/** 游戏事件存储 */
readonly eventStore: IGameEventStore;
/** 朝向绑定 */
readonly roleFace: IRoleFaceBinder;
/** 朝向管理 */

View File

@ -1,182 +0,0 @@
import { IFacedTileLocator, logger } from '@motajs/common';
import {
IHeroChangeFloorInfo,
IHeroState,
IMapLayer,
IMapState,
IStateBase
} from '@user/data-base';
import {
ChangeFloorPos,
ChangeFloorTarget,
ChangeFloorType,
FaceDirection,
IChangeFloorData,
IHeroAttr
} from '@user/data-common';
import {
BaseTrigger,
ITrigger,
ITriggerCollection,
ITriggerHandler,
TriggerCollection
} from '@user/data-system';
import { isNil } from 'lodash-es';
export const enum TriggerType {
/** 楼层切换触发器 */
ChangeFloor
}
export class ChangeFloorTrigger extends BaseTrigger implements ITrigger {
readonly type: number = TriggerType.ChangeFloor;
readonly priority: number = 10;
constructor(
state: IStateBase,
readonly maps: IMapState,
readonly hero: IHeroState<IHeroAttr>
) {
super(state);
}
/**
*
* @param data
*/
private getFloorTarget(data: IChangeFloorData): string | undefined {
if (data.floorType === ChangeFloorType.Specified) {
if (isNil(data.targetFloor)) {
logger.warn(165);
return void 0;
}
return data.targetFloor;
} else {
if (isNil(data.relatedFloor)) {
logger.warn(166);
return void 0;
}
const curr = this.hero.location.floorId;
if (isNil(curr)) {
logger.warn(167);
return void 0;
}
const index = this.maps.maps.indexOf(curr);
if (index === -1) {
logger.warn(168);
return void 0;
}
if (data.relatedFloor === ChangeFloorTarget.Next) {
const next = this.maps.maps[index + 1];
if (isNil(next)) {
logger.warn(169);
return void 0;
}
return next;
} else {
const next = this.maps.maps[index - 1];
if (isNil(next)) {
logger.warn(169);
return void 0;
}
}
}
}
private getPosTarget(
data: IChangeFloorData,
layer: IMapLayer
): IFacedTileLocator | undefined {
if (data.posType === ChangeFloorType.Specified) {
if (!data.targetPos) {
logger.warn(165);
return void 0;
}
return data.targetPos;
} else {
if (isNil(data.relatedPos)) {
logger.warn(166);
return void 0;
}
const { x, y, floorId } = this.hero.location;
if (isNil(floorId)) {
logger.warn(167);
return void 0;
}
const { width, height } = layer;
switch (data.relatedPos) {
case ChangeFloorPos.Stand:
return {
direction: FaceDirection.Unknown,
x,
y
};
case ChangeFloorPos.SymmetryX:
return {
direction: FaceDirection.Unknown,
x: width - x - 1,
y
};
case ChangeFloorPos.SymmetryY:
return {
direction: FaceDirection.Unknown,
x,
y: height - y - 1
};
case ChangeFloorPos.CentralSymmetry:
return {
direction: FaceDirection.Unknown,
x: width - x - 1,
y: height - y - 1
};
}
}
}
/**
*
* @param handler
*/
private async trigger(handler: ITriggerHandler): Promise<void> {
if (!handler.layer || !handler.locator || !handler.mapLayer) {
logger.warn(164);
return;
}
const { x, y } = handler.locator;
const data = handler.mapLayer.getLocationData(x, y);
if (!data || !data.block.changeFloor) return;
const floor = this.getFloorTarget(data.block.changeFloor);
const pos = this.getPosTarget(data.block.changeFloor, handler.mapLayer);
if (isNil(floor) || isNil(pos)) return;
const hero = handler.state.hero;
const isUnknown = pos.direction === FaceDirection.Unknown;
const defaults = hero.location.mover.faceDirection;
const info: IHeroChangeFloorInfo = {
target: floor,
x: pos.x,
y: pos.y,
face: isUnknown ? defaults : pos.direction
};
return handler.state.hero.changeFloor(info);
}
collection(): ITriggerCollection {
return new TriggerCollection([this]);
}
onEnter(handler: ITriggerHandler): Promise<void> {
return this.trigger(handler);
}
onHit(handler: ITriggerHandler): Promise<void> {
return this.trigger(handler);
}
onLeave(): Promise<void> {
return Promise.resolve();
}
onCannotEnter(): Promise<void> {
return Promise.resolve();
}
}

View File

@ -16,9 +16,12 @@ import {
IEnemyAttr,
ISaveSystem,
SaveSystem,
GameEventStore,
IGameEventStore,
IItemStore,
ItemStore,
IMapStore
IMapStore,
MapStore
} from '@user/data-common';
import {
EnemyManager,
@ -38,12 +41,10 @@ import {
import {
DamageSystem,
EnemyContext,
GameEventSystem,
IEnemyContext,
ITriggerCollector,
ITriggerRegistry,
MapDamage,
TriggerCollector,
TriggerRegistry
IGameEventSystem,
MapDamage
} from '@user/data-system';
import {
CommonAuraConverter,
@ -76,7 +77,6 @@ import { ILoadProgressTotal, LoadProgressTotal } from '@motajs/loader';
import { isNil } from 'lodash-es';
import { logger } from '@motajs/common';
import { DefaultHeroMoveTopImpl } from './hero';
import { MapStore } from '../../data-common/src/store/mapStore';
export class CoreState implements ICoreState {
// Layer 0 公共层,最底层的接口,不会依赖任何其他内容,一般是工具性接口及不需要存档的数据
@ -86,6 +86,7 @@ export class CoreState implements ICoreState {
readonly tileStore: ITileStore<LegacyTileData>;
readonly itemStore: IItemStore<IHeroAttr, LegacyItemData>;
readonly mapStore: IMapStore;
readonly eventStore: IGameEventStore;
// Layer 1 数据层,所有可存档内容都在这,一般用于数据存储
readonly maps: IMapState;
@ -95,8 +96,7 @@ export class CoreState implements ICoreState {
// Layer 2 执行层,游戏逻辑对象都在这,包括一些需要操作数据层的逻辑系统等
readonly enemyContext: IEnemyContext<IEnemyAttr, IHeroAttr>;
readonly triggerRegistry: ITriggerRegistry;
readonly triggerCollector: ITriggerCollector;
readonly eventSystem: IGameEventSystem;
// Layer 3 用户层,也就是最顶层的内容,一般仅用于初始化以及仅供渲染端调用的顶层模块
readonly loadProgress: ILoadProgressTotal;
@ -147,6 +147,10 @@ export class CoreState implements ICoreState {
// 地图
const mapStore = new MapStore();
this.mapStore = mapStore;
// 游戏事件
const eventStore = new GameEventStore();
this.eventStore = eventStore;
// TODO: 后续在此初始化路径注册外部序列化事件定义与地图事件 id 绑定。
//#endregion
@ -199,12 +203,9 @@ export class CoreState implements ICoreState {
enemyContext.bindHero(heroAttribute);
this.enemyContext = enemyContext;
// 触发器注册与收集器
const triggerRegistry = new TriggerRegistry(this);
const triggerCollector = new TriggerCollector();
triggerCollector.attachRegistry(triggerRegistry);
this.triggerRegistry = triggerRegistry;
this.triggerCollector = triggerCollector;
// 游戏事件系统
const eventSystem = new GameEventSystem(this);
this.eventSystem = eventSystem;
//#endregion

View File

@ -0,0 +1,3 @@
export * from './executor';
export * from './system';
export * from './types';

View File

@ -0,0 +1,23 @@
import { IStateBase } from '@user/data-base';
import { IGameEventStore } from '@user/data-common';
import { AnonTokyoInterpreter } from 'anon-tokyo';
import { EventExecutor } from './executor';
import { IGameEventExecutor, IGameEventSystem } from './types';
export class GameEventSystem implements IGameEventSystem {
readonly executor: IGameEventExecutor;
store: IGameEventStore | null;
constructor(readonly state: IStateBase) {
this.store = state.eventStore;
const interpreter = new AnonTokyoInterpreter({
builtInFunctions: [],
globalFunctions: []
});
this.executor = new EventExecutor(interpreter, () => this.store);
}
useStore(store: IGameEventStore | null): void {
this.store = store;
}
}

View File

@ -1,4 +1,4 @@
export * from './combat';
export * from './trigger';
export * from './event';
export * from './types';

View File

@ -1,87 +0,0 @@
import {
ITrigger,
ITriggerCollection,
ITriggerHandler,
TriggerType
} from './types';
export class TriggerCollection implements ITriggerCollection {
/** 当前集合内部维护的触发器列表 */
private readonly triggerList: ITrigger[];
constructor(triggers: Iterable<ITrigger>) {
this.triggerList = [...triggers];
}
count(): number {
return this.triggerList.length;
}
async trigger(
condition: TriggerType,
handler: ITriggerHandler
): Promise<void> {
for (const trigger of this.triggerList) {
await this.dispatch(trigger, condition, handler);
}
}
async *triggerIter(
condition: TriggerType,
handler: ITriggerHandler
): AsyncGenerator<ITrigger, void, ITriggerHandler | null> {
let currentHandler = handler;
for (const trigger of this.triggerList) {
await this.dispatch(trigger, condition, currentHandler);
const nextHandler = yield trigger;
if (nextHandler) {
currentHandler = nextHandler;
} else {
currentHandler = handler;
}
}
}
/**
*
* @param trigger
* @param condition
* @param handler
*/
private dispatch(
trigger: ITrigger,
condition: TriggerType,
handler: ITriggerHandler
): Promise<void> {
switch (condition) {
case TriggerType.Enter:
return trigger.onEnter(handler);
case TriggerType.Leave:
return trigger.onLeave(handler);
case TriggerType.Hit:
return trigger.onHit(handler);
case TriggerType.CannotEnter:
return trigger.onCannotEnter(handler);
}
}
iterate(): Iterable<ITrigger> {
return this.triggerList.values();
}
push(trigger: ITrigger): void {
this.triggerList.push(trigger);
}
unshift(trigger: ITrigger): void {
this.triggerList.unshift(trigger);
}
concat(...others: ITriggerCollection[]): ITriggerCollection {
const merged = [...this.triggerList];
for (const other of others) {
merged.push(...other.iterate());
}
return new TriggerCollection(merged);
}
}

View File

@ -1,112 +0,0 @@
import { IMapLayer } from '@user/data-base';
import {
ITrigger,
ITriggerCollection,
ITriggerCollector,
ITriggerRegistry
} from './types';
import { logger } from '@motajs/common';
import { TriggerCollection } from './collection';
export class TriggerCollector implements ITriggerCollector {
/** 当前收集器使用的注册对象 */
private registry: ITriggerRegistry | null = null;
collect(x: number, y: number, layer: IMapLayer): ITriggerCollection {
if (!this.registry) {
logger.warn(135);
return new TriggerCollection([]);
}
const staticType = layer.getTriggerType(x, y);
const staticTrigger = this.registry.create(staticType);
const dynamics = [...layer.dynamicLayer.getDynamicTilesAt(x, y)];
if (dynamics.length === 0) {
// 没有动态图块
if (staticTrigger) {
return new TriggerCollection([staticTrigger]);
} else {
return new TriggerCollection([]);
}
} else if (dynamics.length === 1) {
// 一个动态图块,只需要进行一次额外判断即可
const dynamic = dynamics[0];
const dynamicTrigger = this.registry.create(dynamic.triggerType);
// 直接穷举所有可能情况
if (!staticTrigger && !dynamicTrigger) {
return new TriggerCollection([]);
} else if (staticTrigger && !dynamicTrigger) {
return new TriggerCollection([staticTrigger]);
} else if (!staticTrigger && dynamicTrigger) {
return new TriggerCollection([dynamicTrigger]);
} else {
// 静态动态都有,则需要额外判断优先级,动态图层在前,因此包含等号
if (dynamicTrigger!.priority >= staticTrigger!.priority) {
const arr = [dynamicTrigger!, staticTrigger!];
return new TriggerCollection(arr);
} else {
const arr = [staticTrigger!, dynamicTrigger!];
return new TriggerCollection(arr);
}
}
} else {
// 动态图块大于两个,使用通用方案,记录重复触发器并抛出警告
const usedPriority = new Set<number>();
const duplicate = new Set<number>();
if (staticTrigger) {
// 有静态触发器
const lessTriggers: ITrigger[] = [];
const greaterTriggers: ITrigger[] = [];
// 先收集所有的触发器,并记录重复情况
for (const tile of layer.dynamicLayer.getDynamicTilesAt(x, y)) {
const trigger = this.registry.create(tile.triggerType);
if (trigger) {
if (usedPriority.has(trigger.priority)) {
duplicate.add(trigger.priority);
}
usedPriority.add(trigger.priority);
// 同优先级下动态在前,因此包含等号
if (trigger.priority >= staticTrigger.priority) {
greaterTriggers.push(trigger);
} else {
lessTriggers.push(trigger);
}
}
}
if (duplicate.size > 0) {
logger.warn(136, [...duplicate].join(','));
}
const arr = [
...greaterTriggers.sort((a, b) => b.priority - a.priority),
staticTrigger,
...lessTriggers.sort((a, b) => b.priority - a.priority)
];
return new TriggerCollection(arr);
} else {
// 没有静态触发器
const triggers: ITrigger[] = [];
for (const tile of layer.dynamicLayer.getDynamicTilesAt(x, y)) {
const trigger = this.registry.create(tile.triggerType);
if (trigger) {
if (usedPriority.has(trigger.priority)) {
duplicate.add(trigger.priority);
}
usedPriority.add(trigger.priority);
triggers.push(trigger);
}
}
if (duplicate.size > 0) {
logger.warn(136, [...duplicate].join(','));
}
return new TriggerCollection(
triggers.sort((a, b) => b.priority - a.priority)
);
}
}
}
attachRegistry(registry: ITriggerRegistry | null): void {
this.registry = registry;
}
}

View File

@ -1,5 +0,0 @@
export * from './collection';
export * from './collector';
export * from './registry';
export * from './trigger';
export * from './types';

View File

@ -1,27 +0,0 @@
import { logger } from '@motajs/common';
import { ITrigger, ITriggerRegistry, TriggerFactory } from './types';
import { IStateBase } from '@user/data-base';
export class TriggerRegistry implements ITriggerRegistry {
/** 数字类型到触发器工厂的映射 */
private readonly typeMap: Map<number, TriggerFactory> = new Map();
constructor(public readonly state: IStateBase) {}
register(type: number, factory: TriggerFactory): void {
if (this.typeMap.has(type)) {
logger.warn(132, 'type', type.toString());
}
this.typeMap.set(type, factory);
}
get(type: number): TriggerFactory | null {
return this.typeMap.get(type) ?? null;
}
create(num: number): ITrigger | null {
const factory = this.get(num);
if (!factory) return null;
return factory(num, this.state);
}
}

View File

@ -1,22 +0,0 @@
import { IStateBase } from '@user/data-base';
import { ITrigger, ITriggerCollection, ITriggerHandler } from './types';
import { TriggerCollection } from './collection';
export abstract class BaseTrigger implements ITrigger {
abstract type: number;
abstract priority: number;
constructor(readonly state: IStateBase) {}
abstract onEnter(handler: ITriggerHandler): Promise<void>;
abstract onLeave(handler: ITriggerHandler): Promise<void>;
abstract onHit(handler: ITriggerHandler): Promise<void>;
abstract onCannotEnter(handler: ITriggerHandler): Promise<void>;
collection(): ITriggerCollection {
return new TriggerCollection([this]);
}
}

View File

@ -1,151 +0,0 @@
import { ITileLocator } from '@motajs/common';
import {
IGameMap,
IMapLayer,
IStateBase,
IDataBaseExtended
} from '@user/data-base';
export const enum TriggerType {
/** 进入图块 */
Enter,
/** 离开图块 */
Leave,
/** 撞击图块 */
Hit,
/** 无法进入图块 */
CannotEnter
}
export interface ITriggerHandler {
/** 当前全局状态对象 */
readonly state: IStateBase;
/** 当前楼层状态对象 */
readonly layer?: IGameMap;
/** 当前参与触发的图层对象 */
readonly mapLayer?: IMapLayer;
/** 当前触发点定位符 */
readonly locator?: ITileLocator;
}
export type TriggerFactory = (type: number, state: IStateBase) => ITrigger;
export interface ITrigger extends IDataBaseExtended {
/** 触发器类型标识 */
readonly type: number;
/** 触发器优先级 */
readonly priority: number;
/**
*
* @param handler
*/
onEnter(handler: ITriggerHandler): Promise<void>;
/**
*
* @param handler
*/
onLeave(handler: ITriggerHandler): Promise<void>;
/**
*
* @param handler
*/
onHit(handler: ITriggerHandler): Promise<void>;
/**
*
* @param handler
*/
onCannotEnter(handler: ITriggerHandler): Promise<void>;
/**
*
*/
collection(): ITriggerCollection;
}
export interface ITriggerRegistry extends IDataBaseExtended {
/**
*
* @param type
* @param factory
*/
register(type: number, factory: TriggerFactory): void;
/**
*
* @param type
*/
get(type: number): TriggerFactory | null;
/**
* `null`
* @param num
*/
create(num: number): ITrigger | null;
}
export interface ITriggerCollection {
/**
*
*/
count(): number;
/**
*
* @param type
* @param handler
*/
trigger(type: TriggerType, handler: ITriggerHandler): Promise<void>;
/**
*
* @param condition
* @param handler
*/
triggerIter(
condition: TriggerType,
handler: ITriggerHandler
): AsyncGenerator<ITrigger, void, ITriggerHandler | null>;
/**
*
*/
iterate(): Iterable<ITrigger>;
/**
*
* @param trigger
*/
push(trigger: ITrigger): void;
/**
*
* @param trigger
*/
unshift(trigger: ITrigger): void;
/**
*
* @param others
*/
concat(...others: ITriggerCollection[]): ITriggerCollection;
}
export interface ITriggerCollector {
/**
*
* @param x
* @param y
* @param layer
*/
collect(x: number, y: number, layer: IMapLayer): ITriggerCollection;
/**
* collector 使
* @param registry
*/
attachRegistry(registry: ITriggerRegistry | null): void;
}

View File

@ -1,15 +1,13 @@
import { IStateBase } from '@user/data-base';
import { IEnemyContext } from './combat';
import { ITriggerCollector, ITriggerRegistry } from './trigger';
import { IEnemyAttr, IHeroAttr } from '@user/data-common';
import { IGameEventSystem } from './event';
export interface IStateSystem extends IStateBase {
/** 怪物上下文 */
readonly enemyContext: IEnemyContext<IEnemyAttr, IHeroAttr>;
/** 触发器注册 */
readonly triggerRegistry: ITriggerRegistry;
/** 触发器收集器 */
readonly triggerCollector: ITriggerCollector;
/** 游戏事件系统 */
readonly eventSystem: IGameEventSystem;
}
export interface IStateSystemExtended {