[0.2.0]

Breaking changes

  • Cauldron2D.Grid.Coarse routes through regions of open tiles instead of whole blocks: a path is tile centres ending at the goal point, placed continuously across a wrapping grid's edges, and covers the next :ahead regions (default 3) of a longer way. find/4 takes options (:ahead) where it took a search limit, :block defaults to 8, and passable?/2, block_of/2 and centre/2 are gone. waypoint/4 takes the body's radius (default 0.0) and looks at the first 16 points.
  • Cauldron2D.Arenas, Arenas.Table, Arenas.OnDemand and Arenas.Sweeper are now Cauldron2D.Worlds, Worlds.Table, Worlds.OnDemand and Worlds.Sweeper, with the same functions. Rename the module in every call.
  • The registry and supervisor the worlds run under are Cauldron2D.Worlds.Registry and Cauldron2D.Worlds.Supervisor. A world's own supervisor is registered under Worlds.via({:supervisor, id}), not {:arena, id}.
  • Cauldron2D.Worlds.start/1 returns {:error, :unknown_world} for an unregistered id (was :unknown_arena).
  • Cauldron2D.Client.Game callback arenas/1 is now worlds/1, and the arena() type is world(). Rename the callback; the entries keep the same keys.
  • arena/3 in Cauldron2D.Client.Game is now Cauldron2D.Client.Game.world/3.
  • The screen a world is played on is :play, not :arena: cue(view, :play) and cue(view, {:static, :play}), and :play in the screen() type.
  • outcome/1's :next is :next_world or :same_world (was :next_arena or :same_arena).
  • arenas/0 in Cauldron2D.Stats is now Cauldron2D.Stats.worlds/0, and its arena() type is world().
  • Cauldron2D.Ledger.board/4 narrows to one world with world: (was arena:); an arena: option is ignored.
  • Cauldron2D.Ledger.record/4 stores the world's name on a row as :label (was :arena), so a forget/2 predicate reads row.label. Rows recorded before still match board/4's world:.
  • Cauldron2D.Ledger.Recorder takes :label for the name results are kept under; :arena is still read when :label is absent.

Added

  • Cauldron2D.Grid.Coarse.reachable?/3 and spots/1, and Cauldron2D.Grid.Coarse.Space, which measures positions across joined edges.

  • A host is named <name>@cauldron.host, a name tied to none of its addresses, and Cauldron2D.Distribution.join/3 points that name at the address given in its own host table, so a host is joined through any of its addresses (its home network's, a VPN's, 127.0.0.1 from the same machine).

  • Cauldron2D.Distribution.addresses/0: every address of the machine's interfaces, private networks first and physical interfaces before virtual ones (a VPN's, a virtual machine's bridge). address/0 is its first, so a host names itself on its home network rather than on whichever interface the system lists first.

  • A player can ask for fewer frames than the world ticks: Cauldron2D.World.join/4's :frames takes a number a second, and Cauldron2D.World.frames/3 (Cauldron2D.Player.frames/3) changes it. The world builds a view only for a frame it sends, and each frame carries the events of every step since the last.

  • Cauldron2D.Distribution.join/3 says why a join failed: :bad_address, :unreachable (nothing answers at the address), :not_hosting (no such node there) or :refused (the node refused the code), where it said :unreachable for all.

  • Cauldron2D.Client.Game: an ask of {:menu, arg} and the optional open_menu/2 callback, so a world can ask a client to open a particular menu of the game's.

  • Cauldron2D.Audio.keyed/4 takes music: true: the sound's recording from the new :recordings option, played in its own channels as a looping stage music layer from its start, at the music level; moved/2 sets its gain and stopped/2 drops it.

  • Cauldron2D.Client.Game's optional recordings/0, passed to the audio by audio_options/1.

  • Cauldron2D.Audio's :master option, :sfx for the effects level to scale the music too; a game sets it through Cauldron2D.Client.Game's audio/0.

  • Cauldron2D.Audio.muted/1: the event that silences everything until another lets it sound again.

  • Cauldron2D.Loop.wait/2: the milliseconds until the next step falls due.

  • Cauldron2D.DrawList.extent/2: the rectangle an item's picture can cover.

  • {:gamemaker, mode} drawing: a blend with GameMaker's factors applied to the alpha channel as well as the colours, as GameMaker draws onto a surface.

  • Cauldron2D.Blend and Cauldron2D.DrawList draw with mode: :subtract, GameMaker's bm_subtract: each canvas channel times one less the picture's colour times its alpha.

  • Cauldron2D.Distribution: play across machines over BEAM distribution. host/2 names the VM <name>@<address> with a fresh join code as its cookie and calls on the local network; join/3 connects to a host with its code; stop/1, address/0, code/0.

  • Cauldron2D.Worlds.world_name/2: the name of a world on a connected node, started there on first use.

  • Additive drawing: a draw item's or placement's mode: :add adds the picture's colour times its alpha to what is beneath (Cauldron2D.Blend.draw/6, Cauldron2D.DrawList).

  • Cauldron2D.Audio.keyed/4 takes loop: true: a voice playing a recording goes round it — its own loop, or the whole of it — until stopped/2 lets it go.

  • Cauldron2D.Client.Game's optional ask/1: what the world asks of a player's client, {number, :menu} to open the game's menu or {number, :quit} to end the client, each number acted on once.

  • Cauldron2D.Audio.Music: a piece with render: :on_cue renders a section only when it is cued and keeps only the playing section in memory, for pieces of whole recorded tracks.

  • Cauldron2D.Fit: a frame of a fixed shape fitted into a picture of another, with bars, and the point of the frame under a picture fraction.

  • Cauldron2D.World.join/4 and Cauldron2D.Player.join/4 take frames: false for a player that sends input and is sent no frames.

  • Cauldron2D.Client.Game optional callback local_seats/1: further players on the same machine, a keymap each.

  • Cauldron2D.Blend.draw/6 draws a picture onto an RGBA canvas at a pixel position, with an origin, a scale per axis (negative mirrors), an angle, a blend colour and an alpha, in C where the library loaded and in Elixir where it did not.

  • Cauldron2D.Render.render/3: a frame drawn once from a list of operations, each drawn by its shape ({:sprite, picture, placement}, {:raster, picture, {x, y}}), in bands of rows across the schedulers, in C where the library loaded. :format is :rgba or :rgb; :background is a colour or a picture the frame's size.

  • Cauldron2D.DrawList: a frame as a list of pictures placed in world pixels, drawn greatest depth first through a view and a zoom into a raster for a :picture scene, leaving out items that cannot reach the frame.

  • Collision by pixels: Cauldron2D.Mask (solid pixels, one bit each, from bits or from a raster's alpha), Cauldron2D.Shape (a rectangle or a mask placed with an origin, a scale and an angle), and Cauldron2D.Overlap testing shapes against each other, a point, a rectangle, a circle or a line.

  • Cauldron2D.Turn.cos_sin/1, exact for whole quarter turns.

  • Keyed sound events: Cauldron2D.Audio.keyed/4 sounds a name under a key, replacing what sounds there; moved/2 moves it and stopped/2 lets it go. A keyed sound is placed again when the listener moves and when the effects level changes, and one past earshot keeps sounding at no level. They go to Cauldron2D.Audio.play/2 with other events, as Cauldron2D.Audio.Keyed structs. A keyed sound given {:level, gain, pan} instead of a position plays at the game's own level, scaled by the effects level, and only moved/2 changes it.

  • Cauldron2D.Client.Game optional callback audio/0: :voices and :earshot for the game's sound, read with Cauldron2D.Client.Game.audio_options/1.

  • A game's own menu, through optional Cauldron2D.Client.Game callbacks: options/0 (the game's player options and defaults), open_menu/1 and menu_event/3 (the menu's state and its keys, mouse and controller buttons; it changes settings, plays sounds, sends the world numbered requests, or leaves), and input_options/1 (Cauldron2D.Input options from the player's options). Cauldron2D.Input.spec_of/1 turns a press into the key spec it binds.

  • Cauldron2D.Input binds the wheel and the pointer's movement like tapped keys: {:mouse, :wheel_up | :wheel_down} and {:mouse, :move_left | :move_right | :move_up | :move_down}, each holding its actions for :gesture_ms. Input.binds?/2 says whether a key spec is bound, and Input.Pad.binds_axis?/2 whether a padmap binds a stick axis.

  • Sector maps: Cauldron2D.Sectors.Map (rooms of any shape with their own floor and ceiling heights, joined by lines), Sectors.Bsp (front-to-back order and point location, read from a level or built from its lines), and Sectors.View, which draws one from inside it — walls with upper, lower and see-through middle pictures, perspective floors and ceilings, sky, and sprites clipped against the walls in front of them.

  • Cauldron2D.Sectors.Frame: an indexed-colour picture drawn a column, a plane or a line at a time, in C with an Elixir fallback that draws the same picture; Sectors.Pictures holds the textures and light tables it draws from.

  • Cauldron2D.Sectors.Light: light tables built from a palette, and which table a surface is drawn through at a light level and distance.

  • Movement and sight through sector maps: Sectors.Blockmap (lines and bodies filed by block), Sectors.Trace (what a straight line crosses, nearest first), Sectors.Move (fitting, stepping up, drop-offs, sliding along walls), Sectors.Sight, Sectors.Noise (how far a sound carries through open doors) and Sectors.Automap.

  • Sectors.Overlay, a picture drawn flat over the view, for a held weapon or a status bar.

  • Cauldron2D.States (state tables: a picture, a duration, an action and the next state), Cauldron2D.Thinkers (things that change by themselves each tick, in a fixed order) and Cauldron2D.Steer (choosing one of eight directions toward a target round obstacles).

  • Cauldron2D.Client.Game scenes take a :picture, a raster the game draws itself.

  • Five projections of the same world, each taking the atlas, a :focus or :position and the scene's :cell:

    • Cauldron2D.Projection.Oblique — a square grid seen from above and in front, standing arts as a front face with their top above it; :facings of 1 or 4.
    • Cauldron2D.Projection.Iso — floors as diamonds, standing arts as blocks with a top and two shaded sides, movers upright; :facing, :facings (4 to 32 stamped, any other count or :free sampled), :tilt and :shrink.
    • Cauldron2D.Projection.Mode7 — the ground plane in perspective to a horizon, turning freely, movers as billboards.
    • Cauldron2D.Projection.FirstPerson — raycast from :position along :heading with :pitch and :eye: walls from standing arts, textured floors, movers as billboards, fog to the atlas's void colour.
    • Iso, Oblique and Mode7 answer project/3, unproject/3 and tile_at/3, all taking the atlas first; Iso and Oblique also cells/2 and ground_map/2, and Iso up/1.
  • Cauldron2D.Projection.Glyphs.compose/3: an isometric or oblique view drawn as the atlas's two-character glyphs.

  • Cauldron2D.Projection.Canvas (RGBA rows, stamps, quad/5) and Cauldron2D.Projection.Ground (the perspective ground sampler, in C with an Elixir fallback; available?/0).

  • Cauldron2D.Projection.Volume: a coloured region of space that tints what is seen through it, drawn by every projection and by Cauldron2D.Surface. A volume has :at, :shape, :colour, :strength, a falloff of :depth, :flat or :shell, and :follows to move with a subject (anchored/2). Cauldron2D.Projection.Volume.Shapes gives a sphere, a cylinder, a cone, a beam and a set of whole tiles; Cauldron2D.Projection.Volume.Shape is the behaviour for a shape of your own. Volume.Native paints the built-in shapes in C.

  • Cauldron2D.Parallax: background layers each moved by their own share of the camera's motion (factor, drift, origin, void, front: true to draw over the world). layer/1, strip/2 and legend/2 for authored tiles, scatter/1 for art placed by hashing the tile coordinate, panorama/1 for one repeated picture, bands/1 for a factor per range of rows, compose/3, view/2, forget/1. Parallax.Preview.frames/3 runs a camera across a set of layers.

  • Cauldron2D.Blend.over/2: source-over compositing of two RGBA binaries, in C with an Elixir fallback (available?/0).

  • Cauldron2D.Input.Pad: a padmap from action to controller buttons and axes in the Xbox layout. new/2, handle/2 for Gamepad.Wire events, held/1, strength/1, default_map/1; {:stick, side} or {:stick, side, push: [...]} bound to :aim makes that stick aim (aim/1, aim_stick/1, push/1).

  • Cauldron2D.Input.merge/3 takes how hard each outside action is held; Cauldron2D.Input.state/1 carries :strength when any action is partly held.

  • Cauldron2D.Game.input() type: the input a game's handle_input/3 receives, which may carry :strength and, from a client looking through an eye, :heading.

  • Cauldron2D.Route: a click on the ground turned into a walk. to/4 finds the tile centres to pass through with Cauldron2D.Grid.Path; advance/3, heading/2, arrived?/1, goal/1.

  • Cauldron2D.Client.Game optional callbacks: start/0 (:lobby, {:lobby, title: false}, {:play, id}, {:play, id, title: true}, or a function of the client's state), look/0 (the view a client opens in: projection, projections to cycle, facing, facings, tilt, heading, pitch, zoom, density, and keys of the game's own), scene/2 (the scene given the look), padmap/1 and client_pad/0. client_keys/0 may bind a list of keys, and {:look, key} adds a key that flips a look key of the game's own.

  • A scene may carry :heading, :volumes and :layers; movers may be {art, {x, y}, heading}.

  • Cauldron2D.Atlas.put/4 options :height (storeys), :faces (:top, :side, :north, :east, :south, :west), :poses (:front, :back, :side), :stand and :eye; Atlas.face/3, pose/4, standing/2, eye_height/2 and tallest/1.

  • The atlas manifest (atlas.txt) takes four more columns after the derivation: height, faces, poses and stand. An art the manifest alone names is built from its faces or its front pose.

  • Cauldron2D.Surface.compose/4 options :volumes, :void (:opaque or :transparent) and :under (a raster drawn beneath); Surface.stack/2, Surface.sprite_rgba/4; tile_binary/5 takes the void mode. A mover may be {art, {x, y}, tint} or {art, {x, y}, tint, pose, flip}.

  • Cauldron2D.Renderer.resolved/1: the mode from CAULDRON_MODE and configuration, before any terminal is asked.

Changed

  • The compiled native code is built into and loaded from priv/native/<system architecture> (Cauldron2D.Native) instead of priv/, so builds for macOS and Linux in one checkout no longer overwrite each other.

  • Cauldron2D.Surface.compose/4 draws its grid, movers and labels, and Cauldron2D.Parallax.compose/3 its layers, through Cauldron2D.Render.render/3; the pictures are the same.

  • available?/0 of Blend, Projection.Ground, Projection.Volume.Native and Sectors.Frame reports whether the C library loaded.

  • Effects reach the stage through TuningFork.Stage.play_pcm/3: a process standing in for the stage in a test receives {:play_pcm, pcm, opts}, opts empty for an unkeyed sound.

  • An art may be any size. A cell's base must still be one tile; a larger overlay stands on its cell, bottom edge on the cell's bottom and centred across it, and a mover is drawn at its own size. All frames of one art must be the same size.

  • Cauldron2D.Atlas.load/3 reads a multi-frame .pic frame by frame; a .png that is not one tile or a strip of tiles loads as one art of its own size instead of {:error, {:art, file, :size}}.

  • lobby?/0 is read as start/0: false means {:play, :first, title: true}.

  • Cauldron2D.Surface blends tiles through Cauldron2D.Blend.

  • The package builds C NIFs for the ground plane, volumes and blending through elixir_make (c_src/, Makefile, and Makefile.win for nmake and cl on Windows; elixir_make is a new dependency). Without a C compiler the build still succeeds and the Elixir code does the same work.

Fixed

  • Cauldron2D.Distribution.join/3 accepts a host's code through any of the host's addresses, not only the one in its name.

  • Cauldron2D.World.join/4 refuses an id another process already plays as, with {:error, :taken}, where it gave that id to the newcomer and left the first process without its player.

  • Cauldron2D.World times its next tick once the last has run, for when the next step falls due, so its mailbox never holds more than one tick. A world whose steps take longer than their time no longer queues ticks ahead of input; input waits at most one tick, and steps past Cauldron2D.Loop's :max_steps are dropped.

  • Cauldron2D.Input without key releases: a tapped key lets go after its tap window on a clock that reads below zero, as the BEAM's monotonic clock does. A tapped key stayed held for good.

  • Cauldron2D.Blend and Cauldron2D.Render never write or read outside a picture: a placement with a huge or non-finite position, origin, scale or angle draws nothing beyond the frame's edges, negative and zero sizes are refused, and sizes are checked without overflowing.

  • :alpha is held to 0.0–1.0 and each :blend channel to 0–255, the same in C and in Elixir.

  • Cauldron2D.Render.render/3 raises ArgumentError for a picture or background of the wrong size and for a colour that is not four values in 0..255.

  • Cauldron2D.DrawList keeps a picture whose origin lies outside it when the picture lands in the frame.

  • Cauldron2D.Surface.compose/4 keeps the colour of the grid's see-through pixels.

  • -ffp-contract=off is kept when CFLAGS is set in the environment, so C and Elixir draw the same pixels.