作品见证 · 技术文章
一张牌如何变成一条可追溯的因果链
CardWorld 为什么要保存根卡、来源实体与生成回合,以及这些字段如何把 LLM 叙事变成可检查的世界历史。
内容核验:2026-08-01
问题不是生成,而是来路
让 LLM 写出“雾港出现一座潮汐塔”并不难。真正困难的是下一回合:玩家把另一张牌放到潮汐塔上时,系统能否回答它属于哪个地域、由哪张牌触发、在哪一回合出现,又为什么会影响新的事件。
如果只保存最终文本,世界看起来在变化,却没有可计算的历史。CardWorld 因此把每次生成拆成规则事实、因果引用和叙事表达三层。叙事可以重写,事实和引用不能凭空漂移。
三类对象构成世界
世界根与地域
世界根是第一次落牌时的锚点。根卡可以创建初始地域;后续地域保留 origin_root_card_id,因此地图能画出“这片地域从哪张根卡长出来”的第一层关系。
地域实体
实体存在于地域中,可以是人物、组织、建筑、资源或事件。实体记录创建回合、生命周期和剩余回合;短期事件会自然衰减,新实体不会在诞生的同一回合被错误老化。
卡牌放置
一次放置不仅包含卡牌,还包含目标类型与目标 ID。目标可能是世界根、地域或已有实体。这个选择决定模拟上下文,也为后续保存 source_card_id 与 parent_entity_id 提供来源。
从动作到历史的路径
玩家选择卡牌与目标
│
▼
服务端校验身份、所有权、房间阶段
│
▼
原子认领 room + turn 的模拟权
│
▼
规则层收集地域、实体、事件种子与历史
│
▼
LLM 返回受约束的结构化变化
│
▼
一个事务写入地域、实体、种子、奖励卡、回合历史
│
▼
地图与叙事面板展示“发生了什么”及“从哪里来”关键点在“一个事务”。如果叙事已经生成,但回合历史写入失败,系统不能留下半个新地域或几张孤立奖励卡。提交失败时,所有本回合写入一起回滚,内存中的临时 ID 也恢复原状。
为什么根边还不够
只有 origin_root_card_id 时,地图最多能解释地域与世界根的关系。实体之间更细的链路还需要两个引用:
source_card_id:这次变化由哪张卡直接触发。parent_entity_id:这次变化基于哪个已有实体继续生长。
二者不能互相替代。卡牌回答“玩家施加了什么意图”,父实体回答“世界中的哪个对象承接了意图”。当一张卡直接作用于地域时,父实体可以为空;当它作用于人物或建筑时,父实体就成为链上的上一个节点。
可视化不应重新发明事实
因果图应当消费与世界状态相同的存储字段,而不是在浏览器里根据名称或叙事猜关系。一个可靠的视图至少要区分:
- 根卡到地域的起源边。
- 来源卡到新实体的触发边。
- 父实体到子实体的延续边。
- 缺少引用的历史数据,用明确的“来源未记录”状态表示。
选择地域时,地图和详情面板可以只突出相关链路,避免把完整世界历史一次性堆到屏幕上。移动端则应把图和详情拆成可切换视图,并保留同一组选中状态。
LLM 在链条中的正确位置
LLM 可以建议新地域、实体、事件种子和叙事,但它不直接决定数据库主键,也不能自行提交房间阶段。服务端先验证结构,再在事务中分配真实 ID 和建立引用。
这条边界带来一个有用性质:即使更换模型、重试叙事或以后重新生成文案,已经保存的因果关系仍然稳定。模型负责让历史值得读,数据模型负责让历史能够被证明。
如何验证这不是一张静态示意图
验证应从一条真实动作链开始:根卡创建地域,第二回合把卡放到地域生成实体,第三回合再把卡放到该实体。随后同时检查数据库引用、世界状态接口和前端选中链路。
单元测试负责字段与回滚,集成测试负责公开的结束回合接口,端到端测试负责用户能否从卡牌一路看到地图变化。只有三层对上,因果图才是产品能力,而不是一张和运行时脱节的插图。