[0.2.2]

Added

  • Cauldron2D.Drafter.Client.Picture: how a game's picture reaches a terminal that keeps images (native, native:N, size:WxH, png, swap, none), read from the client's :picture mount prop, the application env config :cauldron_2d_drafter, picture:, the CAULDRON_PICTURE environment variable or the game's look, with parse/1 for a mix task's --picture. Default native:2,swap.
  • A look's :native and :picture keys: a picture's own size, and the game's default picture options.
  • The client's :mute mount prop, config :cauldron_2d_drafter, mute: true and CAULDRON_MUTE=1: no sound, whatever sink the game or session would have had.

Breaking changes

  • A scene's :native_scale, :picture_format and :swap keys are no longer read; the client's picture options decide them. A scene's :native still declares the picture's own size.

[0.2.0]

Breaking changes

  • The :start mount prop names a flow, not a first screen. :lobby now opens on the title, then the lobby; pass {:lobby, title: false} for the lobby first (what start: :lobby did before, and what an embedding game passes). :title is read as :lobby.
  • A game embedding the client under its own Drafter.App must add controller: true and mouse_hover: true to its use Drafter.App line.
  • The client binds keys of its own by default: v cycles the projection, [/] turn, '/; tilt or pitch, and the controller's Start and bumpers. A game using any of them for its own actions rebinds or drops them through client_keys/0 and client_pad/0.
  • arenas/1 in Cauldron2D.Drafter.Client is now worlds/1, calling the game's worlds/1.
  • next_arena/1 in Cauldron2D.Drafter.Client is now next_world/1, reading :next_world and :same_world from the outcome.
  • The screen a world is played on is :play (was :arena), and the geometry event is :play_geometry (was :arena_geometry). The client's state holds the joined world as :joined (was :arena) and the summary's as :summary_world (was :summary_arena). Chat inside a world arrives as {:chat, {:world, id}, message}.
  • arena/1 in Cauldron2D.Drafter.Client.Screens is now play/1, and arena_at/3 is now world_at/3.

Added

  • Three options for a surface's :picture on a kitty terminal, each independent and off by default: :native {width, height} asks the picture at its own size times a whole number (:native_scale, :fit or a number) for the terminal to scale to the pane; picture_format: :png sends it as a PNG; swap: true replaces it without a blank pane between frames. A game's scene gives them by the same names.
  • Cauldron2D.Drafter.Server.start/1 takes port: 0 for any free port, and returns the port it serves on as :port.
  • A world can ask the client to open the game's menu with an argument ({:menu, arg}), which a game answers with open_menu/2; one without it gets open_menu/1.
  • Game controllers are tracked by name (Cauldron2D.Drafter.Client.Controllers): each plays a player of the client — the player itself, then its seats — by the settings' pads (a controller's name to a player number or :none), else the lowest free one in the order they connected; a controller with no player is ignored during play. A disconnect frees its player. The settings' controllers list the connected ones for the game's menus, and any controller works the game's menu while it is open.
  • A game's ask/1 can ask {:enter, props}: the client leaves its world, merges props into its mount props and joins the first world worlds/1 then lists. When that world goes down, the client takes its earlier props back and joins the first of their worlds again.
  • local_seats/1 is given the client's props with :settings, and the seats take new keymaps when the game's menu changes the settings.
  • The client acts on the game's ask/1: :menu opens the game's menu as the menu key does, :quit ends the client.
  • A local seat joins with the prop :seat_of, the player's own username.
  • Local seats: a game's local_seats/1 keymaps each play a further player in the same world from the same terminal, joined as "<username> 2" and on without frames; keys reach every seat and count where they are mapped, and controllers go to the seats, the player's own first, in the order they first report (Cauldron2D.Drafter.Client.Seats).
  • The client starts its audio with the game's audio/0 settings (:voices, :earshot).
  • The terminal client opens a game's own menu on its menu key (unbound until the game binds it): the player's held actions are let go, a world of their own is paused, and keys, mouse buttons, the wheel and the controller's d-pad, A, B and Start go to the game's menu_event/3; settings it changes are saved and take effect at once, and the scene is drawn again ten times a second for the menu's animations (:menu_frame on the look). Settings keep the game's own :options, which reach scene/2 on the look and the world with every input, with the menu's requests.
  • A game that binds the wheel gets it during play instead of the zoom, and a right stick the game's padmap binds holds the game's actions instead of nudging the aim. A notch of the wheel can be bound in the settings.
  • Saved settings naming a preset the game no longer has start from the game's first preset, and the saved bindings go with the old preset.
  • Cauldron2D.Drafter.Surface takes :picture — fn {width, height}, aspect -> raster end — and draws the game's own picture through the same pixel protocols and block glyphs as the atlas projections. The client passes a scene's :picture through.
  • Five projections on Cauldron2D.Drafter.Surface through :projection — :top, :oblique, :iso, :mode7, :first_person — with :facing, :facings (a number, or a map per projection), :tilt, :heading, :pitch, :eye and :density (a number, or a map per projection). Surface.projections/0.
  • Surface :mode takes the block-character glyph sets :half, :quad, :sextant and :octant beside :braille; every projection draws in them, and the plain glyph mode draws the isometric and oblique views.
  • Surface options :volumes and :layers (Cauldron2D.Parallax layers, top-down); movers may be {art, {x, y}, heading} and are posed by their heading.
  • Surface.world_at/3 and cell_at/3 map between a pane cell and a world point in every projection and drawing mode; picture/2, drawn_mode/1, fitted/1, fitting_zoom/2, image_active?/1, and zoom_levels/3 for a projection.
  • The isometric and oblique panes are stored once and shown through a moving source rectangle on a terminal with kitty placements; what moves is sent as patches (Cauldron2D.Drafter.Surface.Patch.plan/4).
  • Cauldron2D.Drafter.Client flows: enter/3, to_lobby/1, to_title/1, to_guide/1 and home/1 for a game's own start/0 function.
  • Controllers: {:controller, event} goes through the game's padmap/1 into the input sent to the world. The right stick nudges the aim or looks around, the triggers tilt or pitch, the d-pad and A choose and join in the lobby. A stick bound to :aim aims eight tiles from the focus and follows the focus while held.
  • View keys and look: client_keys/0 gains projection, turn_left, turn_right, tilt_up, tilt_down, and the unbound display, glyphs and mouse_look; the game's look/0 sets the view a world opens in and scene/2 is called when defined. A note under the world names the projection, its facing, tilt or heading and pitch, and how it is drawn.
  • A drag turns and pitches the first-person and mode 7 eye, and turns an isometric view of more than four facings; a click still reaches the game. turns_with_pointer?/1.
  • Cauldron2D.Drafter.Client.steered/2: direction actions turned with the isometric or oblique facing; in first person and mode 7 the input carries the eye's :heading.
  • Cauldron2D.Drafter.Client.scene/1, look/1 and eye_height/2; a scene's :eye, a height in tiles or an art, sets how high the first-person camera sits.
  • Cauldron2D.Drafter.Client.Settings display choices :half, :quad, :sextant and :octant; frame_rate/3 takes whether the terminal places images.

Changed

  • gamepad is a dependency, so the client's game controllers work in every game without the game listing it; a game that listed it only for the client can drop it.
  • The client asks its world for frames at the rate it draws (20 a second over ssh, 30 locally, or the frame rate chosen in settings) instead of every tick, so the world builds and sends no views that would not be drawn.
  • Cauldron2D.Drafter.Server.stop/2 takes a notice shown to each player it disconnects, whose terminal is restored. It returns once the port is closed, and disconnects the players and stops the accounts afterwards from a process of its own, so it can be called from a world those players' games call as they leave.
  • A session served over ssh draws at 20 frames a second when its frame rate is :auto; local sessions keep 30.
  • Cauldron2D.Drafter.Server gives each connection a 64 KiB send buffer (:send_buffer), so a session drawing faster than its connection carries skips frames instead of falling seconds behind, and players 10 minutes to log in (:login_timeout), ssh's host key question included, where they had two.
  • A client whose name is already playing in a world joins it as "<name> (2)", up to "<name> (9)", instead of taking the other's player.
  • Cauldron2D.Drafter.Surface.world_at/3 on a :picture surface gives the point of the picture under the pointer as fractions of its width and height, and games playing a picture receive it as their input's aim. A scene with a :frame (the shape its screen is shown at) gets the point of that frame as Cauldron2D.Fit.letterbox/3 places it, and nil on the bars around it. With the terminal reporting the pointer in pixels (drafter's pixel_mouse), a picture game's pointer is that pixel, not its cell.
  • :auto frame rate is 12 on inline images (iTerm2's own, sixel), 20 on kitty without placements, 30 otherwise (was 15 and 30).
  • Surface.protocol/1 takes a forced mode, then Cauldron2D.Renderer.resolved/1, then Drafter.Widget.Chart.Pixel.terminal/0. Whether a terminal places stored images is FrenchCurve.Capability.placements?/1 of the session's environment, which is false for every iTerm2.
  • The client is mouse_hover: true, so a pointer aim previews without a click.
  • Zoom levels follow the projection; zooming the isometric view out draws its world smaller in the same picture.

Fixed

  • A session served over ssh plays its sound to its account's tunnel port (TuningFork.Sink.Tcp to 127.0.0.1:<pulse_port>), or nowhere without one.
  • A player whose world stops — it crashed, or was stopped — is taken to the lobby and told "<world> stopped".