Cauldron2D.Drafter

Puts a cauldron_2d world on screen through drafter. Two tiers, and a game picks the one it needs:

Installation

def deps do
[
{:cauldron_2d_drafter, "~> 0.1"},
{:tuning_fork_speaker, "~> 0.1"}
]
end

The surface

{:cauldron_surface,
[
id: :map,
atlas: :my_game,
focus: {x, y},
bounds: {width, height},
cell: &MyGame.cell/1,
movers: [{:ball, {7.42, 3.15}}],
labels: [{"alice", {7.0, 2.0}, {255, 255, 0}}],
flex: 1
]}

An art with frames is drawn at the frame showing on the clock; a label is text in Linocut.Font over the world, or characters over the glyphs in text mode.

Register it before the first render, loading it first:

Code.ensure_loaded!(Cauldron2D.Drafter.Surface)
Drafter.Widget.Registry.register(Cauldron2D.Drafter.Surface)

Registration reads the module's component_tag/0, which is only there once the module is loaded; registering an unloaded module registers nothing, and its tag then renders as an empty pane. The same goes for Drafter.run/2's :widget_libraries option.

The ready-made client

Cauldron2D.Drafter.Client runs a game's Cauldron2D.Client.Game module through a title, a lobby (arenas in columns in a scrolling window — name, the game's kind of arena if it gives one, players, map — the chosen one described beside them with its players, redrawn as players come and go, on Cauldron2D.World.Presence; teams, and a chat unless the game says chat?: false), settings and the arena, holding the player's Cauldron2D.Input, their Cauldron2D.Audio and their Cauldron2D.World connection. The game supplies every name and every element the screens show: its title, its actions and key presets, its arenas, how a view becomes a scene, the hud — rows under the world, or a column beside it with rows under both — its sounds and music, the sink its audio plays through, and any pages of its own reachable from the title. The client supplies the flow between screens and the keys that drive it (Esc back, q title, ? hints, ^Q quit — each replaceable or dropped by the game's client_keys/0; an fps key, bound only by the game, shows the frames a second the world sends). A game with screens of its own embeds the client in its own drafter app, started at the lobby (start: :lobby) and driven through the client's callbacks; its arenas/1 gets the mount props. A game with a guide/0 — sections of entries, each an art, a name and a line — gets a guide screen on h from the title and the lobby: the arts on a surface down the left beside the text, scrolled with the arrows, the page keys, Home and End.

Settings — control preset and rebinding, pointer steering, effects and music levels, pixels or glyphs — are kept per player under $XDG_CONFIG_HOME/<game>/<player>.settings and, when the player has an account, in its props.

Cauldron2D.Drafter.Server.start(game: MyGame.Client, port: 2222, web: {:my_game, MyGame.Web.Endpoint, 2280})

Cauldron2D.Drafter.Server starts the ssh daemon with the game's accounts (a sound port each), the game's web endpoint and the beacon; the arenas are the game's, registered with Cauldron2D.Arenas. mix cauldron.input measures drafter's input path from a DRAFTER_TRACE log.

Placements

On a kitty terminal whose cell size the session knows, the grid is one stored image sent again only when its tiles change, and every mover is a placement of a sprite stored once by Cauldron2D.Drafter.Sprites.transmit/2 — about forty bytes per mover per frame, at sub-cell pixel offsets. Elsewhere the movers are composited into one image per frame, as below.

Two renderings, one view

The pixel path also returns blank strips, because the cells under an image still have to be painted with something or the previous frame shows through the transparent parts of the art.

Compression

kitty transmits are compress: true by default here. A tilemap is a handful of tiles stamped hundreds of times, which is about as compressible as image data gets — measured at ~300× on a full viewport, for roughly a millisecond of deflate. Pass compress: false for a terminal whose kitty support is partial.

License

MIT