Adv 2.0 L4f-prep: DisplayName migration scaffold (schema + dual-write + helper)

Adds player_meta.display_name with idempotent backfill, dual-writes from
createAdvCharacter (in-tx) and saveAdvCharacter (post-commit), and a
loadDisplayName helper with AdvCharacter fallback. Readers are not flipped
yet — soak begins now; per-call-site swap follows in a later session.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
prosolis
2026-05-09 09:28:48 -07:00
parent d300008812
commit 5000c3d696
6 changed files with 211 additions and 3 deletions

View File

@@ -2,6 +2,7 @@ package plugin
import (
"database/sql"
"log/slog"
"time"
"gogobee/internal/db"
@@ -557,6 +558,16 @@ func createAdvCharacter(userID id.UserID, displayName string) error {
return err
}
// Adv 2.0 Phase L4f-prep — dual-write display_name into player_meta.
// Inside the same tx so the two rows are created atomically.
if _, err = tx.Exec(
`INSERT INTO player_meta (user_id, display_name) VALUES (?, ?)
ON CONFLICT(user_id) DO UPDATE SET display_name = excluded.display_name`,
string(userID), displayName,
); err != nil {
return err
}
// Create tier-0 equipment in all slots
for _, slot := range allSlots {
def := equipmentTiers[slot][0]
@@ -697,7 +708,14 @@ func saveAdvCharacter(char *AdventureCharacter) error {
boolToInt(char.TreasuresLocked),
string(char.UserID),
)
return err
if err != nil {
return err
}
// Adv 2.0 Phase L4f-prep — dual-write display_name into player_meta.
if dnErr := upsertPlayerMetaDisplayName(char.UserID, char.DisplayName); dnErr != nil {
slog.Error("player_meta: display_name dual-write failed", "user", char.UserID, "err", dnErr)
}
return nil
}
func boolToInt(b bool) int {