summaryrefslogtreecommitdiff
path: root/src/LivingVillage.Kernel/WorldSave.fs
Commit message (Collapse)AuthorAgeFilesLines
* p55: ↵Somhairle H. Marisol6 days1-4/+14
| | | | 当日任务经济闭环——设计口径物品报酬入账(夜捕鱼×2/送粮食物×1, 其余不发) + 连续完成天数(streak 跨日保留/漏完成清零) + v3 streak 尾段与 save(None) 兼容 + LV_P55_SHOT 证据
* p51: 职业系统实现首切片——开局钱/体力/背包侧车 + ↵Somhairle H. Marisol6 days1-7/+39
| | | | 当日任务/跨日刷新 + StartNewGameWith 读回(design §2.4-1)
* feat(kernel,desktop): P31 step3 story annals + identity entry + glyph atlasSomhairle H. Marisol12 days1-0/+2
| | | | | | | | | | | | | | | | | | - Sim.AnnalKind gains StoryAnnal (summary lives in the existing Summary field); WorldSave v3 serializes it with the new 'story' token. v1/v2 saves never contain the token and stay byte-identical; v3 save -> load -> save roundtrips byte-for-byte (tested). - Occupation.Story.identitySummaryOf: plain chronicle-style identity line written to the yearbook at game start (design section 3), e.g. yi-da-yu-wei-sheng. - Desktop: story summaries render in the chronicle panel; StartNewGame appends the identity annal; chooseIntent now routes responses through Occupation.biasResponse and advances the storyline (one stage per player dialogue) with the summary appended as a StoryAnnal. - M5View gains Seed (world seed source for the story start-day hash). - Chinese copy synced into requiredUiLabels; cjk-glyphs.txt + CjkGlyphAtlas.characterTable extended 238 -> 307 glyphs (384x480), atlas regenerated with the deterministic script. - 4 new kernel tests (115 -> 119); performance digest unchanged.
* perf(kernel): P27 第二步 — Rumor 扫描去装箱 + 裁剪判定 O(1)Somhairle H. Marisol13 days1-0/+3
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | 两条互相独立、均不改变保留集合与查询结果的优化: 1) 扫描去装箱:latestRumorFor / rumorIsDuplicate 用 `=` 比较 [<Struct>] 的 NpcId/RumorId(含 RumorId option)会走泛型结构相等并逐条装箱(实测 ~24 B/条, Sim.chat 单独测得 N=16000 时 393,736 B/次、随 N 线性)。改为先解构底层 int/int64 再比较;Parent 用 int64 option 比较替代 RumorId option 结构相等。 语义完全等价,扫描不再逐条分配。 2) 裁剪判定 O(1):World 新增派生镜像 RumorCount/RumorOldestDay(不参与存档, 读档由列表经 rumorWorkingSetStats 重建)。appendRumor 用「count+1>capacity 或 min(oldest,day)<cutoff」O(1) 判定,仅真需裁剪时才走 trimRumors;旧 consRumor 每次追加都 rumorsNeedTrim 全表扫一遍。条件与 needTrim 逐字等价,保留集合相同。 等价性验证:5 天 2000 聊天/天跑两遍 WorldSave.save 逐字节相同, RumorCount/RumorOldestDay 与对列表重算一致(count=8000、oldest_day=1)。 Kernel 长跑基准 30 NPC × 2000 聊天/天(--rumor-bench D 30 2000): days 修复前 alloc / ms 修复后 alloc / ms 3 930,716,456 / 1626.7 482,716,192 / 1511.3 30 13,419,045,864 / 12182.5 4,884,067,640 / 8879.6 60 27,297,668,608 / 23769.7 9,774,660,360 / 17078.2 60 天分配 -64%、耗时 -28%,retained_id_checksum=927964000 前后一致。 门槛:final_digest=953775FAEB2F... 三次一致、performance_determinism=PASS; Kernel 92 / Desktop 143 全绿;--cost-probe 1/5 天 18.7s/18.8GB、90.4s/94.6GB (全模拟分配由逐 tick NPC 快照主导,谣言子系统占比很小,故全模拟基本持平)。
* feat(sim): M6a 谣言工作集容量上界+按天淘汰——DayIndex ↵Somhairle H. Marisol13 days1-0/+1
| | | | 派生字段/纯函数 trim/窗口化扫描/O(1) nextId
* feat(occupation): P15 补齐货郎/书生各 2 条每日任务模板 + ↵Somhairle H. Marisol13 days1-0/+8
| | | | | | | | | | | | | | 既有事件通道信号桥(Kernel 纯函数 TDD) - TaskTemplateId 扩出 BuyGoods(收货)/Resell(转卖)/ObserveNotes(观察记事)/ReasonDebate(论理), taskPoolOf 货郎=[收货,转卖]、书生=[观察记事,论理],四职业池均非空。 - TaskSignal 增 Purchased(玩家买入)/Observed(观察),satisfiesCompletion 给出四模板完成判定; 新增 occupationSeedOf/refreshToday(跨日才重生成,同日含 Done/Failed 终态保留)。 - signalOfInteraction/advanceTask:仅 playerId 发起的 DialogueEvent/TradeEvent 产生信号, NPC 互动不派任务;Offered 须先 acceptTask,终态不可复活。WorldSave 模板 token 双向同步。 - 测试 83 全绿(新增 6:池非空、货郎书生逐日池内确定、模板中文名、完成信号正负、 事件桥含玩家买/卖与 NPC 忽略、既有 DialogueEvent→accept→Done 全链路、refreshToday 逐日)。 - 不动 Sim.step 数值路径;旧档 v3 模板 token 仅新增合法值,未知模板仍拒绝。
* feat(occupation): P11 存档 v3 today-task 尾段(annals 后追加)+ ↵Somhairle H. Marisol13 days1-33/+118
| | | | | | 双向兼容 Occupation.State 增 Today: DailyTask option(State 声明移至 DailyTask 之后;TaskToken 保留为 v3 前缀占位扩展位)。WorldSave:saveWith 有任务时在 annals 之后追加尾段 task-today|<template>|<state>|<offered>|<due>|npc-none/some|tile-none/some;无任务/无职业不追加(默认 None = 旧行为,v2 逐字节不变)。load 重构为内部 parse 返回 (World, Occupation.State option):尾段可选——旧 v3(P10 版式无尾段)新 reader 读出 Today=None;v1/v2 无任务;尾段后多余 token 或未知模板 token 拒绝(不吞档)。loadFromFileWith 顺手修正:不再经 stateOf 重置,前缀 task/stage 与 Today 全量保真。5 例测试:尾段位置(marker 在 annal 之后)+ 全字段 round-trip + 重存逐字节一致、无任务 v3 旧版式、v1/v2 无尾段 + 未知模板拒绝、尾段后垃圾拒绝、字节级双跑一致。
* feat(occupation): 职业系统第一片 — Kernel 结构 / bootstrap 注入 ↵Somhairle H. Marisol13 days1-4/+68
| | | | | | / 存档 v3 Occupation.fs(Kind/Profile/State + 农夫/渔夫/货郎/书生 中文名常量,按设计 §1 表给初始 Money/Energy/Inventory);ItemKind 扩出 Fish/Spice/Scroll(basePrice 分支补齐,Food 10.0f 数值路径不变 → 无职业 digest 不动);WorldBootstrap.initialWorldWithOccupation 默认 None = 旧行为(Money/Energy 覆盖,库存字段先留数据侧、物理挂接留交易单——取舍见 design §2-C 反对加字段);WorldSave: v3 头仅在有职业时写入(保存 saveToFileWith / loadFromFileWith,v1/v2/v3 无职业类型默认,token 顺序 version|w|h|kind|task|story|…),默认路径 save 输出逐字节等同现行 v2;P10 测试 5 例(§1 表值、v3 round-trip、v1/v2 不吞档、双 seed 逐字节、中文名);Kernel.Tests 引用 Desktop 以驱动 WorldBootstrap(取舍:不做测试专用 shim)。TaskToken/StoryStage 为 v3 占位(任务系统后续单实现)。
* feat(kernel): 地图边界参数化与存档 v2Somhairle H. Marisol14 days1-3/+9
| | | | Sim 地图边界由常量 52 改为可配置(configureBounds,默认 64x48 = 与全部基线一致);Sim.step 裁剪、initialWorld 中心出生时点按当前边界读取;WorldSave 升级 V2 头携带 width/height 两字段,V1 旧存档照常读入并自动回退到 64x48;回归:512x384 边界裁剪/初界转正/双份存档往返/案例 v1 档兼容读。修复前一段中报错已修正的测试代码(Ensure saved-file timeouts)。
* feat(m6a): add file save acceptance smokeSomhairle H. Marisol2026-09-201-2/+4
| | | | | | | | | | | | | | | | | [Change Nature] This commit adds the bounded M6a file-save acceptance path. [New Capability] - Add file-level round-trip and deterministic continuation coverage. - Add Headless --m6a-smoke with digest checks and explicit PASS/FAIL output. [Implementation] - Reject non-finite numeric values and negative inventory quantities. - Exercise dialogue, chat/rumor, trade, events, annals, time, and RNG state. [Impact] - Affects Kernel save parsing, Kernel tests, and Headless smoke tooling. - Preserves the existing save format and M5 smoke behavior.
* feat(m6a): add save speed and day-night controlsSomhairle H. Marisol2026-09-201-0/+533