summaryrefslogtreecommitdiff
path: root/src/LivingVillage.Kernel.Tests/OccupationBiasTests.fs
blob: 76e02f3583e3e57ee3139bce7649bf34735a04dd (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
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 } }

[<TestClass>]
type OccupationBiasTests () =

    // ---- 无职业 = 既有 responseFor 输出逐字节不变 ----

    [<TestMethod>]
    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(求助更友好)----

    [<TestMethod>]
    member _.FarmerAskHelpTurnsRefusalIntoHelpfulness () =
        // 不诚实村民对常人 Refused,对农夫给 Helpful
        Assert.AreEqual<DialogueResponse>(Refused, Sim.responseFor AskHelp BiasHarness.withdrawn)
        Assert.AreEqual<DialogueResponse>(Helpful, Occupation.biasResponse (Some Occupation.Farmer) AskHelp BiasHarness.withdrawn)
        // 本就 Helpful 的路径不变
        Assert.AreEqual<DialogueResponse>(Helpful, Sim.responseFor AskHelp BiasHarness.outgoing)
        Assert.AreEqual<DialogueResponse>(Helpful, Occupation.biasResponse (Some Occupation.Farmer) AskHelp BiasHarness.outgoing)

    // ---- 渔夫 SmallTalk:Reserved → Friendly(聊天气鱼汛,冷场变热络)----

    [<TestMethod>]
    member _.FisherSmallTalkWarmsUpAReservedVillager () =
        Assert.AreEqual<DialogueResponse>(Reserved, Sim.responseFor SmallTalk BiasHarness.withdrawn)
        Assert.AreEqual<DialogueResponse>(Friendly, Occupation.biasResponse (Some Occupation.Fisher) SmallTalk BiasHarness.withdrawn)
        Assert.AreEqual<DialogueResponse>(Friendly, Sim.responseFor SmallTalk BiasHarness.outgoing)
        Assert.AreEqual<DialogueResponse>(Friendly, Occupation.biasResponse (Some Occupation.Fisher) SmallTalk BiasHarness.outgoing)

    // ---- 货郎 SmallTalk:Reserved → Bargaining(三句不离买卖,更易进讨价还价)----

    [<TestMethod>]
    member _.PeddlerSmallTalkSlidesIntoBargaining () =
        Assert.AreEqual<DialogueResponse>(Reserved, Sim.responseFor SmallTalk BiasHarness.withdrawn)
        Assert.AreEqual<DialogueResponse>(Bargaining, Occupation.biasResponse (Some Occupation.Peddler) SmallTalk BiasHarness.withdrawn)
        // 外向村民本就 Friendly,不被货郎偏差改写
        Assert.AreEqual<DialogueResponse>(Friendly, Occupation.biasResponse (Some Occupation.Peddler) SmallTalk BiasHarness.outgoing)

    // ---- 书生 Joke:完全覆盖为 Reserved(与 personality 相反的确定性偏差)----
    // 语义说明:书生讲笑话走「完全覆盖」——无论村民性格如何,回应一律 Reserved。
    // 刻意不做概率偏向(禁随机);反差感即设计意图(design §5)。

    [<TestMethod>]
    member _.ScholarJokeIsAlwaysReservedRegardlessOfPersonality () =
        Assert.AreEqual<DialogueResponse>(Amused, Sim.responseFor Joke BiasHarness.outgoing)
        Assert.AreEqual<DialogueResponse>(Reserved, Sim.responseFor Joke BiasHarness.withdrawn)
        for personality in [ BiasHarness.outgoing; BiasHarness.withdrawn; BiasHarness.grumpy ] do
            Assert.AreEqual<DialogueResponse>(
                Reserved,
                Occupation.biasResponse (Some Occupation.Scholar) Joke personality)

    // ---- 偏差只作用于各自的签名意图,其余意图原样穿透 ----

    [<TestMethod>]
    member _.BiasOnlyAppliesToTheSignatureIntents () =
        // 农夫:仅 AskHelp 受偏差
        for intent in [ SmallTalk; OfferTrade; Joke; Apologize; Provoke ] do
            for personality in [ BiasHarness.outgoing; BiasHarness.withdrawn ] do
                Assert.AreEqual<DialogueResponse>(
                    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<DialogueResponse>(
                    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<DialogueResponse>(
                    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<DialogueResponse>(
                    Sim.responseFor intent personality,
                    Occupation.biasResponse (Some Occupation.Scholar) intent personality)

    // ---- 确定性:同输入两次调用结果一致 ----

    [<TestMethod>]
    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<DialogueResponse>(first, second)

    // ---- 与 chooseDialogueWith 复合:偏差写进 DialogueOutcome/记忆/年鉴 ----

    [<TestMethod>]
    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<DialogueOutcome>
        Assert.AreEqual<DialogueResponse>(Sim.responseFor AskHelp npc.Mind.Personality, identityOutcome.Response)

    [<TestMethod>]
    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<DialogueResponse>(
                    Occupation.biasResponse (Some Occupation.Farmer) AskHelp npc.Mind.Personality,
                    outcome.Response)
                outcome.Response, next
            | DialogueRejected (failure, _) -> Assert.Fail($"rejected: {failure}"); Unchecked.defaultof<_>, Unchecked.defaultof<World>
        // 目标村民记忆里记录的是偏差后的回应
        let remembered =
            outcomeWorld.Npcs.[0].Mind.Memory
            |> List.tryPick (fun memory ->
                match memory.Kind with
                | Dialogue (_, _, response) -> Some response
                | _ -> None)
        Assert.AreEqual<DialogueResponse option>(Some response, remembered)