Source code for mud_server.axis.types

"""Immutable result types for the axis resolution engine.

These frozen dataclasses represent the inputs and outputs of a single
mechanical resolution step — the permanent record of what happened when
two characters interacted.  They flow between the resolver functions,
the engine, and the JSONL ledger.

Design note: all score values stored here reflect the **post-clamping**
state (i.e. clamped to [0.0, 1.0]).  The engine clamps before writing;
these types never carry out-of-range floats.
"""

from __future__ import annotations

from dataclasses import dataclass


[docs] @dataclass(frozen=True) class AxisDelta: """The result of a resolver function for one axis and one entity. ``new_score`` and ``delta`` reflect the actual changes applied to the database after clamping, not the raw resolver output. A character whose health is already at ``0.0`` would show ``delta = 0.0`` here even though the resolver returned ``-0.01``. Attributes: axis_name: Name of the axis that was (potentially) mutated, e.g. ``"demeanor"`` or ``"health"``. old_score: Score read from the database before this resolution. Always in [0.0, 1.0] for valid DB rows. new_score: Score written to the database after clamping. Always in [0.0, 1.0]. delta: Actual applied change = ``new_score - old_score``. May be smaller in magnitude than the resolver's raw delta if clamping was needed (e.g. health floor at ``0.0``). """ axis_name: str old_score: float new_score: float delta: float
[docs] @dataclass(frozen=True) class EntityResolution: """The axis mutations produced for one entity during an interaction. Attributes: character_id: Database primary key of the character. character_name: Display name at resolution time (informational; not re-read after resolution completes). deltas: Tuple of :class:`AxisDelta` objects — one per axis whose resolver produced a non-zero actual delta. Axes with ``no_effect`` resolvers or axes that were fully clamped to zero are omitted. """ character_id: int character_name: str deltas: tuple[AxisDelta, ...]
[docs] @dataclass(frozen=True) class AxisResolutionResult: """The complete result of one mechanical axis resolution. Returned by :meth:`~mud_server.axis.engine.AxisEngine.resolve_chat_interaction` after all ledger writes and database mutations are committed. The ``ipc_hash`` is the authoritative fingerprint of this interaction. It incorporates the world, channel, both character IDs, and the pre-interaction axis snapshot. Passing it to the translation service enables deterministic rendering. Note on ``ipc_hash`` computation (deviation from plan): :func:`~pipeworks_ipc.compute_ipc_id` requires a ``system_prompt_hash`` string — a concept that has no meaning in a purely mechanical resolution (there is no LLM call). Instead, :func:`~pipeworks_ipc.compute_payload_hash` is used directly on the resolution payload dict. This is documented in :func:`~mud_server.axis.engine._compute_resolution_hash`. When the translation service uses the ``ipc_hash`` for deterministic Ollama seeding, it calls :func:`~pipeworks_ipc.compute_ipc_id` with this hash as the ``input_hash``, which is the intended design. Attributes: ipc_hash: SHA-256 hex digest of the mechanical resolution payload (``compute_payload_hash`` output). world_id: World in which the interaction occurred. channel: Chat channel (``"say"``, ``"yell"``, ``"whisper"``). speaker: :class:`EntityResolution` for the character who spoke. listener: :class:`EntityResolution` for the character who received the message. axis_snapshot_before: Pre-interaction scores for the axes that participate in the resolution (i.e. axes with non-``no_effect`` resolvers). Shape: ``{str(character_id): {axis_name: score}}``. Stored in both the DB event ledger and the JSONL ledger for audit and replay purposes. """ ipc_hash: str world_id: str channel: str speaker: EntityResolution listener: EntityResolution axis_snapshot_before: dict[str, dict[str, float]]