diff --git "a/docs/Buff\351\207\215\346\236\204\346\226\271\346\241\210.md" "b/docs/Buff\351\207\215\346\236\204\346\226\271\346\241\210.md"
new file mode 100644
index 00000000..8b3ff805
--- /dev/null
+++ "b/docs/Buff\351\207\215\346\236\204\346\226\271\346\241\210.md"
@@ -0,0 +1,347 @@
+# 注意
+
+本方案仅为草案,若对具体实现细节有疑问,请在评论区指出。该草案讨论事件截止到下周一(2025.10.13),届时重构方案将会定型,本discussion关闭,在Github Wiki上传新的确定版的重构方案文档。
+
+## 现状和背景
+
+ZSim开发进程推进至今,`Buff`模块已经成为了最大的瓶颈,也是目前开源社区参与开发的最大障碍。
+所以,我们决定对Buff模块进行重构,本次重构规模极大,涉及到的功能较多,所有和Buff有关的业务逻辑都被彻底推翻重来。
+
+- **关于数据库:**
+ - 现状
+ - 目前的Buff数据库分为三个部分:`触发判断.csv`、`激活判断.csv`、`buff_effect.csv`,
+ - `触发判断.csv`记录了Buff的各种属性和参数,是需要保留的表,
+ - `激活判断.csv`则存放了简单触发条件,在未来,这部分内容会被全新的出发判定逻辑所取代,所以这张表格的内容完全不需要;
+ - `buff_effect.csv`中记录了Buff的效果;
+- **现有问题:**
+ - 系统耦合程度高,buff_instance直接持有sim_instance作为上下文,导致难以进行测试
+ - 运行性能差,整个触发系统存在大量的重复创建Buff实例的情况,导致性能浪费严重
+ - 类型提示空缺
+- **重构方向**
+ - Buff功能解耦:将Buff的复杂判定逻辑解耦,Buff保留`start`、`end`、`refresh`等状态管理方法
+ - 数据库重构:根据新的业务架构,设计新的Buff数据库,剔除老数据库中的冗余数据,所有Buff不再以中文名(原`buff.ft.index`)作为索引值,而引入`buff.id: int`。
+ - Buff触发结构重构:底层业务逻辑改写,构建起由`event_router`担任逻辑中枢的新业务逻辑
+ - 重构原有的`event_listener`,将所有监听器探针交给`event_router`,修改对应角色的监听器业务逻辑。
+
+## 新系统的思考和架构
+
+- 新结构:
+ - `GlobalBuffController`
+ - `_buff_box`——内部的Buff仓库,存储本次模拟中构造的所有Buff对象。
+ - `buff_initiate_factory`——原`buff_0_manager`,负责Buff初始化
+
+ ```python
+ class GlobalBuffController:
+ def __init__(self):
+ self._buff_box: dict[int, Buff] = defaultdict()
+
+ def buff_register(self, buff: Buff):
+ """注册传入的Buff"""
+ assert buff.id not in self._buff_box.keys(), f"企图对id为{buff.id}的Buff进行重复注册!"
+ self._buff_box[buff.id] = buff
+
+ def buff_initiate_factory(self, sim_config: "SimConfig") -> None:
+ """读取配置单(SimConfig)、筛选出所有和本次模拟有关的Buff,初始化并进行注册"""
+ buff_candidate_list: list[dataframe] = self.select_buff(sim_config) # 根据配置单从数据库筛选、读取出有关buff的原数据并返回列表
+ for df in buff_candidate_list: # 构造这些Buff,存入本地的buff_box中。
+ buff_new = Buff()
+ self.buff_register(buff_new)
+ ```
+
+ - `BuffManager`——虽然Buff自身可以完成最基础的`start`、`end`等操作,但是角色对象持有的有效Buff的CRUD还是需要通过`BuffManager`进行的。
+ - `BuffOperator`——对角色的`active_buff_list`进行操作,新增、去除Buff。
+
+ ```python
+ class GlobalBuffController:
+ class BuffManager:
+ class BuffOperator:
+ def add_buff(self, buff: Buff, target: str | Character | ...) -> None:
+ ...
+
+ def remove_buff(self, buff: Buff, target: str | Character | ...) -> None:
+ ...
+ ```
+
+ - `event_router`——解析复杂对象,触发事件。
+ - `__init__`——包含一个HandlerMap、一个Buff事件树,以及一个激活事件列表。
+
+ ```python
+ from abc import ABC, abstract_method
+
+ class EventHandler(ABC):
+ @abstract_method
+ def excute(): # 具体业务逻辑
+ ...
+
+ class SkillEventHandler(EventHandler):
+ def excute():
+ ...
+
+ class EventRouter:
+ def __init__(self):
+ self.event_handler_map: dict[str, EventHandler] = {
+ "skill_event": SkillEventHandler,
+ "buff_event": BuffEventHandler,
+ ...
+ } # Handler仓库,随业务拓展,在开发时,要注意所有Handler所需要的信息都通过Context传递,Context高度解耦处理。
+ self.event_trigger_tree = None # 事件触发器树
+ self.active_event_list: list[ZSimEvent] = [] # 动态事件列表
+
+ def update_event_list(): # 更新事件列表的业务逻辑,可能是多个函数。
+ ...
+
+ def register_event() -> None: # 在触发器树中注册事件,
+ ...
+ ```
+
+ - `ZSimEvent`和`EventProfile`——模拟器事件和事件画像。这是ZSim的两个重要概念,是新架构得以成立的基石。
+ - `ZSimEvent`——ZSim中的事件,这里只展示基类。
+
+ ```python
+ ZSimEventType = Literal["skill_event", "anomaly_event", "schedule_preload_event", ...]
+ event_type_map = {
+ SkillNode: "skill_event",
+ AnomalyBar: "anomaly_event",
+ SchedulePreload: "schedule_preload_event",
+ ...
+ }
+
+ class ZSimEvent:
+ def __init__(self, event: SkillNode | AnomalyBar | ...):
+ try:
+ self.event_type: ZSimEventType = event_type_map.get(type(event))
+ self.event_obj = event
+ except KeyError:
+ raise f"未找到{type(event).__name__}类对象对应的事件类型"
+
+ """对于不同的封装对象,应该构造不同的事件,这样不同的事件就可以分别重写同名方法来获取对应属性了。"""
+ ```
+
+ - `EventProfile`——事件画像类,对ZSim事件的封装。并且提供对外接口,以获取内部所封装的复杂对象的参数。
+
+ ```python
+ class EventProfile:
+ def __init__(self, event_group: list[ZSimEvent]):
+ self.event_group: list[ZSimEvent | None] = []
+
+ def get_skill_type(self) -> int:
+ ...
+
+ def get_trigger_buff_level(self) -> int:
+ ...
+ ```
+
+ - `Buff`——原`buff_class`,定义了Buff类
+ - `buff_feature`——原`buff_feature`或`buff.ft`,记录了Buff的静态信息(最大持续时间、层数、更新规则)
+ - `buff_dynamic`——原`buff_dynamic`或`buff.dy`,记录了Buff的动态信息(更新时间、动态层数等)
+ - `bonus_class`——记录Buff效果的基类
+ - `effect: effect_base_class`——`effect_base_class`类:Buff效果对象
+ - `Character`和`Calculator`中进行的对应适配改动:
+ - `dynamic_attribute`——重构动态属性类
+ - `attribute_calculator`——负责动态属性的计算(需要调用`buff_manager.bonus_applier`)
+ - `Load`阶段的关于技能事件相关的功能整合进`event_router`中。
+
+-----------------------------------
+
+## **关于EventRouter和新触发器系统的运作方式**
+
+### **“事件”与“计划事件”的区别**
+
+> ***ZSim中,抛出并立刻处理的是“事件”,而抛出后,等待未来某个tick再处理的是“计划事件”。***
+> “事件”没有中转地,在被`publish`时,会被立刻调用对应的`handler`进行处理,
+> 而“计划事件”则需要构造成一个新的业务类(类似于`SchedulePreload`),并将其抛入`Schedule.event_list`中,在`Schedule`阶段再进行处理。
+> 在本次Buff系统重构中,绝大部分的对象都会被封装为“事件”而非“计划事件”
+
+### **事件触发器树`event_trigger_tree`的构造**
+
+> `event_trigger_tree`是本次重构中提出的一个新概念,在初始化时,会有一个基本的树,包含了技能事件、异常事件的节点,然后在初始化Buff的过程中,不断根据Buff的需要,在事件树中注册不同的Buff触发器事件。
+
+### **事件画像`EventProfile`和`event_trigger_tree`的交互**
+
+> ZSim在每个tick(频率存疑,可能还需要进一步讨论)构造一个事件画像,并且`event_trigger_tree`的各个节点调用该对象的各种方法获取自己关心的信息。一旦满足条件,就执行自身的`publish`方法,调用对应的handler实现事件的触发。
+
+-----------------------------------
+
+## **关于Buff系统新架构的一些重要信息**
+
+### **Buff系统重构的基本原则:**
+
+#### 1. *Buff是角色的一个属性,角色/Enemy对象只持有激活的Buff/Debuff*
+
+#### 2. *仅在初始化阶段对Buff进行统一构造,模拟过程中只进行Buff信息的更新,而不重复构造。*
+
+#### 3. *Buff负责自身状态管理:保留`buff.start`、`buff.end`、`buff.refresh`等核心状态管理方法,移除`buff.judge`、`buff.update`、`buff.exit_judge`等外部判定逻辑*
+
+#### 4.*`event_router`将通过监听器组以及逻辑树来承担触发Buff的全部业务*
+
+#### 5.*Buff的CRUD操作由`BuffManager`统一管理*
+
+> ### Buff底层逻辑的变更
+>
+> #### **老框架:Buff自身具有判定能力**
+>
+> 老框架认为:需要一个专门用于判定的`Buff0`来执行`Buff.judge()`,以判定Buff是否应该触发。
+> 如果在新框架中继续沿用这一内核,那么将无法摆脱`Buff0`【我们总需要一个对象来运行`Buff.judge()`,特别是在模拟器刚启动、角色尚未拥有任何Buff时】
+>
+>```mermaid
+>graph LR
+>F[外部函数] -->|直接操作| B[Buff]
+>B -->|Buff判定| J[Buff.judge]
+>B -->|Buff更新| U[Buff.update]
+>U -->|Buff开始| S[Buff.start]
+>U -->|Buff结束判定| E[Buff.exit_judge]
+>E -->|Buff结束| E1[buff.end]
+>```
+>
+> #### **新框架:Buff负责状态管理,不负责判定**
+>
+> 新框架中,Buff保留了`start()`、`end()`、`refresh()`等状态管理方法,但移除了所有判定逻辑。
+> Buff的触发判定完全由外部的`event_router`负责,Buff只负责在接收到信号后执行相应的状态变更。
+> 这样既保持了Buff对象的完整性,又实现了系统的解耦。
+>
+>```mermaid
+> graph LR
+> A[Buff] -->|根据自身LogicID
注册对应Handler|B[event_router.
event_trigger_tree]
+> E[复杂对象] -->|封装|ZE[ZSimEvent1]
+> ZE -->|封装|EP
+> ZE1[ZSimEvent2] -->|封装|EP[事件画像
EventProfile]
+> ZE2[ZSimEvent3] -->|封装|EP
+> ZE3[ZSimEvent4] -->|封装|EP
+> ZE4[...] -->|封装|EP
+> EP -->|提供信息|B
+> B -->|激活|N[节点]
+> N-->|执行|P[publish
事件]
+> P -->|调用|H[BuffHandler]
+> H -->|调用|B1[buff.start
buff.end
...]
+> H-->|发布|B2[Buff更新事件]
+> B2-->|调用|BM[BuffManager
执行CRUD]
+> ```
+
+## **Buff的分类**
+
+### 注意,在理念上,这些Buff需要被进行分类,但是在实现过程中,Buff是不分类的。这里的分类讨论只是为了明确Buff的业务逻辑框架。根据Buff的功能性,我们可以将Buff分成2类
+
+- 增减益Buff:这类Buff总是会给予对象**数值上的改变**,比如:增幅、削弱属性,影响乘区等;
+- 触发器Buff:这类Buff不包含任何的数值改变,但是它的触发本身会导致一些其他事情的发生——可能是造成一些附加伤害、可能是触发别的Buff、或者是修改角色某个特殊状态等
+不光是ZSim,可以说所有游戏中的Buff都可以被概括为这两个类别。
+**这一Buff分类准则作为ZSimBuff系统的底层设计,在本次重构中并未改变。只是,新系统重,不同类型的Buff需要构建不同给的Handler来处理它们的业务逻辑**
+
+## **Buff的生命周期管理(CRUD):**
+
+- Buff的创建
+ - Buff只在初始化时被创建,在整个模拟过程中,构造函数只被调用一次。新架构中,Buff对象只有一个,由该Buff对象来统一记录、管理不同对象身上的Buff的情况(持续时间、层数等)
+ - Buff在创建时,还需要根据自身的`logic_id`中记录的子条件组合,调用`event_router`的注册器,将自己注册到对应的逻辑树节点上。
+- Buff的新增/刷新:
+ - Buff的新增、刷新事件业务链:
+ 1. `event_router`提供的事件画像触发了逻辑树上的对应节点,节点激活时,会调用`publish`方法,调用`buff_event_handler`来调用buff的`start`和`end`方法,同时发布一个`buff更新事件`.
+ 2. `buff.start()`或者`buff.end()`方法调用时,会更新自身的信息,
+ 3. `BuffManager`收到`buff更新事件`时,会执行对应角色的Buff增删操作。
+
+- Buff的查找:
+ - 通过调用`buff_manager`的对应接口来实现Buff的查找
+- Buff的消退:
+ - Buff的消退的流程和其新增流程类似,同样是通过`event_router`或是`GlobalBuffController`抛出事件、调用对应的Handler执行。注意,部分Buff的消退不依赖于自然时间的流逝,而是具有特殊的判定行为,这部分业务交由`event_router`的逻辑树管理,Buff自身不负责判定何时消退。
+
+-----------------------------------
+
+## **关于Buff效果系统的重构**
+
+### 老框架
+
+- 通过读取`buff_effect.csv`获取Buff对应的效果`dict[str, int | float]`,然后借助`data_analyzer.py`等模块,最终在构造乘区类`MultiplierData`时,转译成各属性、乘区加成
+- 缺陷:
+ - `data_analyzer.py`的业务逻辑基本就是字符串解释器,扩展性较差,而且维护、拓展非常烧脑,并且运行需要传入`Generator`来构造`list[Buff]`,耦合程度太高,难以测试。
+ - `MultiplierData`框架设计于立项初期,未考虑拓展和解耦,导致处理任何计算事件时都需要把全部属性、乘区都构造一遍,且生命周期极短,用完就扔,性能浪费严重,
+ - `MultiplierData`没有设计供外部调用的接口,导致外部模块(例如`Buff.logic`或是`Character`)需要知道角色的动态属性时,就不得不调用大量参数就地构建一个新的`MultiplierData`
+
+### 新框架
+
+- 新框架将对整个系统(涉及到:`Buff`, `Character`, `Calculator`等多个模块)进行了重构,彻底实现“Buff生效”功能的解耦。
+- 核心思路如下:
+ 1. 将属性和乘区归还给`Character`对象,一同归入`Character`的还有计算属性和乘区的一些方法
+ 2. 将Buff的效果对象化,并且在`Character`内部构造专门的容器用来存放效果对象,容器私有,外部只能访问`Character`提供的接口来获取所关心属性的实时加成
+ 3. 在`buff_effect`池和`active_buff_list`池之间,构建自动化的同步流程,保证Buff新增时,加成池同步更新(但是需要考虑类似于席德强袭Buff这种“存在但不生效”的情况)
+ - 切入点:`buff_effect`自己不知道是否应该加入效果池,所以,在`active_buff_list`更新时,`buff_effect`池子默认保持更新,而独立存在一个“去除Buff效果”或者是“使Buff存在但效果静默”的事件来执行这件事情——这属于一个Buff的额外效果,需要注册到事件树中。
+ 4. 在`Calculator`中,对新架构进行适配(工作量略大)
+
+### 相关重构细节如下(仅限于`buff_effect`以及角色属性、乘区相关)
+
+- `effect_base_class`相关(新增)
+ - `effect_base_class`就是本轮重构中,为“Buff效果”设计的类,专门服务于更改属性、乘区的Buff效果而创建,至于Buff触发器,将直接通过事件树进行注册,而不走`effect_base_class`路径,也不会进入`Character`的加成池。
+ - 该对象的构造依赖数据库中记录的Buff效果json:
+
+ ```json
+ [
+ {
+ "target_attribute": "固定攻击力",
+ "value": 100,
+ "element_type": [1,2,3],
+ "skill_tag": ["1301_SNA_1", "1301_SNA_2"]
+ },
+ {
+ "target_attribute": "增伤",
+ "value": 0.3
+ },
+ ]
+ ```
+
+ - 根据以上格式的`json`数据,由`Buff`对象来负责构建`Buff.effect`对象。该对象会在Buff激活时,直接加入`BuffManager.buff_effect_pool`中,注意,一个Buff对应的effect的`json`字段可能有多个,此时我们需要构造多个`effect`对象,做到一个`effect`对象仅管理一种效果。考虑到Buff的效果被分为“属性值增减益”和“事件触发器两类”,所以,设计两个继承自`effect_base_class`的类,分别处理两种不同的业务。
+ - `bonus_effect_class(effect_base_class)`对象,具有属性和方法:
+ - `value`:每一层Buff增幅的数值
+ - `target_attribute`:增幅的项目
+ - `apply_condition_list: list`:能够使Buff生效的额外条件,除`target_attribute`和`value`字段以外的其他字段,都会被视作生效条件约束,它们都会被编入`apply_judger.apply_condition_list`中
+ - `trigger_class(effect_base_class)`对象,具有属性和方法:
+ - 前提条件:`json`字段中含有`trigger`参数,且对应值为True时,其他参数除`event_id`以外,全部失效(当然,最好要通过pydantic进行检测,这样可以尽早暴露JSON文件填写的问题)
+
+- `Character`相关
+ - 在`Character`下,构建一个新的`dynamic_attribute`(暂时名)类,与原有的`Statement`并列
+ - 将原本属于`MultiplierData`管理的动态属性和乘区占位符合并、转移到`dynamic_attribute`下
+ - 新增`attribute_calculator`对象,迁移位于`Calculator.py`中的大量计算属性、乘区的方法,业务逻辑上:通过调用`Character`原有的`Statement`方法获取静态面板,然后调用`buff_manager.bonus_applier`方法获取当前的动态加成,最后计算出实时属性。
+- `Character`相关的新组件
+ - `buff_effect_selector`方法,接收核心参数`environment_profile`(事件画像),该参数由外部结构`event_router`抛出,根据该对象中记录的事件标签组合,从当前激活的Buff中筛选出适配的效果
+ - `bonus_applier`方法,该方法仅接受核心参数:`target_attribute`和`applied_buff_effect_list`,通过遍历`applied_buff_effect_list`,计算`target_attribute`的加成,返回给`Character.dynamic_attribute.attribute_calculator`
+ - `active_buff_list`:Character级别的动态Buff列表,通过订阅Buff状态变更事件自动维护
+ - `bonus_pool`:Character级别的增益池对象,封装了效果池,以及效果池所需的CRUD方法。
+ - `buff_effect_data`:这是整个`bonus_pool`对象的核心,是一个手动搭建的二维数据结构,但不能`data_frame`
+ > 需要二维数据结构但又不选择`data_frame`的原因:
+ > - 要满足Buff的CRUD操作中,通过buff的id或是index来锁定对应buff_effect的需求,
+ > - 要满足计算阶段,搜索某类属性加成时能够返回全部的适配buff_effect
+ > - 而`data_frame`的内核其实是遍历,无法真正做到性能的节省。在buff系统中, 关于`buff_effect`的操作是非常频繁的,所以必须考虑性能问题。
+ - `add_buff_effect`:向`buff_effect_data`中新增Buff效果的方法
+ - `cancel_buff_effect`:向`buff_effect_data`中删除Buff效果的方法
+ - 其他方法按照业务需求进行拓展。
+
+## **buff_effect_data数据结构示意图**
+
+以下是复合字典结构在ZSim中的应用示例:
+
+### 多字典索引结构
+
+| Buff名称或者id | 攻击力相关 | 生命值相关 | 增伤区相关 | 防御区相关 |
+|:---------|:-----------|:-----------|:-----------|:-----------|
+| **席德-围杀** | ✅ [+100] | ❌ | ✅ [+15%] | ❌ |
+| **席德-强袭** | ✅ [+200] | ❌ | ✅ [+25%] | ❌ |
+| **拂晓生花-普攻增伤** | ❌ | ❌ | ✅ [+10%] | ❌ |
+| **拂晓生花-四件套常驻** | ✅ [+50] | ✅ [+300] | ❌ | ❌ |
+| **机巧心种-暴击** | ✅ [+12%] | ❌ | ❌ | ❌ |
+| **机巧心种-电属性增伤** | ❌ | ❌ | ✅ [+20%] | ❌ |
+
+### 对应的字典结构
+
+```python
+# 按Buff管理的字典 - 用于快速增删
+_effects_by_buff = {
+ 1001: [attack_effect_1001, damage_bonus_effect_1001], # 席德-围杀
+ 1002: [attack_effect_1002, damage_bonus_effect_1002], # 席德-强袭
+ 2001: [damage_bonus_effect_2001], # 拂晓生花-普攻增伤
+ # ...
+}
+
+# 按属性索引的字典 - 用于快速查询
+_effects_by_attribute = {
+ "攻击力": [attack_effect_1001, attack_effect_1002, attack_effect_2002, crit_effect_3001],
+ "生命值": [hp_effect_2002],
+ "增伤": [damage_bonus_effect_1001, damage_bonus_effect_1002, damage_bonus_effect_2001, damage_bonus_effect_3002],
+ "防御": [], # 当前无防御相关Buff
+}
+```
\ No newline at end of file