@zigrivers/scaffold 3.4.1 → 3.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (194) hide show
  1. package/README.md +91 -0
  2. package/content/knowledge/game/game-accessibility.md +328 -0
  3. package/content/knowledge/game/game-ai-patterns.md +542 -0
  4. package/content/knowledge/game/game-asset-pipeline.md +359 -0
  5. package/content/knowledge/game/game-audio-design.md +342 -0
  6. package/content/knowledge/game/game-binary-vcs-strategy.md +396 -0
  7. package/content/knowledge/game/game-design-document.md +260 -0
  8. package/content/knowledge/game/game-domain-patterns.md +297 -0
  9. package/content/knowledge/game/game-economy-design.md +355 -0
  10. package/content/knowledge/game/game-engine-selection.md +242 -0
  11. package/content/knowledge/game/game-input-systems.md +357 -0
  12. package/content/knowledge/game/game-level-content-design.md +455 -0
  13. package/content/knowledge/game/game-liveops-analytics.md +280 -0
  14. package/content/knowledge/game/game-localization.md +323 -0
  15. package/content/knowledge/game/game-milestone-definitions.md +337 -0
  16. package/content/knowledge/game/game-modding-ugc.md +390 -0
  17. package/content/knowledge/game/game-narrative-design.md +404 -0
  18. package/content/knowledge/game/game-networking.md +391 -0
  19. package/content/knowledge/game/game-performance-budgeting.md +378 -0
  20. package/content/knowledge/game/game-platform-certification.md +417 -0
  21. package/content/knowledge/game/game-project-structure.md +360 -0
  22. package/content/knowledge/game/game-save-systems.md +452 -0
  23. package/content/knowledge/game/game-testing-strategy.md +470 -0
  24. package/content/knowledge/game/game-ui-patterns.md +475 -0
  25. package/content/knowledge/game/game-vr-ar-design.md +313 -0
  26. package/content/knowledge/review/review-art-bible.md +305 -0
  27. package/content/knowledge/review/review-game-design.md +303 -0
  28. package/content/knowledge/review/review-game-economy.md +272 -0
  29. package/content/knowledge/review/review-netcode.md +280 -0
  30. package/content/knowledge/review/review-platform-cert.md +341 -0
  31. package/content/methodology/custom-defaults.yml +25 -0
  32. package/content/methodology/deep.yml +25 -0
  33. package/content/methodology/game-overlay.yml +145 -0
  34. package/content/methodology/mvp.yml +25 -0
  35. package/content/pipeline/architecture/ai-behavior-design.md +87 -0
  36. package/content/pipeline/architecture/netcode-spec.md +86 -0
  37. package/content/pipeline/architecture/review-netcode.md +78 -0
  38. package/content/pipeline/foundation/performance-budgets.md +91 -0
  39. package/content/pipeline/modeling/narrative-bible.md +84 -0
  40. package/content/pipeline/pre/game-design-document.md +89 -0
  41. package/content/pipeline/pre/review-gdd.md +74 -0
  42. package/content/pipeline/quality/analytics-telemetry.md +98 -0
  43. package/content/pipeline/quality/live-ops-plan.md +99 -0
  44. package/content/pipeline/quality/platform-cert-prep.md +129 -0
  45. package/content/pipeline/quality/playtest-plan.md +83 -0
  46. package/content/pipeline/specification/art-bible.md +87 -0
  47. package/content/pipeline/specification/audio-design.md +96 -0
  48. package/content/pipeline/specification/content-structure-design.md +141 -0
  49. package/content/pipeline/specification/economy-design.md +104 -0
  50. package/content/pipeline/specification/game-accessibility.md +82 -0
  51. package/content/pipeline/specification/game-ui-spec.md +97 -0
  52. package/content/pipeline/specification/input-controls-spec.md +81 -0
  53. package/content/pipeline/specification/localization-plan.md +113 -0
  54. package/content/pipeline/specification/modding-ugc-spec.md +116 -0
  55. package/content/pipeline/specification/online-services-spec.md +104 -0
  56. package/content/pipeline/specification/review-economy.md +87 -0
  57. package/content/pipeline/specification/review-game-ui.md +73 -0
  58. package/content/pipeline/specification/save-system-spec.md +116 -0
  59. package/dist/cli/commands/adopt.d.ts.map +1 -1
  60. package/dist/cli/commands/adopt.js +25 -0
  61. package/dist/cli/commands/adopt.js.map +1 -1
  62. package/dist/cli/commands/adopt.test.js +28 -1
  63. package/dist/cli/commands/adopt.test.js.map +1 -1
  64. package/dist/cli/commands/build.test.js +3 -0
  65. package/dist/cli/commands/build.test.js.map +1 -1
  66. package/dist/cli/commands/init.d.ts +1 -0
  67. package/dist/cli/commands/init.d.ts.map +1 -1
  68. package/dist/cli/commands/init.js +6 -0
  69. package/dist/cli/commands/init.js.map +1 -1
  70. package/dist/cli/commands/init.test.js +12 -1
  71. package/dist/cli/commands/init.test.js.map +1 -1
  72. package/dist/cli/commands/knowledge.test.js +8 -0
  73. package/dist/cli/commands/knowledge.test.js.map +1 -1
  74. package/dist/cli/commands/next.d.ts.map +1 -1
  75. package/dist/cli/commands/next.js +19 -5
  76. package/dist/cli/commands/next.js.map +1 -1
  77. package/dist/cli/commands/next.test.js +56 -0
  78. package/dist/cli/commands/next.test.js.map +1 -1
  79. package/dist/cli/commands/rework.d.ts.map +1 -1
  80. package/dist/cli/commands/rework.js +11 -2
  81. package/dist/cli/commands/rework.js.map +1 -1
  82. package/dist/cli/commands/rework.test.js +5 -0
  83. package/dist/cli/commands/rework.test.js.map +1 -1
  84. package/dist/cli/commands/run.d.ts.map +1 -1
  85. package/dist/cli/commands/run.js +54 -4
  86. package/dist/cli/commands/run.js.map +1 -1
  87. package/dist/cli/commands/run.test.js +384 -0
  88. package/dist/cli/commands/run.test.js.map +1 -1
  89. package/dist/cli/commands/skip.test.js +3 -0
  90. package/dist/cli/commands/skip.test.js.map +1 -1
  91. package/dist/cli/commands/status.d.ts.map +1 -1
  92. package/dist/cli/commands/status.js +16 -3
  93. package/dist/cli/commands/status.js.map +1 -1
  94. package/dist/cli/commands/status.test.js +55 -0
  95. package/dist/cli/commands/status.test.js.map +1 -1
  96. package/dist/cli/output/auto.d.ts +3 -0
  97. package/dist/cli/output/auto.d.ts.map +1 -1
  98. package/dist/cli/output/auto.js +9 -0
  99. package/dist/cli/output/auto.js.map +1 -1
  100. package/dist/cli/output/context.d.ts +6 -0
  101. package/dist/cli/output/context.d.ts.map +1 -1
  102. package/dist/cli/output/context.js.map +1 -1
  103. package/dist/cli/output/context.test.js +87 -0
  104. package/dist/cli/output/context.test.js.map +1 -1
  105. package/dist/cli/output/error-display.test.js +3 -0
  106. package/dist/cli/output/error-display.test.js.map +1 -1
  107. package/dist/cli/output/interactive.d.ts +3 -0
  108. package/dist/cli/output/interactive.d.ts.map +1 -1
  109. package/dist/cli/output/interactive.js +76 -0
  110. package/dist/cli/output/interactive.js.map +1 -1
  111. package/dist/cli/output/json.d.ts +3 -0
  112. package/dist/cli/output/json.d.ts.map +1 -1
  113. package/dist/cli/output/json.js +9 -0
  114. package/dist/cli/output/json.js.map +1 -1
  115. package/dist/config/loader.d.ts.map +1 -1
  116. package/dist/config/loader.js +3 -2
  117. package/dist/config/loader.js.map +1 -1
  118. package/dist/config/schema.d.ts +641 -15
  119. package/dist/config/schema.d.ts.map +1 -1
  120. package/dist/config/schema.js +26 -1
  121. package/dist/config/schema.js.map +1 -1
  122. package/dist/config/schema.test.js +192 -1
  123. package/dist/config/schema.test.js.map +1 -1
  124. package/dist/core/assembly/overlay-loader.d.ts +24 -0
  125. package/dist/core/assembly/overlay-loader.d.ts.map +1 -0
  126. package/dist/core/assembly/overlay-loader.js +190 -0
  127. package/dist/core/assembly/overlay-loader.js.map +1 -0
  128. package/dist/core/assembly/overlay-loader.test.d.ts +2 -0
  129. package/dist/core/assembly/overlay-loader.test.d.ts.map +1 -0
  130. package/dist/core/assembly/overlay-loader.test.js +106 -0
  131. package/dist/core/assembly/overlay-loader.test.js.map +1 -0
  132. package/dist/core/assembly/overlay-resolver.d.ts +15 -0
  133. package/dist/core/assembly/overlay-resolver.d.ts.map +1 -0
  134. package/dist/core/assembly/overlay-resolver.js +58 -0
  135. package/dist/core/assembly/overlay-resolver.js.map +1 -0
  136. package/dist/core/assembly/overlay-resolver.test.d.ts +2 -0
  137. package/dist/core/assembly/overlay-resolver.test.d.ts.map +1 -0
  138. package/dist/core/assembly/overlay-resolver.test.js +246 -0
  139. package/dist/core/assembly/overlay-resolver.test.js.map +1 -0
  140. package/dist/core/assembly/overlay-state-resolver.d.ts +26 -0
  141. package/dist/core/assembly/overlay-state-resolver.d.ts.map +1 -0
  142. package/dist/core/assembly/overlay-state-resolver.js +63 -0
  143. package/dist/core/assembly/overlay-state-resolver.js.map +1 -0
  144. package/dist/core/assembly/overlay-state-resolver.test.d.ts +2 -0
  145. package/dist/core/assembly/overlay-state-resolver.test.d.ts.map +1 -0
  146. package/dist/core/assembly/overlay-state-resolver.test.js +256 -0
  147. package/dist/core/assembly/overlay-state-resolver.test.js.map +1 -0
  148. package/dist/core/assembly/preset-loader.d.ts +1 -0
  149. package/dist/core/assembly/preset-loader.d.ts.map +1 -1
  150. package/dist/core/assembly/preset-loader.js +2 -0
  151. package/dist/core/assembly/preset-loader.js.map +1 -1
  152. package/dist/core/dependency/eligibility.test.js +3 -0
  153. package/dist/core/dependency/eligibility.test.js.map +1 -1
  154. package/dist/e2e/game-pipeline.test.d.ts +10 -0
  155. package/dist/e2e/game-pipeline.test.d.ts.map +1 -0
  156. package/dist/e2e/game-pipeline.test.js +298 -0
  157. package/dist/e2e/game-pipeline.test.js.map +1 -0
  158. package/dist/e2e/init.test.js +3 -0
  159. package/dist/e2e/init.test.js.map +1 -1
  160. package/dist/project/adopt.d.ts +3 -1
  161. package/dist/project/adopt.d.ts.map +1 -1
  162. package/dist/project/adopt.js +29 -1
  163. package/dist/project/adopt.js.map +1 -1
  164. package/dist/project/adopt.test.js +51 -1
  165. package/dist/project/adopt.test.js.map +1 -1
  166. package/dist/types/config.d.ts +50 -4
  167. package/dist/types/config.d.ts.map +1 -1
  168. package/dist/types/config.test.d.ts +2 -0
  169. package/dist/types/config.test.d.ts.map +1 -0
  170. package/dist/types/config.test.js +97 -0
  171. package/dist/types/config.test.js.map +1 -0
  172. package/dist/utils/eligible.d.ts +3 -2
  173. package/dist/utils/eligible.d.ts.map +1 -1
  174. package/dist/utils/eligible.js +18 -4
  175. package/dist/utils/eligible.js.map +1 -1
  176. package/dist/utils/errors.d.ts +4 -0
  177. package/dist/utils/errors.d.ts.map +1 -1
  178. package/dist/utils/errors.js +31 -0
  179. package/dist/utils/errors.js.map +1 -1
  180. package/dist/utils/errors.test.js +4 -1
  181. package/dist/utils/errors.test.js.map +1 -1
  182. package/dist/wizard/questions.d.ts +4 -0
  183. package/dist/wizard/questions.d.ts.map +1 -1
  184. package/dist/wizard/questions.js +59 -1
  185. package/dist/wizard/questions.js.map +1 -1
  186. package/dist/wizard/questions.test.js +178 -4
  187. package/dist/wizard/questions.test.js.map +1 -1
  188. package/dist/wizard/wizard.d.ts +1 -0
  189. package/dist/wizard/wizard.d.ts.map +1 -1
  190. package/dist/wizard/wizard.js +4 -1
  191. package/dist/wizard/wizard.js.map +1 -1
  192. package/dist/wizard/wizard.test.js +102 -4
  193. package/dist/wizard/wizard.test.js.map +1 -1
  194. package/package.json +1 -1
@@ -0,0 +1,452 @@
1
+ ---
2
+ name: game-save-systems
3
+ description: Save formats, versioning and migration, cloud save integration, auto-save design, corruption detection, and platform requirements
4
+ topics: [game-dev, save, persistence, cloud-save, corruption, migration]
5
+ ---
6
+
7
+ Save systems are the custodians of player investment. A corrupted save file can destroy hundreds of hours of progress and generate visceral negative reviews. A missing cloud sync can strand progress on the wrong device. A format that cannot be versioned locks the game out of future content updates. Despite this criticality, save systems are frequently under-engineered — treated as simple serialization when they are actually distributed state management with backward compatibility, corruption recovery, and platform-specific compliance requirements. Build the save system early, test it adversarially, and treat save data loss as a severity-one bug.
8
+
9
+ ## Summary
10
+
11
+ ### Save Format Selection
12
+
13
+ Three viable formats for game save data, each with distinct tradeoffs:
14
+
15
+ **Binary (custom or Protocol Buffers / FlatBuffers):**
16
+ - Smallest file size; fastest read/write; most compact on disk
17
+ - No human readability — debugging requires custom tooling
18
+ - Version migration requires careful field tagging (protobuf-style) or manual offset management
19
+ - Best for: console games with tight storage quotas, games with very large save states, competitive games where save tampering must be discouraged
20
+
21
+ **JSON:**
22
+ - Human readable; easy to inspect, edit, and debug during development
23
+ - Larger file size than binary (typically 3–10x)
24
+ - Schema-flexible — adding new fields is trivial (absent fields get default values)
25
+ - Version migration is straightforward (read old JSON, apply transformers, write new JSON)
26
+ - Best for: indie/AA games, development builds, games where modding is supported
27
+
28
+ **SQLite:**
29
+ - Structured relational storage; supports queries over save data
30
+ - ACID transactions protect against partial writes (corruption resistance built-in)
31
+ - Larger overhead than flat files but provides indexing and query capabilities
32
+ - Best for: games with large inventories, procedural worlds with chunk-based storage, games that need to query save data (e.g., "find all items of rarity legendary")
33
+
34
+ ### Save Data Architecture
35
+
36
+ Separate save data into layers with different persistence frequencies:
37
+
38
+ - **Profile data**: Player settings, achievements, statistics. Saved on change. Small. Shared across save slots.
39
+ - **World state**: Level progress, NPC states, quest flags, discovered map areas. Saved at checkpoints or manual save. Medium to large.
40
+ - **Entity state**: Positions, health, inventories of all dynamic entities. Saved at checkpoints. Potentially very large.
41
+ - **Volatile state**: Camera position, current animation frame, particle system state. Not saved — reconstructed on load.
42
+
43
+ ### Versioning and Migration
44
+
45
+ Every shipped build that changes the save format must increment the save version. Migration code transforms old-version saves to new-version saves.
46
+
47
+ **Rules:**
48
+ - Never delete or reorder fields in a binary format — mark fields as deprecated and add new ones
49
+ - Always write the save version as the first field in the file header
50
+ - Test migration from every previously shipped version to the current version
51
+ - Keep migration code permanently — do not remove v1->v2 migration when you ship v5; a player may return after years
52
+ - Migration must be idempotent — applying it to an already-migrated save should be a no-op
53
+
54
+ ### Cloud Save Integration
55
+
56
+ Cloud save synchronizes progress across devices and protects against local storage loss. Each platform provides its own cloud save API.
57
+
58
+ **Platform services:**
59
+ - **Steam Cloud**: File-based; the game reads/writes local files and Steam syncs them. Simple API but the game must handle conflicts.
60
+ - **PlayStation Plus Cloud Storage**: Automatic for PS Plus subscribers. Save data is uploaded on console suspend. The game can trigger manual uploads.
61
+ - **Xbox Cloud Saves**: Integrated into the Connected Storage API. Automatic for all Xbox users. Supports blob-based storage with conflict resolution.
62
+ - **iCloud (iOS/macOS)**: Key-value storage (NSUbiquitousKeyValueStore) for small data or document-based (iCloud Documents) for large saves.
63
+ - **Google Play Games Services**: Snapshot API for Android. Supports conflict resolution callbacks.
64
+
65
+ ### Auto-Save Design
66
+
67
+ Auto-save is expected in modern games but must be designed to avoid data loss and player frustration.
68
+
69
+ **Auto-save triggers:**
70
+ - After major accomplishments (boss defeated, quest completed, level cleared)
71
+ - At area transitions (entering a new zone, passing through a door)
72
+ - On a timer (every 5–15 minutes of active gameplay)
73
+ - Before risky situations (pre-boss encounter, before point-of-no-return)
74
+ - On application suspend (mobile backgrounding, console suspend)
75
+
76
+ **Auto-save rules:**
77
+ - Never auto-save during combat, cutscenes, or dialogue — the player may be in an unrecoverable state
78
+ - Show a save indicator (spinning icon) during auto-save to prevent the player from quitting
79
+ - Auto-save to a rolling set of 2–3 slots to allow recovery from bad auto-saves
80
+ - Allow the player to disable auto-save in settings (some players want full manual control)
81
+
82
+ ## Deep Guidance
83
+
84
+ ### Save System Architecture
85
+
86
+ ```typescript
87
+ // Save system with versioning, migration, corruption detection, and cloud sync
88
+
89
+ interface SaveHeader {
90
+ magic: number; // Magic number for format identification (e.g., 0x53415645)
91
+ version: number; // Save format version — increment on every schema change
92
+ timestamp: number; // Unix timestamp of save creation
93
+ checksum: string; // SHA-256 hash of the payload for corruption detection
94
+ playTimeSeconds: number; // Total play time (for display in load screen)
95
+ slotIndex: number; // Which save slot this belongs to
96
+ }
97
+
98
+ interface SavePayload {
99
+ profile: ProfileData;
100
+ world: WorldState;
101
+ entities: EntityState[];
102
+ }
103
+
104
+ interface ProfileData {
105
+ playerName: string;
106
+ settings: Record<string, unknown>;
107
+ achievements: string[];
108
+ statistics: Record<string, number>;
109
+ }
110
+
111
+ interface WorldState {
112
+ currentLevel: string;
113
+ questFlags: Record<string, boolean>;
114
+ discoveredAreas: string[];
115
+ npcStates: Record<string, NpcState>;
116
+ worldTime: number;
117
+ }
118
+
119
+ interface NpcState {
120
+ alive: boolean;
121
+ disposition: number;
122
+ dialogueProgress: number;
123
+ }
124
+
125
+ interface EntityState {
126
+ id: string;
127
+ type: string;
128
+ position: { x: number; y: number; z: number };
129
+ health: number;
130
+ inventory: InventoryItem[];
131
+ customData: Record<string, unknown>;
132
+ }
133
+
134
+ interface InventoryItem {
135
+ itemId: string;
136
+ quantity: number;
137
+ durability?: number;
138
+ customProperties?: Record<string, unknown>;
139
+ }
140
+
141
+ // --- Save Manager ---
142
+
143
+ const CURRENT_SAVE_VERSION = 5;
144
+ const SAVE_MAGIC = 0x53415645; // "SAVE" in ASCII
145
+
146
+ class SaveManager {
147
+ private migrations: Map<number, MigrationFn> = new Map();
148
+ private redundantCopies = 2; // Number of backup copies
149
+
150
+ constructor() {
151
+ // Register all migrations — NEVER remove old ones
152
+ this.migrations.set(1, migrateV1toV2);
153
+ this.migrations.set(2, migrateV2toV3);
154
+ this.migrations.set(3, migrateV3toV4);
155
+ this.migrations.set(4, migrateV4toV5);
156
+ }
157
+
158
+ async save(slot: number, payload: SavePayload): Promise<void> {
159
+ const serialized = JSON.stringify(payload);
160
+ const checksum = await computeSHA256(serialized);
161
+
162
+ const header: SaveHeader = {
163
+ magic: SAVE_MAGIC,
164
+ version: CURRENT_SAVE_VERSION,
165
+ timestamp: Date.now(),
166
+ checksum,
167
+ playTimeSeconds: payload.profile.statistics["playTime"] ?? 0,
168
+ slotIndex: slot,
169
+ };
170
+
171
+ const saveData = JSON.stringify({ header, payload });
172
+
173
+ // Write primary save file
174
+ const primaryPath = this.getSavePath(slot, "primary");
175
+ await this.atomicWrite(primaryPath, saveData);
176
+
177
+ // Write redundant backup copies
178
+ for (let i = 0; i < this.redundantCopies; i++) {
179
+ const backupPath = this.getSavePath(slot, `backup_${i}`);
180
+ await this.atomicWrite(backupPath, saveData);
181
+ }
182
+
183
+ // Trigger cloud sync if available
184
+ await this.syncToCloud(slot, saveData);
185
+ }
186
+
187
+ async load(slot: number): Promise<SavePayload> {
188
+ // Try primary file first, fall back to backups
189
+ const paths = [
190
+ this.getSavePath(slot, "primary"),
191
+ ...Array.from({ length: this.redundantCopies },
192
+ (_, i) => this.getSavePath(slot, `backup_${i}`)
193
+ ),
194
+ ];
195
+
196
+ for (const path of paths) {
197
+ try {
198
+ const raw = await readFile(path);
199
+ const parsed = JSON.parse(raw);
200
+ const { header, payload } = parsed as {
201
+ header: SaveHeader;
202
+ payload: SavePayload;
203
+ };
204
+
205
+ // Validate magic number
206
+ if (header.magic !== SAVE_MAGIC) {
207
+ console.warn(`Invalid magic in ${path}, trying next copy`);
208
+ continue;
209
+ }
210
+
211
+ // Validate checksum
212
+ const expectedChecksum = await computeSHA256(
213
+ JSON.stringify(payload)
214
+ );
215
+ if (header.checksum !== expectedChecksum) {
216
+ console.warn(`Checksum mismatch in ${path}, trying next copy`);
217
+ continue;
218
+ }
219
+
220
+ // Apply migrations if save version is older
221
+ let migrated = payload;
222
+ for (let v = header.version; v < CURRENT_SAVE_VERSION; v++) {
223
+ const migration = this.migrations.get(v);
224
+ if (!migration) {
225
+ throw new Error(
226
+ `Missing migration from v${v} to v${v + 1}`
227
+ );
228
+ }
229
+ migrated = migration(migrated);
230
+ console.log(`Migrated save from v${v} to v${v + 1}`);
231
+ }
232
+
233
+ return migrated;
234
+ } catch (err) {
235
+ console.warn(`Failed to load ${path}: ${err}`);
236
+ continue;
237
+ }
238
+ }
239
+
240
+ throw new Error(
241
+ `All save copies for slot ${slot} are corrupted or missing`
242
+ );
243
+ }
244
+
245
+ // Atomic write: write to temp file, then rename
246
+ // Prevents corruption if the process is killed during write
247
+ private async atomicWrite(
248
+ path: string, data: string
249
+ ): Promise<void> {
250
+ const tempPath = path + ".tmp";
251
+ await writeFile(tempPath, data);
252
+ await renameFile(tempPath, path);
253
+ }
254
+
255
+ private getSavePath(slot: number, suffix: string): string {
256
+ return `saves/slot_${slot}_${suffix}.sav`;
257
+ }
258
+
259
+ private async syncToCloud(
260
+ slot: number, data: string
261
+ ): Promise<void> {
262
+ // Platform-specific cloud sync implementation
263
+ // Steam: write to Steam Cloud path, auto-synced
264
+ // PlayStation: trigger SCE save data upload
265
+ // Xbox: write to Connected Storage container
266
+ // Mobile: write to iCloud/Google Play snapshot
267
+ }
268
+ }
269
+
270
+ type MigrationFn = (payload: any) => SavePayload;
271
+
272
+ // Example migrations — each transforms the old format to the next version
273
+ function migrateV1toV2(payload: any): any {
274
+ // V2 added NPC disposition tracking
275
+ if (payload.world?.npcStates) {
276
+ for (const npc of Object.values(payload.world.npcStates) as any[]) {
277
+ npc.disposition = npc.disposition ?? 50; // Default neutral
278
+ }
279
+ }
280
+ return payload;
281
+ }
282
+ function migrateV2toV3(payload: any): any { return payload; }
283
+ function migrateV3toV4(payload: any): any { return payload; }
284
+ function migrateV4toV5(payload: any): any { return payload; }
285
+
286
+ // Placeholder functions for file I/O and hashing
287
+ async function readFile(path: string): Promise<string> { return ""; }
288
+ async function writeFile(path: string, data: string): Promise<void> {}
289
+ async function renameFile(from: string, to: string): Promise<void> {}
290
+ async function computeSHA256(data: string): Promise<string> { return ""; }
291
+ ```
292
+
293
+ ### Corruption Detection and Recovery
294
+
295
+ Save corruption occurs from power loss during write, storage media failure, interrupted cloud sync, or bugs in serialization code. A robust save system assumes corruption will happen and plans for recovery.
296
+
297
+ **Detection mechanisms:**
298
+ - **Checksum validation**: Compute a SHA-256 (or CRC-32 for speed) hash of the payload at save time. Store it in the header. On load, recompute and compare. Any mismatch means corruption.
299
+ - **Magic number**: The first bytes of the file should be a known constant (e.g., 0x53415645). If the magic number is wrong, the file is not a valid save at all.
300
+ - **Structural validation**: After deserialization, validate that required fields exist and have valid ranges (health >= 0, position within world bounds, enum values within valid set).
301
+
302
+ **Recovery strategies:**
303
+ - **Redundant saves**: Write 2–3 copies of every save file. If the primary is corrupted, load the newest valid backup. This is the single most effective corruption defense.
304
+ - **Atomic writes**: Write to a temporary file, then atomically rename it over the target. This prevents half-written files if the process is killed mid-write.
305
+ - **Write-ahead log (WAL)**: For SQLite-based saves, WAL mode provides crash recovery. For custom formats, write an intent log before modifying the save, and replay it on next load if the save is incomplete.
306
+ - **Tombstone markers**: Before starting a save write, create a `.saving` marker file. Delete it after write completes. On load, if the marker exists, the last save was interrupted — fall back to backup.
307
+ - **Never overwrite the only copy**: Always write to a new file or backup slot before deleting/overwriting the old one.
308
+
309
+ ### Platform-Specific Save Requirements
310
+
311
+ Each platform has unique requirements that affect save system design:
312
+
313
+ **PlayStation:**
314
+ - Save data uses the PlayStation save data API (libSceUserService + libSceSaveData)
315
+ - Save data is associated with a user account and stored in system-managed directories
316
+ - The game must display a "saving" indicator and prevent the user from powering off during save (platform requirement)
317
+ - Trophy data must remain consistent with save data (if a trophy is earned, the save must reflect that state)
318
+ - Maximum save data size varies by title profile (typically 256 MB–1 GB)
319
+
320
+ **Xbox:**
321
+ - Connected Storage API for save data — blob-based with containers
322
+ - Save data is tied to Xbox Live account and auto-synced to cloud for all users (not just subscribers)
323
+ - The game must handle the case where cloud save is newer than local save (conflict resolution UI required)
324
+ - Quick Resume: save state must persist through suspend/resume cycles; the game should serialize critical state to Connected Storage on suspend
325
+
326
+ **Nintendo Switch:**
327
+ - Save data uses the Nintendo save data API
328
+ - Strict save data size limits (varies by title approval, typically 32 MB–256 MB)
329
+ - No cloud save backup for non-Nintendo Switch Online subscribers
330
+ - Save data is bound to the console, not the user (complicates console transfer scenarios)
331
+ - The game must implement its own backup strategy since the platform provides limited protection
332
+
333
+ **Steam (PC):**
334
+ - Steam Cloud provides transparent file sync — write files to a designated local path, Steam uploads them automatically
335
+ - Configure Steam Cloud settings in the Steamworks partner portal (max file count, max total size)
336
+ - Handle the Steam Cloud conflict dialog: Steam shows a prompt when local and cloud saves diverge; the game should display meaningful timestamps and progress info to help the player choose
337
+ - ISteamRemoteStorage API for programmatic control; Auto-Cloud for zero-code file sync
338
+
339
+ **Mobile (iOS):**
340
+ - iCloud key-value store for small data (<1 MB total across all keys) — simple but limited
341
+ - iCloud Documents for larger save files — requires managing file coordinators for conflict resolution
342
+ - NSFileProtection for save file encryption at rest (required for games handling sensitive user data)
343
+ - Handle the case where iCloud is disabled or full — fall back to local-only with a warning
344
+ - App deletion on iOS deletes local save data — iCloud is the only persistence across reinstalls
345
+
346
+ **Mobile (Android):**
347
+ - Google Play Games Saved Games (Snapshot API) for cloud saves — supports binary data and cover images
348
+ - Conflict resolution callback provides both conflicting snapshots; the game must merge or choose
349
+ - Internal storage (`getFilesDir()`) for local saves — survives app updates but not uninstall
350
+ - External storage is accessible to other apps and the user — do not store saves there without encryption
351
+ - On Android, `onSaveInstanceState` / `onRestoreInstanceState` handles OS-initiated process death (out-of-memory kill); save critical state there in addition to explicit save files
352
+
353
+ ### Cloud Save Conflict Resolution
354
+
355
+ When a player plays on two devices without syncing, cloud save conflicts occur. The game must resolve them without losing progress.
356
+
357
+ **Resolution strategies:**
358
+ - **Latest timestamp wins**: Simple but can lose meaningful progress from the older save. Acceptable for simple games.
359
+ - **Highest progress wins**: Compare completion percentage, level, or total play time. Choose the save with more progress. May lose recent changes if the player switched tasks.
360
+ - **Merge**: Combine data from both saves. Achievements and discoveries are unioned (player gets everything from both saves). Conflicting values (position, current quest) use the newer save. This is the most player-friendly but hardest to implement.
361
+ - **Player choice**: Present both saves with timestamps, play time, and progress summary. Let the player decide. This is the safest approach for games with meaningful branching choices.
362
+
363
+ **Implementation rule:** Never silently discard a save. If the game auto-resolves a conflict, log it and keep the discarded save as a hidden backup for a retention period (7–30 days).
364
+
365
+ ### Save File Security
366
+
367
+ For competitive and economy-based games, save file tampering is a concern. Players editing save files to give themselves unlimited currency or items undermines the game's integrity.
368
+
369
+ **Defense layers:**
370
+ - **Checksum validation**: Detect tampering by validating the stored checksum against the payload. A casual editor will not know to update the checksum.
371
+ - **Encryption**: Encrypt the save payload with a key derived from the player's account ID or a hardware identifier. This stops plaintext editing. Use AES-256-GCM for authenticated encryption.
372
+ - **Server-side validation**: For games with online economies, validate save data against server records. If the client claims to have 999,999 gold but the server's ledger shows 500, reject the save.
373
+ - **Binary format**: Binary formats are harder to edit than JSON/XML. Not a security measure on its own, but raises the effort bar.
374
+ - **Obfuscation**: Rename fields, reorder data, add dummy fields. Again, not security, but raises the effort bar above casual tampering.
375
+
376
+ **Important caveat:** In single-player games, players editing their own saves is a feature, not a bug. Many games have thriving modding communities built on save editing. Only invest in save security when multiplayer fairness or real-money economies are affected.
377
+
378
+ ### Auto-Save UX Patterns
379
+
380
+ ```yaml
381
+ # Auto-save configuration — adjust per game genre and platform
382
+
383
+ auto_save:
384
+ enabled: true
385
+ player_can_disable: true
386
+
387
+ triggers:
388
+ - type: "timer"
389
+ interval_minutes: 10
390
+ conditions:
391
+ - "not_in_combat"
392
+ - "not_in_cutscene"
393
+ - "not_in_menu"
394
+ - "player_is_grounded"
395
+
396
+ - type: "event"
397
+ events:
398
+ - "quest_completed"
399
+ - "boss_defeated"
400
+ - "area_entered"
401
+ - "major_item_acquired"
402
+ - "checkpoint_reached"
403
+
404
+ - type: "app_lifecycle"
405
+ events:
406
+ - "app_suspending" # Mobile background, console suspend
407
+ - "app_losing_focus" # Alt-tab on PC (optional)
408
+
409
+ slot_management:
410
+ strategy: "rolling"
411
+ slot_count: 3 # Rotate across 3 auto-save slots
412
+ separate_from_manual: true # Auto-saves don't overwrite manual saves
413
+
414
+ ui:
415
+ show_indicator: true # Spinning icon during save
416
+ indicator_duration_ms: 2000 # Minimum display time (even if save is instant)
417
+ indicator_position: "bottom_right"
418
+ block_quit_during_save: true
419
+ show_notification: false # Don't spam "Game saved" — the icon is enough
420
+
421
+ safety:
422
+ min_interval_seconds: 30 # Prevent rapid-fire auto-saves
423
+ validate_state_before_save: true # Don't save if player health <= 0
424
+ write_to_backup_first: true # Atomic save via backup rotation
425
+ ```
426
+
427
+ ### Save Data Size Optimization
428
+
429
+ Large save files cause slow saves (visible hitches), slow cloud sync, and may exceed platform storage quotas.
430
+
431
+ **Optimization techniques:**
432
+ - **Delta saves**: Store only what changed from the default state. An NPC that is alive at its default position with default dialogue needs zero bytes. Only NPCs that have moved, died, or changed state need entries.
433
+ - **Bit packing**: Boolean flags (quest completed, area discovered, achievement unlocked) pack 8 values per byte instead of 1 per byte. A game with 1000 boolean flags needs 125 bytes packed vs 1000 bytes unpacked.
434
+ - **String interning**: Replace repeated strings with integer IDs. "legendary_sword_of_fire" repeated 50 times in an inventory costs 50 * 26 bytes. An ID table + integer references costs 26 + 50 * 2 bytes.
435
+ - **Compression**: Apply zlib/LZ4/zstd compression to the serialized payload before writing to disk. Typical compression ratios for game save data: 3:1 to 10:1 for JSON, 1.5:1 to 3:1 for already-compact binary.
436
+ - **Chunked saves**: For open-world games, save each world region as a separate chunk. Only load/save chunks that have been visited. Unvisited regions have no save data (they use default state).
437
+
438
+ ### Testing Save Systems
439
+
440
+ Save systems require adversarial testing because failures are catastrophic and often invisible until the player next loads.
441
+
442
+ **Test cases (minimum):**
443
+ 1. Save and load: verify all data round-trips correctly (the obvious one)
444
+ 2. Kill the process during save (simulate power loss): verify the backup save loads correctly
445
+ 3. Corrupt the primary save file (flip random bytes): verify the backup loads and the player is warned
446
+ 4. Load a save from every previously shipped version: verify migration succeeds
447
+ 5. Fill the save with maximum data (max inventory, all quests complete, all areas discovered): verify it stays within platform size limits
448
+ 6. Save on device A, sync to cloud, load on device B: verify full data transfer
449
+ 7. Save on device A, go offline, save on device B, go online: verify conflict resolution works correctly
450
+ 8. Delete the save file while the game is running: verify graceful error handling
451
+ 9. Save to a full disk: verify the game detects the write failure and does not corrupt existing saves
452
+ 10. Load a save created on a different platform (if cross-save is supported): verify endianness, path, and format compatibility