[{"data":1,"prerenderedAt":346},["ShallowReactive",2],{"doc-global_essentials\u002Fadvanced\u002Fsave_restoration":3},{"id":4,"title":5,"body":6,"description":16,"extension":337,"meta":338,"navigation":66,"path":342,"seo":343,"stem":344,"__hash__":345},"docs\u002Fdocs\u002Fglobal_essentials\u002Fadvanced\u002Fsave_restoration.md","Save Restoration",{"type":7,"value":8,"toc":331},"minimark",[9,13,17,20,25,148,151,158,161,165,172,175,178,180,184,192,199,263,288,296,298,302,327],[10,11,5],"h1",{"id":12},"save-restoration",[14,15,16],"p",{},"When a game is shipped as ongoing chapters, the data shape changes constantly: a stat gets renamed, a status loses a tag, a property is added to a template. Saved characters and items carry the OLD shape — extra keys, missing new keys, outdated status grants. Without a restoration pass, players load old saves and find their characters subtly broken.",[14,18,19],{},"The engine ships a default migration helper that rebuilds every character and every item from the current data definitions whenever the save's version differs from the current one.",[21,22,24],"h2",{"id":23},"the-helper","The helper",[26,27,32],"pre",{"className":28,"code":29,"language":30,"meta":31,"style":31},"language-js shiki shiki-themes github-light github-dark","const { game } = window.engine;\n\ngame.on('game_initiated', () => {\n    game.runDefaultSaveMigration({\n        ignoreStats: [],\n        ignoreTraits: [],\n        ignoreAttributes: [],\n        ignoreItemTraits: [],\n        ignoreItemAttributes: [],\n    });\n});\n","js","",[33,34,35,61,68,94,106,112,118,124,130,136,142],"code",{"__ignoreMap":31},[36,37,40,44,48,52,55,58],"span",{"class":38,"line":39},"line",1,[36,41,43],{"class":42},"szBVR","const",[36,45,47],{"class":46},"sVt8B"," { ",[36,49,51],{"class":50},"sj4cs","game",[36,53,54],{"class":46}," } ",[36,56,57],{"class":42},"=",[36,59,60],{"class":46}," window.engine;\n",[36,62,64],{"class":38,"line":63},2,[36,65,67],{"emptyLinePlaceholder":66},true,"\n",[36,69,71,74,78,81,85,88,91],{"class":38,"line":70},3,[36,72,73],{"class":46},"game.",[36,75,77],{"class":76},"sScJk","on",[36,79,80],{"class":46},"(",[36,82,84],{"class":83},"sZZnC","'game_initiated'",[36,86,87],{"class":46},", () ",[36,89,90],{"class":42},"=>",[36,92,93],{"class":46}," {\n",[36,95,97,100,103],{"class":38,"line":96},4,[36,98,99],{"class":46},"    game.",[36,101,102],{"class":76},"runDefaultSaveMigration",[36,104,105],{"class":46},"({\n",[36,107,109],{"class":38,"line":108},5,[36,110,111],{"class":46},"        ignoreStats: [],\n",[36,113,115],{"class":38,"line":114},6,[36,116,117],{"class":46},"        ignoreTraits: [],\n",[36,119,121],{"class":38,"line":120},7,[36,122,123],{"class":46},"        ignoreAttributes: [],\n",[36,125,127],{"class":38,"line":126},8,[36,128,129],{"class":46},"        ignoreItemTraits: [],\n",[36,131,133],{"class":38,"line":132},9,[36,134,135],{"class":46},"        ignoreItemAttributes: [],\n",[36,137,139],{"class":38,"line":138},10,[36,140,141],{"class":46},"    });\n",[36,143,145],{"class":38,"line":144},11,[36,146,147],{"class":46},"});\n",[14,149,150],{},"It will rebuild every character and every item in the world on version change ignoring the properties listed in the ignore list.",[14,152,153,154,157],{},"Drop the snippet in any ",[33,155,156],{},".mjs"," file that's imported by your game's main script.",[159,160],"hr",{},[21,162,164],{"id":163},"triggering-a-migration","Triggering a migration",[14,166,167,168,171],{},"Bump ",[33,169,170],{},"manifest.version",". That's it.",[14,173,174],{},"The new value mismatches the old save's stamped version on next load, the migration runs once, the save is re-stamped, and subsequent loads are no-ops until the next bump.",[14,176,177],{},"Mods get the same treatment — each mod's version is part of the signature. If a mod ships an update with a breaking data shape, players see the migration run on first load with the new mod.",[159,179],{},[21,181,183],{"id":182},"ignore-lists","Ignore lists",[14,185,186,187,191],{},"The defaults reset every template-driven field on every entity. Sometimes you want a field to ",[188,189,190],"strong",{},"survive"," the reset because it carries player-set state.",[14,193,194,195,198],{},"Common example: a character ",[33,196,197],{},"info"," trait holding biography prose that gets mutated during scenes. You don't want it reset to the template's default each time you bump the version.",[26,200,202],{"className":28,"code":201,"language":30,"meta":31,"style":31},"game.runDefaultSaveMigration({\n    ignoreTraits: ['info', 'sire_id', 'sire_name', 'mother', 'conceived_day', 'born_day'],\n    ignoreAttributes: ['life_stage'],\n});\n",[33,203,204,212,249,259],{"__ignoreMap":31},[36,205,206,208,210],{"class":38,"line":39},[36,207,73],{"class":46},[36,209,102],{"class":76},[36,211,105],{"class":46},[36,213,214,217,220,223,226,228,231,233,236,238,241,243,246],{"class":38,"line":63},[36,215,216],{"class":46},"    ignoreTraits: [",[36,218,219],{"class":83},"'info'",[36,221,222],{"class":46},", ",[36,224,225],{"class":83},"'sire_id'",[36,227,222],{"class":46},[36,229,230],{"class":83},"'sire_name'",[36,232,222],{"class":46},[36,234,235],{"class":83},"'mother'",[36,237,222],{"class":46},[36,239,240],{"class":83},"'conceived_day'",[36,242,222],{"class":46},[36,244,245],{"class":83},"'born_day'",[36,247,248],{"class":46},"],\n",[36,250,251,254,257],{"class":38,"line":70},[36,252,253],{"class":46},"    ignoreAttributes: [",[36,255,256],{"class":83},"'life_stage'",[36,258,248],{"class":46},[36,260,261],{"class":38,"line":96},[36,262,147],{"class":46},[264,265,266,279],"ul",{},[267,268,269,222,272,222,275,278],"li",{},[33,270,271],{},"ignoreStats",[33,273,274],{},"ignoreTraits",[33,276,277],{},"ignoreAttributes"," — character fields.",[267,280,281,222,284,287],{},[33,282,283],{},"ignoreItemTraits",[33,285,286],{},"ignoreItemAttributes"," — item fields. Items don't have stats; the analogous \"ignore\" wouldn't apply.",[14,289,290,291,295],{},"Keep the lists small and well-justified. Each entry is a field where you're saying \"I trust the save more than the definition.\" Default to ",[292,293,294],"em",{},"not"," ignoring — easier to add later than to remove.",[159,297],{},[21,299,301],{"id":300},"what-this-wont-fix","What this won't fix",[264,303,304,314],{},[267,305,306,309,310,313],{},[188,307,308],{},"Characters whose template was removed"," (e.g. a mod was uninstalled). Their ",[33,311,312],{},"templateId"," doesn't resolve to anything, so the helper skips them. They linger in the save with stale state but don't crash anything. You can delete them manually if needed.",[267,315,316,323,324,326],{},[188,317,318,319,322],{},"Direct ",[33,320,321],{},"setStat"," calls"," outside of statuses. Those get wiped as they mutate characters _core Status that we're trying to restore from the template value. Route persistent boosts through status-grants instead, or add the stat to ",[33,325,271],{},".",[328,329,330],"style",{},"html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":31,"searchDepth":63,"depth":63,"links":332},[333,334,335,336],{"id":23,"depth":63,"text":24},{"id":163,"depth":63,"text":164},{"id":182,"depth":63,"text":183},{"id":300,"depth":63,"text":301},"md",{"plugin":339,"category":340,"page":341},"global_essentials","advanced","save_restoration","\u002Fdocs\u002Fglobal_essentials\u002Fadvanced\u002Fsave_restoration",{"title":5,"description":16},"docs\u002Fglobal_essentials\u002Fadvanced\u002Fsave_restoration","pnnT5NTB_QRIgnVD-Ckq57ZF_QEAYKSrVdYFnO_x4SU",1779582262192]