mud_server.axis.resolvers

Axis resolver functions.

Each resolver computes raw (pre-clamping) axis deltas for both the speaker and the listener during one mechanical resolution step. The engine clamps final scores to [0.0, 1.0] after calling the resolver — resolvers operate in unbounded float arithmetic and must not clamp internally.

Resolver contract: - Accept keyword-only arguments (except the positional score values for

dominance_shift).

  • Return (speaker_delta, listener_delta) as a tuple[float, float].

  • Be pure functions: no I/O, no shared state, no side effects.

  • Never raise on valid float inputs.

The resolver registry in engine.py maps YAML resolver names → callables:

_RESOLVER_REGISTRY: dict[str, Callable] = {

“dominance_shift”: dominance_shift, “shared_drain”: shared_drain, “no_effect”: no_effect,

}

Functions

dominance_shift(speaker_score, listener_score, *, ...)

Compute demeanor deltas from a dominance contest between two characters.

shared_drain(*, base_magnitude, multiplier)

Compute the universal health cost of a social interaction.

no_effect()

Return (0.0, 0.0) — explicit no-op for axes not involved in this interaction.

Module Contents

mud_server.axis.resolvers.dominance_shift(speaker_score, listener_score, *, base_magnitude, multiplier, min_gap_threshold)[source]

Compute demeanor deltas from a dominance contest between two characters.

The character with the higher score is the “winner” and gains a positive delta; the lower-scored character loses the same magnitude (symmetric transfer).

Delta formula:

gap       = abs(speaker_score - listener_score)
magnitude = base_magnitude * multiplier * gap

If gap < min_gap_threshold both deltas are zero — two similarly-matched characters interact without either gaining social ground.

Note on ties (speaker_score == listener_score)

A zero gap is always below threshold, so (0.0, 0.0) is returned. This is consistent: a true tie produces no dominance delta.

Parameters:
  • speaker_score (float) – Speaker’s current score on this axis (0.0–1.0).

  • listener_score (float) – Listener’s current score on this axis (0.0–1.0).

  • base_magnitude (float) – Scaling factor from the grammar (e.g. 0.03).

  • multiplier (float) – Channel multiplier (e.g. 1.5 for yell, 0.5 for whisper).

  • min_gap_threshold (float) – Minimum gap below which no delta is produced (e.g. 0.05).

Returns:

(speaker_delta, listener_delta) — positive for the winner, negative for the loser, or (0.0, 0.0) when the gap is below threshold.

Return type:

tuple[float, float]

mud_server.axis.resolvers.shared_drain(*, base_magnitude, multiplier)[source]

Compute the universal health cost of a social interaction.

Both the speaker and the listener lose the same amount of health regardless of the demeanor outcome. Social interaction has a physical cost — the conversation happened whether or not either character dominated.

This applies even when the demeanor gap is below min_gap_threshold (i.e. health drains even when demeanor does not shift).

Delta formula:

drain = -(base_magnitude * multiplier)
Parameters:
  • base_magnitude (float) – Scaling factor from the grammar (e.g. 0.01).

  • multiplier (float) – Channel multiplier (e.g. 1.5 for yell, 0.5 for whisper).

Returns:

(speaker_delta, listener_delta) — both are the same negative float.

Return type:

tuple[float, float]

mud_server.axis.resolvers.no_effect()[source]

Return (0.0, 0.0) — explicit no-op for axes not involved in this interaction.

Axes are listed explicitly in the grammar with resolver: no_effect rather than silently omitted so that the engine can assert complete axis coverage. Future stimulus types that affect these axes will add their own YAML blocks (e.g. interactions.environmental.axes.wealth) without modifying existing blocks.

Returns:

(0.0, 0.0)

Return type:

tuple[float, float]