@@ -49,10 +49,10 @@ const writeDismissed = (sig: string) => {
4949 }
5050} ;
5151
52- /** The guided data-sync experience: a themed modal that pops when mod drift is
53- * detected (or is opened from the nav / Settings), lets you ignore or re-sync, and
54- * then walks the dump as a step-by-step progress flow ending in a summary. Mounted
55- * once in the root; it owns the periodic drift check and the sync polling. */
52+ /** The guided data-sync experience: a themed modal that pops when mod or importer
53+ * drift is detected (or is opened from the nav / Settings), lets you ignore or
54+ * re-sync, and then walks the dump as a step-by-step progress flow ending in a
55+ * summary. Mounted once in the root; it owns the periodic drift check and polling. */
5656export function DriftModal ( ) {
5757 const qc = useQueryClient ( ) ;
5858 const isOpen = useDriftModalOpen ( ) ;
@@ -97,8 +97,12 @@ export function DriftModal() {
9797 prevConnected . current = connected ;
9898 } , [ connected , qc ] ) ;
9999
100- // Auto-open once per drift signature (until it's dismissed or resolved by a sync).
101- const sig = drift . data ?. needsRedump ? JSON . stringify ( drift . data . drift ) : null ;
100+ // Auto-open once per combined drift signature (until dismissed or resolved).
101+ // Include the reader versions so each future format bump prompts exactly once,
102+ // even when the mod set itself has not changed.
103+ const sig = drift . data ?. needsRedump
104+ ? JSON . stringify ( { mods : drift . data . drift , dataFormat : drift . data . dataFormat } )
105+ : null ;
102106 useEffect ( ( ) => {
103107 if ( sig && readDismissed ( ) !== sig ) driftModal . open ( ) ;
104108 } , [ sig ] ) ;
@@ -123,6 +127,8 @@ export function DriftModal() {
123127 if ( ! isOpen ) return null ;
124128
125129 const hasDrift = ! ! drift . data ?. needsRedump ;
130+ const hasModDrift = ! ! drift . data ?. modsChanged ;
131+ const hasDataFormatDrift = ! ! drift . data ?. dataFormat . stale ;
126132 const gameUp = gameRunning . data ?. running === true ; // can't sync while the game runs
127133 const running = RUNNING . has ( phase ) ;
128134 const view : "prompt" | "running" | "done" | "error" = running
@@ -170,8 +176,8 @@ export function DriftModal() {
170176 < HelpButton title = "Data drift" className = "mr-7 ml-auto" >
171177 < p >
172178 PyOps plans against a snapshot of the game's prototype data (recipes, items,
173- machines, techs). When the game's mod set changes, that snapshot drifts out of
174- date and this dialog offers a re-sync.
179+ machines, techs). When the game's mod set or PyOps' data reader changes,
180+ that snapshot drifts out of date and this dialog offers a re-sync.
175181 </ p >
176182 < p >
177183 A re-sync launches a headless copy of Factorio to re-dump the data, then imports it.
@@ -189,7 +195,10 @@ export function DriftModal() {
189195 { view === "prompt" && (
190196 < PromptBody
191197 hasDrift = { hasDrift }
198+ hasModDrift = { hasModDrift }
199+ hasDataFormatDrift = { hasDataFormatDrift }
192200 drift = { drift . data ?. drift ?? null }
201+ dataFormat = { drift . data ?. dataFormat }
193202 icons = { icons }
194203 setIcons = { setIcons }
195204 gameUp = { gameUp }
@@ -272,13 +281,19 @@ const TITLES: Record<"running" | "done" | "error", TitleSpec> = {
272281
273282function PromptBody ( {
274283 hasDrift,
284+ hasModDrift,
285+ hasDataFormatDrift,
275286 drift,
287+ dataFormat,
276288 icons,
277289 setIcons,
278290 gameUp,
279291} : {
280292 hasDrift : boolean ;
293+ hasModDrift : boolean ;
294+ hasDataFormatDrift : boolean ;
281295 drift : Parameters < typeof DriftChanges > [ 0 ] [ "drift" ] ;
296+ dataFormat ?: { current : number ; imported : number | null ; stale : boolean } ;
282297 icons : boolean ;
283298 setIcons : ( v : boolean ) => void ;
284299 gameUp : boolean ;
@@ -292,11 +307,24 @@ function PromptBody({
292307 </ Callout >
293308 ) }
294309 < p className = "text-sm text-muted-foreground" >
295- { hasDrift
296- ? "The game's mods changed since your last sync — re-sync to plan against the current data."
297- : "Re-dump the game's prototype data from the current mods and import it." }
310+ { hasModDrift && hasDataFormatDrift
311+ ? "The game's mods and PyOps' data reader changed since your last sync. Re-sync to rebuild this project's reference data."
312+ : hasModDrift
313+ ? "The game's mods changed since your last sync — re-sync to plan against the current data."
314+ : hasDataFormatDrift
315+ ? "PyOps now reads the Factorio dump differently than the version that built this project's reference data. Re-sync to rebuild it with the current reader."
316+ : hasDrift
317+ ? "This project's reference data needs to be rebuilt before planning continues."
318+ : "Re-dump the game's prototype data from the current mods and import it." }
298319 </ p >
299- { hasDrift && (
320+ { hasDataFormatDrift && dataFormat && (
321+ < div className = "border border-warning/30 bg-warning/10 p-2 text-sm text-warning" >
322+ Imported data format:{ " " }
323+ { dataFormat . imported == null ? "unversioned" : `v${ dataFormat . imported } ` }
324+ { " · " } Current reader: v{ dataFormat . current }
325+ </ div >
326+ ) }
327+ { hasModDrift && drift && (
300328 < div className = "max-h-48 overflow-y-auto border border-border bg-muted/20 p-2" >
301329 < DriftChanges drift = { drift } />
302330 </ div >
0 commit comments