Universe-as-Code¶
axiom.compile¶
axiom.compile — Universe-as-Code: compiling a source tree into a .db cache.
A universe is defined as a versionable tree of text files (TOML/MD); the SQLite .db is only a compiled cache derived from the text. The text is the source of truth. This module reads the tree and populates a runtime .db.
Zero Qt dependency: pure engine, drivable from the CLI (axiom compile).
Boundary: only the definition tables are produced here. The runtime/save tables (Saves, Event_Log, State_Cache, Snapshots, Timeline, …) stay empty in the cache — they do not belong to the universe definition.
TOML reading: tomllib (stdlib). Writing (decompile) uses tomlkit.
- exception axiom.compile.CompileError[source]¶
Universe compilation error (malformed source, missing required field).
- axiom.compile.hash_directory(src_dir)[source]¶
Stable hash of the source content (relative path + bytes of each file).
axiom.decompile¶
axiom.decompile — Universe-as-Code: decompiling a .db into a source tree.
The inverse of axiom.compile: reads an existing SQLite universe and regenerates the equivalent tree of text files (TOML/MD). Also used to migrate v1 .db universes to the versionable text format.
Preserves: entity_ids, rule_ids, calendar, full lore, locations + connections, scheduled events, story setup, item & stat definitions, personas.
TOML writing: tomlkit (keeps the formatting clean, helpful for human editing). Zero Qt dependency.
- axiom.decompile.read_definition(db_path)[source]¶
Read all the definition tables of a universe into normalised structures.
Never reads the runtime/save tables. Used by the decompilation and by the round-trip tests (semantic comparison).
axiom.package¶
axiom.package — Universe-as-Code: .axiom v2 packaging + v1 compat.
A .axiom v2 archive is a zip of the source tree (TOML/MD) including the compiled cache .axiom-cache/universe.db for instant loading.
Backward compatibility: old .axiom v1 archives (zip of JSON files) are detected and converted on the fly (v1 -> .db -> decompile -> v2 tree -> recompile).
Zero Qt dependency: pure engine. The Qt worker import_export_worker.py is a thin shell calling these functions.
- axiom.package.pack_universe(src_dir, output_path)[source]¶
Pack a source tree into a .axiom v2 archive.
Recompiles the cache (.axiom-cache/universe.db) first so it ships in the archive, then zips the tree. An archive only publishes the definition (same contract as exporting a flat .db, TICKET-039):
.git/ and the WAL sidecars (-wal/-shm) are excluded;
the embedded cache is a copy purged of the runtime tables (legacy embedded saves — private play history — do not travel).
- axiom.package.export_db_to_axiom(db_path, output_path)[source]¶
Export a .db universe to a .axiom v2 archive (decompile -> pack).
For universes that still only exist as a .db (legacy GUI). The archive only contains the definition (the decompiled tree + its recompiled cache): saves embedded in the .db are not exported — same contract as the v1 export.
- axiom.package.detect_format(axiom_path)[source]¶
Return ‘v2’ or ‘v1’, or raise PackageError, depending on the archive content.
axiom.library¶
axiom.library — Universe-as-Code: discovery of installed universes.
The Hub historically listed flat *.db files in ~/AxiomAI/universes/. With Universe-as-Code, an imported universe (.axiom v2) or one created in source mode lives in a folder universes/<name>/ (universe.toml + .axiom-cache/universe.db). This module provides the unified discovery of both forms, engine-side (zero Qt) — the Hub’s DbWorker is just a shell on top.
- axiom.library.universe_root_for(db_path)[source]¶
Return a universe’s source folder when db_path is its compiled cache.
universes/<name>/.axiom-cache/universe.db maps to universes/<name>/. Returns None for a flat .db (legacy) or any other path.
- axiom.library.discover_universes(library_dir)[source]¶
List the playable universes of a library folder.
Two forms are recognised:
flat *.db (legacy);
a subfolder containing universe.toml (Universe-as-Code), compiled on demand when the cache is missing/stale (no-op when the hash is unchanged).
A malformed source folder is skipped (warning logged): it must not prevent the Hub from showing the rest of the library.
- axiom.library.diff_source_trees(before_dir, after_dir)[source]¶
Text diff between two source trees (protected areas excluded).
Used for previews: before_dir is the real source, after_dir a temporary tree reflecting what the source would become.
- axiom.library.apply_staged_source(staged_dir, src_dir, db_path)[source]¶
Apply a validated source tree: mirror it to the source + recompile.
staged_dir is a full snapshot of the future source (produced by the preview sandbox): it replaces the source — protected areas (.axiom-cache/, .git/) untouched — then the definition is recompiled in-place into db_path (runtime intact).
- axiom.library.convert_flat_db_to_folder(db_path)[source]¶
Convert a flat legacy .db into a Universe-as-Code folder universe.
the definition is decompiled to <parent>/<stem>/ (same universe key: the saves keep their saves/<stem>/ folder);
every embedded save is extracted to its own separate file, then linked to the new source (resynchronised on open);
the source is compiled (.axiom-cache/universe.db cache);
the original is kept as <name>.db.bak (manual recovery stays possible, and it disappears from the Hub discovery — no duplicate).
- axiom.library.sync_source_if_any(db_path)[source]¶
After a definition write (Creator Studio, Populate): when the .db is the cache of a folder universe, rewrite the text tree so it stays the source of truth (TICKET-027).
Never raises: a Studio save must not fail because the text mirror failed (warning logged).
- axiom.library.sync_source_from_db(db_path, src_dir)[source]¶
Rewrite a universe’s source tree from its .db (mirror).
Decompiles to a temporary folder then aligns src_dir on it: files are added/overwritten, orphan files removed (definition removed in the Studio also disappears from the text). .axiom-cache/ and .git/ are never touched. Entities with origin=’runtime’ (the player, NPCs born in game) are not exported to the source.
The cache hash is updated: the .db that was just written IS the compilation of the freshly rewritten source (lossless round-trip).
axiom.dev¶
axiom.dev — Universe-as-Code: dev mode with hot reload.
axiom dev <src_dir> watches the source tree and, on every change, recompiles the definition into the target .db. The real “modder” mode: edit entities/bob.toml in an editor and see the effect on the next turn.
Key point: legacy saves may live in the same `.db` as the definition. A full compile_universe rebuilds the file from scratch and would destroy games in progress. Hot reload therefore goes through refresh_definition: an in-place update of the definition tables only, in a single transaction, runtime tables untouched (Saves, Event_Log, State_Cache, Snapshots, Timeline, Items_Inventory, Active_Modifiers, Fired_Scheduled_Events).
FK constraint: three definition tables have runtime children with ON DELETE CASCADE — Entities (Items_Inventory, Active_Modifiers), Item_Definitions (Items_Inventory), Scheduled_Events (Fired_Scheduled_Events). For those, the sync uses targeted UPDATE/INSERT/DELETE statements (a delete-all-then-reinsert would purge the children of kept rows). A row removed from the source loses its runtime children: that is intended (the text is the truth).
Zero Qt dependency: pure engine, drivable from the CLI. The watch is a hash_directory polling loop (no watchdog dependency; universe trees are small, hashing is instant).
- axiom.dev.refresh_definition(src_dir, db_path=None)[source]¶
Recompile the universe definition into an existing .db, in place.
Runtime/save tables are not touched. When the .db does not exist yet, this is equivalent to compile_universe(force=True).
- Parameters:
- Returns:
The path of the refreshed .db.
- Raises:
CompileError – malformed source (the .db is left unchanged, the transaction is rolled back).
- Return type:
- axiom.dev.ensure_compiled(src_dir, db_path=None)[source]¶
Guarantee an up-to-date compiled cache without ever destroying saves.
Use this wherever a folder universe must be made playable (Hub, axiom play): missing cache -> full compilation; stale cache -> in-place refresh_definition (a full compile_universe would rebuild the file and erase any saves it contains); fresh cache -> no-op.
- axiom.dev.poll_once(src_dir, db_path, last_hash)[source]¶
One watch iteration: recompile when the source changed since last_hash.
- Parameters:
- Returns:
Tuple (current hash, True when a refresh was performed).
- Raises:
CompileError – the source changed but is malformed. The current hash is carried by the exception (src_hash attribute) so the caller can record it and avoid retrying the same content in a loop.
- Return type:
- axiom.dev.watch_universe(src_dir, db_path=None, interval=1.0, on_event=<built-in function print>, should_stop=None)[source]¶
Watch the source tree and recompile the definition on every change.
A momentarily malformed source (mid-typing save, invalid TOML) is reported through on_event but does not kill the loop: the next fix triggers a refresh again.
- Parameters:
db_path (str | Path | None) – Target .db (None = <src>/.axiom-cache/universe.db).
interval (float) – Polling period in seconds.
on_event (Callable[[str], None]) – Log callback (one line per event).
should_stop (Callable[[], bool] | None) – Optional predicate tested at each iteration (for tests / integration); None = loop until KeyboardInterrupt.
- Return type:
None