Add persistent AFK reserves and recovery (Refs #151)
CI / checks (push) Successful in 7m7s

This commit is contained in:
2026-10-17 16:57:26 -04:00
parent 7f2205e9c4
commit d723f23d67
22 changed files with 2646 additions and 16 deletions
+49
View File
@@ -0,0 +1,49 @@
import type Database from 'better-sqlite3';
/** Accounts, receipts and audit evidence deliberately outlive matches and servers. */
export function migrateAfk(handle: Database.Database): void {
handle.exec(`
CREATE TABLE IF NOT EXISTS afk_accounts (
guid TEXT PRIMARY KEY, free_seconds INTEGER NOT NULL CHECK(free_seconds >= 0),
saved_seconds INTEGER NOT NULL DEFAULT 0 CHECK(saved_seconds >= 0),
free_started_at INTEGER NOT NULL, active_seconds INTEGER NOT NULL DEFAULT 0,
kill_progress INTEGER NOT NULL DEFAULT 0, headshot_progress INTEGER NOT NULL DEFAULT 0,
bonus_hour INTEGER NOT NULL DEFAULT 0, bonus_seconds INTEGER NOT NULL DEFAULT 0
);
CREATE TABLE IF NOT EXISTS afk_operations (
server TEXT NOT NULL, request_id TEXT NOT NULL, instance_id TEXT NOT NULL,
guid TEXT NOT NULL, fingerprint TEXT NOT NULL, result TEXT NOT NULL,
created_at TEXT NOT NULL, PRIMARY KEY(server,request_id)
);
CREATE TABLE IF NOT EXISTS afk_progress (
instance_id TEXT NOT NULL, match_key TEXT NOT NULL, guid TEXT NOT NULL,
seq INTEGER NOT NULL, active_seconds INTEGER NOT NULL, kills INTEGER NOT NULL,
headshots INTEGER NOT NULL, rounds INTEGER NOT NULL, revives INTEGER NOT NULL,
PRIMARY KEY(instance_id,match_key,guid)
);
CREATE TABLE IF NOT EXISTS afk_bonus_rewards (
guid TEXT NOT NULL, active_second INTEGER NOT NULL, seconds INTEGER NOT NULL
);
CREATE INDEX IF NOT EXISTS afk_bonus_rewards_guid ON afk_bonus_rewards(guid,active_second);
CREATE TABLE IF NOT EXISTS afk_sessions (
session_id TEXT PRIMARY KEY, server TEXT NOT NULL, instance_id TEXT NOT NULL,
guid TEXT NOT NULL, match_key TEXT NOT NULL, match_id INTEGER,
state TEXT NOT NULL CHECK(state IN ('reserved','started','settled','unresolved','corrected')),
authorized_seconds INTEGER NOT NULL, free_seconds INTEGER NOT NULL,
saved_seconds INTEGER NOT NULL, free_started_at INTEGER NOT NULL,
consumed_seconds INTEGER, charged_seconds INTEGER, free_charge_at INTEGER, confirmed_seconds INTEGER NOT NULL DEFAULT 0,
end_reason TEXT, created_at TEXT NOT NULL, started_at TEXT, ended_at TEXT
);
CREATE UNIQUE INDEX IF NOT EXISTS afk_pending_guid ON afk_sessions(guid)
WHERE state IN ('reserved','started','unresolved');
CREATE TABLE IF NOT EXISTS afk_ledger (
id INTEGER PRIMARY KEY AUTOINCREMENT, guid TEXT NOT NULL, server TEXT NOT NULL,
match_id INTEGER, operation_id TEXT NOT NULL, session_id TEXT,
kind TEXT NOT NULL, free_delta INTEGER NOT NULL, saved_delta INTEGER NOT NULL,
free_seconds INTEGER NOT NULL, saved_seconds INTEGER NOT NULL,
event_at TEXT NOT NULL, created_at TEXT NOT NULL, details TEXT NOT NULL DEFAULT '{}'
);
CREATE INDEX IF NOT EXISTS afk_ledger_guid ON afk_ledger(guid,id DESC);
CREATE INDEX IF NOT EXISTS afk_sessions_guid ON afk_sessions(guid,created_at DESC);
`);
}
+3 -1
View File
@@ -25,6 +25,7 @@ import { migrateGameFileSchema } from '../game-files/schema.js';
import { migrateLeaderboardReports } from './leaderboard-reports.js';
import { migratePlayerScoreTotal } from './player-score-total.js';
import { migrateBank } from './bank.js';
import { migrateAfk } from './afk.js';
import { migrateBankCredentials } from './bank-credentials.js';
import { migrateTemperatures } from '../hosts/temperatures.js';
import { migrateModeration, migrateModerationCases } from '../moderation/schema.js';
@@ -168,7 +169,8 @@ export const migrations: readonly Migration[] = [
name: 'moderation case workflow and linked restrictions',
mode: 'foreign-keys-off',
run: migrateModerationCases
}
},
{ version: 30, name: 'persistent AFK reserves and gameplay rewards', run: migrateAfk }
];
export const databaseVersion = migrations.length;