Skip to content

[replay 7288] fix(trackers): use manual Tracker Panel edits as the base for the next generation - #468

Open
Promansis wants to merge 2 commits into
replay-base/7288from
replay/7288-gfa
Open

Promansis wants to merge 2 commits into
replay-base/7288from
replay/7288-gfa

Conversation

@Promansis

Copy link
Copy Markdown
Owner

Dottore replay shadow (fork only) of upstream PR 7288 at 5263dee, the first head CodeRabbit reviewed.

Original title: fix(trackers): use manual Tracker Panel edits as the base for the next generation

SpicyMarinara and others added 2 commits October 8, 2026 02:05
…t generation

A regeneration started from the reply before the regenerated one and
excluded that reply, but a Tracker Panel edit lives only on the edited
reply's own row. Regenerating or swiping the corrected reply therefore
brought back the wrong value the user had just fixed.

- Record every tracker value a manual edit changes (scene text by value,
  lists and playerStats keys by fingerprint) in the existing
  manualOverrides column.
- getForGeneration lays the edits still visible on any swipe of the
  regenerated reply over its base, so the prompt, lorebook scan, tracker
  agents and Peek Prompt start from them. A tracker run that changes the
  value retires the edit.
- Values the trackers leave out keep the edit, the GM tool base keeps it,
  and a regenerated swipe no tracker wrote gets a row with it.
- A tracker run that rewrites its own row keeps that row's edit record.

Closes Pasta-Devs#7286

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…-edit-base

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@github-actions

github-actions Bot commented Oct 15, 2026 •

Copy link
Copy Markdown

🎭 Dottore Review — Concluded

Tip

Review posted. The specimen has left the observation table.

  • Commit: 5263dee - Merge remote-tracking branch 'origin/staging' into fix/tracker-manual-edit-base

@github-actions

Copy link
Copy Markdown

🎭 Dottore Review

Dottore Verdict: Repair Required

Warning

REPAIR REQUIRED
Actionable findings or blocking proof gaps remain for this head.

Findings Nitpicks Controls Reviewed Head Mode
4 0 none 5263deed full

Note

Mode: full
Head: 5263dee - Merge remote-tracking branch 'origin/staging' into fix/tracker-manual-edit-base
Base: d0dc3dc - Merge pull request Pasta-Devs#7281 from Pasta-Devs/fix/excerpts-privacy-code-followup

🧭 Specimen Summary

  • The specimen replaces one-shot scene overrides with persistent edit records, adding fingerprints for structured trackers and laying edits from every swipe over a regeneration baseline. It also preserves records across row replacement and backfills regenerated swipes when trackers write nothing; the interesting fractures appear when edit order, automatic rewrites, and synthetic baselines meet those new persistence rules.

🔎 Isolated Defects

Severity Location Finding
⚠️ MEDIUM packages/server/src/services/storage/game-state.storage.ts:325 (RIGHT) Swipe order defeats the latest manual correction
⚠️ MEDIUM packages/server/src/services/storage/game-state.storage.ts:333 (RIGHT) Retired edits revive when automatic values return
⚠️ MEDIUM packages/server/src/services/storage/game-state.storage.ts:342 (RIGHT) Whole-tracker overlays carry unrelated automatic changes
⚠️ MEDIUM packages/server/src/routes/generate.routes.ts:13704 (RIGHT) Preserve edited tracker baselines on Game regenerations

🧹 Minor Imperfections

  • None recorded.
🤖 Copy prompt for isolated Dottore findings
Task: Fix `packages/server/src/services/storage/game-state.storage.ts:325` on the RIGHT side.
Finding: Swipe order defeats the latest manual correction
Severity: medium
Goal: The most recent still-valid manual correction for a field wins across a reply's swipes, independently of swipe index.
Cover: Regeneration prompt selection, sibling-swipe fallback, and manual-tracker fallback must resolve competing edits consistently.
Avoid: Snapshot creation timestamps change during tracker rewrites and are not manual-edit timestamps.; Editing one field must not refresh the precedence of unrelated fields.
Acceptable fixes: Persist per-field edit revisions or timestamps and apply valid corrections in that order, with deterministic handling for legacy records.
Proof required: Edit a higher swipe, then the same field on a lower swipe; assert the lower swipe's newer value reaches both the prompt and persisted fallback.; Reverse the edit order and verify unrelated corrections retain independent precedence.
Run the narrowest relevant check. If stale, leave code unchanged and record why.

Task: Fix `packages/server/src/services/storage/game-state.storage.ts:333` on the RIGHT side.
Finding: Retired edits revive when automatic values return
Severity: medium
Goal: Once an automatic write changes a manually edited value on its source swipe, that edit cannot become eligible again without another manual correction.
Cover: Replacement through create and in-place writes through _applyUpdate must enforce the same retirement rule.; Retirement must cover text fields, structured tracker fingerprints, and playerStats keys.
Avoid: Compare against the final normalized persisted value, including preserved inventory, rather than an incomplete incoming patch.; Do not retire untouched edits merely because another field changed.
Acceptable fixes: Prune invalidated entries during automatic writes and persist the pruned record with the updated snapshot.; Use explicit edit-validity metadata that automatic divergence permanently invalidates.
Proof required: Exercise manual A, automatic B, automatic A on one swipe through both write paths; regeneration must retain its earlier-turn baseline instead of replaying A.; Verify unchanged edits survive rewrites and a subsequent genuine manual correction becomes eligible again.
Run the narrowest relevant check. If stale, leave code unchanged and record why.

Task: Fix `packages/server/src/services/storage/game-state.storage.ts:342` on the RIGHT side.
Finding: Whole-tracker overlays carry unrelated automatic changes
Severity: medium
Goal: Regeneration overlays only manually changed structured values onto its preceding-turn baseline, not unrelated generated values from the edited collection.
Cover: Cover worldCustomFields, presentCharacters, personaStats, and structured values under playerStats.; Manual additions, removals, and clears must retain their meaning when replayed onto the baseline.
Avoid: A fingerprint of the complete collection cannot identify which member or property was manually changed.; Array positions are unstable when generated rows are reordered or removed.
Acceptable fixes: Store identity-keyed manual patches or before/after differences at the edited member/property granularity and replay those patches onto the baseline.
Proof required: Generate X=1 from baseline X=0/Y=0, manually edit only Y=2, and assert regeneration prompts and fallback snapshots contain X=0/Y=2.; Exercise collection additions, removals, and reordered members without copying unrelated generated changes.
Run the narrowest relevant check. If stale, leave code unchanged and record why.

Task: Fix `packages/server/src/routes/generate.routes.ts:13704` on the RIGHT side.
Finding: Preserve edited tracker baselines on Game regenerations
Severity: medium
Goal: When regeneration produces no target snapshot, supported Tracker Panel edits must survive on the new swipe in Game as well as other modes.
Cover: Manual trackers and failed tracker runs with no ruleset, state-tool, or map write.; An earlier committed snapshot competing with the edited reply's uncommitted snapshot.
Avoid: Do not replace a target snapshot already written by trackers or game systems.; Do not let manual legacy location values override authoritative spatial state.
Acceptable fixes: Use a shared, mode-aware service to materialize the edited baseline when the regenerated swipe has no snapshot.
Proof required: Regenerate a Game reply with a manually corrected date, an older committed snapshot, and trackers disabled; verify the new swipe, HUD reload, and next generation retain the correction.; Verify existing ruleset and authoritative spatial writes remain intact.
Run the narrowest relevant check. If stale, leave code unchanged and record why.

🧪 Observations

  • Finder: Traced getForGeneration's visible, committed, excluded-message, and null-baseline selection into the generation prompt and tracker baseline.
  • Finder: Examined scalar clearing, JSON fingerprints, playerStats-key removal, and whole-collection overlays; the compact record loses both edit chronology and entry-level provenance.
  • Finder: Followed create and _applyUpdate through replacement and repeated automatic values, including the manual A → automatic B → automatic A case.

📝 Lab Notes

  • Dottore examined 11 candidate(s) against the code: 4 confirmed, 7 rejected, 0 unresolved.

@github-actions github-actions 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.

Dottore Review — isolated defects from the specimen.

.select()
.from(gameStateSnapshots)
.where(and(eq(gameStateSnapshots.chatId, chatId), eq(gameStateSnapshots.messageId, messageId)))
.orderBy(asc(gameStateSnapshots.swipeIndex));

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ MEDIUM: Swipe order defeats the latest manual correction

Location: packages/server/src/services/storage/game-state.storage.ts:325 (RIGHT)

A swipe index has been promoted to a clock, with predictable results. Edit swipe 2's location, return to swipe 0 and correct it again, then regenerate: the ascending scan applies swipe 2 last, replacing the newer correction with the older one. The regeneration prompt and fallback state consequently start from the stale edit.

Tip

Suggested fix: Record per-field edit order and resolve competing corrections by that order, rather than by the swipe's position.

// is taken whole from the edited swipe. A per-row diff would carry only the edited rows.
for (const row of rows) {
for (const [key, value] of Object.entries(parseStoredManualOverrides(row.manualOverrides) ?? {})) {
if (manualEditValue(row, key) !== value) continue;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ MEDIUM: Retired edits revive when automatic values return

Location: packages/server/src/services/storage/game-state.storage.ts:333 (RIGHT)

The equality check confuses present value with uninterrupted manual provenance. On the same swipe, a manual value A followed by automatic writes B and A leaves A's record intact throughout; regeneration then treats the final automatic A as a live correction again. Both replacement through create and automatic _applyUpdate writes preserve the record that the B write supposedly retired.

Tip

Suggested fix: Invalidate affected edit records when an automatic write changes their persisted values, while retaining records for values actually left unchanged.

[statKey]: storedPlayerStats(row)[statKey],
});
} else {
Object.assign(edited, { [key]: row[key as keyof GameStateRow] });

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ MEDIUM: Whole-tracker overlays carry unrelated automatic changes

Location: packages/server/src/services/storage/game-state.storage.ts:342 (RIGHT)

The edit record identifies an entire collection, then generously carries every member across the regeneration boundary. With a preceding snapshot X=0/Y=0, an automatic change to X=1 and a manual correction only to Y=2 cause regeneration to start from X=1/Y=2 rather than X=0/Y=2. The same whole-value transfer affects characters, persona stats, and lists inside playerStats.

Tip

Suggested fix: Persist and replay the actual manual differences within structured trackers, leaving unrelated automatic changes on the regenerated reply's side of the boundary.

// stay on screen and carry into the next message.
if (
input.regenerateMessageId &&
chatMode !== "game" &&

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ MEDIUM: Preserve edited tracker baselines on Game regenerations

Location: packages/server/src/routes/generate.routes.ts:13704 (RIGHT)

The fallback preserves manual edits, then exempts Game from the treatment. In a ruleset-free Game chat with manual trackers and no state-tool or map writes, regenerating the edited reply creates no snapshot for the new swipe. If an earlier reply has a committed snapshot, the HUD falls back to that row and displays its old date instead of the correction, contrary to the promised no-tracker regeneration behavior.

Tip

Suggested fix: Extend no-row materialization to Game through a mode-aware service path, retaining authoritative spatial and ruleset state rather than overwriting them.

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.

2 participants