# hud
The screen `amagiri_hud`: the corner ledger you play under, and the full-glass
cards that open and close a run. Both live on one screen because they are never
both the thing you are looking at.
## The ledger
Three Resonance slots — Chime, Edge, Hush — each a diamond that fills in and
takes its own colour when you earn it, over the name of who gave it to you.
Under them, **the light**: a meter that empties as the Fading advances, and a
line that appears once the three who remain have started losing words.
## The cards
A card takes the whole glass over a dimmed, blurred world: an eyebrow, a title,
a rule, a few lines, and — on an end card — a button back to the beginning.
`rite.cards` is what each one says.
## Surface
Hud.render(model) -- model = { resonances, light, faded, card, showLedger }
Hud.tree(model) -- the widget tree alone
Hud.hide()
`render` re-registers the screen when a card comes or goes (a card draws at a
higher layer than the ledger) and swaps the tree otherwise. `rite` calls it only
when the model would actually look different.
The end card's button fires the callback id `Hud.RESTART_CALLBACK`, which `rite`
receives through `ui.registerCallbackEnv`.
# game.Game
The shared interface of **Amagiri: Hollow Bell**. Every part of the game talks
through this module and nothing else.
```lua
local Game = require("game.Game")
```
It holds:
- **the anchors** — `Game.ANCHORS`, `Game.CLIFF_PATH`, `Game.WATER_LEVEL`,
`Game.FLOOD_FLOOR`: one set of world coordinates the valley builds to and the
cast stands on.
- **the vocabularies** — `Game.EMOTION`, `Game.RESONANCE`, `Game.ENDING`,
`Game.CHARACTERS`.
- **the run state** — resonances held, how far the Fading has advanced,
per-character flags, and the ending, mutated through `grantResonance`,
`setFading`, `endRun`, `resetRun`.
- **an event bus** — `Game.on(event, fn)` returning an unsubscribe, and
`Game.emit(event, payload)`.
- **three registries** — interactables (what the player can walk up to),
dialogue scripts, and character portraits.
- **the mounting** — `Game.boot()`, `Game.rebuild(slice)`, `Game.tick(dt, mode)`,
called by the scene's entrypoint.
It owns no entities, no UI and no behaviour. It is the contract.
# grade
The Fading's look: one full-screen post-process pass, `amagiri_fading`, that
carries both the valley's aerial fog and the drain of colour out of it.
Everything it does is a function of one number — `Game.fading()`, where `0` is a
whole valley and `1` is one that has finished forgetting itself.
| At `t = 0` | At `t = 1` |
|---|---|
| full colour, a cold blue-white haze on the far hills | almost no colour left, and the haze closed in to a wall |
The pass runs on the **scene** layer, so the HUD keeps its own colour while the
valley loses its. It reads the renderer's reversed-Z depth buffer, where a raw
value of exactly zero means nothing was drawn — the sky — and gives the sky its
own fog ceiling so a hollow sky stays a sky instead of collapsing into a flat
field of fog colour.
## Surface
| Call | Does |
|---|---|
| `Grade.install()` | Register (or re-register) the pass. Safe on every build. |
| `Grade.apply(t)` | Push the look for a Fading value in 0..1. |
| `Grade.sample(t)` | The numbers `apply(t)` would push, for verification. |
| `Grade.remove()` | Drop the pass. |
`rite` calls `install` once per build and `apply` from `fading:changed`.
# cards
What the title card and the four end cards say.
`Cards.TITLE` opens a run: it rises when you first enter play and clears itself
after a few seconds without taking the controls away from you.
`Cards.forEnding(ending)` returns the card for one of `Game.ENDING` —
`true_chord`, `partial_chord`, `flat_toll`, `silence` — each with its own colour
and its own three lines. An ending it does not recognise still gets a card, so a
run always closes on screen rather than leaving the player in a dead world.
Text and colour only: `rite` decides when a card is up, `rite.hud` decides what
one looks like.
# bell
The great bell on the crown at `Game.ANCHORS.bell` — its bronze, its
interactable, and the moment it is struck.
The body is built from primitives under one hub entity: a yoke and shaft, a
domed crown, a squat barrel with a shoulder ring and a heavy lip, and the
striking log drawn back on its rope. A light inside the mouth lets the bell read
from the cliff path below.
## Surface
| Call | Does |
|---|---|
| `Bell.build(root)` | Build the bronze under `root` and register the interactable. |
| `Bell.register()` | Re-register the interactable; the label reads the ledger, so walking up carrying nothing says so. |
| `Bell.setEnabled(on)` | Turn the prompt on or off. |
| `Bell.strike(strength)` | Swing the log and run heat down the bronze; `strength` in 0..1. |
| `Bell.cool()` | Back to the cold rest pose. |
| `Bell.update(dt)` | Per-frame motion. A no-op when nothing is ringing. |
| `Bell.position()` | Where to aim a camera push-in. |
It decides nothing about the ending — `rite` reads the ledger and tells the bell
how hard it is being rung.
# toll
What the bell sounds like.
Three tones, synthesised once into `.soundClip` assets under
`/zero/source/rite/audio/` and reused from then on:
| Ending | Clip | Shape |
|---|---|---|
| `true_chord` | `amagiri_true_chord` | three strike tones entering a beat apart over a long hum, 5.0s |
| `partial_chord` | `amagiri_partial_chord` | two, and a shorter tail, 3.6s |
| `flat_toll` | `amagiri_flat_toll` | one, damped almost as soon as it starts, 1.9s |
Silence has no clip. That is the point of it.
A struck bell is not harmonic: its partials sit at stubborn irrational ratios
above the hum and decay at their own rates, so each tone is a sum of
independently-decaying sines plus a short bright transient on the leading edge,
rather than a filtered waveform.
## Surface
| Call | Does |
|---|---|
| `Toll.generate()` | Synthesise any clip that does not exist yet; returns how many it made. |
| `Toll.ensure(root)` | Put the bell's voice under `root` and generate on first use. |
| `Toll.play(ending)` | Ring the clip for an ending; returns the clip name, or nil when that ending has no voice. |
| `Toll.stop()` | Silence it. |
The source is 2D on purpose: the toll is the whole valley hearing itself, not a
point in it, so it needs no listener placed by another slice.