summaryrefslogtreecommitdiff
path: root/src/LivingVillage.Headless
diff options
context:
space:
mode:
authorSomhairle H. Marisol <[email protected]>2026-09-20 12:18:38 +0800
committerSomhairle H. Marisol <[email protected]>2026-09-20 12:18:38 +0800
commit4f8aca922e6cf47dce95aab79541065d487d5272 (patch)
tree3d0e259440912ccb7ab62b910c576e928326fa96 /src/LivingVillage.Headless
parent23a9235f29e92adb51c3bf1a71f92cd8434e0939 (diff)
downloadliving-village-4f8aca922e6cf47dce95aab79541065d487d5272.tar.gz
feat(m4b): 增加可追踪谣言传播与回放
[变更性质] - 本提交新增 M4b 谣言传播与确定性验收能力,不是单纯缺陷修复。 [新增功能] - 支持带来源、父节点、深度、强度和路径的谣言传播、去重、衰减与过期处理。 - Headless 支持稳定谣言 dump、replay,以及与 batch 模式的互斥校验。 [实现方案] - 通过不可变 World 更新和失败原子返回保持模拟确定性。 - 新增多跳传播、重复转述、过期父节点、非法参与者和稳定 trace 测试。 [影响范围] - 影响 Kernel、Headless 和 Kernel 测试;不包含 M5/M6 或无关配置与文档改动。
Diffstat (limited to 'src/LivingVillage.Headless')
-rw-r--r--src/LivingVillage.Headless/Program.fs43
1 files changed, 31 insertions, 12 deletions
diff --git a/src/LivingVillage.Headless/Program.fs b/src/LivingVillage.Headless/Program.fs
index bcf2e41..6ba0f5f 100644
--- a/src/LivingVillage.Headless/Program.fs
+++ b/src/LivingVillage.Headless/Program.fs
@@ -164,6 +164,16 @@ let dumpRelations (world: World) : unit =
printfn "rel_counts %s" (String.concat " " cells)
printfn "rel_total=%d rel_max=%d rel_min=%d" (Array.sum counts) (Array.max counts) (Array.min counts)
+let dumpRumors (world: World) : unit =
+ printfn "%s" (Sim.rumorTraceText world)
+
+let replayRumors (days: int64) (seed: uint64) (npcCount: int) (world: World) : bool =
+ let expected = Sim.rumorTraceText world
+ let replayed = runSimulation false days seed npcCount |> fun stats -> Sim.rumorTraceText stats.World
+ let same = expected = replayed
+ printfn "rumor_replay=%s tick=%d count=%d" (if same then "PASS" else "FAIL") world.Tick world.Rumors.Length
+ same
+
let gini (xs: int64[]) : float =
let n = float xs.Length
let s = float (Array.sum xs)
@@ -266,45 +276,54 @@ let main argv =
(seed: uint64 option)
(npc: int option)
(dump: bool)
+ (rumorTrace: bool)
+ (rumorReplay: bool)
(batch: (int * int64) option)
- : Result<int64 * uint64 * int * bool * (int * int64) option, string> =
+ : Result<int64 * uint64 * int * bool * bool * bool * (int * int64) option, string> =
if i >= argv.Length then
match batch with
- | Some(k, d) -> Ok(defaultArg days 1L, defaultArg seed 42UL, defaultArg npc 30, dump, Some(k, d))
+ | Some(k, d) -> Ok(defaultArg days 1L, defaultArg seed 42UL, defaultArg npc 30, dump, rumorTrace, rumorReplay, Some(k, d))
| None ->
match days, seed, npc with
- | Some d, Some s, Some n -> Ok(d, s, n, dump, None)
+ | Some d, Some s, Some n -> Ok(d, s, n, dump, rumorTrace, rumorReplay, None)
| _ -> Error "missing --days/--seed"
else
match argv.[i] with
| "--days" when i + 1 < argv.Length ->
(match Int64.TryParse argv.[i + 1] with
- | true, d when d > 0L -> parse (i + 2) (Some d) seed npc dump batch
+ | true, d when d > 0L -> parse (i + 2) (Some d) seed npc dump rumorTrace rumorReplay batch
| _ -> Error $"invalid --days '{argv.[i + 1]}'")
| "--seed" when i + 1 < argv.Length ->
(match UInt64.TryParse argv.[i + 1] with
- | true, s -> parse (i + 2) days (Some s) npc dump batch
+ | true, s -> parse (i + 2) days (Some s) npc dump rumorTrace rumorReplay batch
| _ -> Error $"invalid --seed '{argv.[i + 1]}'")
| "--npc" when i + 1 < argv.Length ->
(match Int32.TryParse argv.[i + 1] with
- | true, n when n > 0 && n <= 1000 -> parse (i + 2) days seed (Some n) dump batch
+ | true, n when n > 0 && n <= 1000 -> parse (i + 2) days seed (Some n) dump rumorTrace rumorReplay batch
| _ -> Error $"invalid --npc '{argv.[i + 1]}'")
- | "--dump-relations" -> parse (i + 1) days seed npc true batch
+ | "--dump-relations" -> parse (i + 1) days seed npc true rumorTrace rumorReplay batch
+ | "--dump-rumors" -> parse (i + 1) days seed npc dump true rumorReplay batch
+ | "--replay-rumors" -> parse (i + 1) days seed npc dump rumorTrace true batch
| "--batch" when i + 2 < argv.Length ->
(match Int32.TryParse argv.[i + 1], Int64.TryParse argv.[i + 2] with
- | (true, k), (true, d) when k > 0 && d > 0L -> parse (i + 3) days seed npc dump (Some(k, d))
+ | (true, k), (true, d) when k > 0 && d > 0L -> parse (i + 3) days seed npc dump rumorTrace rumorReplay (Some(k, d))
| _ -> Error $"invalid --batch '{argv.[i + 1]} {argv.[i + 2]}'")
| other -> Error $"unknown argument '{other}'"
- match parse 0 None None (Some 30) false None with
- | Ok(days, seed, npc, dump, batch) ->
+ match parse 0 None None (Some 30) false false false None with
+ | Ok(days, seed, npc, dump, rumorTrace, rumorReplay, batch) ->
match batch with
+ | Some(k, d) when rumorTrace || rumorReplay ->
+ eprintfn "headless: rumor trace/replay cannot be combined with --batch"
+ 2
| Some(k, d) -> runBatch k d
| None ->
let stats = runSimulation true days seed npc
if dump then dumpRelations stats.World
- if stats.NonFinite > 0L || stats.OutOfBounds > 0L then 1 else 0
+ if rumorTrace then dumpRumors stats.World
+ let replayOk = if rumorReplay then replayRumors days seed npc stats.World else true
+ if stats.NonFinite > 0L || stats.OutOfBounds > 0L || not replayOk then 1 else 0
| Error msg ->
eprintfn $"headless: {msg}"
- eprintfn "usage: dotnet run -c Release --project src/LivingVillage.Headless -- --days N --seed S [--npc N] [--dump-relations] | --batch K D"
+ eprintfn "usage: dotnet run -c Release --project src/LivingVillage.Headless -- --days N --seed S [--npc N] [--dump-relations] [--dump-rumors] [--replay-rumors] | --batch K D"
2