Skip to content

Determinism and vanilla parity

Status: Implemented for the guarantees below. Game random sources and Java-semantics maths helpers for mods, and clight test parity checks, are Planned.

Celestial’s core promise is that unmodded play is identical to 26.3: visuals, physics, game logic, timing and packets. CLight keeps that promise and asks mods to stay predictable themselves.

Guarantees CLight makes

  1. No mods means vanilla. Event dispatch and mixin targets cost one branch when unused and never change results. A mixin that passes everything through leaves the game bit-identical.
  2. Data-only mods behave like data packs. A data mod gives the same results as the same files used as a vanilla resource or data pack.
  3. Fixed handler order. Callbacks run in priority order, then mod load order (dependencies first, ties by id). That order doesn’t depend on install order, file timestamps or thread timing.
  4. Vanilla order for vanilla randomness. Where CLight shows a mod a random value before the game uses it (the xp of block.break), it rolls a preview on a copy of the random source, so the game’s own sequence stays exactly vanilla’s.

What mods should do

Don’t let your own randomness change vanilla’s

Vanilla’s random consumption order matters: an extra draw changes mob behaviour, loot and worldgen. CLight doesn’t give mods access to the game’s random sources yet, so a mod can’t disturb them. For your own gameplay randomness use Lua’s math.random (a separate generator per mod) or your own generator in C/C++, never one shared with the game.

Use game time, not wall time

Use cl_level_game_time(level) / cl_level_day_time(level) and tick counts for gameplay timers (Lua level:game_time()). cl_time_now_ns() and os.clock() are for profiling and UI animation only.

Floating point

WebAssembly floating point is strict IEEE (like Java’s), and the SDK compiles without fast-math, so a vanilla formula ported to C or C++ gives the same results. Keep it that way: don’t add -ffast-math to custom.clight_build.cflags.

Don’t depend on hash-map order

If your output depends on iteration order (spreading something to neighbours, picking “the first” player), iterate in a defined order. In Lua, pairs order is not defined; use arrays and ipairs, or sort the keys.

Planned

  • The game’s random sources (level random, positional worldgen forks) and bit-identical ports of vanilla’s generators.
  • Java-semantics maths helpers (the sine table, floor division, String#hashCode).
  • clight test: run a scenario twice and diff the results, golden worldgen digests, and a “mod installed but unused” baseline.