可运行作品 · 多人世界模拟
CardWorld:把卡牌变成持续演化的世界
玩家用卡牌给世界施加意图;规则引擎维护可验证的状态,LLM 负责把结构化变化讲成故事。
内容核验:2026-08-01
它解决什么
CardWorld 不是一次性聊天窗口,而是一套可以连续玩多个回合的世界模拟。玩家创建或加入房间,把手牌放到地域、实体或世界根节点上;服务端结算规则、生成新状态,再由 LLM 写出本回合叙事。
核心问题是:怎样让生成式叙事既有想象力,又不破坏游戏事实。 因此,系统把“事实如何变化”和“变化如何表达”拆成两个边界。数据库与规则引擎拥有状态真相,LLM 只在约束后的结构上补充命名、描述与叙事。
第一次如何玩
- 登录后创建一个世界,单人房间会直接进入放置阶段。
- 从手牌选择一张卡;世界还没有地域时,可以先把卡放到世界根节点。
- 结束回合,等待服务端完成推演。
- 查看新叙事、最近变化地域、活跃事件与补充后的手牌。
- 连续推进三个回合,观察前一回合的实体如何成为下一回合的放置目标。
空过回合会得到明确确认;断线重连后,客户端会重新同步世界、历史与手牌,而不是只依赖可能错过的实时消息。
一次回合如何穿过系统
浏览器(Next.js / React Flow)
│ 放置卡牌、结束回合、WebSocket 同步
▼
Cardroller API(Go / Gin)
│ 校验身份、回合、所有权与阶段
▼
规则与模拟层
│ 认领本回合 → 计算变化 → 调用 LLM → 原子提交
▼
SQLite(WAL)
│ 房间、地域、实体、事件种子、回合历史
└──────────► 广播新回合、手牌与结算叙事浏览器并不自己判定世界事实。它可以保留尚未落库的放置草稿,但在服务端阶段、新回合或服务端草稿到达时会主动收敛。地图只消费服务端返回的地域、实体与根卡关系。
关键设计取舍
规则拥有事实,LLM 拥有表达
LLM 返回结构化结果,但持续回合数、实体生命周期、卡牌消耗与房间阶段由确定性逻辑校验。这样可以把模型失败降级成可处理的推演错误,而不是让世界进入无法解释的中间状态。
结算只成功一次
同一房间、同一回合通过带回合条件的原子更新认领模拟权。地域、实体、事件种子、奖励卡与回合历史随后在一个数据库事务内提交;后段写入失败时,前面的变化会一起回滚。
实时消息是提示,不是唯一真相
WebSocket 用于快速通知阶段和叙事变化。客户端在连接建立与重新进入放置阶段时,会并行重取世界状态、历史与手牌,因此漏掉一条 hand_drawn 消息不会永久丢失手牌。
首体验必须能自证
作品以“单人连续完成三个回合”为最小验收链:创建房间、落牌、结算、抽牌、看到叙事与地图变化。后端集成测试走公开的结束回合接口,前端端到端测试同时覆盖桌面、移动端、明暗主题和断点切换。
可核验的能力
- Go 服务端:阶段机、身份与所有权校验、事务提交、并发认领、WebSocket 广播。
- React 前端:卡牌放置、世界地图、最近变化提示、移动端分栏、键盘可达性。
- LLM 边界:结构化输出、失败关闭、可替换 Provider 与测试 Mock。
- 工程交付:容器化运行、静态检查、单元测试、集成测试与 Playwright 关键路径。
当前边界
CardWorld 仍是一件持续演进的个人作品:它优先证明完整回合、状态一致性与因果可读性,而不是追求海量卡牌或商业化匹配系统。公开介绍页不要求登录;真正创建或加入世界时,统一账号系统才接管身份边界。