Skip to content

feat(save-editor): move an NPC and let him stay there - #62

Open
dh0er wants to merge 13 commits into
fix/release-blockers-from-verificationfrom
claude/npc-relocation-analysis-54aff3
Open

feat(save-editor): move an NPC and let him stay there#62
dh0er wants to merge 13 commits into
fix/release-blockers-from-verificationfrom
claude/npc-relocation-analysis-54aff3

Conversation

@dh0er

@dh0er dh0er commented Aug 9, 2026

Copy link
Copy Markdown
Owner

What this is

NPC position editing, back — and this time it holds.

The Position tab's NPC half was made read-only a week ago on the finding that the game restores an NPC's placement from the level and discards the saved pose. That finding was wrong. Written on its own, CharacterLocation really is applied on load: an NPC written to a freepoint 82,000 units from where he stood was standing on that freepoint after the load. His daily routine then walks him back within seconds — which is what the earlier tests saw and misread.

Making a move stick takes a second write:

PositionByGlobalId{id}     › CharacterLocation = target
DailyRoutineByGlobalId{id} › DailyRoutineClass = /Script/Angelscript.DailyRoutine_Empty

DailyRoutine_Empty is the one class of 2777 in the script cache whose bytecode does nothing at all — constructor only, no Activate_Implementation, no HandleAvatarChanged.

How the old conclusion went wrong

Two independent mistakes that confirmed each other:

  • Both NPCs it moved had an active routine task, so the routine reclaimed them instantly (a streamed-out NPC is teleported back, not walked).
  • It then reached for DailyRoutine_Empty_StayAtSpawn, whose Activate_Implementation calls SetCharacterLocationAndDirection(GetPreferredLocation().StartingPosition, …)the level actor, never the save. Of 2777 routine classes it picked the one guaranteed to undo the edit.

A third confound was ruled out along the way: teleporting an NPC onto the hero's exact coordinates is not rejected for capsule overlap.

Both dead ends are now documented at the constant and the panel, so the next person does not re-derive them.

Verification

Proven in game, not inferred. A/B/C in one save, one load, one variable each:

NPC writes result
SC_NOV_Shrat_1356 position + DailyRoutine_Empty stayed
FM_GRD_Guard04_286 (has an assigned UsedSpot) position + DailyRoutine_Empty stayed — so the spot need not be cleared
OM_STT_Alberto_300 position only walked back

The undo

Swapping the routine destroys something the save then holds nowhere: the class an NPC was on is story state (_Collapsed, _WaitYard, _TcWait), not the _Start the naming convention suggests. It cannot be derived back, so it is written down.

crates/gore-save/src/placement.rs keeps it beside the save, in the same shape and with the same care as the backup-label map — staged and swapped, published only while the file is still what was read, an unreadable file blocking a write instead of being overwritten.

Beside rather than inside on purpose: the game re-serializes the whole save from live state on the next in-game save, so an in-save marker would be gone exactly when the undo is wanted, while DailyRoutine_Empty — live state by then — would survive.

write_save takes placementNotes / clearPlacementNotes, parsed before the write (a malformed note fails the request rather than letting the save land with no undo) and written after it (a note never describes a move that did not happen). A sidecar failure is reported beside a good save, not turned into a failed one.

UI

The routine is a checkbox of its own, below the fields and not inside the location picker: the picker is deliberately write-agnostic, which is what lets one dialog serve the hero and an NPC, and its existing box ("apply the spot's facing") is a property of the SPOT while this one is a property of the NPC.

  • It shows the stored state, so it is always visible and ticking it alone freezes an NPC where he stands. Only a difference from the stored state is ever written.
  • It starts off. Wiping a schedule is a deliberate act, not a side effect of editing a coordinate — at the cost, stated in the subtitle, that a plain move does not stick.
  • Unticking gives the routine back from the note; "take the move back" restores position and routine together. Both are offered only while the core still vouches for the note, and the core reports the two verdicts separately so a moved position cannot block giving the routine back.
  • The lock is gated on the stored state, never on the tick just made. Gating it on the tick greyed the box out for every NPC the moment it was ticked, claiming there was no way back from a change that had not been saved.

Tests

385 Rust, 512 Flutter, flutter analyze clean. New coverage: the sidecar's round-trip, its refusal to overwrite an unreadable file, and nine widget tests over the checkbox semantics — starts off, ticking alone freezes, ticking does not lock, locked only without a note, and the restore paths.

🤖 Generated with Claude Code


Note

Medium Risk
Changes how save files and placement sidecars are written and how partial failures are reported; mistakes could strand NPCs or lose undo metadata, though the flow is heavily tested and notes are designed not to fail the main save.

Overview
NPCs can be moved again from the Characters → Position tab: coordinates are editable (manual entry or the same location picker as the hero), with per-NPC pending keys, validation that blocks Save on bad input, and draft rehydration when switching NPCs.

A move that should stick queues both CharacterLocation (and optional rotation) and a swap to the core’s inert DailyRoutine_Empty class via a separate “disable daily routine” checkbox, plus optional undo when the core still considers the recorded placement restorable.

Placement undo is not part of the .sav bytes: PendingSaveEdit now carries placementNotes / clearPlacementNotes, aggregated on the first write_save sub-write; partial saves and retries keep those notes. The UI surfaces placementNoteWarning on successful saves, failed multi-writes, profile assign, restore, and backup delete when the sidecar note fails.

Domain/UI updates include struct-based private.typed.setValue edits, routine/undo metadata from private.npc.position, and isolated invalid-edit keys for npc.position: vs attributes. The old read-only NPC position test is removed in favor of new widget/notifier coverage; changelog and strings are updated across locales.

Reviewed by Cursor Bugbot for commit ede36f7. Bugbot is set up for automated code reviews on this repo. Configure here.

dh0er and others added 2 commits August 9, 2026 19:11
Moving an NPC in the save DOES take effect on load — verified in game,
against the opposite conclusion this repo recorded a week ago. What the
save does not control is whether he stays: his daily routine reclaims him
within seconds, and a streamed-out NPC is teleported back rather than
walked. Writing DailyRoutineClass = DailyRoutine_Empty alongside the
position is what makes a move stick; that class is the one of 2777 whose
bytecode does nothing at all.

The earlier round of tests reached for DailyRoutine_Empty_StayAtSpawn
instead, whose Activate_Implementation snaps to
GetPreferredLocation().StartingPosition — the level actor, never the
save. It undid the very edit it was meant to protect, and the two NPCs it
moved both had an active routine task, so nothing in that experiment
could have worked. Both dead ends are now written down where the next
person will look.

private.npc.position gains routineClass, its typed path, the inert class
by name (so the app never spells it out and drifts), and any recorded
undo.

Swapping the routine destroys something the save then holds nowhere: the
class an NPC was on is story state (_Collapsed, _WaitYard, _TcWait), not
the _Start the naming convention suggests, so it cannot be derived back.
placement.rs writes it down beside the save, in the same shape and with
the same care as the backup-label map — staged and swapped, published
only while the file is still what was read, and an unreadable file blocks
a write instead of being overwritten.

Beside the save rather than inside it on purpose: the game re-serializes
the whole save from live state on the next in-game save, so an in-save
marker would be gone exactly when the undo is wanted, while
DailyRoutine_Empty — live state by then — would survive.

write_save takes placementNotes/clearPlacementNotes. They are parsed
BEFORE the write, so a malformed note fails the request instead of the
save landing with no undo recorded, and written AFTER it, so a note never
describes a move that did not happen. A sidecar failure is reported
beside a good save rather than turned into a failed one.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The Position tab's NPC half was made read-only a week ago on the finding
that the game restores an NPC's placement from the level and discards the
saved pose. That finding was wrong: written on its own, CharacterLocation
really does put him there on load — an NPC written to a freepoint 82,000
units away was standing on it — and his daily routine then walks him back
within seconds. So the editing machinery comes back, together with the
second write that makes a move hold.

The routine is a checkbox of its own, below the fields and not inside the
location picker. The picker is deliberately write-agnostic — no command,
no path, no pending key — which is what lets one dialog serve the hero and
an NPC; and its existing box ("apply the spot's facing") is a property of
the SPOT, while this one is a property of the NPC and has to hold just as
much when coordinates are typed by hand.

It shows the NPC's stored state rather than modifying a pending move, so
it is always visible, ticking it alone freezes him where he stands, and
only a DIFFERENCE from the stored state is ever written. It starts off:
wiping a schedule is a deliberate act, not a side effect of editing a
coordinate. The cost is that a plain move does not stick unless the box is
ticked too, which the subtitle says.

Unticking gives the routine back from the recorded note, and taking a
whole move back restores position and routine together. Both are offered
only while the core still vouches for the note — once the save no longer
holds what that move wrote, restoring it would discard whatever happened
since. The core reports the two verdicts separately, because a moved
position must not block giving the routine back.

The lock is gated on the STORED state, never on the tick just made.
Gating it on the tick greyed the box out for every NPC the moment it was
ticked, claiming there was no way back from a change that had not been
saved — while unticking would simply have dropped the pending edit.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Comment thread apps/save-editor/lib/features/editor/domain/editor_notifier.dart
Comment thread apps/save-editor/lib/features/editor/ui/position_detail.dart Outdated

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: f1bacd5edf

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread apps/save-editor/lib/features/editor/ui/position_detail.dart Outdated
Comment thread crates/gore-save/src/lib.rs Outdated
Comment thread crates/gore-save/src/lib.rs Outdated
Four findings from the automated review, all real.

A queued pin was lost on a revisit. The checkbox was reseeded from the
save while the pending registry still held the routine swap, and the next
coordinate keystroke rebuilt the entry without it — saving the move as
position-only, which is the one shape that does not stick. The panel now
resumes the box from the QUEUE, which decides it in both directions: the
inert class means a pending pin, any other class a pending restore.

The undo note was dropped whenever the pending set was rebuilt after a
failed or partial save, so a retry could commit the move with nothing
recording the routine it replaced. Both rebuild sites carry it now.
Re-recording a note whose sub-write did commit is harmless — it is keyed
by NPC and identical — while losing it strands an NPC with no way back.

With outputPath the bytes land in the export while the note was recorded
against the source: the export got no undo and an untouched file got a
note for a move it does not contain. The sidecar now follows the file the
bytes landed in.

A failed sidecar write was reported by the core and read by nobody, so
the UI announced success and cleared the drafts while the replaced
routine was gone unrecorded. The warning now rides the save message.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@dh0er

dh0er commented Aug 9, 2026

Copy link
Copy Markdown
Owner Author

@codex review

Comment thread apps/save-editor/lib/features/editor/domain/editor_notifier.dart
A sub-write that committed put the NPC move on disk; a later sub-write
failing does not take it back. The placement warning from the committed
write was collected and then dropped on that path, so the user saw only
the save error while the routine the pin replaced was recorded nowhere.
The warning now rides the error message too.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@dh0er

dh0er commented Aug 9, 2026

Copy link
Copy Markdown
Owner Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 0624d4860c

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread crates/gore-save/src/lib.rs Outdated
Comment thread crates/gore-save/src/placement.rs Outdated
Comment thread apps/save-editor/lib/features/editor/ui/position_detail.dart
Three findings from the automated review, all real.

A move that also turned the NPC — the location picker can apply a spot's
heading — recorded only the two locations. Taking the move back then put
the position and routine right, left the new facing, and cleared the only
record of the old one. The note now carries the rotation on both sides,
the freshness check tests it, and the restore writes it back.

The freshness check compared the noted f64 against the stored value. A
12-byte Vector is written as three f32, so a note would never equal what
reads back: every fresh note on such a save was stale on arrival and the
undo was dead before it was offered. The comparison now accepts the f32
round-trip as well as exact equality.

Deleting the sidecar released its claim and only then unlinked, so a
second editor publishing in that window had its notes deleted — and the
ignored unlink error would have hidden it. The delete now goes through
the same claim-guarded path the backup-label map already used, extracted
so both maps share one implementation rather than two.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@dh0er

dh0er commented Aug 9, 2026

Copy link
Copy Markdown
Owner Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 83b2ba4e70

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread crates/gore-save/src/placement.rs
Comment thread crates/gore-save/src/placement.rs
Comment thread crates/gore-save/src/npc.rs Outdated
Three findings from the automated review, all real.

A DailyRoutineByGlobalId entry without a DailyRoutineClass member still
handed back a path, so the editor offered the pin and the save then
failed: private.typed.setValue patches an existing property and cannot
create a missing one. The path is withheld when the member is absent —
"a path came back" is the caller's only signal that a leaf is writable.

Importing an external save copies its bytes to a new slot, but the notes
are keyed by file name and stayed with the source. The import held
DailyRoutine_Empty with no record of what it replaced, which locks the
routine control for good. The notes are copied across after the bytes
land; the source keeps its own, because both files now describe the same
pinned NPCs.

A backup captures the save as it is, pinned NPCs included, and so has to
capture what those pins replaced. Without that, undoing a pin spent the
only copy and restoring the backup later brought DailyRoutine_Empty back
with nothing to undo it. Creating a backup now snapshots the notes under
the backup's file name, restoring puts that snapshot back wholesale
(including "none", so a pre-pin backup stops offering an undo the
restored bytes do not contain), and deleting a backup drops its snapshot
so the next file to reuse the name does not inherit it.

None of the three may fail the operation they ride on: the bytes are
already safe by then, so a sidecar failure is reported beside a completed
backup, restore or import instead of turning one into an error.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@dh0er

dh0er commented Aug 9, 2026

Copy link
Copy Markdown
Owner Author

@codex review

Comment thread crates/gore-save/src/placement.rs
carry upserted only the NPCs the source had, so entries left behind by an
earlier file of the destination's name survived and offered to undo moves
the copied bytes do not contain. A source with no notes did not clear the
destination key at all. The copy is wholesale now, including the empty
case — the same rule the backup snapshot already followed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@dh0er

dh0er commented Aug 9, 2026

Copy link
Copy Markdown
Owner Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 6dc3a5ce25

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread crates/gore-save/src/lib.rs
Restoring a backup installs the notes that describe it, and the core
already reported a failure to do so beside the completed restore — but
nothing read the field, so the UI announced a clean restore while the
restored save could hold a pinned NPC with no record of the routine that
pin replaced. The warning now rides the restore message.

The same field on deleting a backup and on importing an external save was
equally unread, and for the same reason: those two also write notes after
their bytes are already safe. Both now report it too.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@dh0er

dh0er commented Aug 9, 2026

Copy link
Copy Markdown
Owner Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 90402b128c

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread crates/gore-save/src/lib.rs Outdated
Comment thread crates/gore-save/src/placement.rs
The backup snapshot lived inside the byte writer and discarded its own
error, so a save whose notes could not be captured still reported a fully
successful backup. Undoing the live pin later spent the only copy, and
restoring that backup brought DailyRoutine_Empty back with nothing to
undo it. The snapshot moves out to the three places that back up a SAVE —
write, restore and profile assignment — each of which already carries a
warning field for exactly this: the bytes are safe by then, so it is
reported beside a completed operation, never turned into a failure.

The transfer paths also read through the forgiving reader, which turns an
unreadable sidecar into "no notes". That is right for a display and wrong
here: an import would have reported success while clearing the
destination's notes, and a restore would have decided there was nothing
to install. Transfers now read strictly and say which file they could not
read.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@dh0er

dh0er commented Aug 9, 2026

Copy link
Copy Markdown
Owner Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 5712e726d1

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread apps/save-editor/lib/features/editor/ui/position_detail.dart
Comment thread crates/gore-save/src/placement.rs Outdated
Moving an NPC who was already pinned queued only the pose edit. The note
still named his previous position, so the save no longer held what the
note said it wrote and the undo went stale the moment that move landed —
stranding the original position and routine it recorded. The note is
refreshed on such a move, keeping the ORIGINAL half from the first pin,
because a restore has to reach the state before he was ever pinned.

The backup snapshot returned early for a save with no notes, so a reused
backup file name — delete and recreate inside the same timestamp second,
or an earlier cleanup that failed — kept the deleted file's notes and
would have installed them on restore. The snapshot is wholesale now, the
same rule the import copy already followed. Removing the sidecar
short-circuits when there was nothing there, because the claim it stages
needs the backups folder to exist.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@dh0er

dh0er commented Aug 9, 2026

Copy link
Copy Markdown
Owner Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: a95da53ade

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread apps/save-editor/lib/features/editor/ui/position_detail.dart Outdated
Comment thread apps/save-editor/lib/features/editor/ui/position_detail.dart Outdated
Comment thread crates/gore-save/src/lib.rs Outdated
…l needs

A rotation-only edit on a pinned NPC skipped the note refresh, so the
saved facing no longer matched the one the note named and the undo went
stale on the spot. Either half of the pose moving now refreshes it.

A second move that changed only the position dropped written_rotation
while original_rotation survived from the first pin. The core then
stopped checking the facing for staleness while the undo would still
restore it, so an in-game turn could be overwritten by taking the move
back. A note that restores a facing now always guards it.

Deleting a backup cleared the shared placement key even when the same
file name still existed in the other supported backup location, losing
the surviving copy's undo metadata. Gated on "no backup carries that name
any more", exactly as the label cleanup beside it already was.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@dh0er

dh0er commented Aug 9, 2026

Copy link
Copy Markdown
Owner Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 3c31c09adc

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread crates/gore-save/src/lib.rs
Comment thread apps/save-editor/lib/features/editor/domain/npc_position.dart Outdated
… pin

A write with outputPath copies the bytes wholesale but only applied the
notes this request supplied. Another NPC already pinned in the source
arrived in the export pinned with no undo, while notes left by an earlier
file of the export's name survived. The export now starts from the
source's whole note set, with this request's records and clears as the
delta on top.

The checkbox was offered for an NPC whose routine leaf is writable but
whose stored location is absent. Ticking it queued nothing while the box
stayed visibly ticked, because a note needs the location it replaced.
Offering it now requires a location to record.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@dh0er

dh0er commented Aug 9, 2026

Copy link
Copy Markdown
Owner Author

@codex review

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 3ae794b. Configure here.

Comment thread crates/gore-save/src/lib.rs Outdated

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 3ae794b95f

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread crates/gore-save/src/lib.rs
Comment thread crates/gore-save/src/placement.rs Outdated
…er location

The snapshot ran after the guarded replace, so a write that aborted there
left a listed, restorable backup of a pinned save with no record of the
routine its pins replaced. It now runs the moment the backup file exists,
and the write reports what it could not capture.

Backup notes were keyed by file name alone, but the same name can exist
twice — beside the save and in goresave_backups — and both are listed and
restorable. A snapshot of either overwrote the other's undo metadata, and
a restore could install the wrong one. The key now distinguishes the two
locations, and the surviving-copy check that gates deletion follows it.

A failed carry on export no longer suppresses this request's own notes:
they describe the move the write just put on disk, so they are attempted
regardless and every warning is reported together.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@dh0er

dh0er commented Aug 9, 2026

Copy link
Copy Markdown
Owner Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: ede36f75a7

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +2189 to +2190
let placement_note_warning = placement::snapshot_backup(path, &current_backup_path)
.and_then(|()| restore_backup_placement_notes(path, backup_path))

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Snapshot restore safety notes when creating the backup

When restoring a pinned save, the safety backup is created before staging and guarded replacement, but its placement notes are not snapshotted until after both replacements commit. If validation, a concurrent file change, or the companion replacement aborts the restore, the safety backup remains listed without its NPC undo metadata; after the live pin is undone, restoring that backup brings back DailyRoutine_Empty with no recoverable original routine. Fresh evidence beyond the earlier write_save finding is that the current restore path still performs this snapshot only after every fallible replacement step.

Useful? React with 👍 / 👎.

Comment on lines +4404 to +4408
let placement_note_warning = backup_path
.as_deref()
.map_or(Ok(()), |backup| {
placement::snapshot_backup(target_path, Path::new(backup))
})

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Snapshot profile-assignment backups before guarded writes

When assigning an already-pinned save to another profile, the slot backup is created before the two guarded replacements, while this placement snapshot runs only after both replacements succeed. If either replacement fails—for example because PersistentDataList.sav changes concurrently—the backup remains on disk without its placement-note key; undoing the live pin and later restoring that backup then loses the original routine. Fresh evidence beyond the earlier write_save finding is that the profile-assignment workflow retains the same post-commit snapshot ordering.

Useful? React with 👍 / 👎.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant