// P66(每日任务目标具体化)证据报告:按 hash 在可达 NPC/瓦片内定点的命中数字 + 同 seed 复现断言。 // 运行:dotnet fsi scripts/p66-target-report.fsx > docs/evidence/p66-analysis.txt // 前置:已 dotnet build LivingVillage.sln -c Release。 #r "../src/LivingVillage.Kernel/bin/Release/net8.0/LivingVillage.Kernel.dll" #r "../src/LivingVillage.Desktop/bin/Release/net8.0/LivingVillage.Desktop.dll" open System open LivingVillage.Kernel open LivingVillage.Kernel.Sim open LivingVillage.Desktop let seed = 42UL let world = WorldBootstrap.initialWorldWithOccupation false seed Sim.npcCount None let reachable = WorldBootstrap.reachableTargets world let npcLabel (NpcId id) = string id let tileLabel (x, y) = sprintf "(%d,%d)" x y let stateAt (kind: Occupation.Kind) (day: int64) = WorldBootstrap.occupationStateForWorld seed kind (day * Sim.ticksPerDay + 10L) world printfn "P66 每日任务目标具体化 — 证据报告" printfn "seed=%d npcs=%d reachable_npcs=%d reachable_tiles=%d (%s)" (int seed) world.Npcs.Length reachable.Npcs.Length reachable.Tiles.Length (reachable.Tiles |> List.map tileLabel |> String.concat ",") printfn "" let mutable totalTasks = 0 let mutable npcTargeted = 0 let mutable tileTargeted = 0 let mutable wildcard = 0 let mutable membershipOk = true let mutable reproduceOk = true let mutable concreteSamples = 0 for kind in Occupation.all do let mutable hits = 0 for day in 0L .. 59L do let state = stateAt kind day match state.Today with | None -> () | Some task -> totalTasks <- totalTasks + 1 let again = (stateAt kind day).Today.Value if again <> task then reproduceOk <- false match task.TemplateId with | Occupation.HelpWork -> npcTargeted <- npcTargeted + 1 hits <- hits + 1 match task.TargetNpc with | Some npc when List.contains npc reachable.Npcs -> concreteSamples <- concreteSamples + 1 if concreteSamples <= 12 then printfn " %-4s day=%2d HelpWork TargetNpc=%s" (Occupation.saveToken kind) day (npcLabel npc) | _ -> membershipOk <- false | Occupation.TillSoil -> tileTargeted <- tileTargeted + 1 hits <- hits + 1 match task.TargetTile with | Some tile when List.contains tile reachable.Tiles -> concreteSamples <- concreteSamples + 1 if concreteSamples <= 12 then printfn " %-4s day=%2d TillSoil TargetTile=%s" (Occupation.saveToken kind) day (tileLabel tile) | _ -> membershipOk <- false | _ -> wildcard <- wildcard + 1 if task.TargetNpc.IsSome || task.TargetTile.IsSome then membershipOk <- false printfn " %-8s 60 天内定点模板命中=%d" (Occupation.nameOf kind) hits printfn "" printfn "统计(4 职业 × 60 天 = %d 条任务)" totalTasks printfn " 定点 NPC(帮工) = %d" npcTargeted printfn " 定点瓦片(翻土) = %d" tileTargeted printfn " 保持通配(其余) = %d" wildcard printfn " 目标 ∈ 可达集合 = %b" membershipOk printfn " 同输入两次逐字节一致 = %b" reproduceOk printfn "" // 空候选 → None(保留历史通配口径)。 let legacyNone = (Occupation.dailyTaskOf seed (Occupation.occupationSeedOf Occupation.Farmer) 3L Occupation.Farmer).Value printfn "历史口径 dailyTaskOf(空候选):TargetNpc=%b TargetTile=%b(应全 false)" legacyNone.TargetNpc.IsSome legacyNone.TargetTile.IsSome let verdict = if membershipOk && reproduceOk then "PASS" else "FAIL" printfn "" printfn "verdict: %s" verdict