- Java 73.8%
- Python 19.1%
- Shell 3.7%
- Nix 3.4%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
| compat/piratecraft-ships-sable | ||
| config | ||
| crew-screen-proof | ||
| docs | ||
| mods | ||
| scripts | ||
| tools | ||
| .gitignore | ||
| .packwizignore | ||
| AGENTS.md | ||
| flake.lock | ||
| flake.nix | ||
| index.toml | ||
| pack.toml | ||
| README.md | ||
| renovate.json | ||
| TODO.md | ||
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:
- One-time install: import
http://foundry.gentoo-matrix.ts.net:8081/Piratecraft-0.4.0-sable.2.mrpackwith 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.jarlands at.minecraft/packwiz-installer-bootstrap.jar, and the first-party addon JAR atoverrides/mods/piratecraft-ships-sable-0.3.0-sable.1.jar. - 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 athttp://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. - Ongoing updates:
packwiz-installer-bootstrappolls the samehttp://foundry.gentoo-matrix.ts.net:8081/packwiz/pack.tomlURL 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 inindex.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-5122fbd81d17e91…) andoverrides/packwiz-installer-bootstrap.jar(98,989 B, SHA-256a8fbb24dc604…). The addon JAR is built deterministically fromcompat/piratecraft-ships-sable/source plustools/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.jarand injected into the server/client runtimes, the NixOS module, andclient-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-chaincbc-spikeclientclient-compatibilityclient-smokeclient-world-smokefresh-policymetadatamodulenormal-worldpack-serverpackwiz-runtimeplaytest-serverprismsailingserverstandalone-policystrict-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
- Runs
nix build --out-link "$ROOT/result" "path:$REPO#server" --option builders ''for the current checkout. - Reads the freshly built launcher to resolve the
piratecraft-server-runtime-*pack (stripping the literal trailing/$pathfrom the prepare script'sln -stargets) and copiesconfig/mods/defaultconfigsout 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. - Writes
eula.txtandserver.propertiesatomically 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. - 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). - Waits (bounded, default 300s) for a
Done (line written after the launch (a byte offset recorded inserver.staterejects 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. stopsignals only a verified-owned process (pid +/procstarttime + uid + host identity reject PID reuse), waitsPLAYTEST_STOP_TIMEOUT(default 30s), escalates to SIGKILL, and clears state. Failures terminate only a server started by that invocation and always preserveconsole.logandworld.
State, world, and safety
- Runtime root
$ROOTdefaults 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 — onlyconfig/mods/defaultconfigsare refreshed, and never while a server is running. - One
flock-based lock per root fences concurrent lifecycle commands; repeatedstartwhile running reports the same PID and does not rebuild. - The default port is
25565(the standard Minecraft port).statusprints the PID, root, port, ANSI-cleaned latestDone (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 asSTALEand cleared onstop, 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
- Builds
path:$REPO#packwiz-site,path:$REPO#mrpack, andpath:$REPO#packwiz-installer-bootstrapwith the caller's nix (--option builders '', no network builders). The bootstrap jar is pinned inflake.nixviapackwizInstallerBootstrapFor(pkgs.fetchurl, SRIsha256-qPuyTcYEJ46X9GiOgtPZGjGLmO/AjV2/y8vKtkQ9EWw=; 98,989 bytes, SHA-256a8fbb24dc604278e97f4688e82d3d91a318b98efc08d5dbfcbcbcab6443d116c). The mrpack build is hermetic: the addon's manifest size/hash come from the localfinalJarForoutput (packwiz resolves it from a pre-seeded content cache), never from the live public host. - 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 atmods/piratecraft-ships-sable-0.3.0-sable.1.jar), the exact unique mrpack from the immutable#mrpackoutput (filename must match the pack version declared inpack.toml; size/SHA-256 are derived from that fresh output), andpackwiz-installer-bootstrap.jar— both at the root. Nothing from a prior launch is preserved; the old root is fully discarded. - Detaches
python3 -m http.serverwithsetsid(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. - Before reporting
OK, verifies over loopback HTTP: HEAD 200 on/packwiz/pack.toml,/Piratecraft-<version>.mrpack, and/packwiz-installer-bootstrap.jar; GETpack.tomlparses 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.jarmatches the fresh-output size and SHA-512; and GET mrpack/testing-instance/bootstrap bytes match the fresh-output size/SHA-256. stopsignals only a verified-owned process (pid +/procstarttime + uid + host identity reject PID reuse), waitsPACK_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.mrpackformat 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
$ROOTdefaults 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/servedon 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; repeatedstartwith the same configuration while healthy is an idempotent no-op (same PID, no rebuild, one listener); a configuration mismatch or unhealthy state requiresrestart. - Ports
25565and25566are 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. statususes 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:
- The addon JAR is built reproducibly by the
addonderivation: a nixpkgsmitm-cachereplay proxy serves every dependency from the content-addressedcompat/piratecraft-ships-sable/deps.json, andgradle clean test jarruns 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).createbigcannonsis a required BOTH-side dependency (neoforge.mods.toml). - The
addon-assetsderivation 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. - 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 andTHIRD_PARTY_NOTICES.txtembedded. - The resulting
piratecraft-ships-sable-0.3.0-sable.1.jaris embedded once inserverFilesFor/clientFilesFor(and therefore the NixOS module and both runtime packs), injected into the Prism.mrpackoverrides next to the pinned bootstrap jar, indexed as one of 43 Packwiz metafiles (its public bytes served by the pack host), and validated byclient-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.