Skip to content

Neo Angband 0.26.0

Pre-release
Pre-release

Choose a tag to compare

@github-actions github-actions released this 22 Aug 02:34
· 2805 commits to master since this release

What you can download

Platform File
Windows (installer) Neo Angband Setup 0.26.0.exe
Windows (portable, one file) Neo Angband-0.26.0-portable.exe
macOS .dmg, or the .zip if you prefer to unpack it yourself
Linux .AppImage (no install), .deb, or .tar.gz
Self-hosting neo-angband-web-0.26.0.zip - static files, any web server

The portable Windows build and the AppImage need no installer:
download, run, and the game keeps its saves in a folder beside itself.

These builds are not code-signed

There is no Apple Developer identity or Windows certificate behind this
project yet, so your OS blocks the first launch.

Windows is one click: SmartScreen says "Windows protected your PC",
choose More info then Run anyway.

macOS is not one click, and the dialog it shows you does not contain the
way through
- it offers only Done and Move to Trash. Do this:

  1. Drag the app out of the .dmg, then double-click it and press Done
    on the refusal. This step is required: it is what makes the permission
    below appear, and it expires about an hour later.
  2. Open System Settings -> Privacy & Security and scroll to
    Security, near the bottom.
  3. Press Open Anyway on the line naming Neo Angband, authenticate, and
    launch it again.

Or, the same decision in one command:
xattr -d com.apple.quarantine "/Applications/Neo Angband.app"

The old right-click -> Open trick does NOT work: Apple removed that
bypass in macOS 15 Sequoia.

On Apple Silicon take the arm64 build. The x64 one is for Intel Macs,
not a fallback - Apple is withdrawing Rosetta 2, so on a current Mac it is
likelier to refuse to launch than to run slowly.

If that trade is not one you want to make, build it yourself -
docs/INSTALL.md - or play in the browser, which needs no trust decision.

Your save

Saves survive an update. Every save-format change ships the conversion that
reads the version before it, and a save this build cannot open is left
untouched rather than replaced.


A mod-authoring release: a mod can now drive the debug/wizard command set on a
throwaway session, load and run for a single session without installing,
conjure an item or creature into the live game, install a content mod through
the same door the player's own import uses, and draw a real HTML panel over
the game. Alongside these, an autoplayer no longer stalls at a -more- prompt
or loops forever on a locked door, a stale capability check that compared kind
rather than action is fixed, quitting a living character shows the two pauses
upstream shows, and several help screens are transcribed from lib/help
instead of a curated subset that had drifted from it.

Added

  • A mod can drive the game's debug commands, on a session that has stopped being
    saved.
    A new capability, debug:wizard, hands a consenting mod ctx.wizard:
    the depth jumps, the experience and gold and stat edits, acquirement, summoning,
    banishment, mapping, lighting and lore, plus a catalogue of every item, creature
    and artifact the running game has with the pack that added each one. Every
    command is the function the ^A menu already dispatches to, called from a mod's
    screen instead of from a text prompt, so nothing about the rules is reproduced
    here.

    What makes it offerable is that it refuses to run until the session has been cut
    loose from its save. The character on disk keeps whatever the last save left,
    everything after that is discarded, and there is no way back - so a mod holding
    this can never write a cheated character over one somebody cares about. The
    autosave runs at the tail of a turn and throttles to three seconds, so what is
    given up is at most three seconds of turns; reloading the page returns to the
    character select with the character waiting as it was.

    It is a separate grant from debug:spawn and neither covers the other. Spawning
    happens to the character the player is actually playing and costs them that
    character's score permanently; this one costs the session and leaves the save
    alone, which makes it the safer of the two for anything on disk.

  • A mod can be loaded for one session, without joining your library. A new
    tier of mod source holds an archive in session storage instead of installing it:
    the game picks it up on the next reload, composes it exactly as it composes any
    other pack, and forgets it when the game is closed. It is reached two ways.

    The player's way is a new row on Import a mod - "Try a .zip for this session
    only" - and it accepts an archive that ships CODE, because a player choosing a
    file is making the same decision they make when they import one permanently. It
    asks in a screen of its own first, which names the code files, shows the archive's
    size and digest, and lists what the manifest asks for in the same words and the
    same order the install consent prompt uses. Grants made there are held beside the
    archive rather than written to the stored consents, so trying somebody's mod once
    leaves no standing permission behind.

    A mod's way is ctx.loadModForSession(bytes), behind the new mod:session
    capability, and it is CONTENT ONLY on exactly the terms mod:install is: code
    under any extension is refused, and so is an archive whose manifest asks for a
    capability. A mod handing the engine another mod's code to run is a different act
    from a player choosing a file, and it stays refused. mod:session is a separate
    grant from mod:install rather than a relaxation of it - an install arrives
    switched off and waits for the player, a session load is on as soon as the game
    reloads - and the capability comparison checks the action so neither consent
    sentence can be spent on the other.

    Everything that makes an install safe runs here too, through the same functions:
    the third-party switch before the archive is opened, the archive ceilings and the
    zip-slip check, the standards inspection that refuses a mod which would install
    and then do nothing, and the origin pin, so a staged archive cannot shadow an
    installed mod of the same id under a different origin. A staged copy of an id you
    already have DOES shadow the installed one for the session, which is the point of
    trying a draft, and the collision is reported on that mod's row.

    What is temporary is the mod, not what it does. The archive is forgotten; a
    character it changed stays changed, anything it stored stays stored, and anything
    it sent has been sent. Every screen involved says so, because "just for this
    session" reads as a safety feature and is not one. The lifetime is also a strong
    convention rather than a boundary: session storage survives a reload, which is
    what makes the tier work, and a browser restoring a closed or crashed window
    restores it too. So a session mod is always listed, always marked SESSION ONLY,
    and its detail screen offers Drop it instead of an on/off switch - it is on
    because it was staged, and dropping the archive is the only thing that stops it.

  • A mod can conjure an item or a creature into the live game, and the character
    is marked for it.
    ctx.debug, behind the new debug:spawn capability, drops
    one item at the player's feet or scatters one creature near them, exactly as the
    debug commands do. It adds almost no ability and a good deal of honesty: every
    primitive behind it was already on ctx.core, and the gate they check reads a
    debug flag out of a deps bag the caller assembles - so a mod could always have
    conjured whatever it liked, with no capability and no trace.

    What the capability adds is the mark. The first use in a character asks the
    game's own debug question, in the same words and through the same function ^A
    uses, and accepting sets the same permanent NOSCORE.DEBUG bit - before
    anything is placed, so nothing can arrive in a character the player did not
    agree to spend. It also adds a line on the consent list, its own kind with no
    wildcard over it, so a player can see exactly which of their mods can do this.

    The question is asked on the character grid, which a mod's own modal panel would
    be covering, so the first spawn in a character is refused by name while one is
    open rather than posing a prompt nobody can see or answer. Placement is the
    game's and the API has no coordinates.

  • A mod can install a content mod. ctx.installMod(bytes), behind the new
    mod:install capability, takes the bytes of a mod archive and lands it through
    the same door the player's own zip import uses - so it is read under the same
    ceilings, inspected against the same requirements in the same words, keyed and
    digested the same way, and pinned to the same origin on first import. This is
    the caller ModProject was written for: it has emitted a mod folder's exact
    bytes since it was written and had nothing to hand them to, because nothing a
    mod can reach turns bytes into an installed mod.

    The door installs CONTENT only. An archive that ships code, under any name and
    not merely plugin.js, is refused by file; so is one whose manifest asks for
    any capability. That is what keeps the grant the size of its own sentence: "may
    add records and tweaks to your library" rather than "may write a program,
    install it, and have you enable something it authored". And an install is not
    an enable - what arrives is switched off, the player is shown its own
    capability list before any of it runs, and a mod takes effect on reload - which
    is what stops one grant becoming every grant. The consent line says both halves
    rather than leaving either to be discovered.

    Refusals are values, never throws, because the caller is a mod that will be
    showing the answer to a player. The bytes are copied before anything
    asynchronous runs, so what was inspected is what is stored.

  • A mod can draw with real HTML instead of the character grid. A mod holding
    the new ui:panel.mount capability gets ctx.ui.openPanel(spec), which mounts
    a panel on the page above the game and hands back a shadow root to build in.
    The five UI seams before it all paint character cells through the same seven
    methods, which is right for a compass or a carried-weight readout and wrong for
    a form: a field with a caret, a list with a scrollbar and a table the player
    sorts by clicking a column are three things every browser already has and this
    codebase has none of.

    The part a mod could not do for itself is the keyboard. The front end has one
    keydown registration - window, capture phase, installed at import - and every
    modal handler behind it cancels the event, so a real <input> on this page
    received nothing and its keystrokes were read as game commands instead: typing
    a name walked the character across the level. The door now stands down for a
    keystroke whose composed path runs through the top panel before it reaches the
    game's canvas, so the caret decides whose key it is, per keystroke.

    Escape belongs to the player and a mod cannot take it. It closes the topmost
    panel, decided at the door before the panel is offered the key, and focus
    returns to the game rather than to whatever the panel had focused; a modal
    panel also carries a close control the host draws outside the mod's shadow
    root, because a phone has no Escape key. The suppression fails OPEN: a
    container that has been detached, moved out of the panel layer, or made a
    parent of the game's own canvas is closed and the keyboard goes back, checked
    as each key arrives rather than once at mount, and a panel under another is
    inert. A panel the player closed puts that mod on a brief pause, so reopening
    cannot outrun the key they used to get out, and eight panels is the ceiling
    because Escape closes one at a time.

    The shadow root is style hygiene rather than a sandbox and the consent text
    says so: a plugin runs in the page's own realm, so a mod can reach the document
    with or without this grant. What the grant carries is the sentence the player
    reads before enabling the mod - that it can draw something which looks exactly
    like the game's own screens and read what is typed into it - and a container the
    host owns, places, stacks and takes away. Panels come down when the mod set
    changes, after each plugin's uninstall() and before the save.

Fixed

  • An autoplayer mod stopped dead at every -more-, and only a human could
    free it.
    A turn's tail can raise a prompt that blocks for a keypress - two
    screenfuls of messages, the forced -more- a level change puts in front of the
    stair message, the floor-item list on a pile of two or more, a shop screen - and
    the autoplayer clock skipped every tick while one was up. So a mod that plays the
    game by itself could not go downstairs: it printed "You enter a maze of down
    staircases." and waited for a key that was never coming. The auto_more option
    cleared the pager and reached neither of the other two, and it is stored per
    character, so it reverted every time the autoplayer started a new one.

    While an autoplayer holds the keyboard, the host now answers the prompt itself:
    one ESCAPE through the same input door every real keystroke goes through, logged
    each time. That is upstream's own mechanism in this shell's terms - its borg
    installs itself as the hook inkey() consults for every key the game reads, and
    answers a -more- with a space before it thinks about a move at all. Nothing is
    answered before there is a game to play, so character creation still belongs to
    the player. See docs/modding/BORG.md.

  • CellView.trap reported a locked door as a trap, and an agent that believed
    it hung.
    The field meant "this grid holds any trap record", and a closed door's
    lock IS a trap record - so is a glyph of warding, a web and a decoy. The disarm
    command wants a VISIBLE PLAYER trap and refuses anything else without spending a
    turn, so an autoplayer that walked up to a locked door disarmed it forever with
    game time frozen. The field is now square_isdisarmabletrap, the same predicate
    disarm tests and the trap layer draws from.

    It also closes a leak: an undetected trap used to be readable through this
    field, on a view whose own rule is that a trap the player has not found is not on
    the screen and therefore not in the view. This is a behaviour change for any mod
    reading CellView.trap
    ; see docs/modding/MOD_COMPATIBILITY.md.

  • debug:spawn would have carried a second debug: capability along with it.
    The grant check compared the capability's kind and not its action, which was
    correct by accident while spawn was the only debug action. Adding a second one
    would have meant a player who agreed to a mod conjuring one monster had also
    agreed to the depth jumps, the experience grants and the acquirement. The action
    is now compared, as it already was for mod: and ui:.

  • ^X pauses on the way out, and shows the score it would have scored. Saving and
    quitting a living character prints "Press Return (or Escape)." and waits, and
    then, unless that key was Escape, opens the character's would-be Hall of Fame
    entry - "Killed by nobody (yet!)" - to page through before the game leaves. Both
    are what close_game does once playing goes false, and the port had neither:
    it wrote the save and went straight to the title screen, so the last thing a
    quitting player saw was the dungeon. Neither pause asks a question or can cancel
    the quit, and the preview writes nothing to the score table - a score is only
    ever entered at a real death. ^S, which saves without quitting, is unchanged,
    because save_game stops at the save.

  • The ? command summary is lib/help's own again, keyset for keyset. It had
    been a curated subset written when most of upstream's keyset was unbound here,
    and the port has bound all of it since: the page had drifted into saying that
    S saves the game and V shows the hall of fame (they are See abilities and
    Display version info, as upstream), listed a p that recites prayers, and
    omitted the staircase keys that the playing-guide page beside it explains. It
    now prints commands.txt or r_comm.txt according to rogue_like_commands,
    the way do_cmd_help chooses between index.txt and r_index.txt, and names
    the keyset in its title. The rows this build has nothing behind are named
    once, above the table, instead of being left out of it.

  • The symbol legend prints symbols.txt, two glyphs to a line as the file has
    them, with the two paragraphs a curated version had dropped: / identifies a
    symbol here and user pref files load here, so both were true all along.
    Flattening the file into one column had also dropped upstream's x - row and
    slid "Xorn/Xaren" onto the lowercase x, so the legend named a glyph no xorn
    has.

  • The help index offers "Available symbols", index.txt's own wording, and
    carries upstream's pointer to the manual - which had been sitting at the foot
    of the command summary, 56 rows below where a player looks for it.

  • V prints the Angband copyright notice, which is what upstream's
    do_cmd_version puts on that screen and which this build had replaced with
    credits alone.

  • The targeting banner uses target_display_help's own wording again ("r
    displays details", "+ and - cycle through places", "t targets
    selection").


Found something that does not match Angband 4.2.6? Open an issue or come and say so in the Discord.