Mundane Backend

No dragons. No spells. Just Tuesday.

This is the reference implementation of Mundane: a rules engine and a thin HTTP API over it. The engine is a referee, not a player — a whole game is a fold over a stream of actions, and one function, mundane.engine.rules.apply_action(), validates each action against the current state and then transitions it. Illegal moves are rejected and the state is left untouched, never crashed on. The API is a thin shell that turns HTTP requests into engine actions; all the rules live in the engine.

Note

The game’s rules live in the meta/spec repository, not here: the specification and rulebook. Card content is published as JSON card sets in mundane-cards. This site documents the implementation; the spec describes the game.

Architecture

The engine

mundane.engine.rules.apply_action() is the one door: every state change goes through it. Each move checks its preconditions first — a failed check raises mundane.engine.actions.IllegalAction and mutates nothing — and only then transitions. Because it returns the state, it composes as a reducer:

final_state = reduce(apply_action, actions, initial_state)

The state is fully JSON-serialisable. Cards are referenced by id everywhere (the composed set_id:id); card objects — built from JSON sets by mundane.engine.cards.build_card() — and the effect closures they carry live only in the per-game pool, never in the state. That separation is what lets a whole mundane.engine.state.GameState round-trip through JSON. A mundane.engine.game.Game pairs that state with the card pool, the ordered log of accepted actions, and a card snapshot, so games are event-sourced: the log plus the snapshot can rebuild — and replay — the state.

The API

mundane.api is a Litestar application. It never mutates game state directly: it parses each request body into an engine action, calls Game.submit, and maps a rejected move (IllegalAction) onto HTTP 422. Games are kept in an in-memory store behind a small create / get / save interface, so they are volatile (lost when the process restarts) but the store is swappable for Redis or SQLite later.

Loading a game’s cards — allowlisting set URLs, fetching them with hardening, validating against a vendored copy of the card-set JSON Schema, and snapshotting the resolved pool with a content hash — lives in mundane.api.set_loader. The engine never reaches the network; it receives only the resolved pool. A non-allowlisted URL, a schema-invalid set, an unknown effect, bad params, or a duplicate id give HTTP 422; an upstream fetch failure gives HTTP 502. Either way the store is left untouched.

Method and path

Purpose

POST /games

Create a game (optional {"set_urls": [...]} body, default the core set); return its id and initial state.

GET /games/{id}

Read the current state.

POST /games/{id}/actions

Submit a move (HTTP 422 if illegal; the stored game is unchanged).

GET /games/{id}/export

Download the action log, final state, and card snapshot (resolved cards + hash).

API reference

The full module reference is generated from the source.

Extras