namespace LivingVillage.Kernel.Tests open Microsoft.VisualStudio.TestTools.UnitTesting open LivingVillage.Kernel open LivingVillage.Kernel.Sim module private BiasHarness = /// 覆盖 responseFor 全部分支的两个人格样本: /// 外向+诚实(E>=0.5, H>=0.4)与 内向+不诚实(E<0.5, H<0.4)。 let outgoing = { Drive = 0.5f; Aggression = 0.2f; Extraversion = 0.8f; Honesty = 0.9f; Greed = 0.3f } let withdrawn = { Drive = 0.5f; Aggression = 0.2f; Extraversion = 0.2f; Honesty = 0.1f; Greed = 0.3f } let grumpy = { Drive = 0.5f; Aggression = 0.8f; Extraversion = 0.2f; Honesty = 0.1f; Greed = 0.3f } let atNpc (id: int) (world: World) : World = let npc = world.Npcs.[id] { world with Avatar = { world.Avatar with Pos = npc.Pos } } [] type OccupationBiasTests () = // ---- 无职业 = 既有 responseFor 输出逐字节不变 ---- [] member _.NoOccupationReproducesTheHistoricResponsesForEveryIntent () = for intent in [ SmallTalk; AskHelp; OfferTrade; Joke; Apologize; Provoke ] do for personality in [ BiasHarness.outgoing; BiasHarness.withdrawn; BiasHarness.grumpy ] do Assert.IsTrue( Sim.responseFor intent personality = Occupation.biasResponse None intent personality, $"no-occupation bias must be identity for {intent}") // ---- 农夫 AskHelp:Refused → Helpful(求助更友好)---- [] member _.FarmerAskHelpTurnsRefusalIntoHelpfulness () = // 不诚实村民对常人 Refused,对农夫给 Helpful Assert.AreEqual(Refused, Sim.responseFor AskHelp BiasHarness.withdrawn) Assert.AreEqual(Helpful, Occupation.biasResponse (Some Occupation.Farmer) AskHelp BiasHarness.withdrawn) // 本就 Helpful 的路径不变 Assert.AreEqual(Helpful, Sim.responseFor AskHelp BiasHarness.outgoing) Assert.AreEqual(Helpful, Occupation.biasResponse (Some Occupation.Farmer) AskHelp BiasHarness.outgoing) // ---- 渔夫 SmallTalk:Reserved → Friendly(聊天气鱼汛,冷场变热络)---- [] member _.FisherSmallTalkWarmsUpAReservedVillager () = Assert.AreEqual(Reserved, Sim.responseFor SmallTalk BiasHarness.withdrawn) Assert.AreEqual(Friendly, Occupation.biasResponse (Some Occupation.Fisher) SmallTalk BiasHarness.withdrawn) Assert.AreEqual(Friendly, Sim.responseFor SmallTalk BiasHarness.outgoing) Assert.AreEqual(Friendly, Occupation.biasResponse (Some Occupation.Fisher) SmallTalk BiasHarness.outgoing) // ---- 货郎 SmallTalk:Reserved → Bargaining(三句不离买卖,更易进讨价还价)---- [] member _.PeddlerSmallTalkSlidesIntoBargaining () = Assert.AreEqual(Reserved, Sim.responseFor SmallTalk BiasHarness.withdrawn) Assert.AreEqual(Bargaining, Occupation.biasResponse (Some Occupation.Peddler) SmallTalk BiasHarness.withdrawn) // 外向村民本就 Friendly,不被货郎偏差改写 Assert.AreEqual(Friendly, Occupation.biasResponse (Some Occupation.Peddler) SmallTalk BiasHarness.outgoing) // ---- 书生 Joke:完全覆盖为 Reserved(与 personality 相反的确定性偏差)---- // 语义说明:书生讲笑话走「完全覆盖」——无论村民性格如何,回应一律 Reserved。 // 刻意不做概率偏向(禁随机);反差感即设计意图(design §5)。 [] member _.ScholarJokeIsAlwaysReservedRegardlessOfPersonality () = Assert.AreEqual(Amused, Sim.responseFor Joke BiasHarness.outgoing) Assert.AreEqual(Reserved, Sim.responseFor Joke BiasHarness.withdrawn) for personality in [ BiasHarness.outgoing; BiasHarness.withdrawn; BiasHarness.grumpy ] do Assert.AreEqual( Reserved, Occupation.biasResponse (Some Occupation.Scholar) Joke personality) // ---- 偏差只作用于各自的签名意图,其余意图原样穿透 ---- [] member _.BiasOnlyAppliesToTheSignatureIntents () = // 农夫:仅 AskHelp 受偏差 for intent in [ SmallTalk; OfferTrade; Joke; Apologize; Provoke ] do for personality in [ BiasHarness.outgoing; BiasHarness.withdrawn ] do Assert.AreEqual( Sim.responseFor intent personality, Occupation.biasResponse (Some Occupation.Farmer) intent personality) // 渔夫:仅 SmallTalk 受偏差 for intent in [ AskHelp; OfferTrade; Joke; Apologize; Provoke ] do for personality in [ BiasHarness.outgoing; BiasHarness.withdrawn ] do Assert.AreEqual( Sim.responseFor intent personality, Occupation.biasResponse (Some Occupation.Fisher) intent personality) // 货郎:仅 SmallTalk 受偏差 for intent in [ AskHelp; OfferTrade; Joke; Apologize; Provoke ] do for personality in [ BiasHarness.outgoing; BiasHarness.withdrawn ] do Assert.AreEqual( Sim.responseFor intent personality, Occupation.biasResponse (Some Occupation.Peddler) intent personality) // 书生:仅 Joke 受偏差 for intent in [ SmallTalk; AskHelp; OfferTrade; Apologize; Provoke ] do for personality in [ BiasHarness.outgoing; BiasHarness.withdrawn ] do Assert.AreEqual( Sim.responseFor intent personality, Occupation.biasResponse (Some Occupation.Scholar) intent personality) // ---- 确定性:同输入两次调用结果一致 ---- [] member _.BiasIsDeterministicForTheSameInputs () = for kind in Occupation.all do for intent in [ SmallTalk; AskHelp; OfferTrade; Joke; Apologize; Provoke ] do for personality in [ BiasHarness.outgoing; BiasHarness.withdrawn; BiasHarness.grumpy ] do let first = Occupation.biasResponse (Some kind) intent personality let second = Occupation.biasResponse (Some kind) intent personality Assert.AreEqual(first, second) // ---- 与 chooseDialogueWith 复合:偏差写进 DialogueOutcome/记忆/年鉴 ---- [] member _.ChooseDialogueWithIdentityMatchesTheHistoricPath () = let world = Sim.initialWorldN 42UL 4 |> BiasHarness.atNpc 0 let npc = world.Npcs.[0] let identityOutcome = match Sim.chooseDialogueWith Sim.responseFor npc.Id AskHelp world with | DialogueSucceeded (outcome, _) -> outcome | DialogueRejected (failure, _) -> Assert.Fail($"rejected: {failure}"); Unchecked.defaultof Assert.AreEqual(Sim.responseFor AskHelp npc.Mind.Personality, identityOutcome.Response) [] member _.ChooseDialogueWithBiasRecordsTheBiasedResponse () = let world = Sim.initialWorldN 42UL 4 |> BiasHarness.atNpc 0 let npc = world.Npcs.[0] let biasedRespond = Occupation.biasResponse (Some Occupation.Farmer) let response, outcomeWorld = match Sim.chooseDialogueWith biasedRespond npc.Id AskHelp world with | DialogueSucceeded (outcome, next) -> Assert.AreEqual( Occupation.biasResponse (Some Occupation.Farmer) AskHelp npc.Mind.Personality, outcome.Response) outcome.Response, next | DialogueRejected (failure, _) -> Assert.Fail($"rejected: {failure}"); Unchecked.defaultof<_>, Unchecked.defaultof // 目标村民记忆里记录的是偏差后的回应 let remembered = outcomeWorld.Npcs.[0].Mind.Memory |> List.tryPick (fun memory -> match memory.Kind with | Dialogue (_, _, response) -> Some response | _ -> None) Assert.AreEqual(Some response, remembered)