[0.2.0]
Breaking changes
Cauldron2D.Grid.Coarseroutes 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:aheadregions (default 3) of a longer way.find/4takes options (:ahead) where it took a search limit,:blockdefaults to 8, andpassable?/2,block_of/2andcentre/2are gone.waypoint/4takes the body's radius (default 0.0) and looks at the first 16 points.Cauldron2D.Arenas,Arenas.Table,Arenas.OnDemandandArenas.Sweeperare nowCauldron2D.Worlds,Worlds.Table,Worlds.OnDemandandWorlds.Sweeper, with the same functions. Rename the module in every call.- The registry and supervisor the worlds run under are
Cauldron2D.Worlds.RegistryandCauldron2D.Worlds.Supervisor. A world's own supervisor is registered underWorlds.via({:supervisor, id}), not{:arena, id}. Cauldron2D.Worlds.start/1returns{:error, :unknown_world}for an unregistered id (was:unknown_arena).Cauldron2D.Client.Gamecallbackarenas/1is nowworlds/1, and thearena()type isworld(). Rename the callback; the entries keep the same keys.arena/3inCauldron2D.Client.Gameis nowCauldron2D.Client.Game.world/3.- The screen a world is played on is
:play, not:arena:cue(view, :play)andcue(view, {:static, :play}), and:playin thescreen()type. outcome/1's:nextis:next_worldor:same_world(was:next_arenaor:same_arena).arenas/0inCauldron2D.Statsis nowCauldron2D.Stats.worlds/0, and itsarena()type isworld().Cauldron2D.Ledger.board/4narrows to one world withworld:(wasarena:); anarena:option is ignored.Cauldron2D.Ledger.record/4stores the world's name on a row as:label(was:arena), so aforget/2predicate readsrow.label. Rows recorded before still matchboard/4'sworld:.Cauldron2D.Ledger.Recordertakes:labelfor the name results are kept under;:arenais still read when:labelis absent.
Added
Cauldron2D.Grid.Coarse.reachable?/3andspots/1, andCauldron2D.Grid.Coarse.Space, which measures positions across joined edges.A host is named
<name>@cauldron.host, a name tied to none of its addresses, andCauldron2D.Distribution.join/3points 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.1from 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/0is 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:framestakes a number a second, andCauldron2D.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/3says 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:unreachablefor all.Cauldron2D.Client.Game: anaskof{:menu, arg}and the optionalopen_menu/2callback, so a world can ask a client to open a particular menu of the game's.Cauldron2D.Audio.keyed/4takesmusic: true: the sound's recording from the new:recordingsoption, played in its own channels as a looping stage music layer from its start, at the music level;moved/2sets its gain andstopped/2drops it.Cauldron2D.Client.Game's optionalrecordings/0, passed to the audio byaudio_options/1.Cauldron2D.Audio's:masteroption,:sfxfor the effects level to scale the music too; a game sets it throughCauldron2D.Client.Game'saudio/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.BlendandCauldron2D.DrawListdraw withmode: :subtract, GameMaker'sbm_subtract: each canvas channel times one less the picture's colour times its alpha.Cauldron2D.Distribution: play across machines over BEAM distribution.host/2names the VM<name>@<address>with a fresh join code as its cookie and calls on the local network;join/3connects 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: :addadds the picture's colour times its alpha to what is beneath (Cauldron2D.Blend.draw/6,Cauldron2D.DrawList).Cauldron2D.Audio.keyed/4takesloop: true: a voice playing a recording goes round it — its own loop, or the whole of it — untilstopped/2lets it go.Cauldron2D.Client.Game's optionalask/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 withrender: :on_cuerenders 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/4andCauldron2D.Player.join/4takeframes: falsefor a player that sends input and is sent no frames.Cauldron2D.Client.Gameoptional callbacklocal_seats/1: further players on the same machine, a keymap each.Cauldron2D.Blend.draw/6draws 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.:formatis:rgbaor:rgb;:backgroundis 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:picturescene, 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), andCauldron2D.Overlaptesting 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/4sounds a name under a key, replacing what sounds there;moved/2moves it andstopped/2lets 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 toCauldron2D.Audio.play/2with other events, asCauldron2D.Audio.Keyedstructs. A keyed sound given{:level, gain, pan}instead of a position plays at the game's own level, scaled by the effects level, and onlymoved/2changes it.Cauldron2D.Client.Gameoptional callbackaudio/0::voicesand:earshotfor the game's sound, read withCauldron2D.Client.Game.audio_options/1.A game's own menu, through optional
Cauldron2D.Client.Gamecallbacks:options/0(the game's player options and defaults),open_menu/1andmenu_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), andinput_options/1(Cauldron2D.Inputoptions from the player's options).Cauldron2D.Input.spec_of/1turns a press into the key spec it binds.Cauldron2D.Inputbinds 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?/2says whether a key spec is bound, andInput.Pad.binds_axis?/2whether 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), andSectors.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.Picturesholds 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) andSectors.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) andCauldron2D.Steer(choosing one of eight directions toward a target round obstacles).Cauldron2D.Client.Gamescenes take a:picture, a raster the game draws itself.Five projections of the same world, each taking the atlas, a
:focusor:positionand 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;:facingsof1or4.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:freesampled),:tiltand:shrink.Cauldron2D.Projection.Mode7— the ground plane in perspective to a horizon, turning freely, movers as billboards.Cauldron2D.Projection.FirstPerson— raycast from:positionalong:headingwith:pitchand: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/3andtile_at/3, all taking the atlas first; Iso and Oblique alsocells/2andground_map/2, and Isoup/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) andCauldron2D.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 byCauldron2D.Surface. A volume has:at,:shape,:colour,:strength, a falloff of:depth,:flator:shell, and:followsto move with a subject (anchored/2).Cauldron2D.Projection.Volume.Shapesgives a sphere, a cylinder, a cone, a beam and a set of whole tiles;Cauldron2D.Projection.Volume.Shapeis the behaviour for a shape of your own.Volume.Nativepaints 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: trueto draw over the world).layer/1,strip/2andlegend/2for authored tiles,scatter/1for art placed by hashing the tile coordinate,panorama/1for one repeated picture,bands/1for a factor per range of rows,compose/3,view/2,forget/1.Parallax.Preview.frames/3runs 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/2forGamepad.Wireevents,held/1,strength/1,default_map/1;{:stick, side}or{:stick, side, push: [...]}bound to:aimmakes that stick aim (aim/1,aim_stick/1,push/1).Cauldron2D.Input.merge/3takes how hard each outside action is held;Cauldron2D.Input.state/1carries:strengthwhen any action is partly held.Cauldron2D.Game.input()type: the input a game'shandle_input/3receives, which may carry:strengthand, from a client looking through an eye,:heading.Cauldron2D.Route: a click on the ground turned into a walk.to/4finds the tile centres to pass through withCauldron2D.Grid.Path;advance/3,heading/2,arrived?/1,goal/1.Cauldron2D.Client.Gameoptional 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/1andclient_pad/0.client_keys/0may 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,:volumesand:layers; movers may be{art, {x, y}, heading}.Cauldron2D.Atlas.put/4options:height(storeys),:faces(:top,:side,:north,:east,:south,:west),:poses(:front,:back,:side),:standand:eye;Atlas.face/3,pose/4,standing/2,eye_height/2andtallest/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/4options:volumes,:void(:opaqueor:transparent) and:under(a raster drawn beneath);Surface.stack/2,Surface.sprite_rgba/4;tile_binary/5takes the void mode. A mover may be{art, {x, y}, tint}or{art, {x, y}, tint, pose, flip}.Cauldron2D.Renderer.resolved/1: the mode fromCAULDRON_MODEand configuration, before any terminal is asked.
Changed
The compiled native code is built into and loaded from
priv/native/<system architecture>(Cauldron2D.Native) instead ofpriv/, so builds for macOS and Linux in one checkout no longer overwrite each other.Cauldron2D.Surface.compose/4draws its grid, movers and labels, andCauldron2D.Parallax.compose/3its layers, throughCauldron2D.Render.render/3; the pictures are the same.available?/0ofBlend,Projection.Ground,Projection.Volume.NativeandSectors.Framereports 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},optsempty 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/3reads a multi-frame.picframe by frame; a.pngthat is not one tile or a strip of tiles loads as one art of its own size instead of{:error, {:art, file, :size}}.lobby?/0is read asstart/0:falsemeans{:play, :first, title: true}.Cauldron2D.Surfaceblends tiles throughCauldron2D.Blend.The package builds C NIFs for the ground plane, volumes and blending through
elixir_make(c_src/,Makefile, andMakefile.winfornmakeandclon Windows;elixir_makeis a new dependency). Without a C compiler the build still succeeds and the Elixir code does the same work.
Fixed
Cauldron2D.Distribution.join/3accepts a host's code through any of the host's addresses, not only the one in its name.Cauldron2D.World.join/4refuses 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.Worldtimes 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 pastCauldron2D.Loop's:max_stepsare dropped.Cauldron2D.Inputwithout 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.BlendandCauldron2D.Rendernever 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.:alphais held to0.0–1.0and each:blendchannel to0–255, the same in C and in Elixir.Cauldron2D.Render.render/3raisesArgumentErrorfor a picture or background of the wrong size and for a colour that is not four values in0..255.Cauldron2D.DrawListkeeps a picture whose origin lies outside it when the picture lands in the frame.Cauldron2D.Surface.compose/4keeps the colour of the grid's see-through pixels.-ffp-contract=offis kept whenCFLAGSis set in the environment, so C and Elixir draw the same pixels.