D1 Schema Additions¶
These two tables are added on top of the base Aelghar D1 schema. Apply as a separate migration after the base schema migrations.
Migration file: migrations/0200_minigame.sql
Table: minigame_session¶
Stores server-issued minigame challenge tokens. One row per issued session. Row is marked used = 1 after a valid result submission. Expired unused rows pruned daily.
CREATE TABLE IF NOT EXISTS minigame_session (
jti TEXT PRIMARY KEY, -- crypto.randomUUID() — matches token jti
character_id INTEGER NOT NULL
REFERENCES character(id) ON DELETE CASCADE,
activity_type TEXT NOT NULL, -- expedition_accept | craft_* | gather_* | ...
activity_ref_id TEXT NOT NULL, -- board item id, template id, etc.
minigame_type TEXT NOT NULL, -- rhythm | whack | sequence | fishing | ...
seed INTEGER NOT NULL, -- 32-bit signed, seeded content generation
skill_band INTEGER NOT NULL DEFAULT 0, -- 0–6, character's band at issue time
issued_at INTEGER NOT NULL, -- Unix epoch seconds
min_duration_ms INTEGER NOT NULL, -- minimum valid play time (ms)
max_duration_ms INTEGER NOT NULL, -- maximum valid play time (ms)
expires_at INTEGER NOT NULL, -- Unix epoch seconds (issued_at + 300)
used INTEGER NOT NULL DEFAULT 0 -- 0 = pending, 1 = consumed
);
CREATE INDEX IF NOT EXISTS idx_minigame_session_character
ON minigame_session(character_id);
CREATE INDEX IF NOT EXISTS idx_minigame_session_expires
ON minigame_session(expires_at)
WHERE used = 0; -- partial index: only unused sessions need pruning
Notes¶
jtiis the primary key and is included in the signed token payload. The Worker looks up byjtion submission.seedis stored to allow server-side event hash derivation verification.skill_bandis stored at issue time — not re-fetched at submission. Prevents skill changing mid-session edge cases.expires_at = issued_at + 300(5 minutes). Sessions that expire while the player is mid-game will fail on submission; the client must fetch a new session.
Table: minigame_result¶
Stores the validated result of each consumed minigame session. One row per consumed minigame_session. Written in the same D1 transaction that marks the session used.
CREATE TABLE IF NOT EXISTS minigame_result (
jti TEXT PRIMARY KEY
REFERENCES minigame_session(jti),
character_id INTEGER NOT NULL
REFERENCES character(id) ON DELETE CASCADE,
score REAL NOT NULL, -- 0.0–1.0, server-clamped
duration_ms INTEGER NOT NULL, -- actual play duration
event_hash TEXT NOT NULL, -- SHA-256 of seed + event indices
modifier_applied REAL NOT NULL, -- 0.7 + score * 0.7, clamped
submitted_at INTEGER NOT NULL -- Unix epoch seconds
);
CREATE INDEX IF NOT EXISTS idx_minigame_result_character
ON minigame_result(character_id, submitted_at DESC);
Notes¶
jtiis the PK and FK tominigame_session. Inserting a duplicatejtifails with a unique constraint — this is the idempotency guard (see architecture doc).modifier_appliedis stored after calculation so the settlement pass can read it directly without recalculating.scoreandmodifier_appliedare both stored for audit and analytics purposes.
Table: character_flag (addition to base schema)¶
The base schema does not include a character flags table. Add one to support Passive Mode and future per-character toggles.
CREATE TABLE IF NOT EXISTS character_flag (
character_id INTEGER NOT NULL
REFERENCES character(id) ON DELETE CASCADE,
flag_name TEXT NOT NULL,
flag_value INTEGER NOT NULL DEFAULT 1, -- 1 = true, 0 = false
set_at INTEGER NOT NULL,
PRIMARY KEY (character_id, flag_name)
);
Used flags:
| Flag name | Meaning |
|---|---|
passive_mode |
Skip minigames, use modifier 1.0 |
Combined Migration File¶
-- migrations/0200_minigame.sql
CREATE TABLE IF NOT EXISTS minigame_session (
jti TEXT PRIMARY KEY,
character_id INTEGER NOT NULL REFERENCES character(id) ON DELETE CASCADE,
activity_type TEXT NOT NULL,
activity_ref_id TEXT NOT NULL,
minigame_type TEXT NOT NULL,
seed INTEGER NOT NULL,
skill_band INTEGER NOT NULL DEFAULT 0,
issued_at INTEGER NOT NULL,
min_duration_ms INTEGER NOT NULL,
max_duration_ms INTEGER NOT NULL,
expires_at INTEGER NOT NULL,
used INTEGER NOT NULL DEFAULT 0
);
CREATE INDEX IF NOT EXISTS idx_minigame_session_character
ON minigame_session(character_id);
CREATE INDEX IF NOT EXISTS idx_minigame_session_expires
ON minigame_session(expires_at) WHERE used = 0;
CREATE TABLE IF NOT EXISTS minigame_result (
jti TEXT PRIMARY KEY REFERENCES minigame_session(jti),
character_id INTEGER NOT NULL REFERENCES character(id) ON DELETE CASCADE,
score REAL NOT NULL,
duration_ms INTEGER NOT NULL,
event_hash TEXT NOT NULL,
modifier_applied REAL NOT NULL,
submitted_at INTEGER NOT NULL
);
CREATE INDEX IF NOT EXISTS idx_minigame_result_character
ON minigame_result(character_id, submitted_at DESC);
CREATE TABLE IF NOT EXISTS character_flag (
character_id INTEGER NOT NULL REFERENCES character(id) ON DELETE CASCADE,
flag_name TEXT NOT NULL,
flag_value INTEGER NOT NULL DEFAULT 1,
set_at INTEGER NOT NULL,
PRIMARY KEY (character_id, flag_name)
);
Apply:
wrangler d1 migrations apply --database-name aelghar-db
wrangler d1 migrations apply --database-name aelghar-db --env staging
Cron: Prune Expired Sessions¶
Daily cron (03:30 UTC, same slot as idempotency prune) deletes expired unused minigame sessions. Results are kept indefinitely for audit; sessions older than 7 days with used = 0 are deleted.
Add to the existing daily prune Worker cron handler.
Indexes Summary (new rows)¶
| Table | Index | Columns | Type |
|---|---|---|---|
minigame_session |
idx_minigame_session_character |
character_id |
BTree |
minigame_session |
idx_minigame_session_expires |
expires_at WHERE used=0 |
Partial BTree |
minigame_result |
idx_minigame_result_character |
character_id, submitted_at DESC |
BTree |