game_assets

GameGirl Color – Per-game asset manager.

Stores labeled image URLs/references per game session in Redis. Assets are categorized (title_screen, enemy, character, item, background, ui, special) and can be referenced by the LLM during game narration. # πŸŽ¨πŸ’€ CORRUPTED ASSET REGISTRY

game_assets.resolve_asset_path(relative_path)[source]

Resolve a relative asset path under the project root, blocking traversal.

Joins relative_path onto PROJECT_ROOT (this module’s own directory) and then verifies, after symlink/.. resolution, that the result still lives inside that root. This keeps asset lookups portable across machines (no hardcoded absolute paths) while preventing directory-traversal escapes such as ../../etc/passwd. Touches the filesystem only via path resolution; it does not open or create anything.

Exercised by tests/test_game_engine_remediation.py (both the happy path and the traversal-rejection case); no production callers were found via grep.

Parameters:

relative_path (str) – A path fragment to resolve relative to the project root.

Returns:

The resolved path object located within PROJECT_ROOT.

Return type:

Path

Raises:

PermissionError – If the resolved path would fall outside PROJECT_ROOT.

class game_assets.GameAsset(name, category, url, uploaded_by='', turn_added=0, created_at=<factory>)[source]

Bases: object

A labeled image asset belonging to a single game session.

Lightweight dataclass record describing one uploaded image (its name, category, image url, who uploaded it, the game turn it was added on, and a creation timestamp). Instances are serialized to and from the JSON list stored at the game:assets:{game_id} Redis string via to_dict() and from_dict(), and surfaced to the LLM during narration through get_asset_summary(). The category must be one of VALID_CATEGORIES; validation is enforced by upload_asset() rather than in __init__.

Constructed by upload_asset() (new uploads) and get_assets() (rehydration from Redis).

Parameters:
name: str
category: str
url: str
uploaded_by: str = ''
turn_added: int = 0
created_at: float
to_dict()[source]

Serialize this asset to a plain JSON-safe dict.

Delegates to dataclasses.asdict() to flatten every field (name, category, url, uploaded_by, turn_added, created_at) into a dict. The result is what gets persisted to the game:assets:{game_id} Redis string via json.dumps(). No side effects.

Called by upload_asset(), which appends the dict to the asset list before writing it back to Redis.

Returns:

A shallow dict of all dataclass fields.

Return type:

dict[str, Any]

classmethod from_dict(d)[source]

Reconstruct a GameAsset 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_assets() when deserializing each entry of the JSON list loaded from the game:assets:{game_id} Redis key.

Parameters:

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

Returns:

A new instance built from the recognized keys in d.

Return type:

GameAsset

async game_assets.upload_asset(game_id, name, category, url, user_id='', turn=0, redis=None)[source]

Register or update a named image asset for a game session.

Validates the category against VALID_CATEGORIES, loads the existing asset list from the game:assets:{game_id} Redis string, and either updates an asset with a matching (case-insensitive) name in place or appends a new GameAsset (serialized via GameAsset.to_dict()). The updated list is written back with redis.set. New uploads are capped at _MAX_ASSETS; once that limit is hit, creation is refused so the registry cannot grow without bound. All Redis errors are caught and returned as an error payload rather than raised.

Called by the asset-producing tools and UI: tools/game_asset_upload.py, tools/grok_imagine.py, tools/suno_music.py, and game_ui/components.py.

Parameters:
  • game_id (str) – Session identifier used to build the Redis key.

  • name (str) – Asset label; collisions update the existing asset.

  • category (str) – Asset category; must be in VALID_CATEGORIES (case-insensitive).

  • url (str) – Image URL/reference to store.

  • user_id (str) – Uploader identifier, recorded on the asset.

  • turn (int) – Game turn the asset was added on.

  • redis (Any) – Async Redis client; None short-circuits to an error payload.

Returns:

On success, a dict with success plus action ("created" or "updated") and asset details; otherwise a dict with an error message (bad category, limit reached, no Redis, or write failure).

Return type:

dict[str, Any]

async game_assets.get_assets(game_id, category=None, redis=None)[source]

Load a game’s assets from Redis, optionally filtered by category.

Reads the JSON list at the game:assets:{game_id} Redis string and rehydrates each entry into a GameAsset via GameAsset.from_dict(). When category is given, the result is narrowed to assets whose category matches (case-insensitive). Read-only: nothing is written back. Any decode or connection error is logged and reported as an empty list so callers degrade gracefully.

Called by get_asset_by_name() and get_asset_summary() within this module, and by game_ui/components.py to render the asset list.

Parameters:
  • game_id (str) – Session identifier used to build the Redis key.

  • category (str | None) – Optional category filter; None returns all assets.

  • redis (Any) – Async Redis client; None yields an empty list.

Returns:

The matching assets, or an empty list when absent or on any error.

Return type:

list[GameAsset]

async game_assets.get_asset_by_name(game_id, name, redis=None)[source]

Look up a single game asset by name (case-insensitive).

Fetches the full asset list via get_assets() and returns the first GameAsset whose name matches name ignoring case, or None if no such asset exists. Read-only convenience wrapper used when a tool needs to reference one specific asset rather than the whole set.

Called by tools/grok_imagine.py and tools/compose_gameboard.py to pull a named image (e.g. as an img2img reference or board element).

Parameters:
  • game_id (str) – Session identifier used to build the Redis key.

  • name (str) – Asset name to match (case-insensitive).

  • redis (Any) – Async Redis client; None results in None (no assets loaded).

Returns:

The matching asset, or None if not found.

Return type:

GameAsset | None

async game_assets.delete_asset(game_id, name, redis=None)[source]

Remove a named asset from a game’s asset list in Redis.

Loads the raw JSON list at the game:assets:{game_id} Redis string, rebuilds it excluding any entry whose name matches name (case-insensitive), and writes the pruned list back with redis.set. If nothing matched, the list is unchanged and False is returned without a write. Any Redis/decode error is logged and treated as a failed delete.

No callers were found via grep; this is a public maintenance helper for the asset registry (e.g. to clear an asset before re-uploading).

Parameters:
  • game_id (str) – Session identifier used to build the Redis key.

  • name (str) – Asset name to delete (case-insensitive).

  • redis (Any) – Async Redis client; None returns False.

Returns:

True if an asset was found and removed, False if it was absent or on any error.

Return type:

bool

async game_assets.get_asset_summary(game_id, redis=None)[source]

Render a compact, category-grouped asset listing for LLM context.

Loads the game’s assets via get_assets(), buckets them by category, and emits a small human-readable block (a [GAME ASSETS] header followed by one CATEGORY: name, name line per category, sorted) suitable for injection into the model’s prompt so it can reference uploaded images during narration. Returns a fixed placeholder string when no assets exist. Read-only; no Redis writes.

No callers were found via grep; intended for prompt-context assembly that surfaces the asset registry to the LLM.

Parameters:
  • game_id (str) – Session identifier used to build the Redis key.

  • redis (Any) – Async Redis client; None yields the empty-state placeholder.

Returns:

A multi-line summary, or "[GAME ASSETS: None uploaded]" when there are no assets.

Return type:

str