Mod compatibility
Which mods work alongside NoderaMC, which cannot, and the property that decides which is which.
Mirrored from COMPATIBILITY.md.Source last changed Edit COMPATIBILITY.md
The mod-compatibility contract
This file is normative: it fixes what other mods and modpacks can rely on when running alongside
NoderaMC, and what they must not do. It is owned by
engine task 7. The enforcement mechanism is the interference guard (MutationGuard — the single
setBlockState choke point on delegated chunks) plus the delegability policy. The choke point is
live: LevelChunkMixin routes every block write in the game through BlockWriteGuard, which is
inert (one field read) on any server that is not validating a region. Two further mixins suppress
vanilla work the engine owns inside a delegated region — LevelTicksMixin (scheduled ticks) and
ServerLevelRandomTickMixin (the whole chunk random-tick pass, skipped before vanilla consumes
the level RNG). Outside delegated regions all three are no-ops, and Nodera ships no other mixins.
1. Event ordering
Nodera captures/cancels player actions at EventPriority.LOW with receiveCanceled = false.
Protection mods at any earlier priority always win: if a claims/protection mod cancels a
block event before Nodera sees it, Nodera never captures it and the validated lane never learns
it existed.
2. Fake players
Fake players (machine-operated block breakers/placers) never become Nodera actors and never join
committees — block capture refuses them by name (BlockCaptureRules.Reason.FAKE_PLAYER), so a
machine's edit is never signed as somebody's player action. Their effects on delegated regions are foreign writes: converted into certified
external deltas (ServerAuthorityCertificate reason EXTERNAL_MUTATION) in CONVERT mode. A
region with recurring fake-player activity is demoted (FAKE_PLAYER_ACTIVE) and runs pure
vanilla — the machines keep working; the region simply is not validated.
3. Unknown blocks
The binding between the vanilla registry and the palette is VanillaPalette; PaletteMapper reads
a live BlockState through it, and a block with no binding reads as unsupported everywhere the
lane looks (capture refuses it, extraction counts it and records air, the write guard compares it as
air).
Any block outside the Nodera palette makes its region and its neighbor ring
(DELEGABLE_NEIGHBOR_RING) non-delegable (UNSUPPORTED_PALETTE / NEIGHBOR_UNSUPPORTED).
Other mods' machines simply keep vanilla semantics — Nodera never simulates a block it does not
understand, and never delegates a region whose boundary a foreign mechanic could bleed across.
4. Redstone
The validated lane owns redstone in delegated regions (Task 13/14/16, palette v4): wire, torch, repeater, lever, button, pressure plate, piston, sticky piston, redstone block, observer, daylight sensor, comparator, hopper, note block — including quasi-connectivity (a piston also reads power through the cell above it, and only re-evaluates when it receives an update, so BUD behavior emerges from the scheduling model rather than being simulated).
The pressure plate is the one source driven by the entity lane rather than by blocks: it is
pressed while a validated entity's block position is the plate's own, and released after 20 ticks
through the hashed scheduled-tick queue (so the delay is consensus state and survives a delta
boundary). GHOST entities never press a plate — their positions are server-authoritative, and
letting one drive validated state would put a non-validated input into the root. A sticky piston
pulls the block its head was touching back into the vacated cell on retraction; an unmovable
neighbour, a redstone component, or a region border all fail CLOSED and the piston still retracts. Vanilla scheduled ticks for
delegated chunks are cancelled at the source (LevelTicksMixin); a contraption whose signals
cross into a vanilla-lane region demotes its whole group (CONTRAPTION_CROSSES_VANILLA) and
runs pure vanilla — correct, slower — until redelegation.
5. Chunk tickets
Nodera holds delegated chunks loaded with its own ticket type (NODERA_DELEGATED), added on
lease issue and released on revoke. Foreign tickets (ender pearls, portals, other mods' chunk
loaders) are never cancelled — Nodera only adds/removes its own type. Loader-held chunks with
no nearby session player are vanilla lane by policy: the guard does not touch them.
6. What other mods must NOT do
- Mutate delegated chunks from async threads. Async world writes are undefined behaviour even in vanilla; the guard converts main-thread writes only. Async writes into delegated chunks are logged as errors and are outside every compatibility guarantee.
- Depend on same-tick visibility of block changes in delegated regions. Committed effects appear 1–2 ticks after their cause — batch plus quorum latency, which is normative rather than a defect (engine L-16). A mod that reads a delegated block the same tick it caused a change may see the pre-commit state.
7. What other mods CAN rely on
- Main-thread writes into delegated regions are never silently dropped in CONVERT mode (the
default): they land, and Nodera folds them into its version chain as certified external
deltas. STRICT mode (debug/CI:
interference.mode=STRICT) blocks them loudly. - Vanilla-lane regions (anything non-delegated) are untouched: no guard, no capture, no behavioural change.
- Region demotion is graceful: an in-flight validated batch resolves before the region returns
to vanilla execution, and re-delegation waits out
DELEGABILITY_COOLDOWN_TICKS.
8. Entity species — validated parity envelopes (Task 15)
Spawn cycles, mob AI, TNT, projectiles, and minecarts become engine-owned validated state for delegated regions (ledger L-7/L-8/L-9). The contract is player-visible parity, never NMS bit-parity: each species is integer/fixed-point behaviour implemented from observable vanilla, matching what a player sees, never matching a vanilla float trajectory bit-for-bit (that is what makes the lane tractable and replica-deterministic). The envelope per species:
- Spawning (L-8): interval × cap (
SPAWN_INTERVAL_TICKS=20,MOB_CAP=8) reproduces the vanilla hostile rate envelope; gating reads committedLightField(< 8). Engine spawns areGHOSTentities the live lane mirrors like captured mobs. - Mob AI (L-7): seeded wander at a vanilla-ish idle cadence (
AI_INTERVAL_TICKS=10); steps land only on walkable cells. Targeting/combat remain vanilla-side until their increments ship. - TNT (L-9): vanilla fuse (
80) and radius (4); the blast shape is a seeded per-cell destruction sphere (P(destroy)=1−distSq/R²) — a Nodera crater, not vanilla's ray pattern. The blast also knocks back nearby entities (items/projectiles/minecarts and mobs), impulse decaying with squared distance. Chain ignition and border fail-closed (cross-region blast rides the T13 migration lane). - Projectiles (L-9): vanilla-shaped arc (drag
0.99, gravity0.05) in Q32.32; an opaque block stops the shot, and so does a mob within a half-block radius (arrow embedded in a mob). Hit detection marches in ≤1-block sub-steps so a thin wall stops even a fast shot (a true voxel-DDA face-snap + water slow-down is a later refinement). Applying damage to a struck mob is the L-13 lane. - Minecarts (L-9): vanilla top speed (
0.4); a cart follows the rail graph by connectivity (no rail-shape states), powered rails boost, plain rails bleed speed. Slopes/ascent gravity A powered rail boosts only when its cell receives redstone power (a plainREDSTONE_BLOCK/lever/wire beside it); unpowered, it coasts like a plain rail. Slopes/ascent gravity are not yet modelled.
Unretired species keep the ghost fallback (Task 12) — the world is never broken mid-program. A
region reverts to ghosting a species on divergence alarm (the species-retirement rollback, Task
15 acceptance #4). Entity-kind ordinals are wire-stable: ITEM/GHOST bytes are unchanged as
new engine-owned kinds (TNT/PROJECTILE/MINECART) are appended.