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 ablocks/<block>/folder when a block needs several files, as in Wires).ra_lib:redstone/*is written by files underra/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 aprivate const valin that module (ra.clock_oninClock.kt, thera.jp.*set injetpacks/Jp.kt, settings-only objectives insettings/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 inra.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 inra/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);
executechains 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¶
./gradlew koreBuild(strict) passes. Generation also fails on a macro line with no$(variable), whatever the mode (Kore Gotchas).- For a change meant to keep behaviour: copy the previous
build/kore/redstone_additions/aside, rebuild, and runpython3 tools/compare_reference.py --reference <copy> <your namespaces> --difffrom the repository root; explain every difference in the commit message. - No file over ~300 lines; no string calls to the pack's own functions.