package redisstate import ( "encoding/base64" "galaxy/lobby/internal/domain/common" ) // defaultPrefix is the mandatory `lobby:` namespace prefix shared by every // Game Lobby Redis key. const defaultPrefix = "lobby:" // Keyspace builds the Game Lobby Redis keys that survive the PG_PLAN.md // §6A and §6B migrations: per-game ephemeral runtime aggregates, // capability-evaluation guards, gap activation timestamps, and stream // consumer offsets. The four core enrollment entities (game, application, // invite, membership) and the Race Name Directory live in PostgreSQL — // their previous keyspace methods are gone. // // All dynamic key segments are encoded with base64url so raw key structure // does not depend on user-provided or caller-provided characters. type Keyspace struct{} // GapActivatedAt returns the Redis key that stores the gap-window // activation timestamp for one game. func (Keyspace) GapActivatedAt(gameID common.GameID) string { return defaultPrefix + "gap_activated_at:" + encodeKeyComponent(gameID.String()) } // StreamOffset returns the Redis key that stores the last successfully // processed entry id for one Redis Stream consumer. The streamLabel is // the short logical identifier of the consumer (e.g. `runtime_results`, // `gm_events`, `user_lifecycle`), not the full stream name; it stays // stable when the underlying stream key is renamed. func (Keyspace) StreamOffset(streamLabel string) string { return defaultPrefix + "stream_offsets:" + encodeKeyComponent(streamLabel) } // GameTurnStat returns the per-user Redis key that stores the // initial/max stats aggregate for one game. keeps one key per // user so the Lua-backed SaveInitial and UpdateMax scripts can operate // on a single primary key without a secondary index. func (Keyspace) GameTurnStat(gameID common.GameID, userID string) string { return defaultPrefix + "game_turn_stats:" + encodeKeyComponent(gameID.String()) + ":" + encodeKeyComponent(userID) } // GameTurnStatsByGame returns the set key that stores every userID for // which a GameTurnStat key exists for gameID. The set is the lookup // index used by Load and Delete so they avoid a Redis SCAN over the // whole keyspace. func (Keyspace) GameTurnStatsByGame(gameID common.GameID) string { return defaultPrefix + "game_turn_stats_by_game:" + encodeKeyComponent(gameID.String()) } // CapabilityEvaluationGuard returns the Redis key whose presence marks // gameID as already evaluated by the The capability evaluator // uses SETNX on this key to make replayed `game_finished` events safe. func (Keyspace) CapabilityEvaluationGuard(gameID common.GameID) string { return defaultPrefix + "capability_evaluation:done:" + encodeKeyComponent(gameID.String()) } func encodeKeyComponent(value string) string { return base64.RawURLEncoding.EncodeToString([]byte(value)) }