可运行作品 · 多人世界模拟

CardWorld:把卡牌变成持续演化的世界

玩家用卡牌给世界施加意图;规则引擎维护可验证的状态,LLM 负责把结构化变化讲成故事。

内容核验:2026-08-01

它解决什么

CardWorld 不是一次性聊天窗口,而是一套可以连续玩多个回合的世界模拟。玩家创建或加入房间,把手牌放到地域、实体或世界根节点上;服务端结算规则、生成新状态,再由 LLM 写出本回合叙事。

核心问题是:怎样让生成式叙事既有想象力,又不破坏游戏事实。 因此,系统把“事实如何变化”和“变化如何表达”拆成两个边界。数据库与规则引擎拥有状态真相,LLM 只在约束后的结构上补充命名、描述与叙事。

第一次如何玩

  1. 登录后创建一个世界,单人房间会直接进入放置阶段。
  2. 从手牌选择一张卡;世界还没有地域时,可以先把卡放到世界根节点。
  3. 结束回合,等待服务端完成推演。
  4. 查看新叙事、最近变化地域、活跃事件与补充后的手牌。
  5. 连续推进三个回合,观察前一回合的实体如何成为下一回合的放置目标。
空过回合会得到明确确认;断线重连后,客户端会重新同步世界、历史与手牌,而不是只依赖可能错过的实时消息。

一次回合如何穿过系统

text
浏览器(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 仍是一件持续演进的个人作品:它优先证明完整回合、状态一致性与因果可读性,而不是追求海量卡牌或商业化匹配系统。公开介绍页不要求登录;真正创建或加入世界时,统一账号系统才接管身份边界。