No description
  • Java 73.8%
  • Python 19.1%
  • Shell 3.7%
  • Nix 3.4%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
Matt b33c3f4cc6
All checks were successful
buildbot/nix-eval Build done.
buildbot/nix-build Build done.
buildbot/nix-effects Build done.
fix(dev): restore minimized UI editor
2026-08-25 23:09:45 -04:00
compat/piratecraft-ships-sable fix(dev): restore minimized UI editor 2026-08-25 23:09:45 -04:00
config fix(ships): make operator access bypass opt-in 2026-08-16 20:11:38 -04:00
crew-screen-proof fix(ships): make crew screen layout responsive 2026-08-20 18:26:19 -04:00
docs fix(npc): verify and preserve sailor split assignment 2026-08-18 19:18:33 -04:00
mods chore(dev): isolate LDLib2 UI authoring 2026-08-23 16:32:58 -04:00
scripts fix(dev): restore minimized UI editor 2026-08-25 23:09:45 -04:00
tools fix(ships): fit converted wheels into helm bases 2026-08-16 18:57:14 -04:00
.gitignore scrub: drop Fusion experiment attribution and machine-specific runtime paths 2026-08-10 19:14:24 -04:00
.packwizignore feat(ui): add LDLib2 authoring support 2026-08-23 01:22:38 -04:00
AGENTS.md docs: keep task decisions out of agent policy 2026-08-22 01:03:18 -04:00
flake.lock feat: initialize piratecraft modpack 2026-08-02 01:17:56 -04:00
flake.nix fix(dev): restore minimized UI editor 2026-08-25 23:09:45 -04:00
index.toml chore(dev): isolate LDLib2 UI authoring 2026-08-23 16:32:58 -04:00
pack.toml chore(dev): isolate LDLib2 UI authoring 2026-08-23 16:32:58 -04:00
README.md fix(dev): restore minimized UI editor 2026-08-25 23:09:45 -04:00
renovate.json wip: migrate to Forge pirate ship stack 2026-08-03 17:44:54 -04:00
TODO.md fix(dev): restore minimized UI editor 2026-08-25 23:09:45 -04:00

Piratecraft (Sable experiment)

Piratecraft 0.4.0-sable.2 is an experimental pirate RPG modpack on Minecraft 1.21.1, NeoForge 21.1.228, and Java 21. The experiment is strict sailing only: Sable 2.0.3 supplies walkable, block-built sub-level physics, and the first-party water-only sailing addon (compat/piratecraft-ships-sable/, piratecraft-ships-sable-0.3.0-sable.1.jar) is built reproducibly at flake build time and injected into every runtime and package surface (and indexed as a Packwiz source with its public bytes served by the pack host). Create 6.0.10 and Create Big Cannons 5.11.7 (with Ritchie's Projectile Library 2.1.2) provide the cannon ecosystem. No Valkyrien Skies, Eureka, Valkyrien Pirates, VLib, Kotlin-for-Forge, VS Sails, Create Aeronautics, Simulated, Offroad, or any vehicle/airship/aircraft/car mod is active.

This replaces the historical 0.3.0-beta.x Minecraft 1.20.1/Forge/Valkyrien Skies Ships baseline. Its source and playtest evidence are retained in Git history. The last working Sable pack, v0.2.0-beta.2 (MC 1.21.1 / NeoForge 21.1.228 / Sable 2.0.3), is the reference for this migration.

Core stack

Component Version
Minecraft 1.21.1
NeoForge 21.1.228
Java 21
Sable 2.0.3 (sable-neoforge-1.21.1-2.0.3.jar)
Create 6.0.10
Create Big Cannons 5.11.7
Ritchie's Projectile Library 2.1.2
JEI 19.39.0.372 (client; pinned)
Jade Sable Compat 1.2.1 (client)

Strict-sailing policy

The pack's control surface is the Paxi datapack config/paxi/datapacks/piratecraft-sailing-era-policy/ (MC 1.21.1 pack_format 34). It removes every Create Big Cannons recipe except a positive allowlist (re-added from CBC 5.11.7), removes Lightman's Currency automation recipes, and disables Create's modern-propulsion, rail/train, minecart-vehicle, elevator, gantry, and generic mobile-contraption content: propeller, steam engine, steam whistle, train tracks, track station/signal, controller rail, schedules, the sequenced track recipe, cart assembler, minecart coupling and contraption carts, railway casing, track observer, train door/trapdoor, gantry carriage/shaft, elevator pulley, flywheel, and contraption control/portable interface blocks. VS/Eureka balloon and engine denials were removed as dead when Eureka left the pack. A second Paxi datapack, config/paxi/datapacks/piratecraft-dungeons-and-taverns-fix/, restores the Dungeons and Taverns 4.4.4 quest-trader trade advancement and disables the broken wandering-trader map trigger.

The strict-sailing flake check statically enforces the theme: pack identity (MC 1.21.1 / NeoForge 21.1.228 / 0.4.0-sable.2), absence of every banned vehicle/physics mod artifact, presence of the required Sable/Create/CBC/RPL/JEI pins, the policy datapack's propulsion/vehicle denials, and (when supplied the Create 6.0.10 jar) that every denial maps to an exact jar recipe ID and no banned-item recipe escapes.

Distribution

The client distribution format is a launcher-native Prism .mrpack, served by the repo-owned pack host (see Pack host below). The operator flow is:

  1. One-time install: import http://foundry.gentoo-matrix.ts.net:8081/Piratecraft-0.4.0-sable.2.mrpack with Prism's Add Instance then Import flow and run the resulting instance on Java 21. Prism installs Minecraft 1.21.1, NeoForge 21.1.228, the applicable manifest files directly from that archive, and both embedded overrides: overrides/packwiz-installer-bootstrap.jar lands at .minecraft/packwiz-installer-bootstrap.jar, and the first-party addon JAR at overrides/mods/piratecraft-ships-sable-0.3.0-sable.1.jar.
  2. First-run bootstrap: the instance's NeoForge runs the bootstrapped packwiz-installer-bootstrap.jar (http://foundry.gentoo-matrix.ts.net:8081/packwiz-installer-bootstrap.jar) pointed at http://foundry.gentoo-matrix.ts.net:8081/packwiz/pack.toml (the operator-confirmed prelaunch URL; the Packwiz site lives under /packwiz/) to install the pack, including the indexed addon source fetched from /packwiz/mods/piratecraft-ships-sable-0.3.0-sable.1.jar.
  3. Ongoing updates: packwiz-installer-bootstrap polls the same http://foundry.gentoo-matrix.ts.net:8081/packwiz/pack.toml URL on launch and applies any pack changes, so a rebuilt pack reaches clients automatically on their next start.

The four player-facing URLs are served by scripts/pack-server (default port 8081, the port the former hermes-owned download service used; it is now owned by the repo's pack host). The Packwiz site is mounted under /packwiz/; the mrpack and bootstrap jar stay at the root. The same host also serves the separate developer-only UI-authoring ZIP described below.

Current source accounting is exact (PIRA-009 baseline):

  • 43 Packwiz metadata entries: 42 upstream + the first-party addon (mods/piratecraft-ships-sable.pw.toml), all indexed in index.toml
  • 34 server-applicable upstream mods (35 resolved server JARs with the addon)
  • 40 client-applicable upstream mods (41 resolved client JARs with the addon)
  • 86 indexed static policy/config files, including the shipped production config/piratecraft_ships-server.toml
  • Chunky and spark are the only upstream server-only mods
  • Exactly two first-party embedded JAR overrides in the .mrpack: overrides/mods/piratecraft-ships-sable-0.3.0-sable.1.jar (911,038 B, SHA-512 2fbd81d17e91…) and overrides/packwiz-installer-bootstrap.jar (98,989 B, SHA-256 a8fbb24dc604…). The addon JAR is built deterministically from compat/piratecraft-ships-sable/ source plus tools/sable-ship-converter/-generated ship assets; its manifest entry's fileSize/SHA-512 derive from that local build (never the live host), and the same bytes are served at /packwiz/mods/piratecraft-ships-sable-0.3.0-sable.1.jar and injected into the server/client runtimes, the NixOS module, and client-smoke.

Nix packages and apps

Use an explicit path:$PWD reference so commands cannot resolve an unrelated parent flake. The flake currently targets x86_64-linux.

nix build "path:$PWD#server" --option builders ''
nix build "path:$PWD#modpack" --option builders ''
nix build "path:$PWD#client-modpack" --option builders ''
nix build "path:$PWD#prism" --option builders ''
nix build "path:$PWD#ui-authoring-instance" --option builders ''
nix build "path:$PWD#packwiz-site" --option builders ''
nix build "path:$PWD#packwiz-installer-bootstrap" --option builders ''
nix build "path:$PWD#addon" --option builders ''
nix build "path:$PWD#addon-assets" --option builders ''
nix run "path:$PWD#client-smoke"
nix run "path:$PWD#client-world-smoke"
nix run "path:$PWD#update-mods"
nix run "path:$PWD#playtest-server" -- start
nix run "path:$PWD#pack-server" -- start

default aliases the NeoForge server launcher, and mrpack aliases prism. addon is the deterministic merged piratecraft-ships-sable-0.3.0-sable.1.jar; addon-assets is the converter staging tree (13 templates, 141 naval-cannon anchors at 16 light / 38 medium / 87 heavy, 7 loot tables, worldgen, catalog/report/notices). client-smoke launches a clean real Minecraft client under Xvfb and Mesa llvmpipe and retains the fast title-screen gate. client-world-smoke uses the same pinned client but drives empty saves through the default-normal Create World UI, integrated-server world entry, and a clean save/exit. update-mods refreshes metadata and fixed-output hashes. playtest-server manages a temporary on-demand playtest server (see Playtest server below); it is also exported as the packages.<system>.playtest-server package. pack-server serves the Packwiz site, Prism mrpack, auto-updating testing instance, and pinned bootstrap jar from the current checkout (see Pack host below); packwiz-installer-bootstrap is the pinned packwiz-installer-bootstrap-0.0.3.jar used by Prism instances at first boot, exported as a package so the host resolves it through Nix.

Checks

The seventeen exported checks are:

  • ammo-chain
  • cbc-spike
  • client
  • client-compatibility
  • client-smoke
  • client-world-smoke
  • fresh-policy
  • metadata
  • module
  • normal-world
  • pack-server
  • packwiz-runtime
  • playtest-server
  • prism
  • sailing
  • server
  • standalone-policy
  • strict-sailing

server is a deep headless runtime check, not a vacuous boot smoke. It boots the Java-21-pinned NeoForge 1.21.1 server twice under the freshly built runtime pack and drives real converted ships (scripts/sable-runtime-check.py, server scenario): it places and assembles the 1238-block anetum-contatum in a controlled deep-ocean flat world, asserts active Sable physics, persistent force-loading, finite mass/pose, helm/cannon counts, and full water contact; places a barnacle-hopper on a dry stone platform and asserts the water gate yields zero propulsion; sweeps the throttle and asserts bounded monotonic speed (≈3.1 → ≈6.2 → ≈9.3 m/s); fires a naval cannon (typed CBC munition) and asserts finite projectile/recoil bookkeeping; anchors the resting ship and asserts it holds position and stays afloat with buoyancy; then saves/restarts and asserts the same UUID, state, counts, notch, and anchor with working post-restart physics and firing. The scenario also drives the PIRA-002 regression: it locates the assembled ship's ship_core at plot-local coordinates and replays the same gated server-side interaction request repeatedly (5x in boot1, 3x after restart), asserting every duplicate is rejected before gather/assembly (admitted=0), one same-UUID 1246-block ship survives with unchanged counts, no new ship UUID appears, and no removed-sub-level/watchdog/crash-report signature appears. Both boots also assert the JVM is 21, the exact Sable/Create/CBC/RPL/addon versions load, the policy datapack is enabled, no banned mod loads, and there are no datapack/registry/loot/template parse errors, NaN/Infinity, or physics exceptions.

sailing is the deep superset scenario: it additionally assembles the largest converted template, the 5133-block Phantom Leviathan, asserts its exact block count and 17 cannons, sweeps every notch (Furled ≈ 0 → Full), exercises bounded steering (finite inertia-scaled torque, speed stays under the 2× cap), and fires a broadside with shot/recoil bookkeeping (20 PASS / 0 FAIL).

cbc-spike is the isolated diagnostic scenario: it boots a stock player-built CBC artillery fixture and verifies stock CBC cannons still work while the generated fleet does not use them (85 PASS / 0 FAIL).

ammo-chain is the deep munitions scenario: it materializes the runtime with cannonAmmoConsumption=true and drives typed munitions fire (SOLID/GRAPESHOT/ CHAIN), atomic magazine reservations (partial/full/empty/mismatch), chain-shot rigging damage (9.30 -> 5.11 m/s scale), two-boot ammo and rigging persistence, and asserts observed muzzle speed ~2.25 blocks/tick with a bounded vertical ratio (103 PASS / 0 FAIL).

normal-world is the dedicated fresh-generation regression. From an empty writable runtime it omits level-type and generator-settings, uses the characterized seed 123456789, reaches Done (, dwells, accepts list and save-all flush, saves overworld/nether/end, and stops cleanly with no crash report, leaked process, bind/init failure, or reported null-overworld shutdown NPE. PIRA-003 established that the original crash was not a generation defect: the earliest exception was a duplicate server failing to bind an occupied port, followed by vanilla shutdown trying to save before levels existed.

fresh-policy is the PIRA-005 fresh-first-boot policy regression. From empty isolated roots it materializes the final production pack in production mode (the pack SHIPS config/piratecraft_ships-server.toml, so no test probe/ammo rewrite and no backup removal happen), then boots a fresh flat deep-ocean world and a fresh default-normal world and restarts one root without rematerializing. Every boot must have the shipped config present pre-launch with production values (cannonAmmoConsumption=true, probeEnabled=false), both Paxi datapacks enabled, no Couldn't load advancements error, exactly Loaded 3590 advancements / Loaded 3971 recipes, save-all flush ok, clean stop, and the shipped config untouched (no rewrite, no .bak). This is the regression for the fresh-boot A/B where an absent ships config let the startup config-write/reload interleaving leave the sailing-era recipe denials and the Dungeons-and-Taverns advancement overrides unapplied (3588 advancements / 3982 recipes + the advancement error); shipping the complete generated default removes that interleaving by construction.

client-world-smoke independently covers the integrated path with the real client under Xvfb/llvmpipe. Starting with no saves directory, pinned HMC-Specifics clicks Singleplayer, names and creates the default-normal world, accepts the pack's experimental-data confirmation, waits for the offline player to join and the in-world HUD to render, then quits. Postconditions require a real level.dat and region, Sable pipelines and clean saves for all three dimensions, normal integrated shutdown, no precursor/NPE/crash report, and no leaked game process. This complements rather than weakens the original client-smoke title-screen test.

playtest-server is a focused, sandboxed lifecycle check: it runs bash -n and ShellCheck on scripts/playtest-server, then drives the real start/stop/ restart/status state machine against fake Nix/runtime/server fixtures through both the direct checkout script and the packaged flake app (scripts/test_playtest_server.py). It exercises writable store materialization, protocol readiness, idempotence, restart world-preservation, stale/reused-PID ownership fences, escalation, and leak-free cleanup without root, network downloads, a real modpack boot, port 8081, or leftover processes. Only the default-port test binds the playtest default 25565.

pack-server is a focused, sandboxed content/lifecycle check: it runs bash -n and ShellCheck on scripts/pack-server, then drives the real start/stop/restart/status state machine against fake Nix/content fixtures through both the direct checkout script and the packaged flake app (scripts/test_pack_server.py). It exercises four-output rebuilds (packwiz-site, mrpack, testing-instance, packwiz-installer-bootstrap), real loopback HTTP HEAD/GET integrity on free ports (/packwiz/pack.toml, constrained index hash, every indexed file digest, the addon metafile's constrained public URL with the served addon's exact size/SHA-512, and root mrpack, testing instance, and bootstrap size/SHA-256), idempotent and concurrent start, ordered restart with content replacement, stale/reused-PID and foreign-UID ownership fences, escalation, tamper detection (mrpack/testing-instance/bootstrap/index/addon/metafile), missing/tampered addon rejection, and leak-free cleanup without root, downloads, a production listener, port 8081, or leftover processes.

The strict-sailing, metadata, standalone-policy, client, client-compatibility, module, prism, client-smoke, client-world-smoke, normal-world, fresh-policy, server, sailing, cbc-spike, ammo-chain, pack-server, packwiz-runtime, and playtest-server checks are all green. Run a focused check directly, for example:

nix build "path:$PWD#checks.x86_64-linux.strict-sailing" --option builders '' --print-build-logs
nix build "path:$PWD#checks.x86_64-linux.normal-world" --option builders '' --print-build-logs
nix build "path:$PWD#checks.x86_64-linux.fresh-policy" --option builders '' --print-build-logs
nix build "path:$PWD#checks.x86_64-linux.client-world-smoke" --option builders '' --print-build-logs
nix build "path:$PWD#checks.x86_64-linux.server" --option builders '' --print-build-logs
nix build "path:$PWD#checks.x86_64-linux.sailing" --option builders '' --print-build-logs
nix build "path:$PWD#checks.x86_64-linux.cbc-spike" --option builders '' --print-build-logs
nix build "path:$PWD#checks.x86_64-linux.ammo-chain" --option builders '' --print-build-logs
nix build "path:$PWD#checks.x86_64-linux.pack-server" --option builders '' --print-build-logs
nix build "path:$PWD#checks.x86_64-linux.packwiz-runtime" --option builders '' --print-build-logs
nix build "path:$PWD#checks.x86_64-linux.playtest-server" --option builders '' --print-build-logs

Playtest server

playtest-server brings up a fresh, disposable Sable playtest server on demand from the current checkout — it is not a persistent daemon. Its default port is 25565 (the standard Minecraft port; the former Hermes deployment that used it was torn down).

nix run "path:$PWD#playtest-server" -- start     # build + materialize + boot + verify
nix run "path:$PWD#playtest-server" -- status    # RUNNING/BOOTING/STALE/STOPPED + ping health
nix run "path:$PWD#playtest-server" -- restart   # stop -> confirmed exit -> rebuild -> boot
nix run "path:$PWD#playtest-server" -- stop      # SIGTERM, escalate after bound, clear state

Exactly one action is accepted. The flake app targets the operator's live checkout (the directory nix run is invoked from); running from elsewhere needs PLAYTEST_REPO=/path/to/checkout.

What it does

  1. Runs nix build --out-link "$ROOT/result" "path:$REPO#server" --option builders '' for the current checkout.
  2. Reads the freshly built launcher to resolve the piratecraft-server-runtime-* pack (stripping the literal trailing /$path from the prepare script's ln -s targets) and copies config/mods/defaultconfigs out of the read-only nix store into a writable runtime — never symlinked, so first-boot config writes (config/fml.toml, config/spark/tmp-client/ — pre-created) succeed.
  3. Writes eula.txt and server.properties atomically with the verified structure-free deep-ocean flat world (bedrock 1 / water 63, the only world type that boots cleanly on 0.4.0-sable.2 fresh creation), enable-status=true, and the requested defaults/overrides.
  4. Boots the NeoForge server detached with setsid (own session, stdin closed, console retained in $ROOT/console.log; survives agent sessions but not a host reboot — no root, systemd, cron, init, or reboot registration).
  5. Waits (bounded, default 300s) for a Done ( line written after the launch (a byte offset recorded in server.state rejects stale lines from earlier boots), then sends a real Minecraft Server List Ping (VarInt-framed handshake + status request) and requires a valid framed JSON response before reporting success. TCP-connect-only or malformed responses are fatal.
  6. stop signals only a verified-owned process (pid + /proc starttime + uid + host identity reject PID reuse), waits PLAYTEST_STOP_TIMEOUT (default 30s), escalates to SIGKILL, and clears state. Failures terminate only a server started by that invocation and always preserve console.log and world.

State, world, and safety

  • Runtime root $ROOT defaults to a repo-relative path, $REPO_ROOT/.runtime/playtest-server (derived from the checkout, so the manager works from any absolute path); the world ($ROOT/world) is preserved across restarts — only config/mods/defaultconfigs are refreshed, and never while a server is running.
  • One flock-based lock per root fences concurrent lifecycle commands; repeated start while running reports the same PID and does not rebuild.
  • The default port is 25565 (the standard Minecraft port). status prints the PID, root, port, ANSI-cleaned latest Done ( line, ping health, and advertised host:port.
  • State lives in $ROOT/server.state (PID, starttime, uid, host, log offset); stale or reused PIDs are reported as STALE and cleared on stop, never signaled.

Environment overrides

Variable Default Meaning
PLAYTEST_ROOT $REPO_ROOT/.runtime/playtest-server runtime dir
PLAYTEST_PORT 25565 listen port
PLAYTEST_SEED 6742032180467263851 world seed
PLAYTEST_MOTD Piratecraft playtest server (auto-managed) MOTD
PLAYTEST_ONLINE_MODE true true or false
PLAYTEST_HOST foundry.gentoo-matrix.ts.net advertised address only
PLAYTEST_REPO script checkout / $PWD (app) checkout to build
PLAYTEST_NIX PATH lookup nix binary
PLAYTEST_PACK_STORE /nix/store store prefix the prepare script references
PLAYTEST_BOOT_TIMEOUT 300 seconds to wait for Done (
PLAYTEST_PING_TIMEOUT 5 seconds per status ping
PLAYTEST_PING_RETRIES 20 ping attempts after Done (
PLAYTEST_STOP_TIMEOUT 30 seconds before SIGKILL escalation

Invalid ports, non-boolean PLAYTEST_ONLINE_MODE, and newline-bearing property values are rejected before anything is mutated.

Pack host

pack-server is the repo-owned static pack host. It serves everything the operator's Prism client needs to install and auto-update Piratecraft — the Packwiz site tree, the Prism mrpack, the auto-updating testing instance, and the first-run bootstrap jar — from a disposable root that is fully rebuilt from the current checkout on every stopped-to-running launch. The former hermes-owned :8081 download service was torn down; this tool owns the path now.

nix run "path:$PWD#pack-server" -- start     # build + stage + serve + verify
nix run "path:$PWD#pack-server" -- status    # RUNNING/UNHEALTHY/STALE/STOPPED + health
nix run "path:$PWD#pack-server" -- restart   # stop -> confirmed exit -> rebuild -> serve
nix run "path:$PWD#pack-server" -- stop      # SIGTERM, escalate after bound, clear state

Exactly one action is accepted. The flake app targets the operator's live checkout (the directory nix run is invoked from); running from elsewhere needs PACK_REPO=/path/to/checkout.

What it does

  1. Builds path:$REPO#packwiz-site, path:$REPO#mrpack, and path:$REPO#packwiz-installer-bootstrap with the caller's nix (--option builders '', no network builders). The bootstrap jar is pinned in flake.nix via packwizInstallerBootstrapFor (pkgs.fetchurl, SRI sha256-qPuyTcYEJ46X9GiOgtPZGjGLmO/AjV2/y8vKtkQ9EWw=; 98,989 bytes, SHA-256 a8fbb24dc604278e97f4688e82d3d91a318b98efc08d5dbfcbcbcab6443d116c). The mrpack build is hermetic: the addon's manifest size/hash come from the local finalJarFor output (packwiz resolves it from a pre-seeded content cache), never from the live public host.
  2. Stages a wholly fresh served root: the complete Packwiz tree mounted under served/packwiz/ (pack.toml, index.toml, every indexed file, and the served addon JAR at mods/piratecraft-ships-sable-0.3.0-sable.1.jar), the exact unique mrpack from the immutable #mrpack output (filename must match the pack version declared in pack.toml; size/SHA-256 are derived from that fresh output), and packwiz-installer-bootstrap.jar — both at the root. Nothing from a prior launch is preserved; the old root is fully discarded.
  3. Detaches python3 -m http.server with setsid (own session, stdin closed, access log in $ROOT/access.log; survives agent sessions but not a host reboot — no root, systemd, cron, init, or reboot registration). Only the served root is reachable; manager state/log/lock/out-links live outside it.
  4. Before reporting OK, verifies over loopback HTTP: HEAD 200 on /packwiz/pack.toml, /Piratecraft-<version>.mrpack, and /packwiz-installer-bootstrap.jar; GET pack.toml parses its [index] declaration and the served index digests to the declared hash (only supported hash-format); every indexed path under /packwiz/ HEADs 200, is a constrained relative path, and its GET bytes digest to the index entry hash; the served addon metafile's public URL is constrained (http(s), path equal to the served addon location) and GET of /packwiz/mods/piratecraft-ships-sable-0.3.0-sable.1.jar matches the fresh-output size and SHA-512; and GET mrpack/testing-instance/bootstrap bytes match the fresh-output size/SHA-256.
  5. stop signals only a verified-owned process (pid + /proc starttime + uid + host identity reject PID reuse), waits PACK_STOP_TIMEOUT (default 30s), escalates to SIGKILL, and clears state. Failures terminate only a server started by that invocation and discard only that invocation's staged root; the access log is preserved.

Tester and operator URLs

  • https://foundry.gentoo-matrix.ts.net/piratecraft/Piratecraft-0.4.0-sable.2-testing.zip — recommended Prism/MultiMC testing instance. Import it as a ZIP; its pinned Packwiz bootstrap runs before every launch and updates from the URL below.
  • https://foundry.gentoo-matrix.ts.net/piratecraft/Piratecraft-0.4.0-sable.2.mrpack — one-time Modrinth-format import. The .mrpack format cannot carry Prism's required pre-launch command and therefore is not the auto-updating artifact.
  • https://foundry.gentoo-matrix.ts.net/piratecraft/packwiz/pack.toml — public Packwiz update URL used by the testing instance.
  • https://foundry.gentoo-matrix.ts.net/piratecraft/packwiz/mods/piratecraft-ships-sable-0.3.0-sable.1.jar — the indexed first-party addon JAR

The local pack server continues to listen on port 8081. Tailscale Funnel publishes only the /piratecraft HTTPS path; direct port 8081 is not the tester-facing contract.

State and safety

  • Runtime root $ROOT defaults to a repo-relative path, $REPO_ROOT/.runtime/pack-server (derived from the checkout, so the manager works from any absolute path); the served root is a fresh $ROOT/served on every launch, and the state file records PID/starttime/uid/host, bind/port/advertised host, the served-root identity digest, and the exact mrpack/testing-instance/bootstrap/addon sizes and hashes (archives/bootstrap SHA-256, addon SHA-512) that verification is held to.
  • One flock-based lock per root fences concurrent lifecycle commands; repeated start with the same configuration while healthy is an idempotent no-op (same PID, no rebuild, one listener); a configuration mismatch or unhealthy state requires restart.
  • Ports 25565 and 25566 are rejected for binding (25565 is the playtest server's default). The host never reads, locks, signals, restarts, binds, or modifies the playtest server/root/world.
  • status uses the recorded configuration and content identities, reruns the full HEAD + GET/integrity checks, prints the operator URLs (prefixed packwiz site plus root mrpack/testing-instance/bootstrap) only when healthy, and returns nonzero for STALE or UNHEALTHY.

Environment overrides

Variable Default Meaning
PACK_ROOT $REPO_ROOT/.runtime/pack-server runtime dir
PACK_PORT 8081 listen port (25565/25566 rejected)
PACK_BIND 0.0.0.0 bind address
PACK_HOST foundry.gentoo-matrix.ts.net advertised address only
PACK_REPO script checkout / $PWD (app) checkout to build
PACK_NIX PATH lookup nix binary
PACK_VERIFY_TIMEOUT 10 seconds bound per verify run
PACK_READY_RETRIES 30 max verify attempts after launch
PACK_READY_INTERVAL 1 seconds between readiness attempts
PACK_STOP_TIMEOUT 30 seconds before SIGKILL escalation

Invalid ports, newline-bearing values, and non-numeric timeouts are rejected before anything is mutated.

Server operation

Normal server operation uses Minecraft TCP port 25565 with online authentication (online-mode=true). The NixOS module supplies those defaults and opens the TCP firewall port. Operators may override module defaults, but should preserve authenticated access unless they are running an explicitly isolated check harness. Piratecraft does not accept Mojang's EULA; the consuming NixOS configuration must set services.minecraft-servers.eula = true after the operator accepts it.

Addon integration

The first-party water-only sailing addon (compat/piratecraft-ships-sable/) is wired in end-to-end:

  1. The addon JAR is built reproducibly by the addon derivation: a nixpkgs mitm-cache replay proxy serves every dependency from the content-addressed compat/piratecraft-ships-sable/deps.json, and gradle clean test jar runs under Java 21 with the full 171-test suite (state lazy hydration/reconcile, persistent Sable force-load tickets, exact template-mask placement, body-local/inertia physics, bounded steering, buoyant anchor, corrected moving-cannon velocity, collision/recoil fixes, typed munitions and magazine/chain-shot math). createbigcannons is a required BOTH-side dependency (neoforge.mods.toml).
  2. The addon-assets derivation runs the 79-test converter suite, converts the pinned Pirates 1.9.3 templates to 1.21.1, and asserts 13 templates / 13 cores / 13 helms / 141 cannons (16 light / 38 medium / 87 heavy) / zero issues / zero generated CBC fixed mounts or tubes.
  3. The final jar derivation merges the converter staging tree into the fresh addon jar, normalizes timestamps/order, and asserts the exact contract (13 structure/ship/*.nbt, catalog 13/141, neoforge.mods.toml, class major 65, no nested jars, no banned mod references, 7 schema-valid loot tables) with the MIT license and THIRD_PARTY_NOTICES.txt embedded.
  4. The resulting piratecraft-ships-sable-0.3.0-sable.1.jar is embedded once in serverFilesFor/clientFilesFor (and therefore the NixOS module and both runtime packs), injected into the Prism .mrpack overrides next to the pinned bootstrap jar, indexed as one of 43 Packwiz metafiles (its public bytes served by the pack host), and validated by client-smoke (41 resolved / 42 discovered jars).

Because the addon is now a real Packwiz/Modrinth-style source (its metafile is indexed and its JAR is served by the pack host), the metafile count is 43 (42 upstream + the addon); the static-file count stays 86 (66 original policy files plus 16 new Create denials, three Dungeons & Taverns fix files, and the shipped config/piratecraft_ships-server.toml). The addon is excluded from the fetch-based runtime mappings (no duplicate install, no live-host fetch), so resolved jars stay 35 server / 41 client. The shipped config is the exact NeoForge-generated production default (cannonAmmoConsumption=true, probeEnabled=false), so Paxi's policy datapacks are effective from the very first boot on every fresh install (see the fresh-policy check below).

Visual UI authoring

LDLib2 2.2.36.a is a pinned developer tool, not a production modpack feature. The production Packwiz index, client/server runtimes, ordinary testing instance, and first-party addon exclude it. Import the separate Piratecraft-0.4.0-sable.2-ui-authoring.zip Prism/MultiMC instance, enter a single-player world, and press F10. The editor opens the preconverted piratecraft_crew_manage UI Template directly and fits the complete 840x600 baseline into the preview; no blank-template recreation is required. The native minimize button hides the editor without destroying its state; press F10 to restore the same editor window, template tab, zoom, and unsaved changes. Press F10 again to close the editor through LDLib2's Save/Discard/Cancel lifecycle. If you manually open another template tab and leave it dirty, F10 keeps the editor open rather than discarding it; save or close that tab first. A small helper JAR provides that workflow only inside the authoring instance and is absent from the ordinary client, server, Packwiz index, and first-party addon.

The editable seed is .minecraft/ldlib2/assets/ldlib2/resources/global/piratecraft_crew_manage.ui.nbt. It reproduces the current owner/empty Crew Manage screen, includes sample dynamic rows, and preserves stable IDs for every runtime-bound label, list, input, button, permission, status, and response action. Less common selected-member, recipient-response, pending-transfer, and error controls are grouped under the hidden state_variants tree node so they can be designed without cluttering the baseline preview. The helper creates the seed only when it is missing and never overwrites designer edits. Save the open template normally, then return this .ui.nbt and any custom textures for Java/network wiring.

The instance also disables FTB Library's sidebar locally so it cannot cover LDLib2's File/View controls; production FTB settings remain unchanged. The shipped modpack has no PirateCraft editor command or editor control. Commit selected templates and assets only after their runtime data and actions are implemented and the real-client visual gate passes.

Naval cannons

The generated fleet uses 141 one-block piratecraft_ships:naval_cannon anchors, each carrying facing and gun_class (light/medium/heavy, distribution 16/38/87). Zero CBC fixed mounts or tubes are generated and all 13 source structure bounds are preserved (anetum-contatum 1238 blocks / 4 guns; the-phantom-leviathan 5133 blocks / 17 guns). The client renders each anchor through a block entity renderer that composes CBC fixed-mount/chamber/barrel models by reference: one logical block with a roughly two-block visual footprint and a short recoil animation, with no CBC block entities, no contraption assembly, and no copied CBC art. The renderer's original +0.5 X pivot/part placement was the root cause of the visual offset; the tube is now centered at x=0 with the chamber over the anchor and the barrel one block forward, raised according to CBC fixed-mount geometry, using the brighter period-valid CBC bronze chamber/barrel by reference and sampling light above the mount. A manual appearance/recoil gate remains.

Captain controls are captain-only (direct hand fire is disabled): X fires the selected bank, V cycles SOLID/GRAPESHOT/CHAIN, one volley uses one ammo type, and guns fire with a deterministic bow-to-stern stagger. Production default cannonAmmoConsumption=true scans the ship's on-board vanilla Container block entities deterministically, reserves immediately, and consumes only the selected CBC/custom round (partial volleys supported); false is the explicit unlimited test/config mode. There is no powder, ramming, or redstone.

Firing uses a typed, reflection-free projectile factory: it resolves the selected round to a CBC ProjectileBlock and fires through CBC's public ProjectileBlock.getProjectile + setChargePower + shoot, with CBC sound, plume, and renderer, and directly tested Sable recoil and ship-velocity inheritance. The first-party projectile is fail-safe only. The ballistics hotfix root cause was that the 45 m/s muzzle velocity plus the Sable ship's velocity were passed to CBC as blocks/tick (20x too fast); the entire world m/s vector is now converted by /20 before Projectile.shoot and the first-party fallback. The +0.15 (8.5 degree) elevation offset is kept pending playtest. Runtime observed solid/grape/chain muzzle speed is 2.251 blocks/tick with a vertical ratio ~0.13-0.15; visible arc/feel remains a manual gate. Solid keeps stock CBC behaviour, and grapeshot's stock fanout (25 pellets x 20-tick lifetime, catastrophic at 63-72 blocks/tick pellet velocities) is intentionally overridden to 8 pellets, spread .12, lifetime 10, so the corrected pellet reach is bounded (a manual severe-lag retest remains). Custom chain shot applies a persisted 30-second rigging effect that stacks 1-3 levels of 15% propulsion and steering reduction (max 45%) with no physical sail blocks. Stock player-built CBC artillery still works; the fleet simply does not use it. The current candidate prismPackHash is sha256-RgwrdvJJRgjdP/z8j4Y5FXGfROryAJfJGrdvKsd8gyg=; its distribution artifact is Piratecraft-0.4.0-sable.2.mrpack. The integrated revision is published by the pack host (see Pack host); the earlier cannon-aim8 playtest candidate (Piratecraft-0.4.0-sable.1-cannon-aim8.mrpack, 606569 bytes, SHA-256 638c092de5c0ecda8834b834ecb47599017055312e4c279d94068b073601233a) is historical and superseded. The pre-1.0 Sable playtest runtime is managed by scripts/playtest-server (default root $REPO_ROOT/.runtime/playtest-server, see Playtest server); the pack host rejects port 25565 because it is the playtest server's default (d85636e).

Documentation

  • Handoff and TODO is the authoritative current engineering status, evidence ledger, validation record, blockers, and ordered work. It also keeps the still-open manual qualification gates inline.
  • Agent guide defines the supported platform, writable playtest boundary, pack-change procedure, build/check contract, and prohibited stacks.
  • Naval-cannon corrective plan is the implementation-level record for the completed cannon milestone.

When documentation conflicts during this experimental migration, use current source, flake.nix, index.toml, AGENTS.md, and TODO.md as the controlling facts. Writable playtest paths and deployment/backup responsibilities are operator-owned; build checks use isolated temporary roots and never mutate the deployed port 25565 world.