emberline.art.bwbuffer — the CPU pixel buffer the BELL / WARDEN sheets are
drawn into, plus the sheet builder that pushes finished frames at a live
PixelCanvas handle.
Why this exists: every pixelArt handle op crosses a typed wrapper and a
component lookup, so a single `getPixel` costs about 7us and a `fillEllipse`
about 100us. A 150-frame sheet whose outline pass alone reads 96x120 pixels
per frame is minutes of work through the handle. This buffer is a plain
Luau array with the same method NAMES the handle has, so `Pixelkit.on(buf)`
drives it unchanged — limb, blob, poly, ribbon, speck, outline, shadeLeft —
and a whole frame lands in ONE `fromRows` call instead of several hundred.
local Buf = require("emberline.art.bwbuffer")
local index = Buf.buildSheet(handle, { cell, palette, clips, order, drawPose })
hex(s: string) → void
"#rrggbb" -> { r, g, b } in 0..1, the shape the pixelArt palette wants.
| arg | type | description |
|---|
| s | string | |
palette(spec: { [string]: string }) → void
{ name = "#rrggbb" } -> { name = { r, g, b } }
| arg | type | description |
|---|
| spec | { [string]: string } | |
new(w: number, h: number) → any
| arg | type | description |
|---|
| w | number | |
| h | number | |
clear(self: any) → void
| arg | type | description |
|---|
| self | any | |
setPixel(self: any, x: number, y: number, name: string?) → void
| arg | type | description |
|---|
| self | any | |
| x | number | |
| y | number | |
| name | string? | |
getPixel(self: any, x: number, y: number) → string
| arg | type | description |
|---|
| self | any | |
| x | number | |
| y | number | |
span(self: any, x0: number, x1: number, y: number, name: string?) → void
Horizontal run, inclusive, clipped. The inner loop of every fill below.
| arg | type | description |
|---|
| self | any | |
| x0 | number | |
| x1 | number | |
| y | number | |
| name | string? | |
fillRect(self: any, x0: number, y0: number, x1: number, y1: number, name: string?) → void
| arg | type | description |
|---|
| self | any | |
| x0 | number | |
| y0 | number | |
| x1 | number | |
| y1 | number | |
| name | string? | |
drawRect(self: any, x0: number, y0: number, x1: number, y1: number, name: string?) → void
| arg | type | description |
|---|
| self | any | |
| x0 | number | |
| y0 | number | |
| x1 | number | |
| y1 | number | |
| name | string? | |
fillEllipse(self: any, cx: number, cy: number, rx: number, ry: number, name: string?) → void
| arg | type | description |
|---|
| self | any | |
| cx | number | |
| cy | number | |
| rx | number | |
| ry | number | |
| name | string? | |
fillCircle(self: any, cx: number, cy: number, r: number, name: string?) → void
| arg | type | description |
|---|
| self | any | |
| cx | number | |
| cy | number | |
| r | number | |
| name | string? | |
drawCircle(self: any, cx: number, cy: number, r: number, name: string?) → void
| arg | type | description |
|---|
| self | any | |
| cx | number | |
| cy | number | |
| r | number | |
| name | string? | |
drawLine(self: any, x0: number, y0: number, x1: number, y1: number, name: string?) → void
| arg | type | description |
|---|
| self | any | |
| x0 | number | |
| y0 | number | |
| x1 | number | |
| y1 | number | |
| name | string? | |
flush(self: any) → void
The handle surface pixelkit never calls but a caller might; harmless here.
| arg | type | description |
|---|
| self | any | |
setPalette(self: any, _p: any) → void
| arg | type | description |
|---|
| self | any | |
| _p | any | |
shadeEdge(self: any, map: { [string]: string }, depth: number, fromRight: boolean?) → void
Recolour the lit/shadow sides of the figure in one scan. `map` is
{ fromName = toName } applied to the first `depth` filled pixels of every
row (the screen-LEFT edge, away from the stage key light).
| arg | type | description |
|---|
| self | any | |
| map | { [string]: string } | |
| depth | number | |
| fromRight | boolean? | |
outline(self: any, name: string?) → void
One-pixel rim around every drawn cluster. Reads a snapshot so the rim it
writes never seeds more rim.
| arg | type | description |
|---|
| self | any | |
| name | string? | |
rows(self: any, charOf: { [string]: string }) → void
Top-to-bottom text rows for `handle:fromRows`. `charOf` maps palette name
to legend char; empty cells emit "." which is deliberately not in any legend
so it lands as transparent.
| arg | type | description |
|---|
| self | any | |
| charOf | { [string]: string } | |
buildSheet(handle: any, opts: any) → any
Draw a whole sheet and hand back the clip index the combat slice reads.
opts = { cell, palette, clips, order, drawPose }
clips = { idle = { fps = 10, loop = true, poses = { pose, ... } }, ... }
Idempotent: a rebuild trims the canvas back to one frame first, so calling
build twice on the same handle yields the same sheet rather than two.
| arg | type | description |
|---|
| handle | any | |
| opts | any | |
drawStill(handle: any, cell: any, pal: any, draw: (any, any) → void
Draw a single still (a portrait bust) into an already-sized handle.
| arg | type | description |
|---|
| handle | any | |
| cell | any | |
| pal | any | |
| draw | (any, any | |
once(keys: { any }) → void
A one-shot clip: the last key holds, nothing returns to the first pose.
| arg | type | description |
|---|
| keys | { any } | |
cycle(keys: { any }) → void
A looping clip: tweens back to the first pose and drops the duplicate, so
frame N reads into frame 1 with no stutter at the seam.
| arg | type | description |
|---|
| keys | { any } | |
pose(base: any, over: any?) → any
A key pose is the base pose with a handful of joints moved. Every field is
a number so `Pixelkit.blend` interpolates the whole table.
| arg | type | description |
|---|
| base | any | |
| over | any? | |