Repository navigation
Building
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 |
- Java 25 (to build; the jar it produces runs on Java 21+)
- Python 3 with
pyyamlandpillow - Network access on the first build, to fetch the upstream mod
./gradlew buildThat fetches upstream, runs the generator, and assembles the jar with the pack inside.
./gradlew regeneratePack re-runs just the generator.
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.jarThe 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— logsownership verifier active (-Dcmb.debug=true)and writesplugins/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/upstream.properties:
repo=cinchtail/Cinchs_Missing_Blocks
branch=Multi-Loader-1.21.1
version=4.4.0
commit=f7035f29f082616ed44dc7e3b355265aa203c34f
content_sha256=47166b3117ecc3df4645063b6f679b2a682ae5d9fd0c7871743f3f09851c67aaNever edit that by hand — move it with:
python3 tools/upstream.py bumpThe 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.
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: trueAdding 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.
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 |
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.
- Add or adjust config in
tools/build-config.release.yml. ./gradlew regeneratePack- Confirm the summary's counts and that no gate failed.
- Update the docs only if a count actually changed — the check will tell you.
Do not hand-edit anything under pack/.