The amd_render module
AMD → a self-contained HTML document, laid out for print. Drives sbs docs.
Overview
Four lenses, because an .amd file is three documents wearing one syntax. A file with
no fence is prose and converts almost directly; a file whose content is the fence is
a catalog, where printing Color: #ffcc44 as a line of text would be worthless; and a
quest tree's meaning lives in Parent: / Starts when: / Then:, which flattening
destroys. So the number of renderings is the number of audiences:
| Lens | Reader wants |
|---|---|
prose |
sentences — a manual or a story book |
catalog |
to look something up — reference cards, fields rendered as their type |
screenplay |
to perform it — Fountain geometry |
bible |
to see the machine — the quest spine, triggers and graph |
Field typing comes from amd_schema, so a color becomes a swatch, a reference a working
link, a coordinate a cell chip.
Anchors and links
Anchors are path-based and unique per file. Bare keys are not unique — 40 of the
corpus's 374 repeat, one file holds three recover records, and five .amd basenames
repeat across the missions. A per-key anchor does not dangle; it aims a working link at
the wrong record.
The contents list and every cross-reference are built from what the body actually emitted, so a link to a record a lens skipped is demoted to plain text rather than offered as a jump that lands nowhere.
API
AMD -> a self-contained HTML document, laid out for PRINT.
AMD is where a mission's design lives - the quests, the dialogue, the factions, the lore, the help text - and until now the only thing that could read it was the game. A writer reviewing dialogue, a modder looking up faction keys and a playtester reading a mission bible all had to open a code editor and read markup.
The output is one HTML file with its CSS inlined and no external requests, sized
for paper: @page rules, controlled breaks, a table of contents. "Print to PDF"
in any browser is the PDF path, deliberately - there is no PDF library anywhere
in this stack and adding one to sbs.pyz would be a new dependency for a job the
browser already does.
Four lenses, because AMD is three documents wearing one syntax. A file with
no fence at all (help_docs.amd, lore.amd) is prose and converts almost
directly. A file whose content IS the fence (sides, items, drops) is a catalog,
and printing Color: #ffcc44 as a line of text would be worthless - it wants a
swatch. A quest tree's meaning lives in Parent: / Starts when: / Then:, and
flattening it destroys exactly the thing a designer opened the document to see.
So the number of renderings is the number of AUDIENCES:
prose a manual or a story book - the reader wants sentences
catalog a sourcebook - the reader wants to look something up
screenplay a script - the reader wants to perform it
bible a design document - the reader wants to see the machine
Two profiles. player is a HARD filter, not a stylesheet class: an
author-only note must be ABSENT from the file, because "print to PDF" and "view
source" have to agree about what a player was told.
Stdlib only, like every other module here that ships inside sbs.pyz.
NoAssets
The default asset resolver: resolves nothing, and says so on the page.
A printed page should say "a face goes here, of an Arvonian" rather than silently closing the gap - a missing illustration the reader cannot see is a missing illustration nobody fixes.
amd_render_html(docs, lens='prose', profile='author', title=None, assets=None, resolve=None, show_internal=False, faces='canvas')
[(uri, AmdDocument)] -> one self-contained HTML page.
lens picks the audience (see the module docstring); profile is author
or player. resolve is an optional mission-wide key -> AmdNode lookup so
a reference that crosses a file still links - something a single document
cannot do on its own.
blocks_html(blocks, ctx, depth=0)
Render a block list. ctx carries the document, profile and assets.
doc_tokens(docs)
A short, unique id fragment per document.
slug(basename) alone is not unique: five basenames repeat across the
corpus, and OpenUniverse holds captains/ashfang.amd AND
dialogue/ashfang.amd. Two files sharing an anchor prefix do not dangle -
they do something worse, and aim a working link at the wrong record. So a
collision walks back up the path until the token is unique.
facts_html(node, ctx, only=None, drop=None, css='facts')
A record's fence as a definition list, in the schema's field order.
Order comes from amd_schema.template_fields, not from what the author
happened to type, so every card of a kind lists its fields the same way and a
missing field reads as a gap rather than as a reshuffle. Fields the schema
does not know follow, in authored order - which is how a mission's own
registered vocabulary shows up.
lens_bible(docs, ctx)
The design document: the quest spine, its triggers, and the causal graph.
Every structural fact here comes from amd_timeline, which already computes
the beat ranking, the spine/pool split and the cross-file signal join. That
join is the reason the module exists and the reason this lens cannot be done
a document at a time: a signal emitted in one file is waited on in another.
lens_catalog(docs, ctx)
Every record that HAS a fence, grouped by archetype, as reference cards.
This is the lens that proves the field registry was worth building: the fence is the content, so each field is rendered as its TYPE - a color as a swatch, a reference as a working link, a coordinate as a cell - rather than as the line of text it was typed as.
lens_prose(docs, ctx)
Every record, in document order, as a book.
The lens the fence-less files were written for - help_docs.amd,
library_docs.amd, lore.amd - where the body already IS markdown and the
conversion is nearly a straight one.
lens_screenplay(docs, ctx)
Dialogue and beats as a script someone could read aloud.
Selection is by CONTENT as well as by archetype, and that second clause is
what makes the lens work on the corpus that exists: not one shipped .amd
contains a @cue or a (direction). What they contain is Speaker: in the
fence and bare % variant lines, so a record with speech and no cue prints
its fence speaker as an implicit one.
slug(text)
A stable id fragment. Anchors are PATH-based, never bare keys: 40 of the
corpus's 374 keys repeat, and one file alone holds three recover records.