Adv 2.0 L5e: death state migration off AdvCharacter

Eight player_meta columns: alive (default 1), dead_until,
death_reprieve_last, last_pardon_used (DATETIME), last_death_date,
grudge_location, death_source, death_location (TEXT).

DeathState struct + load/upsert/backfill/projection helpers. Dual-write
strategy switches: instead of per-site upserts, the dual-write rides
saveAdvCharacter itself (after the existing display_name dual-write).
Death-state mutation surface spans ~50 save sites; the saveAdvCharacter-
internal hook catches every one without scatter.

Backfill idempotent (only fills rows where every death field is still
default). Tests cover backfill, fallback, round-trip, and the
saveAdvCharacter dual-write.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
prosolis
2026-05-09 11:33:16 -07:00
parent 8fc8941cc9
commit 45586eb697
6 changed files with 388 additions and 1 deletions

View File

@@ -718,6 +718,12 @@ func saveAdvCharacter(char *AdventureCharacter) error {
if dnErr := upsertPlayerMetaDisplayName(char.UserID, char.DisplayName); dnErr != nil {
slog.Error("player_meta: display_name dual-write failed", "user", char.UserID, "err", dnErr)
}
// Adv 2.0 Phase L5e — dual-write death state into player_meta. Mutation
// surface is too wide for per-site upserts (~50 save sites), so the
// dual-write rides every saveAdvCharacter call.
if dsErr := upsertPlayerMetaDeathState(char.UserID, deathStateFromAdvChar(char)); dsErr != nil {
slog.Error("player_meta: death state dual-write failed", "user", char.UserID, "err", dsErr)
}
return nil
}