diff options
| author | Somhairle H. Marisol <[email protected]> | 2026-09-21 12:14:45 +0800 |
|---|---|---|
| committer | Somhairle H. Marisol <[email protected]> | 2026-09-21 12:14:45 +0800 |
| commit | dac7e793eeb56d8de7cad98855366e2c27ce0486 (patch) | |
| tree | 7d6a908c619195f36b2bb5154b6ed34f71e17fdc /docs | |
| parent | d10942ba82837b201bb29d83b65ba6c6db3356bf (diff) | |
| download | living-village-dac7e793eeb56d8de7cad98855366e2c27ce0486.tar.gz | |
docs: 职业系统设计稿
开局职业候选(渔夫/货郎/药童/书生)、与需求/关系/谣言/年鉴/交易的接合点、每日任务池与轻剧情线、Kernel 数据结构草案与存档 v2 兼容策略、确定性要求与分阶段验收清单;仅设计不实现,待用户拍板。
Diffstat (limited to 'docs')
| -rw-r--r-- | docs/profession-system-design.md | 124 |
1 files changed, 124 insertions, 0 deletions
diff --git a/docs/profession-system-design.md b/docs/profession-system-design.md new file mode 100644 index 0000000..efb3517 --- /dev/null +++ b/docs/profession-system-design.md @@ -0,0 +1,124 @@ +# 职业系统设计(江南水乡 · 社会模拟主线扩展) + +状态:**设计稿,待用户拍板;未写任何实现代码。** +范围:在现有社会模拟主线(需求 / 关系 / 谣言 / 年鉴 / 交易)之上加入开局职业选择。本文档只定义设计、数据结构草案与验收标准;实现走后续独立小步 TDD 单。 + +## 1. 设计目标 + +1. 开局有身份感:职业决定玩家起点的经济位、社会位与日常节奏,而不是只改一个数字。 +2. 深化现有模拟主线:每个职业都通过**已有系统**(需求曲线、关系网络、谣言传播、年鉴记录、交易行情)产生不同的运行时表现,不新增平行系统。 +3. 确定性可验收:同 seed 同职业选择两次开局逐字节一致;同 seed 不同职业的差异必须可解释、可测试。 +4. 中文优先:所有职业名、任务名、对话分支、年鉴条目全部中文,走既有 CJK 图集管线(新增用字同步 cjk-glyphs.txt 并重生成图集)。 + +## 2. 开局职业候选(4 个,全部江南背景) + +| 职业 | 定位 | 初始差异(草案) | 一句话体验 | +|---|---|---|---| +| 渔夫 | 生计型 | 初始金钱中下;饥饿/精力消耗略快(劳作);清晨时段行动加成;开局获得"渔获"可交易物 | 靠水吃水,日子看天 | +| 货郎 | 交易型 | 初始金钱最高、初始库存最少;交易报价有小幅优势(更接近行情上限);认识更多村民(初始关系网更广) | 走街串巷,贱买贵卖 | +| 药童 | 社交型 | 初始金钱低;精力消耗慢;对话成功率小幅加成;谣言可信度加成(别人更愿意信他传的话) | 采药问诊,消息灵通 | +| 书生 | 叙事型 | 初始金钱中;各属性均衡;年鉴条目更详细(多一句话注脚);对话分支中多一个"讲道理"选项倾向 | 读过书的人,看事不同 | + +取舍说明:刻意不做"战斗/等级"类职业;全部职业差异都落在现有数据字段(Needs、Inventory、关系初值、对话/谣言参数)上,避免引入新的运行时机制。 + +## 3. 与现有社会模拟主线的接合点 + +| 系统 | 接合方式(全部复用现有机制) | +|---|---| +| 需求(Needs) | 职业修改初始值与每日消耗系数(如渔夫饥饿消耗 ×1.15、药童精力消耗 ×0.9),不改 Needs 结构 | +| 关系(Relations) | 职业决定初始关系网:货郎开局与 3 名村民认识(关系值小正数),其余职业只与近邻认识;职业身份影响关系事件的可选回应 | +| 谣言(Rumors) | 药童传播谣言可信度加成;书生听到的谣言在年鉴多记来源线索;传播算法本身不变 | +| 年鉴(Annals) | 开局写入一条"身份条目"(如"村民 0 以打渔为生");职业任务完成/失败照常进年鉴,形成个人传记线 | +| 交易(Trade) | 货郎报价系数微调;渔夫有渔获可卖;行情、库存、成交逻辑不变 | + +## 4. 每日任务池(示例,每职业 3 类,每日确定性抽取 1 条) + +抽取规则:`hash(seed, day, profession) mod 任务数`,种子化、可重放,不用墙钟。 + +- **渔夫**:「清晨去河埠打渔」(在指定时段到达河岸瓦片附近)→ 产出渔获 ×N;「把渔获卖给村民 X」→ 触发交易事件;「听村民 Y 说说行情」→ 触发对话事件。 +- **货郎**:「收购村民 X 的食物」;「把货物带到石桥边卖掉」;「和村民 Y 攀谈打听新品」。 +- **药童**:「送药给村民 X」(赠予式交易,关系小加成);「听村民 Y 的烦心事」;「在天黑前回到家」。 +- **书生**:「观察村庄记一笔」(执行一次观察);「与村民 X 论理」(特殊对话分支);「翻阅年鉴回顾昨日」。 + +任务状态机:`提出 → 进行中 → 完成 / 失败(过期)`,完成写年鉴并给小额需求/关系反馈;失败也写年鉴(允许"不顺的日子",不做惩罚性数值扣减)。 + +## 5. NPC 对话分支与剧情线影响 + +- 对话意图仍为现有 6 种(闲聊/求助/提议交易/讲笑话/道歉/挑衅);职业**不新增意图**,而是给部分意图加职业限定回应: + - 村民对"渔夫"的闲聊回应可能聊天气与鱼汛; + - 村民对"货郎"的提议交易更容易进入讨价还价回应; + - 村民对"药童"的求助更可能给出友好回应; + - 村民对"书生"的讲笑话更可能给出保留回应(反差感,叙事味)。 +- 轻剧情线(每职业一条,纯年鉴/对话驱动,无过场动画): + - 渔夫线「水位与收成」:连续数日渔获走低 → 可向村民打听原因 → 年鉴结案(三种结局文案,由当时关系均值决定); + - 货郎线「新货的路子」、药童线「时疫传闻」、书生线「庙前的争论」同理,均为"接任务 → 走访 → 收束"三段式。 +- 所有分支产出仍是 `DialogueEvent / AnnalEntry / 关系变化`,复用现有记录与确定性管线。 + +## 6. Kernel 数据结构草案(不实现,仅草案) + +```fsharp +// 新增(Sim.fs 或独立 Profession.fs,命名空间 LivingVillage.Kernel) +type ProfessionKind = + | Fisher // 渔夫 + | Peddler // 货郎 + | Apothecary // 药童 + | Scholar // 书生 + +type DailyTask = + { TaskId: int + Kind: TaskKind // VisitTile / TradeWith / TalkTo / Observe + TargetNpc: NpcId option + TargetTile: TilePosition option + DueTick: int64 + State: TaskState } // Offered / Active / Done / Failed + +type ProfessionProfile = + { Kind: ProfessionKind + NeedsDrift: Needs // 每日消耗系数(乘法,1.0 为无修正) + InitialRelations: (NpcId * float32) list + DialogueBias: Map<DialogueIntent, DialogueResponse> // NPC 回应倾向修正 + RumorCredibility: float32 } + +// World 扩展(v2 字段,全部 Option/默认值,见 §7) +type World = { + ... + Profession: ProfessionState option // v2 新增;旧存档反序列化后 = None +} +and ProfessionState = + { Profile: ProfessionProfile + TodayTask: DailyTask + StoryStage: int } // 轻剧情线进度 0..3 +``` + +要点:`ProfessionState` 挂在 World 上而不是 Avatar 上,便于以后给 NPC 也配职业(NPC 职业超出本单范围,仅留扩展空间)。 + +## 7. 存档 v2 兼容策略 + +1. 存档头部加 `version` 字段(v1 = 现有存档,v2 = 含职业);读取时按 version 分派。 +2. v1 存档读入后 `Profession = None`:游戏内表现为"无职业村民",一切现状不变;UI 显示"无名之辈"式中性文案或直接不显示职业区块(实现单拍板,倾向后者)。 +3. v1 存档允许"补选职业"一次(进入游戏后弹一次选择页),补选后升级为 v2 保存;也可继续以无职业游玩。 +4. 新开局默认必选职业(标题→新游戏→职业选择页,UI 复用现有菜单页机制)。 +5. 迁移与读写各配确定性测试:v1 读入→原样另存为 v1 字节不变;v1+补选→v2 往返一致。 + +## 8. 确定性要求 + +1. 职业选择是**世界输入**的一部分,等价于 seed 之外的第二个种子分量:`hash(seed, profession)`. 同 (seed, profession) 两次开局,`World` 结构逐字节一致(复用现有 `initialWorld` 确定性测试框架,新增参数化用例)。 +2. 每日任务抽取用 `(seed, profession, day)` 纯函数哈希,禁止依赖随机数状态推进次序以外的任何外部输入。 +3. 职业对 Needs/谣言/交易的修正全部是**纯函数系数**,不引入可变全局量;长期批验(M3 冻结管线口径)不受影响——冻结产物不含职业系统。 +4. 同 seed 不同职业的存档互不兼容是预期行为(职业是世界事实),存档头部记录 profession 以便校验。 + +## 9. 分阶段实现与验收标准(供拍板后拆单) + +| 阶段 | 内容 | 验收标准 | +|---|---|---| +| P-a | Kernel:ProfessionKind/Profile/World v2 字段 + 存档 v1/v2 读写 | build/test 全绿;v1 存档读取回归;同 (seed, profession) 两次初始 World 逐字节一致;v1 往返字节不变 | +| P-b | 开局选择页(标题→新游戏→职业页)+ 初始差异生效 | Xvfb 截图:职业选择页 1 张、四种职业开局 HUD/状态差异各 1 张;UI 文案全部走 CJK 图集无缺字 | +| P-c | 每日任务池 + 年鉴接入 | 抽取函数确定性测试(同输入同任务);完成/失败写年鉴回归;真实运行录屏一段 | +| P-d | 对话分支倾向 + 四条轻剧情线 | 回应分布测试(职业偏差可观测且确定);剧情线三种结局由关系均值确定性决定;视频证据 | + +## 10. 明确不做(本设计边界) + +- 不做职业专属技能树、等级、战斗、装备系统。 +- 不做 NPC 职业化(仅预留数据空间)。 +- 不改冻结 M3 管线口径;长测基准世界不含职业系统。 +- 不引入外部素材与付费内容;UI 沿用现有图集与 CJK 管线。 |
