mud_server.axis.engine ====================== .. py:module:: mud_server.axis.engine .. autoapi-nested-parse:: Axis resolution engine. :class:`AxisEngine` is the orchestrator that ties together the resolution grammar, the resolver functions, the JSONL ledger, and the SQLite materialized view. One instance is created per :class:`~mud_server.core.world.World` at startup and remains live for the server's lifetime. Resolution sequence (``resolve_chat_interaction``): 1. Resolve character IDs from names (world-scoped, raises :exc:`CharacterNotFoundError` on miss). 2. Acquire per-character threading locks in ascending ID order (deadlock prevention). 3. Read current axis scores from the SQLite DB. 4. Compute ipc_hash via :func:`~pipeworks_ipc.compute_payload_hash` over the pre-interaction snapshot. 5. Compute axis deltas for every axis in the chat grammar. 6. Write ``chat.mechanical_resolution`` to the JSONL ledger — the authoritative act that makes the interaction permanent. 7. Clamp deltas to ``[0.0, 1.0]`` and apply to the DB via :func:`~mud_server.db.facade.apply_axis_event`. 8. Release locks. 9. Return :class:`~mud_server.axis.types.AxisResolutionResult`. Locking strategy: Each character has a :class:`threading.Lock` stored in a dict keyed by ``character_id``. Locks are always acquired in ascending ID order to prevent deadlocks when two interactions share a character. The dict itself is protected by a separate ``_locks_mutex``. Note on ipc_hash computation (deviation from plan): :func:`~pipeworks_ipc.compute_ipc_id` requires ``system_prompt_hash: str`` — a concept that has no meaning in a purely mechanical resolution (no LLM call). :func:`~pipeworks_ipc.compute_payload_hash` is used directly on the resolution payload dict instead. When the translation service subsequently uses this ``ipc_hash`` for deterministic Ollama seeding it calls :func:`~pipeworks_ipc.compute_ipc_id` with this hash as ``input_hash``, which is the intended design. Attributes ---------- .. autoapisummary:: mud_server.axis.engine.logger Exceptions ---------- .. autoapisummary:: mud_server.axis.engine.CharacterNotFoundError Classes ------- .. autoapisummary:: mud_server.axis.engine.AxisEngine Module Contents --------------- .. py:data:: logger .. py:exception:: CharacterNotFoundError(character_name, world_id) Bases: :py:obj:`Exception` Raised when a character name cannot be resolved within a world. .. attribute:: character_name The name that could not be resolved. .. attribute:: world_id The world in which the lookup was attempted. Initialize self. See help(type(self)) for accurate signature. .. py:attribute:: character_name .. py:attribute:: world_id .. py:class:: AxisEngine(*, world_id, grammar) General-purpose resolver registry for axis mutations. Instantiated once per :class:`~mud_server.core.world.World` (same lifecycle as the translation service). The chat resolver is the first concrete implementation; future stimulus types (environmental, physical, economic) will add new ``resolve_*`` methods following the same pattern. :param world_id: The world this engine is scoped to. Used when reading and writing the JSONL ledger and the DB. :param grammar: The parsed :class:`~mud_server.axis.grammar.ResolutionGrammar` for this world. Immutable for the engine's lifetime. .. py:method:: resolve_chat_interaction(*, speaker_name, listener_name, channel, world_id) Resolve a chat interaction between two characters. This is the primary entry point. All ten steps of the resolution sequence (see module docstring) are executed here atomically under per-character locks. :param speaker_name: Display name of the character who sent the message. :param listener_name: Display name of the character who received it. :param channel: Chat channel — ``"say"``, ``"yell"``, or ``"whisper"``. Governs the channel multiplier applied to every axis delta. :param world_id: World in which the interaction occurs. Must match ``self._world_id`` (passed explicitly to make the call site readable). :returns: :class:`~mud_server.axis.types.AxisResolutionResult` containing the ipc_hash, per-character deltas (only axes with non-zero actual change), and the pre-interaction axis snapshot. :raises CharacterNotFoundError: If either *speaker_name* or *listener_name* is not registered in *world_id*.