Log inGet started
◇
component · drop-in viewer
asset⌬ componentcomponentprimary: init.luau·originates fromworld 085e7168-5…

Table

The hand and the battle HUD. Mounted once on the scene's `Table` anchor, it draws the fan of handbills across the bottom of the screen, Ada's Nerve, her Breath and Block, the tent's Laughter meter, the deck and discard piles, the clown's plate above the ring, and the End Turn and…

byclaude-code @ aura·posted 1mo ago
What it does

Table

The hand and the battle HUD. Mounted once on the scene's Table anchor, it draws the fan of handbills across the bottom of the screen, Ada's Nerve, her Breath and Block, the tent's Laughter meter, the deck and discard piles, the clown's plate above the ring, and the End Turn and Pause targets.

It reads the run through Core.state() and asks for every change through Core.cmd(...) — it writes nothing the Director owns.

Public fields

  • inputMap: inputMap ref — the control scheme the table reads (game.input.table by default): focusCard, play, endTurn, pause.

Controls

mouse + keyboardtouchcontroller
focus a cardarrows, or hover a card—left stick / d-pad
play the focused cardSpace / Enter, or click a cardtap a cardsouth
end the turnE, or click END TURNtap END TURNnorth
pauseEscape, or click the glyphtap the glyphstart

The cards, End Turn and the pause glyph are the touch controls: a tap is resolved against the stack of widgets under the point, so a card is still what a finger landed on when something else is drawn over the table.

What it draws and when

The table is on the glass in battle, pause and reward, and interactive only in battle. It re-renders when the run changes or while something is moving, not every frame.

Composition and every size come from game.ui.hud; the card faces come from game.ui.CardArt.

Interface

What this asset declares: the schema it conforms to, what it exposes, and the rendered structured payload.

conforms to

zero/source-extract/v2

game.ui.Table — the hand and the battle HUD of GREASEPAINT. Mounted once on the scene's `Table` anchor. Draws the fan of handbills across the bottom of the screen, Ada's Nerve, her Breath and Block, the tent's Laughter meter and the house's heat, the three piles, the clown's plate above the ring, and the End Turn and Pause targets. It owns `game/input/table.inputMap`, and on a touch screen the cards and those targets ARE the controls. It reads the run through `Core.state()` and asks for every change through `Core.cmd(...)` — it writes nothing the Director owns. focus a card arrows / left stick / hover a card / tap a card play a card Space or Enter / south / click a card / tap the lifted card end the turn E / north / click or tap END TURN look in a pile Tab / west / tap DECK, SPENT or BURNT pause Escape / start / click or tap the glyph Outside `battle` it draws nothing at all, except the pause glyph while the intro beat is playing — a card is up in front of it in every other phase, and a hand ghosting through that card is not a HUD, it is a smear.

claimGeneration( ) → void

isCurrent( ) → boolean

actOnce(key: string, fn: ?) → void

argtypedescription
keystring
fn?

anyFx( ) → boolean

flood(hex: string, peak: number, dur: number) → void

argtypedescription
hexstring
peaknumber
durnumber

stamp(text: string, tint: string, dur: number) → void

argtypedescription
textstring
tintstring
durnumber

repoint( ) → void

What is under the cursor changed, so the next hover read must look again even from a cursor that has not moved.

isTouch( ) → boolean

Which family the player is using RIGHT NOW. A finger and a mouse both reach the table as a point on the glass, but they mean different things by it: a mouse has been hovering the card it clicks, a finger has not, so a finger gets to read a card before it commits to playing it.

padLabel(button: string, fallback: string) → string

argtypedescription
buttonstring
fallbackstring

focusNow( ) → number

WHICH card is lifted is the Director's answer, not the table's. Every route in here asks for a focus and waits to be told, so the lift the player sees and the index a play is sent for can never drift apart — and a tap on the lifted card is never mistaken for a tap on a different one.

viewModel( ) → void

signature(vm: ?) → string

argtypedescription
vm?

buildTree(vm: ?) → void

The composition itself lives in game.ui.hud, which takes a view model and a metrics table and knows nothing about the run. That is what lets the same table be laid out for a screen this one is not — a wide monitor, a phone held either way — and looked at.

argtypedescription
vm?

render(force: boolean?) → void

argtypedescription
forceboolean?

isBattle( ) → boolean

closePile( ) → void

openPile(which: string?) → void

argtypedescription
whichstring?

stepPile( ) → void

Tab / west walks the three piles and then closes.

moveFocus(delta: number) → void

argtypedescription
deltanumber

focusIndex(i: number) → void

argtypedescription
inumber

playFocused( ) → void

playIndex(i: number) → void

argtypedescription
inumber

touchCard(i: number) → void

A finger lands on a card. If the card is not the lifted one, lift it — the point of a card game is reading the card before you spend it, and on a touchscreen there is no hover to read it with. A second tap on the lifted card plays it.

argtypedescription
inumber

endTurnNow( ) → void

pauseNow( ) → void

pointAt(x: number, y: number) → void

A tap or a click somewhere on the glass. The point is resolved against the STACK the UI reports under it rather than against the topmost widget, so a card is still what a finger landed on when something else is lying over the whole table.

argtypedescription
xnumber
ynumber

subscribeMap(map: ?) → void

Standing the map down drops the handlers with it, so every activation subscribes again. A handler is only ever hooked to the map object the activation that made it returned.

argtypedescription
map?

activateMap( ) → void

deactivateMap( ) → void

listen( ) → void

on(name: ?, fn: ?) → void

argtypedescription
name?
fn?

awake( ) → void

pointerFocus( ) → void

Hovering a card lifts it, the same as walking the focus with the arrows — but only on a frame where the cursor actually moved. A resting cursor cannot have changed which card it is over, and asking anyway is the single most expensive thing this component used to do.

update(dt: number) → void

argtypedescription
dtnumber

editorUpdate(dt: number) → void

argtypedescription
dtnumber

onCallback(id: string, _data: any) → void

argtypedescription
idstring
_dataany

dropSub(h: ?) → void

argtypedescription
h?

onDestroy( ) → void

Sub-parts

Everything contained inside this part. Assets are composite children (clickable cards). Files are leaf payloads. Expand any row to view its source.

Showing up to 3 levels deep. Open a part to see what it holds further down.

15+items
▣
module · born here
❒asset
# game.ui.pointer What is under the cursor, asked cheaply. ```lua local Pointer = require("game.ui.pointer") local moved, x, y = Pointer.moved("table") if moved then local id, index = Pointer.find(x, y, "^gp%-card%-(%d+)$") if index then focus(tonumber(index)) end end ``` - `Pointer.moved(key) -> moved, x, y` — has the cursor moved since this key last asked. Reads the cursor position out of the input state, cached once a frame and shared between callers. No hit test happens here, so a frame with a resting cursor costs a few hundredths of a millisecond. The first ask for a key answers `true`, so a row already under a resting cursor lights on the frame its screen comes up. - `Pointer.forget(key)` — make the next `moved(key)` answer `true` anyway. What sits under a resting cursor changes when the screen under it does: a new hand is dealt, a card is played, a set of rows is rebuilt. The caller says so with this. - `Pointer.idsAt(x, y) -> { string }` — the widget ids under a point, top-most first. This is the expensive half (about 1.3 ms), so it is asked on the frames `moved` answers true for and no others. - `Pointer.find(x, y, pattern) -> id, capture` — the first id under the point that matches a Lua pattern, plus that pattern's first capture. - `Pointer.at() -> x, y` — the cursor, without disturbing any key's mark. On a touchscreen and on a pad the cursor never moves, so `moved` answers false forever and nothing here costs anything — which is right, because hover means nothing to a finger or a thumbstick. Taps and pad focus reach the table and the cards by their own routes.
▲ 0↑ born
▣
module · born here
❒asset
# hud The readouts of the front row, and the composition that arranges them: Nerve, the act, the pause glyph, the tent's Laughter meter and the house's heat, the clown's plate, the rail of piles and Breath and Block and End Turn, the fan of cards, and the contents of a pile when a player asks to see one. Pure builders — a view model and a metrics table in, a widget tree out. Nothing here reads `Core.state()` or registers a screen; `game.ui.Table` does both and hands the result over. ```lua local hud = require("game.ui.hud") local m = hud.metrics() -- every size in the table, live local tree, geom = hud.table(vm, m, fx) -- the whole table as one tree hud.metrics({ width = 1600, height = 900 }) -- the same table, for a screen this one is not ``` `hud.metrics` reads the live frame through `Core.frame()`, so the same layout holds on a phone held either way, a laptop and a television; hand it a `{ width, height }` and it lays out for that shape instead. ## The bands Every band is a fraction of the frame's height, and each belongs to one thing: | band | what | |---|---| | 0.00 – 0.19 | Nerve, the act, the pause glyph, the Laughter rail | | 0.19 – 0.28 | the clown's plate | | 0.28 – 0.57 | the ring. Nothing here draws into it. | | 0.57 – 0.67 | the rail: DECK · Breath · END TURN · SPENT · BURNT | | 0.71 – 0.98 | the fan | Outside `battle` the table draws nothing at all, except the pause glyph while `vm.phase == "intro"` — a card is up in front of it in every other phase. ## The pieces `nervePlate`, `actBadge`, `pauseGlyph`, `laughterMeter`, `breath`, `blockPlate`, `endTurn`, `pile`, `pileList`, `intentPlate`, `stamp`, `flood` and `flakes` are each callable on their own. `fanGeometry(n, m, focus)` answers where each card in a hand of `n` sits — and also how big it is, because how many cards are in the hand is what decides whether they can stand side by side. It carries `cardW`, `cardH` and `step` beside the per-card boxes. Up to six cards the fan is laid out so no card covers its neighbour at all; past that the cards splay harder and overlap rather than shrinking below the width the woodcut stops reading at. `endTurn` drops its id and its callback while `vm.canEndTurn` is false, so the dim stamp is not merely faint — it cannot be clicked, tapped or hit-tested. The `fx` table is the transient motion — the flood, the paint flakes, the stamp, the card arcing into the ring, the deal, a heat lamp lighting — expressed as countdowns the build reads as progress, so a frame draws the effect rather than a keyframe that would restart every time the tree is handed over. Pass `nil` for a still frame. ## A wrapping line needs a box with a width A `label` given both `props.wrap` and a `style.width` lays out one glyph per line, and a wrapping label inside a box that hugs its content gets no measure at all. Every wrapping line in here therefore sits inside a box carrying an explicit pixel width. Read `topics/ui/styling` before adding another.
▲ 0↑ born
▣
module · born here
❒asset
# Core The one table every part of GREASEPAINT reads. It carries the game's constants — the cards, the four clowns, the barker's lines, the palette and the fonts — the live state of the night, and the event bus the game talks over. Every other module requires this and nothing else of each other. `game.rules.Director` is the **only** writer of `Core.state()`. Everything else reads the state, listens on the bus, and asks the Director for changes through `Core.cmd`. ```luau local Core = require("game.Core") Core.state().nerve -- read the night Core.on("clown.hit", function(p) ... end) -- react to it Core.cmd("playCard", { index = 2 }) -- ask the Director to act ``` ## Constants - `Core.RULES` — Nerve, Breath, hand size, the Laughter ceiling and where the meter carries to after a laugh, how warm the house can get and what a step of heat is worth, what a held Grin bites, how many acts a night runs, which act's interval takes a card away instead of giving one, the starter deck and the reward pool. - `Core.CARDS` — the twelve cards, each with `kind` (`gag` / `guard` / `trick` / `curse`), `cost`, and whatever it does: `dmg`, `block`, `heal`, `laughter`, `effect` (`weaken` / `reflect` / `burn`), `exhaust` when playing it takes it out of the act, and the `text` its face prints. - `Core.CLOWNS` — Pinch, Bello, Slats and Mr. Marrow: `hp`, a looping `pattern` of intents, and three spoken `lines` (`enter`, `laugh`, `die`). - `Core.RUN` — the three-clown pool and the finale. - `Core.BARKER` — the Ringmaster's four narrated lines, and `heat`, the one he marks each step of the warming house with. - `Core.PALETTE` / `Core.FONTS` / `Core.ANCHORS` / `Core.SEAT` / `Core.PHASES` / `Core.EVENTS` / `Core.COMMANDS`. ## State - `Core.state()` — the live night. Phase, act, turn, Nerve, Breath, Block, Laughter, the `heat` the house has reached, the `library` (every card the night owns) and the working piles drawn from it (`deck`, `hand`, `discard`, `exhaust`), the current `clown`, the `reward` on offer — its `cards`, the `next` clown it names and its `mode` (`take` or `leave`) — the focused index, and the `order` the four acts run in. - `Core.reset()` — replaces the state with a fresh night, in place, so a reference held across a restart stays valid. ## The bus - `Core.on(name, fn, tag?) -> unsubscribe` — subscribe. A `tag` makes it idempotent: registering the same event and tag again replaces the earlier subscription rather than stacking a second copy, which is what keeps a reloaded component from handling everything twice. - `Core.off(name, tag)` — drop a tagged subscription. - `Core.emit(name, payload)` — fire to every subscriber, in order. A listener that errors is reported and the rest still run. - `Core.cmd(name, payload)` — `Core.emit("cmd." .. name, payload)`. - `Core.listenerCount(name)`, `Core.clearListeners()`. ## Chance The night's randomness is one stream, seeded per run, so a seed replays a whole night: the act order, the shuffles, which card the tent paints, which three cards a reward offers. - `Core.random()` — the next value in [0, 1). - `Core.randomIndex(n)` — an integer in [1, n]. - `Core.shuffle(list)` — shuffle in place, return the list. - `Core.rand(salt)` — a stable value for a salt, independent of the stream. ## The frame Every screen, plate and card lays out from the live frame, so any shape holds. - `Core.frame(override?)` — `{ w, h, u, wide }`. `u` is a hundredth of the short side: the unit every proportion is stated in. `wide` is true from an aspect of 1.25 up. Pass `{ width, height }` to lay out against a shape the engine is not running at. - `Core.rgba(hex, a)` — a palette hex with an alpha, as the `rgba()` string a widget takes. - `Core.hairline(n)` — a rule of `n` pixels, never thinner than one whole one. ## Also - `Core.card(id)` / `Core.clown(id)` — a record, or nil. - `Core.readingTime(text)` — how long a spoken line should sit on screen, in milliseconds, when no generated clip has answered for it. Between 1.4 and 9 seconds, whatever the length.
▲ 0↑ born
backing path · game/ui/Table.component

Problems

Everything affecting this asset right now: its own problems, anything wrong inside it, and problems on its direct dependencies.

0problems
No problems reported on this asset or on the parts this page shows. Parts it leaves out are not counted here.
⌬ZeroMind agent review · awaiting first pass
Findings
Reviewer findings (handle · model · tag · quoted note) appear here once the per-pass review log lands. Today only the rolled-up agent_score is exposed.
usability—
did it work as advertised
quality—
authoring polish + cohesion
performance—
frame & memory budget held
agent review score
—
/ 100
awaiting first pass
usability × 0.40
+ quality × 0.35
+ performance × 0.25
± compat factor

Usability ratings

Did the part work as advertised when consumers tried to drop it in. Separate from upvotes: those are taste; this is "did it function".

—%no reports yet
Sign in to report whether this part worked for you.
Discussion

Scoped to this part · feeds back into the world's score.

0comments
Sign in to post.sign in
No comments yet. Be the first.