mud_server.translation.service

OOC→IC translation service.

OOCToICTranslationService is the single public entry-point for the translation layer. It orchestrates CharacterProfileBuilder, OllamaRenderer, and OutputValidator to produce in-character dialogue from a raw player message.

Caller contract

translate() always returns either:

  • A non-empty IC string on success.

  • None on any failure (missing profile, Ollama error, validation failure).

The caller (GameEngine.chat/yell/whisper) treats None as a signal to use the original OOC message. This is the graceful-degradation guarantee: the layer never breaks the game.

Profile summary injection

Each world’s active canonical prompt template uses a single {{profile_summary}} placeholder to embed the character’s current axis state as a formatted block. Before _render_system_prompt substitutes placeholders, translate() calls _build_profile_summary() to produce this block and injects it into the profile dict under the key profile_summary. Without this step, {{profile_summary}} would reach the LLM as a literal unresolved string and the model would have no character context.

Ledger integration

Every translate() call — success or failure — emits a chat.translation event to the world’s JSONL ledger via append_event().

Events record:

  • status: "success" | "fallback.api_error"
    "fallback.validation_failed"
  • character_name: the character whose voice was translated

  • channel: "say" | "yell" | "whisper"

  • ooc_input: the raw OOC message from the player

  • ic_output: the final IC text, or null on fallback

  • axis_snapshot: {axis_name: {score, label}} for every axis

    present in the character’s profile at translation time

A ledger write failure is never fatal — the game interaction completes and only the audit record is lost. See TODO(ledger-hardening) comments in _emit_translation_event().

The event is not emitted when the character profile cannot be resolved (profile is None) — there is no character data to record.

IPC hash and deterministic mode

translate() accepts an optional ipc_hash: str | None parameter. The axis engine (core/engine.py) computes this hash via AxisEngine.resolve_chat_interaction() and passes it here.

When ipc_hash is provided and config.deterministic is True:

  1. ipc_hash[:16] is converted to an integer seed.

  2. self._renderer.set_deterministic(seed_int) is called, clamping temperature to 0.0 and forwarding the seed to Ollama.

  3. Identical game state + identical OOC input → identical IC output (subject to Ollama model determinism at seed=constant, temp=0.0).

When ipc_hash is None (solo-room interactions, axis resolution disabled, or axis engine failure), deterministic mode is silently skipped and the renderer uses the configured temperature.

Pre-axis-engine era

Events emitted before the axis engine was integrated carry meta: {"phase": "pre_axis_engine"} to distinguish them from post-integration events during ledger replay or analysis. Post-integration events with a real ipc_hash carry meta: {}.

Attributes

logger

Classes

LabTranslateResult

Result returned by OOCToICTranslationService.translate_with_axes.

OOCToICTranslationService

Orchestrates profile building, rendering, and validation.

Module Contents

mud_server.translation.service.logger
class mud_server.translation.service.LabTranslateResult[source]

Result returned by OOCToICTranslationService.translate_with_axes.

Carries the full research context the Axis Descriptor Lab needs to display results: the IC text, outcome status, the profile_summary block as the server formatted it, and the fully-rendered system prompt that was actually sent to Ollama.

ic_text

Validated IC dialogue on success, None on any fallback path.

status

Outcome string — "success", "fallback.api_error", or "fallback.validation_failed".

profile_summary

The {{profile_summary}} block as formatted by the server (canonical format).

rendered_prompt

The fully-rendered system prompt sent to Ollama, with all placeholders resolved.

prompt_template

Raw template text before per-character variable substitution. Consumers that need a stable hash identifying which prompt file was used should hash this field, not rendered_prompt.

ic_text: str | None
status: str
profile_summary: str
rendered_prompt: str
prompt_template: str
class mud_server.translation.service.OOCToICTranslationService(*, world_id, config, world_root)[source]

Orchestrates profile building, rendering, and validation.

One instance is created per World when the world’s translation_layer.enabled is True. It is cached on the World object and reused for every chat call in that world.

_world_id

World this service is scoped to.

_config

Frozen translation config from world.json.

_profile_builder

Builds the character context dict.

_renderer

Calls the Ollama API.

_validator

Validates/cleans the raw LLM output.

_prompt_template

System prompt template text, loaded once at init.

Initialise the service.

Parameters:
Raises:

ValueError – If world_id is empty (same guard as CharacterProfileBuilder).

translate(character_name, ooc_message, *, channel='say', ipc_hash=None)[source]

Translate an OOC message to in-character dialogue.

Full pipeline:

  1. Build character axis profile (DB lookup).

  2. Inject channel and profile_summary into the profile dict so that {{channel}} and {{profile_summary}} placeholders in the active canonical prompt template resolve correctly.

  3. Arm deterministic mode if ipc_hash is provided and config.deterministic is True.

  4. Render the system prompt from the profile + template.

  5. Call Ollama via the renderer.

  6. Validate the raw output.

  7. Emit a chat.translation ledger event (success or fallback).

On any failure at steps 1–6 the method returns None and the caller falls back to the original OOC message. Step 7 always executes on the success/fallback paths (steps 5–6 only) — it is skipped when the profile cannot be resolved (step 1 failure) because there is no character data to record.

Parameters:
  • character_name (str) – Name of the character speaking (must exist in self._world_id; world-scoped lookup is used).

  • ooc_message (str) – The raw, unsanitised message from the player.

  • channel (str) – Chat channel context ("say", "yell", "whisper"). Injected into the profile dict as channel so that prompt templates can tailor tone by delivery mode.

  • ipc_hash (str | None) – Optional IPC hash produced by the axis engine for this interaction. When provided and config.deterministic=True, deterministic mode is armed: temperature is clamped to 0.0 and a seed derived from the hash is forwarded to Ollama, ensuring identical game state + OOC input always produces identical IC output. When None (solo-room interaction, axis engine disabled, or engine failure), deterministic mode is skipped silently.

Returns:

IC dialogue string on success, None on any failure.

Return type:

str | None

property config: mud_server.translation.config.TranslationLayerConfig

Return the world’s frozen translation layer configuration.

translate_with_axes(axes, ooc_message, *, character_name='Lab Subject', channel='say', seed=None, temperature=0.7, prompt_template_override=None)[source]

Translate an OOC message using raw axis values — no DB lookup.

This is the entry point for the Axis Descriptor Lab. It accepts a caller-supplied axis dict instead of looking up a character in the database, then runs steps 2–6 of the standard translate() pipeline (profile injection, prompt rendering, Ollama call, validation). No ledger event is emitted — lab calls are research runs, not production game interactions.

The server filters axes to its configured active_axes before building the profile, so the caller may supply all 11 known axes and the server will silently use only the ones its world is configured for. The response includes world_config so the lab can see exactly which axes were applied.

A fresh OllamaRenderer is created for each call to avoid polluting the persistent game renderer’s deterministic-mode state.

Parameters:
  • axes (dict[str, dict]) – Dict of {axis_name: {"label": str, "score": float}}. Keys not in active_axes are silently ignored.

  • ooc_message (str) – The raw OOC message to translate.

  • character_name (str) – Display name used in the profile_summary first line. Defaults to "Lab Subject".

  • channel (str) – Chat channel context ("say", "yell", "whisper").

  • seed (int | None) – Integer seed for deterministic Ollama output. None means non-deterministic (random).

  • temperature (float) – Sampling temperature forwarded to Ollama. Ignored when seed is provided (clamped to 0.0 for determinism).

  • prompt_template_override (str | None) – Optional full prompt template text. When provided, used instead of self._prompt_template for this single call. The server’s canonical file is never modified.

Returns:

LabTranslateResult with the IC text, status, canonical profile_summary, and fully-rendered system prompt.

Return type:

LabTranslateResult