From 0222501162d18447aa162e9c53e000bc42ccbbd9 Mon Sep 17 00:00:00 2001 From: "Somhairle H. Marisol" Date: Mon, 21 Sep 2026 22:55:01 +0800 Subject: feat(occupation): P15 补齐货郎/书生各 2 条每日任务模板 + 既有事件通道信号桥(Kernel 纯函数 TDD) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - TaskTemplateId 扩出 BuyGoods(收货)/Resell(转卖)/ObserveNotes(观察记事)/ReasonDebate(论理), taskPoolOf 货郎=[收货,转卖]、书生=[观察记事,论理],四职业池均非空。 - TaskSignal 增 Purchased(玩家买入)/Observed(观察),satisfiesCompletion 给出四模板完成判定; 新增 occupationSeedOf/refreshToday(跨日才重生成,同日含 Done/Failed 终态保留)。 - signalOfInteraction/advanceTask:仅 playerId 发起的 DialogueEvent/TradeEvent 产生信号, NPC 互动不派任务;Offered 须先 acceptTask,终态不可复活。WorldSave 模板 token 双向同步。 - 测试 83 全绿(新增 6:池非空、货郎书生逐日池内确定、模板中文名、完成信号正负、 事件桥含玩家买/卖与 NPC 忽略、既有 DialogueEvent→accept→Done 全链路、refreshToday 逐日)。 - 不动 Sim.step 数值路径;旧档 v3 模板 token 仅新增合法值,未知模板仍拒绝。 --- src/LivingVillage.Kernel/Occupation.fs | 61 ++++++++++++++++++++++++++++++---- 1 file changed, 55 insertions(+), 6 deletions(-) (limited to 'src/LivingVillage.Kernel/Occupation.fs') diff --git a/src/LivingVillage.Kernel/Occupation.fs b/src/LivingVillage.Kernel/Occupation.fs index 5c75c81..2168c4e 100644 --- a/src/LivingVillage.Kernel/Occupation.fs +++ b/src/LivingVillage.Kernel/Occupation.fs @@ -51,7 +51,8 @@ module Occupation = | Done | Failed - /// 首期任务池 = 农夫3(送粮/帮工/翻土)+ 渔夫3(夜捕/卖渔/询价),design §3 表。 + /// 首期任务池 = 农夫3(送粮/帮工/翻土)+ 渔夫3(夜捕/卖渔/询价) + /// + P15 补齐货郎2(收货/转卖)+ 书生2(观察记事/论理),design §3 表。 type TaskTemplateId = | DeliverGrain | HelpWork @@ -59,6 +60,10 @@ module Occupation = | NightCatch | SellFish | MarketInquiry + | BuyGoods + | Resell + | ObserveNotes + | ReasonDebate let taskNameOf (template: TaskTemplateId) : string = match template with @@ -68,14 +73,26 @@ module Occupation = | NightCatch -> "夜捕" | SellFish -> "卖渔" | MarketInquiry -> "询价" + | BuyGoods -> "收货" + | Resell -> "转卖" + | ObserveNotes -> "观察记事" + | ReasonDebate -> "论理" - /// 货郎/书生模板留后续单,当前为空池 → 不生成每日任务。 let taskPoolOf (kind: Kind) : TaskTemplateId list = match kind with | Farmer -> [ DeliverGrain; HelpWork; TillSoil ] | Fisher -> [ NightCatch; SellFish; MarketInquiry ] - | Peddler - | Scholar -> [] + | Peddler -> [ BuyGoods; Resell ] + | Scholar -> [ ObserveNotes; ReasonDebate ] + + /// 每职业任务流种子:与世界 seed 异或后驱动 taskHash,使四职业的每日序列互不相同。 + /// 纯常量(splitmix 同源),禁 System.Random。 + let occupationSeedOf (kind: Kind) : uint64 = + match kind with + | Farmer -> 0x1111111111111111UL + | Fisher -> 0x2222222222222222UL + | Peddler -> 0x3333333333333333UL + | Scholar -> 0x4444444444444444UL type DailyTask = { TemplateId: TaskTemplateId @@ -87,12 +104,15 @@ module Occupation = /// 完成判定信号(design §3:走既有事件通道)。 /// Dialogued 来自 DialogueEvent(chooseDialogue);Traded 来自 TradeEvent, - /// 字段序 = 卖方/物品/数量(玩家递出即 seller = playerId);ArrivedAt 为到达瓦片; + /// 字段序 = 卖方/物品/数量(玩家递出即 seller = playerId);Purchased = 玩家买入 + /// (TradeEvent 中 playerId 为 buyer);ArrivedAt 为到达瓦片;Observed 为观察面板; /// NightAtWater 由调用方把 isNight + 水边判定折叠后喂入。Sim.step 零感知。 type TaskSignal = | Dialogued of Sim.NpcId | Traded of Sim.NpcId * Sim.ItemKind * int + | Purchased of Sim.ItemKind * int | ArrivedAt of int * int + | Observed | NightAtWater /// TargetNpc/TargetTile 为 None 时视为通配(指定目标由后续 Desktop 接线单落地)。 @@ -110,6 +130,10 @@ module Occupation = | NightCatch, NightAtWater -> true | SellFish, Traded (seller, item, quantity) -> seller = Sim.playerId && item = Sim.Fish && quantity >= 1 | MarketInquiry, Dialogued _ -> true + | BuyGoods, Purchased (_, quantity) -> quantity >= 1 + | Resell, Traded (seller, _, quantity) -> seller = Sim.playerId && quantity >= 1 + | ObserveNotes, Observed -> true + | ReasonDebate, Dialogued _ -> true | _ -> false /// Offered → Active;其余状态原样返回(终态不可复活)。 @@ -145,7 +169,7 @@ module Occupation = state <- next out - /// 当日任务生成:池内确定性抽 1 条;空池(货郎/书生首期)→ None。 + /// 当日任务生成:池内确定性抽 1 条;空池 → None。 /// TargetNpc/TargetTile 首期留 None(通配),OfferedTick/DueTick 覆盖整日。 let dailyTaskOf (seed: uint64) (occupationSeed: uint64) (dayIndex: int64) (kind: Kind) : DailyTask option = match taskPoolOf kind with @@ -161,6 +185,13 @@ module Occupation = DueTick = offered + Sim.ticksPerDay - 1L State = Offered } + /// 当日任务刷新:已持任务的 OfferedTick 仍落在 nowTick 所属日时原样保留(含 + /// Done/Failed 终态,保证当日面板与存档稳定);跨日才按当日重新生成 Offered。 + let refreshToday (seed: uint64) (occupationSeed: uint64) (kind: Kind) (nowTick: int64) (today: DailyTask option) : DailyTask option = + match today with + | Some task when dayIndexOf task.OfferedTick = dayIndexOf nowTick -> Some task + | _ -> dailyTaskOf seed occupationSeed (dayIndexOf nowTick) kind + /// 挂在玩家侧的职业状态,随存档 v3 保存(design §2)。 /// Today = 当日任务(§3);TaskToken 保留为 v3 前缀占位扩展位(恒 0,暂未启用)。 type State = @@ -172,6 +203,24 @@ module Occupation = let stateOf (kind: Kind) : State = { Profile = profileOf kind; TaskToken = 0; Today = None; StoryStage = 0 } + // ---- 既有事件通道 → 任务信号桥(design §3:完成判定统一走 InteractionEvent)---- + + /// 仅玩家发起的对话/交易才产生任务信号;NPC 之间互动的副作用不派任务。 + /// TradeEvent 字段序 = buyer/seller/item/quantity/price,玩家为卖方 → Traded,为买方 → Purchased。 + let signalOfInteraction (event: Sim.InteractionEvent) : TaskSignal option = + match event.Kind with + | Sim.DialogueEvent outcome when outcome.Actor = Sim.playerId -> Some(Dialogued outcome.Target) + | Sim.TradeEvent (_, seller, item, quantity, _) when seller = Sim.playerId -> Some(Traded(seller, item, quantity)) + | Sim.TradeEvent (buyer, _, item, quantity, _) when buyer = Sim.playerId -> Some(Purchased(item, quantity)) + | _ -> None + + /// 用一条既有事件推进当日任务(Offered 须先 acceptTask;终态不可复活)。 + /// 无职业 / 无当日任务 / 非玩家事件时原样返回。 + let advanceTask (event: Sim.InteractionEvent) (state: State) : State = + match state.Today, signalOfInteraction event with + | Some task, Some signal -> { state with Today = Some(applySignal signal task) } + | _ -> state + // ---- 报价偏置(design §2/§3):Kernel 纯函数旁路,不改 Sim.fs 数值口径 ---- // 4 位定点整数基点(1/10000)表达偏置,避免浮点漂移;结果钳在 [0.95, 1.05]。 // 货郎:买卖双向有利(买压价/卖抬价 ±3.00%);书生:解读行情小幅加成 ±1.00%; -- cgit v1.2.3