Adv 2.0 L5d: streak/action/lifecycle migration off AdvCharacter

Ten player_meta columns: current_streak, best_streak, last_action_date,
streak_decayed, action_taken_today, holiday_action_taken,
combat_actions_used, harvest_actions_used, created_at, last_active_at.

LifecycleState struct with HasLifecycle() marker + load/upsert/backfill/
projection helpers. New resetAllPlayerMetaDailyActions parallels the
existing resetAllAdvDailyActions for the bulk midnight reset.

createAdvCharacter now seeds created_at/last_active_at (CURRENT_TIMESTAMP)
so player_meta rows are fully formed at creation.

Mutation surface turned out to be small: only `rest` writes
ActionTakenToday (combat/harvest counters are dormant in current code);
plus streak halve + streak update in scheduler. Dual-writes wired at all
four sites + the bulk reset.

Tests: TestPlayerMetaLifecycleStateBackfill_Idempotent,
TestLoadLifecycleState_FallsBackToAdvCharacter,
TestUpsertPlayerMetaLifecycleState_RoundTrip,
TestResetAllPlayerMetaDailyActions.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
prosolis
2026-05-09 11:29:50 -07:00
parent c83b73b655
commit 8fc8941cc9
7 changed files with 426 additions and 2 deletions

View File

@@ -388,6 +388,7 @@ func (p *AdventurePlugin) midnightReset() error {
text += " — not all is lost."
}
_ = saveAdvCharacter(&char)
_ = upsertPlayerMetaLifecycleState(char.UserID, lifecycleStateFromAdvChar(&char))
}
if err := p.SendDM(char.UserID, text); err != nil {
slog.Error("adventure: failed to send idle shame DM", "user", char.UserID, "err", err)
@@ -404,6 +405,7 @@ func (p *AdventurePlugin) midnightReset() error {
char.BestStreak = char.CurrentStreak
}
_ = saveAdvCharacter(&char)
_ = upsertPlayerMetaLifecycleState(char.UserID, lifecycleStateFromAdvChar(&char))
}
}
@@ -420,6 +422,10 @@ func (p *AdventurePlugin) midnightReset() error {
if resetErr != nil {
return fmt.Errorf("reset daily actions after 3 attempts: %w", resetErr)
}
// Adv 2.0 Phase L5d — parallel reset on player_meta during dual-write soak.
if err := resetAllPlayerMetaDailyActions(); err != nil {
slog.Error("player_meta: daily action reset failed", "err", err)
}
// Prune expired buffs
if err := pruneAdvExpiredBuffs(); err != nil {