Skip to content

Conventions

Rules for every Kotlin file in redstone_additions/. They exist for the same reason the old Contributing Guidelines did: most data pack bugs are one identifier spelled two ways, or one rule applied in nine places but forgotten in the tenth.

Structure

  • The Kotlin path mirrors the generated path. ns:blocks/<id>/* is written by one file, ra/modules/<module>/<Block>.kt (or a blocks/<block>/ folder when a block needs several files, as in Wires). ra_lib:redstone/* is written by files under ra/lib/redstone/. A function seen in game must map back to a file without searching.
  • Files stay under about 300 lines. Split along generated paths (ElectricFurnace.kt, ElectricFurnaceSmelting.kt, ElectricFurnaceSettings.kt), never into a "misc" file.
  • Registration is separate from emission. Extensions on Ra (fun Ra.registerClock()) declare functions and resources and talk to the registry. private fun Function.process() writes commands into the function being built.
  • Inline by default; make a real function only when something must reference it: a tag, schedule, a macro call, another file, a chat button, or because the beet pack had it as a function players or other packs may call. A Kotlin helper that writes commands into its caller costs nothing at runtime.
  • One object per block, one object per module. object Clock { val block = CustomBlock(...) }; object GatesModule : GameplayModule. Module objects hold order and wiring, not logic.

Identifiers

  • Namespaces come from Ns. Never write "ra_gates" as a string.
  • Shared objectives, tags and storages come from Obj, Tag, Storage (core/Ids.kt). An identifier used by one module is a private const val in that module (ra.clock_on in Clock.kt, the ra.jp.* set in jetpacks/Jp.kt, settings-only objectives in settings/SettingsObjectives.kt).
  • Every identifier written into a world (tag, objective, storage path, data.* key, item component, function path a player may type) is a compatibility contract. Keep the beet spelling exactly; a rename needs a migration in ra.migrations.
  • Version-step names in ra_migrations (5.1.7-to-5.1.8) are identifiers, not the pack version. A version bump never touches them (Release and Versioning).

Function references

  • A function is a value: ref(...), block.fn(...), or an object in ra/api/.
  • Never spell a call to one of the pack's own functions inside a string. Use function(ref), function(ref, nbt { … }), function(ref, Storage.X, "path"). Where the surrounding command must stay vanilla text, take the function as a value anyway:
Helper Package Writes
executeRun(chain, fn, args), executeReturnRun, executeStoreRun, callWith(fn, source), returnRun, macroCall ra.core (Exec.kt) execute <chain> run function <fn>, function <fn> with <source>
line(text, fn, rest…) ra.modules.wires.common (Line.kt) a command with a typed function call inside its chain (execute unless function <fn> run ...); a call at the end is executeRun

In a macro line, build the path from the reference: "\$function ${ref.asId()} …". - The owner of a reference defines it at exactly that path. Another part may keep a private ref to the same path; PackValidator fails the build if the owner never defines it.

Commands: typed first

Use Kore's builders wherever they read at least as clearly as vanilla: execute { }, scoreboard.players.*, scoreboard.objectives.add, tag(...), data(...) { }, function(...), summon, kill, fill, give, schedule, returnValue. Selectors through allEntities { } or the helpers in core/block/Selectors.kt.

raw("…") is accepted for:

  • macro lines ($execute … $(x) …);
  • long SNBT literals copied from the game (display transformations, item stacks, text components);
  • execute chains the builder makes markedly longer than vanilla (anchored eyes positioned ^ ^ ^2 …);
  • commands Kore has no builder for.

raw takes exactly one complete 26.3 command and rejects newlines. A raw command repeated across files is a helper waiting to be written: put it in the part's package, or in core/ if several parts need it.

Ported modules today lean on raw more than these rules aim for (several hundred lines in Wires, mostly long execute if score … run chains). That is allowed by the rules above, and a later pass can convert chains to typed builders file by file, with compare_reference.py proving each conversion changes nothing.

Arithmetic of more than one step — a sum, a clamp, a storage read-modify-write — is one 26.3 /compute built with ra/core/Compute.kt (provider { }, scoreOf, storageOf, computeScore, computeInto, computeStorage), not a chain of scoreboard players operation lines. Write constants as literals in the provider rather than as fake players set at load. Don't hand-write provider SNBT in raw, and don't use Kore's compute { } for it (Developer Guide).

Data-driven files

Use the typed builders (recipesBuilder, advancement, predicate, itemModifier, lootTable, functionTag, blockTag). Generate families of near-identical files from data with one loop:

Family Generated by
ra_jetpacks:fit_<tier>_<n> item modifiers modules/jetpacks/kit/FitModifiers.kt
ra_advancements trees data lists in advancements/CoreTrees.kt, ResourceTrees.kt; one unlock(...) per entry
recipes and their unlock advancements shapedRecipe / blockRecipe / recipeUnlock (core/block/Recipes.kt)
settings admin tree ra.settings.gen
tool and block items ToolItem, CustomBlock, ItemSpec, WiresItem, BaseTier

Comments

The original hand-written source (beet, before the port) was commented with why a line is the way it is ("store result writes zero when the read fails, and zero means every tick"). Those comments are the most valuable thing in it. Keep them as Kotlin comments beside the code they explain, and write new ones the same way:

  • say why, not what; the command already says what;
  • name the bug a line prevents, when there was one;
  • block-capital headings (THE SHAPE THAT MATTERS) for the one rule a reader must not miss.

Comments stay in Kotlin. They are not emitted into the .mcfunction output.

Before you push

  1. ./gradlew koreBuild (strict) passes. Generation also fails on a macro line with no $(variable), whatever the mode (Kore Gotchas).
  2. For a change meant to keep behaviour: copy the previous build/kore/redstone_additions/ aside, rebuild, and run python3 tools/compare_reference.py --reference <copy> <your namespaces> --diff from the repository root; explain every difference in the commit message.
  3. No file over ~300 lines; no string calls to the pack's own functions.