P66(职业收尾切片:每日任务目标具体化 TargetNpc/TargetTile)验证记录 基线:commit cf6672e(P66 补充限定记录)。本单未 push。 范围与设计(design §2.4-3,仅此一条) - DailyTask 的 TargetNpc/TargetTile 从恒 None(通配)改为按 hash 在**可达集合**内定点。 - 纯 Kernel 加函数 + Desktop 接线;禁改 Sim.fs/Sim.step(本单 `git diff -- src/LivingVillage.Kernel/Sim.fs` 为空)。 - 可达集合由世界/样板场景派生(WorldBootstrap.reachableTargets): - NPC = 世界中实际存在的村民 id(30 个开局 → 0..29)。 - 瓦片 = 样板场景菜地 VegetableGarden 的世界瓦片 (33,25)/(34,25)——设计口径「自家地块」,legacy/riverscape 一致。 - 定点 = splitmix 同源的另一段流:`taskHash (seed ⊕ salt) occupationSeed dayIndex mod |候选|`, 候选先排序(NPC 按 id、瓦片按 (x,y))以消除输入顺序影响;禁 System.Random。 每条模板的定点影响(关键:不做超范围语义变更) - 帮工 HelpWork → **TargetNpc 定点**(既有 satisfiesCompletion 已读 `Some target -> target = npc`); 现在玩家必须与指定村民对话才完成,非目标村民不完成(新单测断言)。 - 翻土 TillSoil → **TargetTile 定点**(既有 `Some target -> target = (tileX,tileY)`); 现在必须到达指定地块才完成(新单测断言)。 - 其余 8 模板(送粮/夜捕/卖渔/询价/收货/转卖/观察记事/论理)的 satisfiesCompletion **不读目标**, 故目标保持 None(通配),完成语义逐字节不变;不制造「有目标却不判定」的死数据。 - 历史口径保留:`Occupation.dailyTaskOf` / `refreshToday` / `refreshState` / `WorldBootstrap.occupationStateFor` / `refreshOccupationToday` 仍走空候选(目标恒 None),既有调用点/测试不受影响; 定点走新增的 `dailyTaskOfWith` / `refreshTodayWith` / `refreshStateWith` / `WorldBootstrap.occupationStateForWorld` / `refreshOccupationTodayWorld`(Game 真实开局与跨日刷新已切换)。 改动文件 - src/LivingVillage.Kernel/Occupation.fs(TaskTargets + targetNpcOf/targetTileOf + dailyTaskOfWith/refreshTodayWith/refreshStateWith;旧入口保留为通配包装) - src/LivingVillage.Desktop/WorldBootstrap.fs(reachableTargets + occupationStateForWorld/refreshOccupationTodayWorld) - src/LivingVillage.Desktop/Game.fs(StartNewGame、逐帧跨日刷新、P55 证据刷新、P31 剧情证据改用世界候选定点) - src/LivingVillage.Desktop.Tests/P66TaskTargetTests.fs(新,8 例)+ .fsproj 挂载 - scripts/p66-target-report.fsx(新,可复跑证据生成)+ docs/evidence/p66-analysis.txt 红先 - 先建 P66TaskTargetTests.fs 引用未实现的 TaskTargets/reachableTargets/occupationStateForWorld/ refreshOccupationTodayWorld/dailyTaskOfWith → 暂存实现文件后编译红(FS0039/FS0041),恢复后转绿。 构建/测试 - dotnet build LivingVillage.sln -c Release → 0 错误,1 警告(P40ArtTests.fs FS0760,既有) - dotnet test src/LivingVillage.Desktop.Tests -c Release --no-build → 328/328(320→+8,P66TaskTargetTests) - dotnet test src/LivingVillage.Kernel.Tests -c Release --no-build → 119/119 - P54ProfessionDailyTests / P55ProfessionRewardTests 全绿:二者手工构造的任务目标恒 None, 定点不触碰其完成语义。 证据(真实 Release 程序集,可复跑) - dotnet fsi scripts/p66-target-report.fsx → docs/evidence/p66-analysis.txt,verdict: PASS - seed=42 / 30 NPC / 可达瓦片 (33,25),(34,25) - 4 职业 × 60 天 = 240 条:帮工定点 NPC=22、翻土定点瓦片=16、其余保持通配=202 - 目标 ∈ 可达集合 = true;同输入两次逐字节一致 = true;历史 dailyTaskOf 目标全 None 确定性 / 存档 - dotnet run -c Release --project src/LivingVillage.Headless -- --performance-baseline ×3 → final_digest 全等 953775FAEB2FDDE97289491AA260BD8D390C571E48A7A13AD2CB6FB7124F7F6C,determinism=PASS - save(None) 仍 LV_WORLD_SAVE_V2 且 = saveWith None world(单测逐字节);v3(含定点目标)round-trip 稳定, same-input 两次写盘文本逐字节一致。 边界 - 未改 Sim.fs/Sim.step、HUD/对话框结构、字形表、地图 seed、外部素材;未 push。 - 未做 §2.4-5(旧档→新档迁移 UI)与目标在任务面板的中文展示(属后续独立切片;本单只落状态与判定)。