tangl.vm.runtime

Phase-driven execution state used to advance a story graph one choice at a time.

Related design docs

Related notes

Core runtime types

class ResolutionPhase(*values)[source]

Phases in a single resolution step.

Why

Defines the ordered pipeline for one frame and the vm phase-bus aggregation contracts used by tangl.vm.dispatch.

Key Features

  • OrderINIT VALIDATE PLANNING PREREQS UPDATE JOURNAL FINALIZE POSTREQS.

  • Explicit reduction – each phase has a concrete aggregated result shape enforced by do_* dispatch helpers.

Notes

  • PLANNING in vm is side-effect-only provisioning; handlers must return None (non-None raises TypeError in do_provision).

  • JOURNAL returns Record | Iterable[Record] | None.

  • FINALIZE returns Record | None.

class Frame(graph, cursor, output_stream=<factory>, return_stack=<factory>, local_behaviors=<factory>, ledger_local_behaviors=None, cursor_steps=0, cursor_trace=<factory>, last_redirect=None, redirect_trace=<factory>, step_base=0, step_observer=None, correlation_id=None, logger=None, meta=<factory>, causality_mode=CausalityMode.CLEAN, mark_soft_dirty_callback=None, escalate_to_hard_dirty_callback=None, _random=<factory>, selected_edge=None, selected_payload=None)[source]

Drives cursor traversal through the phase pipeline.

Why

Frame is the ephemeral execution context for one player action (one resolve_choice call). It moves the cursor through the graph by repeatedly calling follow_edge, which runs the phase pipeline at each destination node. Redirects from PREREQS or POSTREQS cause the loop to continue; when the pipeline produces no redirect, the frame either pops the return stack or yields control back to the caller.

Frame does NOT know about containers, scenes, or story semantics. It knows about nodes, edges, the phase pipeline, and the return stack. Container descent is handled by a prereq dispatch handler that detects TraversableNode.is_container and returns an enter() edge.

Key Features

  • Pipeline executionfollow_edge runs phases in order, respecting entry_phase for return edges that skip early phases.

  • Redirect chaining — PREREQS and POSTREQS may return edges; the frame follows them in a loop until the pipeline completes cleanly.

  • Return stack — call edges (return_phase set) are pushed onto the stack. When the pipeline reaches a terminal, the stack is popped and the return edge is followed.

  • Recursion safetyresolve_choice enforces MAX_RESOLVE_DEPTH.

API

  • follow_edge(edge) — move cursor, run pipeline, return redirect or None.

  • resolve_choice(edge) — loop follow_edge until terminal.

  • goto_node(node) — optional stub-provision and jump (skip validation).

Notes

The output stream and return stack are shared references from the Ledger. After resolve_choice returns, the Ledger reads back the updated cursor, step counters, and any output that was appended to the stream.

Examples

Basic pipeline — follow an edge to a leaf node:

>>> from tangl.core import Graph
>>> from tangl.vm.traversable import TraversableNode, AnonymousEdge
>>> from tangl.vm.runtime.frame import Frame
>>> g = Graph()
>>> a = TraversableNode(label="a", registry=g)
>>> b = TraversableNode(label="b", registry=g)
>>> frame = Frame(graph=g, cursor=a)
>>> edge = AnonymousEdge(predecessor=a, successor=b)
>>> result = frame.follow_edge(edge)
>>> frame.cursor is b
True
>>> frame.cursor_steps
1
>>> result is None
True
class Ledger(_ctx=None, *, uid=<factory>, label=None, tags=<factory>, templ_hash=None, graph, output_stream=<factory>, local_behaviors=<factory>, cursor_id, cursor_history=<factory>, replay_algorithm_id='diff_v1', checkpoint_cadence=1, causality_mode=CausalityMode.CLEAN, causality_break_reason=None, causality_break_step_id=None, call_stack_ids=<factory>, last_redirect=None, redirect_trace=<factory>, reentrant_steps=-1, cursor_steps=-1, choice_steps=-1, user=None, user_id=None, worker_dispatcher=None)[source]

Persistent traversal state across player actions.

class VmPhaseCtx(*args, **kwargs)[source]

Canonical runtime context implemented by PhaseCtx.