game_memory

GameGirl Color – Two-tier game memory system.

Manages per-game memories in Redis with two tiers: - Basic: Important, long-lived (characters, plot, bosses, items) - Channel: Ephemeral, recent context (recent events, minor NPCs)

Includes hot-swap memory bleed logic for carrying context between cartridge swaps. # 🧠💀 CORRUPTED MEMORY BANKS

class game_memory.GameMemory(label, content, importance=0.5, turn_created=0, turn_last_referenced=0, reference_count=1, category='general', glitched=False, source_game='', created_at=<factory>)[source]

Bases: object

A single memory entry in a game’s two-tier memory bank.

Represents one remembered fact about an in-progress GameGirl Color game – a character, plot beat, boss, item, or a fleeting scene detail – along with the bookkeeping the tiering logic needs: an importance weight that drives trimming and bleed selection, reference_count and turn_last_referenced that drive channel-to-basic promotion, and the glitched / source_game flags that mark entries carried across a cartridge hot-swap. Instances are a pure in-memory record with no I/O of their own; they are serialized to and from the game:mem:basic:{game_id} and game:mem:channel:{game_id} Redis strings via to_dict() and from_dict().

Constructed by store_basic() and store_channel() when a new fact is recorded, and rebuilt by get_basic_memories() and get_channel_memories() when memories are read back from Redis.

Parameters:
  • label (str)

  • content (str)

  • importance (float)

  • turn_created (int)

  • turn_last_referenced (int)

  • reference_count (int)

  • category (str)

  • glitched (bool)

  • source_game (str)

  • created_at (float)

label: str
content: str
importance: float = 0.5
turn_created: int = 0
turn_last_referenced: int = 0
reference_count: int = 1
category: str = 'general'
glitched: bool = False
source_game: str = ''
created_at: float
to_dict()[source]

Serialize this memory entry to a plain JSON-safe dict.

Delegates to dataclasses.asdict() to flatten every field (label, content, importance, turn_created, turn_last_referenced, reference_count, category, glitched, source_game, created_at) into a dict. The result is appended to the memory list and persisted to the game:mem:basic:{game_id} or game:mem:channel:{game_id} Redis string via json.dumps(). No side effects.

Called by store_basic() and store_channel() when a new entry is created before the list is written back to Redis.

Returns:

A shallow dict of all dataclass fields.

Return type:

dict[str, Any]

classmethod from_dict(d)[source]

Reconstruct a GameMemory from a stored dict, ignoring extras.

Filters d down to the dataclass’s declared fields (via cls.__dataclass_fields__) before constructing the instance, so any stray or legacy keys read back from Redis are dropped rather than raising a TypeError. Missing optional fields fall back to their dataclass defaults. No side effects.

Called by get_basic_memories() and get_channel_memories() when deserializing each entry of the JSON list loaded from the game:mem:basic:{game_id} / game:mem:channel:{game_id} keys.

Parameters:

d (dict[str, Any]) – A dict of stored memory data, typically a single element of the JSON-decoded memory list.

Returns:

A new instance built from the recognized keys in d.

Return type:

GameMemory

async game_memory.store_basic(game_id, label, content, importance=0.7, category='general', turn=0, redis=None)[source]

Store or update a basic (important), long-lived game memory.

Basic memories persist for the life of the game and hold the things worth remembering across the whole playthrough – characters, plot points, bosses, key items, and relationships. The function reads the current memory list from the game:mem:basic:{game_id} Redis string, merges by case-insensitive label (updating content, raising importance to the max seen, bumping the reference count, and stamping the last-referenced turn) or appends a fresh GameMemory, trims the list down to _MAX_BASIC keeping the highest-importance entries, and writes the JSON list back. All Redis failures are caught and logged rather than raised.

Called by store_channel() to promote a frequently referenced ephemeral memory and by bleed_memories() to inject glitch-tagged entries into a new cartridge; no other in-repo callers were found, so it is otherwise invoked from the game subsystem at runtime.

Parameters:
  • game_id (str) – Identifier of the game whose basic memory bank is updated.

  • label (str) – Short name/key for the memory; matched case-insensitively to decide between update and insert.

  • content (str) – The remembered text.

  • importance (float) – Weight in the 0.0-1.0 range; an existing entry keeps the larger of its current and the supplied value.

  • category (str) – Memory category such as general, character, plot, boss, or item.

  • turn (int) – Current game turn, recorded as the last-referenced turn.

  • redis (Any) – Async Redis client; when None the call is a no-op.

Returns:

True if the memory list was written, False when Redis is absent or the write failed.

Return type:

bool

async game_memory.store_channel(game_id, label, content, turn=0, redis=None)[source]

Store a channel (ephemeral) game memory, auto-promoting hot entries.

Channel memories hold recent, disposable context – scene descriptions, minor NPCs, dialogue snippets – under the game:mem:channel:{game_id} Redis string and are evicted FIFO once the list exceeds _MAX_CHANNEL. The function reads the current list, merges by case-insensitive label or appends a fresh low-importance GameMemory, and writes the JSON list back. When a matched entry’s reference count reaches _PROMOTION_THRESHOLD it is promoted: this calls store_basic() to copy it into the durable tier, drops it from the channel list, and logs the promotion. Redis failures are caught and logged rather than raised.

No in-repo callers were found; invoked from the game subsystem at runtime as new ephemeral context is observed.

Parameters:
  • game_id (str) – Identifier of the game whose channel memory bank is updated.

  • label (str) – Short name/key for the memory; matched case-insensitively.

  • content (str) – The remembered text.

  • turn (int) – Current game turn, recorded as creation and last-referenced turn.

  • redis (Any) – Async Redis client; when None the call is a no-op.

Returns:

True if the channel list was written, False when Redis is absent or the write failed.

Return type:

bool

async game_memory.get_basic_memories(game_id, redis=None)[source]

Load all durable (basic) memories for a game from Redis.

Reads the JSON list stored under the game:mem:basic:{game_id} Redis string and rebuilds each element into a GameMemory via GameMemory.from_dict(). A missing key or any Redis/JSON failure yields an empty list (the failure is logged), so callers can treat an empty result as “no memories” without special-casing errors.

Called by get_context_summary() and bleed_memories(); no other in-repo callers were found.

Parameters:
  • game_id (str) – Identifier of the game whose basic memories are read.

  • redis (Any) – Async Redis client; when None an empty list is returned.

Returns:

The stored basic memories, or an empty list when none exist or a read error occurs.

Return type:

list[GameMemory]

async game_memory.get_channel_memories(game_id, redis=None)[source]

Load all ephemeral (channel) memories for a game from Redis.

Reads the JSON list stored under the game:mem:channel:{game_id} Redis string and rebuilds each element into a GameMemory via GameMemory.from_dict(). A missing key or any Redis/JSON failure yields an empty list (the failure is logged), letting callers treat an empty result uniformly.

Called by get_context_summary(); no other in-repo callers were found.

Parameters:
  • game_id (str) – Identifier of the game whose channel memories are read.

  • redis (Any) – Async Redis client; when None an empty list is returned.

Returns:

The stored channel memories, or an empty list when none exist or a read error occurs.

Return type:

list[GameMemory]

async game_memory.get_context_summary(game_id, redis=None)[source]

Build a formatted memory context block for injection into the LLM prompt.

Loads both memory tiers via get_basic_memories() and get_channel_memories(), then renders them into a single human-readable text block: core (basic) memories sorted by importance and labeled by category – with a [BLEED-THROUGH] tag on glitched entries – followed by up to the last ten ephemeral channel entries. The result is meant to be spliced into the system/context prompt so the LLM stays grounded in the game’s accumulated state. When neither tier has any entries an explicit “empty” placeholder is returned instead. Read-only with respect to Redis.

No in-repo callers were found; invoked from the game subsystem’s prompt assembly at runtime.

Parameters:
  • game_id (str) – Identifier of the game whose memory bank is summarized.

  • redis (Any) – Async Redis client passed through to the tier readers; when None both reads return empty and the “empty” placeholder is used.

Returns:

A multi-line memory context block, or an “empty” placeholder string when no memories exist.

Return type:

str

async game_memory.bleed_memories(source_game_id, target_game_id, redis=None, max_bleed=7)[source]

Bleed important memories from one game into another across a cartridge swap.

Implements the hot-swap “memory bleed” effect: the most important durable memories of the source game leak into the target game as corrupted echoes. It loads the source’s basic memories via get_basic_memories(), puts all boss / antagonist entries first, then fills the remainder by importance up to max_bleed. Channel memories are intentionally dropped because they are ephemeral by design. When target_game_id is None the function runs in preview mode and only describes what would bleed. Otherwise, for each selected memory it calls store_basic() on the target with a [BLEED] label, glitch-recontextualized content, and slightly boosted importance, then re-reads the target’s game:mem:basic:{game_id} Redis string to set the glitched and source_game flags on the stored entry. The post-store flag patch swallows its own exceptions so a flag-write hiccup never aborts the bleed.

Called by the hot_swap_game tool’s run() in tools/hot_swap_game.py, which imports it lazily and passes None for the target so this acts as a bleed preview during the swap.

Parameters:
  • source_game_id (str) – Identifier of the game being swapped out.

  • target_game_id (str | None) – Identifier of the new game to receive the bled memories, or None to only preview the selection without writing.

  • redis (Any) – Async Redis client; when None a static placeholder summary is returned and nothing is read or written.

  • max_bleed (int) – Maximum number of memories to carry across.

Returns:

A human-readable [BLEED-THROUGH] summary of what bled (or would bleed), suitable for surfacing in the swap narrative.

Return type:

str

async game_memory.clear_game_memories(game_id, redis=None)[source]

Delete both memory tiers for a game from Redis.

Issues a single DEL for the game:mem:basic:{game_id} and game:mem:channel:{game_id} keys, wiping the game’s entire memory bank. Used to reset state when a game ends or is discarded. Any Redis failure is caught and logged rather than raised, and the function returns nothing.

No in-repo callers were found; invoked from the game subsystem at runtime when a game’s memories are torn down.

Parameters:
  • game_id (str) – Identifier of the game whose memories are deleted.

  • redis (Any) – Async Redis client; when None the call is a no-op.

Return type:

None