Skip to content

Building

castledking edited this page Oct 8, 2026 · 1 revision

Building from source

The important part

The pack is generated at build time from the upstream mod, not hand-maintained. You do not edit pack/configuration/*.yml — those are build output and committing changes to them means the next build discards them.

Two places that are hand-maintained:

file what it is
tools/build-config.release.yml which pieces are generated, and how
src/main/resources/config.yml the plugin's own runtime settings

Requirements

  • Java 25 (to build; the jar it produces runs on Java 21+)
  • Python 3 with pyyaml and pillow
  • Network access on the first build, to fetch the upstream mod

Build

./gradlew build

That fetches upstream, runs the generator, and assembles the jar with the pack inside. ./gradlew regeneratePack re-runs just the generator.

Diagnostics

Seven commands exist behind a JVM argument and a permission. They are not features and are deliberately absent from the player-facing Commands page — but they are how the design decisions below were measured rather than guessed, so they are worth knowing about if you are reading this page.

Enable with a system property, because it is read during onLoad() before the plugin data folder exists:

java -Dcmb.debug=true -jar paper.jar

The argument registers the commands; each also requires the cmb.debug permission to run.

command what it measures
/cmbvshape vanilla collision shapes. This is what justified the wall family being furniture: nothing vanilla collides like a standing half-block, and a bell — the only exact match — covers four states that would have to be taken from real bells
/cmbcut what a stonecutter offers for a given item, asked of a real one. This is what showed a CMB stonecutter recipe was also accepting vanilla blocks, which is why it now takes only its own ingredient
/cmbvslab vertical slab placement, including the double-to-block merge
/cmbstairs the stairs state matrix. Also runs automatically on CraftEngineReloadEvent
/cmb1b does the live server match the generated pack? Expectations come from the pack's own intermediate/content.json and allocation.json — which carrier each block was assigned, which states it declares. Scoped to cubes and pillars, since a cube failure is systemic rather than per-block. Deliberately not a second source of truth
/cmbpieces dumps the served pieces
/cmbcraft crafting behaviour

Three non-command diagnostics come on with the same property:

  • OwnershipVerifier — logs ownership verifier active (-Dcmb.debug=true) and writes plugins/CMB/block-overrides.json. Dumps the actual blockstate bindings after CraftEngine has parsed its packs, and compares them against what the pre-parse scan found. Catches the silent case: a binding from another pack that CMB never discovered looks identical to a clean allocation until the server logs a bind failure.
  • RegistryTimingProbe — how long CraftEngine's registry work takes.
  • StairsMatrixCommand — the automatic run on CraftEngine reload.

If a design decision on this wiki looks arbitrary, one of these is usually why. Running /cmbvshape and /cmbcut before proposing a change tends to save the argument.

Upstream pin

upstream/upstream.properties:

repo=cinchtail/Cinchs_Missing_Blocks
branch=Multi-Loader-1.21.1
version=4.4.0
commit=f7035f29f082616ed44dc7e3b355265aa203c34f
content_sha256=47166b3117ecc3df4645063b6f679b2a682ae5d9fd0c7871743f3f09851c67aa

Never edit that by hand — move it with:

python3 tools/upstream.py bump

The upstream-watch workflow does this and opens a PR when the upstream branch moves. The content_sha256 covers the files the generator reads, so the GitHub copy and the bundled fallback are provably the same content.

Turning pieces on and off

tools/build-config.release.yml:

features:
  vertical-slabs:
    cmb: true
    vanilla: true
    disabled: []
    stonecutting: true
    doubles: both

  horizontal-stairs:
    cmb: true
    vanilla: true
    disabled: []
    stonecutting: true

  furniture-fallback: true

Adding a stonecutting: true emits one stonecutter recipe per piece, one output each, alongside the existing shaped recipe. Chiseled sources are skipped — vanilla does not cut its own chiseled stone bricks either.

Gates

The build fails rather than shipping a pack that is subtly wrong. The checks cover:

gate what it proves
carrier_eligibility no borrowed state is multipart or collision-unsafe
carrier_invariant claimed states are released, lendable and distinct
block_schema every declared variant resolves
model_references every model named by a blockstate exists
l10n_references every translation key referenced is declared
native_functionality drops, recipes and mining tags survive the port
deferred_references nothing emitted names a deferred block
yaml_sizes no generated file is over CraftEngine's limit

Count check

996 pieces / 347 blocks / 649 furniture / 1,822 items / 1,383 recipes is derived from the pack, not typed into the docs. The check counts both trees and subtracts the 268 doubled ids that appear in both, and fails on drift.

This matters because 268 doubled ids exist in the block tree and the furniture tree. Counting both without subtracting gives 1,264, which was a real bug here for three rounds of correction before the overlap was found rather than the arithmetic.

Adding a piece

  1. Add or adjust config in tools/build-config.release.yml.
  2. ./gradlew regeneratePack
  3. Confirm the summary's counts and that no gate failed.
  4. Update the docs only if a count actually changed — the check will tell you.

Do not hand-edit anything under pack/.

Clone this wiki locally