Skip to content

Releases: Maskviva/Pier

Pier 26.51.2

Choose a tag to compare

@Maskviva Maskviva released this 10 Oct 13:53

Built for BDS 1.26.51 and LeviLamina 26.51.5, like 26.51.1. PIER_ABI_VERSION stays at 2 and
no slot moved: a mod built against 26.51.1 loads unchanged. pier-sys-rs is 26.51.2 and
pier-rs 2.1.0; nothing public was removed from either (contract §2.5). The Go and Zig
bindings are new in this release.

Added

  • The documentation site runs on MkDocs Material and lives on GitHub Pages. It is laid
    out as the LegacyScriptEngine documentation is: navigation tabs, a section tree on the
    left, every entry of the page on the right. The pages under Common tasks, in the
    Tutorials tab, document one function per entry, with its call form in Rust, Go and Zig,
    its parameters, return value and type, its slot, and an example in each language, in
    English and Chinese; their overview page maps the data types between the languages. The
    home page follows the layout of levimc.org, in Pier's orange: LeviLamina is the green
    leaves and Pier the fruit. The English and Chinese sites build separately, mkdocs build --strict refusing a broken link, and docs.yml publishes both to GitHub Pages whenever a
    release is published.
  • The API tab lists every public interface of each binding, generated from its source.
    One part per binding: the levilamina crate, 792 functions, methods and macros, 117
    types and 76 constants; the Go package, 628 functions and methods, 173 of them on
    Raw, one per slot without a callback, with 62 types and 316 constants; the Zig module,
    305 functions and methods; and sdk/abi.h for C++ mods compiled with the Pier SDK, its
    201 slots and 6 macros with every constant table and type. An entry gives the
    declaration, the source comment, the parameters and return type, and the slots of abi.h
    the interface reaches, found by following its body through the calls whose target the
    text names; each slot on the C++ pages lists its callers in Rust, Go and Zig.
    tools/gen-api-docs.py writes the pages and the API section of both navigations, and the
    api-docs-current check fails when a binding changes and the pages are not generated
    again. Links in both sites are checked down to the heading they name, so a renamed
    interface fails mkdocs build --strict. The Chinese site translates every description:
    1541 units in docs/i18n/zh/api/, each quoting the English comment it translates and
    keyed by that comment's fingerprint, and the same check fails when a comment has no unit
    or a unit's English has left the source. Reading the Go comments against abi.h on the
    way corrected three that named constants abi.h does not have, and Raw methods that had
    taken a section note in place of their slot's own comment.
  • Documentation and comments describe what happens. Headings, slogans and closing
    metaphors that handed over a verdict were rewritten to show the process instead, in the
    READMEs, the design and why pages, the API pages and the binding comments.
    COMMENTS.md section 9 says why, and the prose-tells check fails on the recognizable
    constructions in every Markdown file and comment.
  • The Zig binding, for Zig 0.15.2: the package pier in bindings/zig, exporting the
    module levilamina, with the example examples/hello-pier-zig. translate-c reads abi.h itself, and levilamina.slot("name") is
    any slot's function pointer behind both gates of contract §10, so the whole table is
    reachable from the start. On it: the lifecycle through exportMod, logging, mod-owned
    tasks, events, commands, services and the bus under the same rules as the Rust and Go
    bindings, one method per property and verb on Player, Entity, BlockAt and Item,
    and an SNBT parser with the same rules and depth cap. zig build test makes the compiler
    analyze every function of the binding against the header and runs the parser's tests; a
    zig job in build.yml runs it, checks the formatting and cross-compiles the example to a
    Windows DLL. tools/gen-zig-api.py writes the property methods and the package's copy of
    abi.h, and the zig-binding check fails when either is stale or a slot or constant
    the sources name does not exist. Documented in English and Chinese.
  • The Go binding, imported as github.com/Maskviva/pier/bindings/go/levilamina and
    written levilamina.… in code, with the example
    examples/hello-pier-go. cgo includes abi.h itself, so the table is laid out by the C
    compiler and not mirrored by hand, and every slot is reached through a generated C
    function that applies both gates of contract §10. Its API is within reach of the Rust
    binding's: every slot without a callback is a typed method of pier.Raw, every property
    and verb of the constant tables is a generated method of Player, Entity, BlockAt or
    Item, and hand-written functions cover the server, world, events with payload parsing,
    commands with typed overloads, containers, key-value stores, the economy, forms, packet
    and connection hooks, region scans, bulk block writes, simulated players, custom and
    supplied-terrain dimensions and client keys, with an SNBT parser of the same rules as the
    Rust one. Every slot is reachable but the two of lanes, which pair only mods of one
    toolchain. Its services and bus topics follow the same rules as the Rust binding's, a veto is true and a provider's error text reaches the caller
    unchanged, so it calls and answers mods of any language and native plugins through the
    bridge. Built with go build -buildmode=c-shared and a MinGW-w64 gcc. Documented in English and Chinese.
  • tools/gen-go-slots.py and the go-binding check. The generator writes the
    per-slot C functions and the Go module's copy of abi.h; the check fails when either is
    stale, when Go calls a C function nothing declares, or when the glue hands the host a
    callback no Go file exports. A go job in build.yml formats, vets and builds the binding
    and the example on Windows.
  • Rust: try_ variants for every call that answered "cannot tell" with a value.
    Host::try_pending_tasks, Host::try_is_wine, World::try_villages,
    World::try_structures_near, money::try_history, money::try_ranking,
    money::try_clear_history, sim::try_list, SimPlayer::try_is_simulated and
    KvDb::try_get return a Result, so a host without the slot is an Err and no longer the
    same answer as an empty list, a 0 or a false. The old functions stay, deprecated, with the
    behavior they had (contract §2.5); KvDb::get is not deprecated, since try_get cannot
    tell a closed database from a missing key either.
  • The documentation is organized by reader, the way LeviLamina's is: a user guide, a
    developer guide split into tutorials, how-to guides, FAQ, architecture and API reference,
    a maintainer guide, and the supported versions. New pages: troubleshooting, FAQ, and an
    event payload reference, which documents the attacker fields of death events among the
    rest. Every existing page keeps its address.
  • Contract §2.5: a binding removes nothing while the ABI version stays the same. It
    applies to every binding in every language: an item to retire is deprecated through the
    language's own mechanism, and removed only in the release whose ABI version changes. The
    adding-a-language guide states it for binding authors.
  • The abi-values check keeps every released PIER_* constant and enum member at its
    value, against the baseline tools/abi-v2.values. A renumbering done in abi.h and the
    mirror together passed every check before.
  • comment-style applies the English rule to Rust, Python, TOML and xmake comments,
    where Chinese documentation had been sitting unseen.
  • The synthetic-names check compares the SDK's ALL_SYNTHETIC with the events the
    host registers, in both directions. The list had drifted by one entry with nothing to see
    it.
  • A watchdog over mod code, on by default. Every place Pier runs a mod's code is
    watched: loading and unloading its DLL, pier_main, the lifecycle callbacks and every
    event, command, task, form, bus, service, packet and hook callback. A mod holding a thread
    past watchdog.warn_ms (2000) is named in a warning with the entry and the thread; past
    watchdog.hang_ms (30000), or watchdog.shutdown_ms (10000) once shutdown has begun, the
    process is ended with exit code 70 so a supervisor can restart it. A server that froze on
    a mod, or never finished stopping, now says which mod and comes back. watchdog.enabled
    turns it off; a limit of 0 turns that one step off. Nothing is ended while a debugger is
    attached.
  • pier_bridge_call_sink and pier_bridge_list_sink. The bridge hands a reply over
    whole, so the provider runs once. pier-bridge.h uses them when present and reports
    through Client::runsOnce() whether it can. The bridge ABI stays at 1.
  • registry_list: the engine's block, item and entity registries, one JSON object per
    entry. A block carries its creative category, solidity, shape families, block entity,
    destroy speed, resistance and light; an item its rarity, stack size, creative category and
    whether commands hide it; an entity its spawn egg, summonability, experiment gate and actor
    type bits. In Rust: registry::blocks(), registry::items(), registry::entities(). A mod
    that hands out random content picks by rules over the game's own content instead of keeping
    a list of names.

Changed

  • Every binding is written levilamina in code, while its package names Pier: the Rust
    package pier-rs exposes the crate levilamina, the Go package
    github.com/Maskviva/pier/bindings/go/levilamina is levilamina, the Zig package pier
    exports the module levilamina, and pier-bridge.h puts its classes in
    levilamina::bridge. pier::bridge remains as a deprecated alias of the new namespace
    (contract §2.5), so a native plugin written against it still builds, with a warning under
    MSVC and clang.
  • **The...
Read more

Pier 26.51.1

Choose a tag to compare

@Maskviva Maskviva released this 23 Sep 03:02

Built for BDS 1.26.51 and LeviLamina 26.51.5. A release is built against the one BDS its
number names, so 26.40.3 stays the build for BDS 1.26.40 and does not run on 1.26.51.
None of the engine changes reaches the ABI: PIER_ABI_VERSION stays at 2 and a mod built
against 26.40.3 keeps working without a rebuild. One behaviour does change for mods: an
ability or permission level written before the player has finished joining is now
refused, see below.

Added

  • Literal subcommands in register_command_ex. A parameter of kind text, with the
    word in its text field, declares a literal: a node in the command tree, which is what
    /scoreboard objectives add is made of, and the only form the client narrows as the
    player types. An enum parameter lists its values but is not a tree node, so the
    client had nothing to filter on, and the spec had no way to ask for a literal. The
    handler receives the word under the parameter's name like any other argument, so
    dispatch does not depend on an overload index that moves when an overload is inserted
    above it. Two overloads that differ only by a literal digest differently, so a reload
    cannot rebind one onto the other. In pier-rs this is OverloadBuilder::text(name, literal). A host before 26.51.1 rejects the kind as unknown and does not register the
    command.

Changed

  • Ability and permission-level writes wait until the player has finished joining.
    PIER_PACT_SET_ABILITY and PIER_PACT_SET_PERMISSION_LEVEL return false, with a
    warning in the log, until Player::isPlayerInitialized. Written earlier, the change
    reaches a client that has not built its ability layers yet: server and client then
    disagree, and the player can be left unable to build or attack until they rejoin. The
    write is refused rather than queued, so a caller that tried too early finds out at the
    call instead of seeing the change land at an unrelated moment. Simulated players have
    no client and are exempt. A mod that sets abilities while a player joins has to make
    the write once the player has fully joined.
  • PIER_SRV_PROTOCOL_VERSION knows 1.26.51, which speaks 2193, read from the
    startup banner of a 1.26.51 server like the 1.26.40 row before it.
  • Custom dimension registration passes the whole definition.
    DimensionManager::_registerCustomDimensionWithDimensionDefinitionGroup is gone in
    26.51. Its replacement, _registerDimensionWithDimensionDefinitionGroup, takes a
    complete DimensionDefinition, so the height and the generator go in at registration
    instead of being corrected afterwards. The definition carries the 1.26.50
    minecraft:dimension_height shape, mMinY and mHeightRange, in place of a minimum
    and a maximum. The top of a dimension is mMinY + mHeightRange, and the
    dimension-data trace prints it that way.
  • MobEffectInstance has its two-argument constructor back. 26.51 exports
    MobEffectInstance(id, duration) again and declares the default constructor without
    defining it. Adding an effect builds the instance through that constructor and assigns
    the amplifier and the visibility; every other field is what the engine's constructor
    sets rather than value-initialized.
  • Common::getGameVersionString is gone on the server. PIER_SRV_BDS_VERSION reads
    Common::StringConstants::mGameVersionString instead, built once through
    Common::_buildStringConstants.
  • GameMode::useItemOn takes a HandSlot, and the PlayerUseItemOnEvent hook
    passes it through unchanged. The event payload is unchanged.
  • VoidGenerator takes the default biome as a constructor argument. A void
    dimension passes the biome its spec names, or plains when that one is missing, as
    before.
  • ChunkState lost NeighborAwareUpgradeNeeded and NeighborAwareUpgrading, and
    the chunk trace no longer names them.
  • The Rust binding moves to pier-rs 2.1.0 / pier-sys-rs 26.51.1.

Pier 26.40.3

Choose a tag to compare

@Maskviva Maskviva released this 14 Sep 04:12

Fixed

  • lip install produced a mod directory LeviLamina refuses to load. The release
    archive kept the packer's output directory as an extra level, so the package installed
    as plugins/pier/Pier/. LeviLamina scans one level under plugins/, finds no
    manifest.json in plugins/pier/, and Pier is never loaded.

    Two things were wrong and both are fixed. tooth.json placed the whole archive root
    into plugins/pier/ instead of placing the mod directory into plugins/Pier/. And the
    release workflow tried to strip the wrapping directory by testing for a literal pier,
    which is silently false on a case-sensitive runner where the packer writes Pier.

    The packaging step now finds the mod directory by the manifest.json it holds, renames
    it to the name that manifest states, and refuses to publish an archive whose root is not
    that directory with its dll and its lang/ beside it. The folder name is not cosmetic:
    ModRegistrar.cpp compares it against the manifest name as a std::string, so pier
    and Pier are two different mods and one of them does not exist.

  • manifest.json named pier.dll while the build produces Pier.dll. Windows
    resolves it either way, which is why it went unnoticed; it now says what the file is
    called.

Pier 26.40.2

Choose a tag to compare

@Maskviva Maskviva released this 14 Sep 03:29

Added

  • A mod that is not a Pier mod can call a Pier mod's services. pier_bridge_call,
    pier_bridge_list and pier_bridge_abi are exported from the host, and
    bindings/bridge/pier-bridge.h is a header-only client that binds them. Until now the
    registry was reachable only with a PierModHandle the loader issues, and the only
    exported symbol pointed the other way, so a permission manager whose value is being the
    backend everyone shares was reachable only by mods that had already adopted Pier.

    The registry is unchanged: api_service_call already accepted a null handle and its
    self-call check was already conditional on one. What a bridge caller lacks is identity,
    which is a real limit and is documented as one -- a provider that grants anything on the
    strength of the caller alone must not be reachable this way. See docs/guide/bridge.md.

  • PlayerEditSignEvent and PlayerOperatedItemFrameEvent are raised. Both were
    declared in ALL_SYNTHETIC and listed as names, and no hook in the tree produced
    either, so a subscription succeeded and never fired. A protection mod gating on them
    reported a refusal its own operator could see while the player edited the sign anyway.
    Sign editing goes through SignBlockActor::_playerCanUpdate, which is the engine's own
    gate and is asked before the text is taken from the packet; the item-frame right click
    goes through ItemFrameBlock::use. Both are in CANCELLABLE now.

  • Every player event with a position carries block, and every one with a player
    carries item.
    Injected by the enricher rather than by each hook, because the events
    that need them are LeviLamina's own and have no hook here to extend. A consumer
    subdividing a permission by what was touched had nowhere to get the answer: the payload
    named the action and never the object. block is the block at the event's coordinates
    and item is what the player held. Both are absent rather than empty when they cannot
    be read, since a name invented here is indistinguishable from a real one and a rule
    keyed on it fires on the wrong block.

  • PlayerOpenContainerEvent carries container and block. The payload had only a
    numeric containerType, so a subscriber wanting to tell a chest from a dropper had to
    carry its own copy of a network enum. container is that enum as a word and block is
    the block's type name, which is empty rather than a placeholder when it cannot be read:
    an invented name is indistinguishable from a real one, and a rule keyed on it fires on
    the wrong block.

Fixed

  • PlayerRequestItemActionEvent is declared observation-only. Nothing raises it and
    there is no point at which it can be cancelled; is_cancellable answered None, which
    a caller reads as "unknown" rather than "no". It now answers Some(false) with where to
    block instead, which is what stops a protection mod from treating a subscription to it
    as a working gate.

Changed

  • Translations are files again, not a generated C++ table.
    packages/pier-support/src/LangShipped.inc, tools/embed-lang.py and
    tools/checks/lang_embedded.py are gone. The host reads plugins/Pier/lang/<code>.lang
    and nothing else is compiled in beyond the built-in English, so a language is a file an
    operator drops in and edits rather than a table only a rebuild can change.

    The packaging half that made the table necessary is handled where it belongs: the
    after_build step in xmake.lua names en_US.lang and zh_CN.lang explicitly and
    fails the build when either is absent, so bin/Pier/lang/ carries both and a release
    cannot ship half-translated. A glob alone still succeeds on one file, which is the
    shape that let the directory go missing before.

  • lang/en_US.lang is added and carries every key. It is what a translator copies and
    what an operator edits; i18n_keys.py now fails when it is missing a key, when either
    shipped file is absent, or when a placeholder count disagrees with the English.

Fixed

  • The lang directory never reached an installed server, so every log was English.
    loadLanguages reads getModDir()/lang, modpacker packages the target and
    manifest.json and nothing else, so lang/ sat in the repository and shipped in no
    archive. Nothing reported it: a missing lang directory is not an error, and the one
    line that would have said so only printed when keys loaded.

    The startup line now prints on every boot rather than only when a file was found, and
    reports how many lines the active locale has rather than how many a file contributed.
    Suppressing it when there is no file would leave exactly the gap this closed.

  • PIER_SRV_PROTOCOL_VERSION reported 2169 for BDS 1.26.40, which speaks 2168. The
    row came from a published metadata table rather than from a server; the banner on a
    real 1.26.40.8 reads ProtocolVersion 2168. A consumer mapped the 2169 back through
    its own table and announced the server as 1.26.45 — a confident wrong answer on a
    clean boot, which is the failure the key was rewritten to stop producing. The 1.21.93
    row went with it: it was taken from a save file's NetworkVersion tag, which is the
    same class of unchecked evidence. An unlisted version fails the key, and failing is
    the correct outcome. 2168 covers 1.26.40 through 1.26.44 and 2169 is 1.26.45, but only
    1.26.40 is listed — a range known second-hand is the evidence that produced the wrong
    row in the first place.

  • tools/checks/i18n_keys.py truncated any English value containing {},. It cut
    the value at the first },, which also matches a placeholder followed by a comma, so
    ... as id {}, registry id {} was read as having one placeholder. A correct
    translation was then reported as having too many. It now collects the C++ string
    literals properly.

Added

  • Seven more log keys: the per-dimension boot lines an operator reads to answer "did my
    world come up the way I wrote it" (dim.height.set, dim.registered, dim.ready,
    dim.terrain.layers, dim.terrain.end), and the host's own ready line
    (host.ready). The grid clause of the layers line is its own key rather than a
    hardcoded tail, because a translation that cannot move that clause has to reorder the
    sentence around it.

Added

  • Three events that used to need iListenAttentively are hooked here.
    PlayerEditSignEvent, PlayerOperatedItemFrameEvent and
    PlayerRequestItemActionEvent moved from ALL_FROM_ILA to ALL_SYNTHETIC.

    Each closes a hole rather than adding a convenience. Placing a sign is a block place
    and was covered; editing the text of one already there was neither a place nor an
    interact. Taking an item out of a frame was covered by PlayerAttackItemFrameEvent;
    rotating it was not. Opening an enchanting table is an interact and was covered;
    everything after that arrives on the item-stack request path and was not.

    Taking them off another mod is deliberate. A protection feature that only works when a
    third-party mod happens to be installed is not a protection feature, and the version
    matrix was already BDS × LeviLamina without adding a third pin that this repository
    cannot fix when it lags a BDS release.

    PlayerRequestItemActionEvent reports only the Craft* action types, as an action
    word (craft / creative / anvil / grindstone / loom) rather than the engine's
    enumerator number, which shifts when Mojang inserts a type. Take, place and swap are
    not reported: several arrive per click, and a cross-mod dispatch on that path would
    cost more than it protects.

Pier 26.40.1

Choose a tag to compare

@Maskviva Maskviva released this 13 Sep 10:10

Built for BDS 1.26.40 and LeviLamina 26.40.0. Two fixes, both of the same shape: a value
that was wrong rather than missing, and therefore acted on.

PIER_ABI_VERSION stays at 2. The two new server_info_str keys are additive — a mod
built against 26.40.0 keeps working without a rebuild, though see the note on
PIER_SRV_PROTOCOL_VERSION below before relying on the number it used to return.

Fixed

  • A per-player sidebar stopped updating after its first send. PIER_PACT_SIDEBAR_SET
    updates a sidebar of unchanged row count in place, sending only the rows whose text
    differs. The client stores a row as scoreboard id -> (name, score) and reads a
    ChangeFakePlayerScore for an id it already knows as a score update, not a rename: the
    number moved, the label did not. So the first send was correct and every later one
    silently did nothing.

    A changed row now releases its id with a RemoveScore before the ChangeFakePlayerScore
    re-adds it. The removes go out as their own packet ahead of the changes: nothing here can
    verify that a client applies one packet's entries in order, and a remove applied after
    the re-add blanks the row instead of updating it. If the second packet cannot be created
    after the removes have gone out, the cached state is dropped so the next call rebuilds
    from scratch — keeping it would leave those rows blank until the row count changed.

  • PIER_SRV_PROTOCOL_VERSION reported the save file's protocol, not the server's. It
    read LevelData::mNetworkVersion, which is a storage tag sitting among
    mStorageVersion, mMinCompatibleClientVersion and mLastOpenedWithVersion, and
    records the version the level was last written by. A 1.21.93 world opened on a 1.26.40
    server answered 819. This looked correct for as long as the test machine's world had
    been created by the server running it.

    It is now derived from SharedConstants::CurrentGameSemVersion, which unlike
    NetworkProtocolVersion is not behind #ifdef LL_PLAT_C and so is readable on a
    dedicated server, mapped through a table of confirmed versions.

    On a version the table does not list, and on any pre-release build, the key now
    fails.
    That is deliberate. The old behaviour was not a crash, it was a confident wrong
    answer, and a caller handed a plausible number acts on it. A pre-release shares its
    major.minor.patch with the retail build but not its protocol — 1.26.40-beta.0 speaks
    2168 where retail 1.26.40 speaks 2169 — so a build carrying a mPreRelease is refused
    rather than answered from a retail row. The table matches the exact triple: Bedrock
    bumps the protocol on patch releases, so matching on major.minor would return a number
    from the wrong end of a release line.

Added

  • PIER_SRV_LEVEL_PROTOCOL_VERSION, the LevelData::mNetworkVersion value under the
    name it deserves. It answers how old the save is, which is a real question and exactly
    what the old key was accidentally answering. Host::level_protocol_version on the Rust
    side.

  • PIER_SRV_GAME_SEM_VERSION, "major.minor.patch" of the running build, so a mod can
    carry its own version-to-protocol table. A Pier release should not be on the critical
    path for a mod supporting a BDS that shipped yesterday. Host::game_sem_version on the
    Rust side.

  • pier-probe covers server_info_str keys 0 through 3. A Refused on key 1 is recorded
    as the correct answer for an unlisted version rather than as a fault; comparing it
    against key 2 shows directly when a save is older than the server.

Added

  • A mod can supply its own terrain, and this host stops trying to understand it.
    md_add_dimension_generated registers a dimension whose spec carries seed, height and
    sky and no terrain section at all; the mod hands over two palettes, resolved once here
    against the block and biome registries, and a callback the chunk workers call with a
    buffer of indices. md_set_dimension_cells gives the confinement rules the grid the
    mod generated, since the hooks are engine hooks but the geometry is not. What crosses
    the boundary per chunk is indices and never names: a chunk is 98304 entries and looking
    names up per chunk is the difference between a generator and a stall. The threading
    contract on PierGenerateChunkFn is the opposite of the rest of this file and is
    written out there rather than left to the default.

    A payload with no terrain section is what says the terrain is a mod's, so md_add_dimension
    refuses one -- taking it there would register a dimension nothing fills and report success --
    and a supplied dimension whose mod is gone loads as a void with its chunks intact rather than
    falling through to the pack branch, where reading the terrain as a pack would throw on a chunk
    thread.

  • terrain.kind: layers is served again, and needs no tool and no file. A layer stack,
    optionally cut into a grid of cells and gaps, is what a plot world and a superflat world
    are, and it had been withdrawn in favour of terrain packs built offline with
    tools/pier-pack. A caller now sends the recipe inline, as before, and it is assembled
    into a template pack in memory and handed to the pack path: the chunk fill, the layout
    expansion and the confinement hook underneath are the ones the pack path uses and the
    ones the tests cover, so this adds a front door rather than a second generator.
    pierpack/from_layers.py performs the identical mapping offline and stays the reference.
    Every expression the assembled pack carries is a constant, because the inline recipe
    names concrete numbers, so nothing has to be bound and no parameter can fail to bind.
    A recipe whose border does not fit inside its cell, or whose stack runs past the top of
    the dimension, is refused with the numbers rather than quietly shrunk. terrain.kind: noise is still not served inline: it is a volume pack and not a layer stack, and the
    in-memory assembly does not reach it.

Fixed

  • A setting with no implementation behind it is refused, not recorded. aquifers_enabled
    and ore_veins_enabled each had a bit in the volume pack format and nothing anywhere that
    read it: the builder wrote the flag, the host loaded the pack, and the terrain came out
    without the aquifers or the ore veins its author had asked for, with no line on either
    side. The tool refuses the two keys now, the host refuses a pack whose settings carry
    either bit, and a bit outside the known set is refused as well, so a pack from a newer
    tool cannot be half-honoured. The bit values stay recorded so their meaning is fixed if
    either is ever implemented.
  • A source key the builder does not read is refused. Neither pack builder validated its
    top-level keys, so a misspelled name, or a field copied from a Java datapack that this
    format has no equivalent for, was dropped without a word and the pack was built as though
    the author had not written it. Both builders now carry the list of keys they act on and
    refuse anything else.
  • The shipped terrain packs are built for the world bottom that exists. fixtures/town.json,
    fixtures/plot.json, pierpack/from_layers.py and two of the tool's tests all declared
    a bottom of -512, the value kWorldMinY held before it was lowered to the vanilla -64;
    nothing updated them at the time. A pack made for -512..320 does not fit a dimension of
    -64..320, so mounting one was refused with PIER_PACK_HEIGHT and no world could be built
    from it. The declared range is -64..320 now, the empty bottom layer that ran from -512 to
    the bedrock is gone, and the two equivalence tests mount at the range the packs actually
    carry.
  • A binary the host cannot open names the path it tried. binary in a pack config is
    joined onto the config's own directory, and a config written with a path relative to the
    server root instead produces a doubled prefix; the refusal said only that a file could
    not be opened, which sends the reader to check a path the host never tried.
  • A mistyped field in a spec is a refusal with a reason, not an exception. The
    conversions on CompoundTagVariant are not uniform: a number reached through the wrong
    type throws std::runtime_error, a string does it through std::get and throws
    std::bad_variant_access, and .get<CompoundTag>() does the same for a section. Any of
    them escaped the reader, was caught by the API guard several frames away, and came back
    as a registration that failed naming neither the field, the dimension, nor the recipe.
    Every field is now type-checked before it is read, a wrong one reads as absent and is
    reported, and md_add_dimension names the dimension and the spec text if anything still
    throws.
  • The host's startup line carries a build stamp, so a report can be told apart from one
    taken against the previous binary without asking.
  • md_add_dimension no longer throws on a layers terrain. The slot refused a pack
    terrain and then read the spec as native with std::get, which throws for the third
    alternative rather than falling through. A layers recipe reached that line and the
    registration came back as a refusal with no reason attached, since the throw was caught
    by the API guard. Both served kinds are read with get_if now, and the biome a layers
    recipe names is verified at registration for the same reason the pack path verifies its
    own: TemplateGenerator reports a biome it cannot find as the registry having changed
    underneath it, which is the wrong thing to tell someone who mistyped a name.
  • This host owns the dimensions it registers. The instance ledger held a WeakRef,
    and the engine does not keep an OwnerPtr handed to DimensionRegistry::registerDimension
    alive: the reference the call returned locked, the level listed the dimension, and the
    weak reference was expired again as soon as the frame that built it returned. Every path
    tha...
Read more

Pier 26.40.0

Pier 26.40.0 Pre-release
Pre-release

Choose a tag to compare

@Maskviva Maskviva released this 13 Sep 10:10

Built for BDS 1.26.40 and LeviLamina 26.40.0. A release is built against the one BDS its
number names, so 26.32.2 stays the build for BDS 1.26.32 and does not run on 1.26.40.
The engine changes are small and none of them reaches the ABI: PIER_ABI_VERSION stays
at 2 and a mod built against 26.32.2 keeps working without a rebuild.

Changed

  • Custom dimension registration carries a pack id.
    DimensionManager::_registerCustomDimensionWithDimensionDefinitionGroup takes a third
    argument in 26.40, the resource pack a definition came from, and
    DimensionDefinitionGroup::DimensionDefinition gained the matching mPackId. A Pier
    dimension comes from no pack and registers with a zero UUID.
  • The scoreboard packet became a payload packet. SetScorePacket derives from
    ll::PayloadPacket<SetScorePacketPayload>, its mType is gone, and a row is one
    alternative of std::variant<RemoveScore, ChangePlayerScore, ChangeEntityScore, ChangeFakePlayerScore> instead of a ScorePacketInfo with an identity type field. The
    sidebar builds ChangeFakePlayerScore rows, which is what the identity field said.
  • Player::startSleepInBed takes setsRespawn and sleepOffset, and the sleep hook
    passes both through unchanged.
  • DedicatedServer::runDedicatedServerLoop returns ::ServerExitCode rather than a
    nested one and takes an EditorAllowList; the client-side-generation hook follows.
  • DimensionDataPacket, LevelChunkPacket and SubChunkPacket are payload packets,
    so their fields are inherited and $write has two overloads. The chunk trace names the
    one-argument overload explicitly. LevelChunkPacket no longer carries
    mClientNeedsToRequestSubchunks: a set mClientRequestSubChunkLimit is the request,
    and the trace reports it that way.
  • NetworkChunkPublisher::moveRegion is gone, and every other entry point of that
    class carrying a region sits behind LL_PLAT_C, so none of them is declared on a
    server build. The send-region trace reads mLastChunkUpdatePosition and
    mLastChunkUpdateRadius from inside the queued-chunk hook instead, and reports the
    region when it changes rather than on every call.
  • MobEffectInstance has no constructor of its own. Adding an effect builds the
    instance empty and assigns the id, duration, amplifier and visibility, which is what
    the removed two-argument constructor did.

Removed

  • PIER_BPROP_IS_DOOR and PIER_BPROP_IS_STAIR are unsupported.
    BlockType::isDoorBlock and isStairBlock are gone in 26.40 and nothing replaces
    them; a block tag would be a guess at the name the engine files carry.
  • PIER_APROP_IS_IN_LOVE is unsupported. Actor::isInLove is gone in 26.40 and
    nothing replaces it. The mInLovePartner field survives, and reading it would be a
    guess at what the accessor tested, so the property is reported as unsupported rather
    than answered with a value that may be wrong.

Pier 26.32.2

Pier 26.32.2 Pre-release
Pre-release

Choose a tag to compare

@Maskviva Maskviva released this 13 Sep 10:09

Terrain packs replace the declarative layers and noise terrains, and the ABI moves
to v2. Nothing in this section had shipped in a tagged release, which is why the
removals below are removals and not deprecations.

Changed

  • ABI v2. PIER_ABI_VERSION and PIER_ABI_MIN_SUPPORTED are both 2. The four slots
    retired in 26.20.3, md_add_simple_dimension, md_add_plot_dimension,
    md_set_plot_grid and md_clear_plot_grid, are deleted from PierApi; every slot
    after them moves, so a mod built against v1 is refused at load with the version range
    in the message. tools/abi-v1.slots was re-blessed for v2.
  • Three terrain kinds. md_add_dimension serves terrain:{kind:"native"} only, now
    with generator:"flat" and "void" beside the three vanilla worlds and a biome for
    the void. A layers or noise terrain is refused with a pointer to the pack tool;
    pier-pack from-layers converts an old layers spec into a template source.
  • Dimension spec. The stored form of a pack terrain is
    terrain:{kind, pack, sha256, params, roles}: the config path, the binary's hash at
    registration, every bound parameter and the role overrides, so the terrain is
    regenerable from the save alone and a changed binary is refused rather than mixed in.

Added

  • md_add_dimension_pack(name, config_path, spec_snbt). Registers a dimension from a
    terrain pack: a directory with a config file and a binary. The host reads four keys of
    the config (pier_terrain, type, binary, sha256), compares the spec's terrain
    kind with the config's type and the binary's magic, hashes the binary against the
    config, binds the parameters by kind (free, fixed, choice, derived), checks the pack's
    constraints, and only then stores the spec. Refusals return one of the PIER_PACK_*
    codes, all negative; a dimension id is never negative.

  • md_pack_inspect(config_path, ctx, sink). What a pack asks for as one JSON
    document, the same shape pier-pack inspect prints, so a mod builds its form from the
    pack's own parameter list without a parser of the binary.

  • Template packs (PIERTPL). A fixed range with parameters and cells whose data is
    the same: zones along each axis with expression lengths, a block stack per combined
    zone, constraints, and 3D structures as a CSG graph (box, cylinder, wedge, translate,
    rotate, mirror, repeat, union, difference, intersect, shell, paint, choose by parameter
    or cell hash, voxel blobs with keep and air cells) picked per cell with weights and
    turns. A CONF section registers the cell grid for the confinement rules from the same
    mount that produced the terrain. The plot world is the acceptance fixture and is
    compared cell by cell with a line-for-line port of the 26.20.2 PlotGenerator.

  • Volume packs (PIERVOL). Java's density function graph: 31 operators including
    splines, shifted noise, the caches, interpolation, end islands, the 1.18 blended
    noise and the weird scaled sampler; the multi-noise biome parameter list; and the
    surface rule subset (block, sequence, condition, biome, noise_threshold,
    vertical_gradient, y_above, water, temperature, steep, not, hole,
    above_preliminary_surface, stone_depth). A pack stores noise parameters, never tables:
    the host builds the tables from the world seed with Java's Xoroshiro128++ or legacy
    random source, so one pack serves any seed. pier-pack from-datapack assembles a
    source from a Java datapack directory.

  • tools/pier-pack. build, inspect, hash, from-layers, from-datapack, a
    Python reference generator for each pack kind, and tests that compile the engine-free
    pack layer under g++ and compare it with the reference cell by cell.

  • Rust SDK. dimensions::add_dimension_pack, dimensions::pack_inspect, PackStatus
    and PackError; GeneratorType::spec_name for the spelling a spec uses. The crate is
    2.0.0, since the four removed functions are a breaking change.

  • tools/migrate_dimension_config.py no longer writes a layers terrain. A void or a
    vanilla generator migrates in place as before; a pre-26.20.3 plot entry is left alone and
    the three steps to rebuild it as a template pack are printed, since the terrain of a plot
    world is a binary the host verifies by hash and cannot be written into the config file.

Removed

  • spec::Layers, spec::Noise, LayersGenerator and NoiseGenerator, superseded by
    the two pack kinds; the Rust SDK's add_simple, add_plot, set_plot_grid,
    clear_plot_grid and PlotLayout, superseded by add_dimension_pack and
    pack_inspect.

Known limits

  • The liquid layer of a voxel blob is decoded but not yet written into the chunk's
    second block layer, so waterlogged blocks in a blob come out dry.
  • A volume pack has no aquifers, ore veins or bandlands, and the biome of a column is the
    one at its surface rather than a 3D field; temperature uses the biome's base
    temperature without the vanilla noise term; sea_level of the pack is used for the
    fluid fill but the dimension's own sea level stays at the sky's default.