Skip to content

The amd_blocks module

One AMD record body, read into a list of typed blocks.

Overview

amd_core.parse gives a renderer everything about a document's structure — the record tree, the fence facts, the reference spans — and nothing about its body, which arrives as raw lines exactly as typed. Every consumer used to re-derive meaning from those lines itself, which is how the same % and the same - [label](target) came to be read four slightly different ways.

This reads them once:

[{"type": "cue",    "line": 12, "speaker": "vex", "surface": "comms"},
 {"type": "speech", "line": 13, "variants": [{"text": "...", "gate": None}]},
 {"type": "choice", "line": 15, "label": "Pay", "target": "paid", ...}]

Every block is a plain JSON-able dict with a 1-based line, so blocks cross a process boundary — the language server, a --format json dump, a golden test — untouched.

This module owns no grammar

Every mark is recognized by the function in procedural.amd that already owned it. What lives here is the order those recognizers are tried in, and where one block ends — which is exactly the part that has to be identical between the game and a printed page.

Deliberately absent: measuring, wrapping, styling, or anything needing the engine. A block says "these lines were a table"; how wide its columns are is the renderer's business, and the in-game one answers differently from a sheet of paper on purpose.

Profiles

profile="player" is a hard filter, not a stylesheet class. An author-only = synopsis, a choice's guard and its outcomes are absent from the result, because "print to PDF" and "view source" have to agree about what a player was told.

API

One AMD record body -> a list of typed blocks.

amd_core.parse gives a renderer everything about a document's STRUCTURE - the record tree, the fence facts, the reference spans - and nothing about its BODY, which arrives as node.body_lines: raw text, exactly as typed. Every consumer so far has re-derived meaning from those lines itself, which is why the same % and the same - [label](target) were being read four slightly different ways.

This module reads them ONCE, into blocks:

[{"type": "cue", "line": 12, "speaker": "vex", "surface": "comms"},
 {"type": "speech", "line": 13, "variants": [{"text": "...", "gate": None}]},
 {"type": "choice", "line": 15, "label": "Pay", "target": "paid", ...}]

Every block is a plain JSON-able dict with a 1-based line, following the house style amd_schema set for descriptors - so blocks cross a process boundary (the LSP, a --format json dump, a golden test) untouched, and a test can pin the MEANING of a document rather than one renderer's HTML.

This module owns no grammar. Every mark is recognized by the function in amd that already owned it. What lives here is only the ORDER those recognizers are tried in and where one block ends and the next begins - which is exactly the part that has to be identical between the game and a printed page.

Deliberately NOT here: measuring, wrapping, styling, or anything that needs the engine. A block says "these lines were a table"; how wide its columns are is the renderer's business, and the in-game one answers differently from a sheet of paper on purpose.

amd_blocks(node, doc=None, profile='author', resolve=None, depth=0, _seen=None)

The blocks of one amd_core.AmdNode's body.

doc lets ![[key]] transclusions and [[key]] links resolve; resolve is an optional key -> AmdNode callable for a MISSION-wide lookup, which a single document cannot do on its own. profile is "author" or "player" - see amd_blocks_filter.

amd_blocks_filter(blocks, profile='author')

Strip everything profile must not see, recursively.

player is a HARD filter, not a stylesheet class: an author-only note or a choice's outcome must be ABSENT from the output, because "print to PDF" and "view source" have to agree about what a player was told. Anything merely hidden by styling is one right-click from being read.

amd_blocks and amd_blocks_text both run this before returning, so a caller never has to remember to. It stays public for a caller that built the author cut once and now wants the player one without re-parsing.

amd_blocks_text(text, profile='author')

The blocks of a raw body string, for callers that have text and no node.

A record read this way has no document around it, so [[links]] are recorded but transclusions cannot resolve.