Contributing: Architecture and Patterns
This page documents the contributor-facing architecture of Kore and the implementation patterns worth reusing.
It is intentionally focused on the parts that help you answer two questions quickly:
- Which module should I edit?
- Which project pattern should I follow instead of inventing a new one?
Project tree at a glance
This tree is intentionally small. It reflects the modules contributors should reason about first; local-only or git-excluded sandboxes are omitted on purpose.
How the main modules relate
bindings/imports datapacks and emits Kotlin bindings for resources and registries.build-logic/centralizes shared Gradle conventions and project metadata.generation/transforms upstream Minecraft data into generated Kotlin/resources consumed bykore/.helpers/andoop/layer higher-level APIs on top of the core DSL.kore/is the main DSL and runtime surface most contributors touch first.website/documents both the public API and contributor workflows.
Module boundaries and edit surfaces
bindings/
- Purpose: datapack importer and Kotlin binding generator.
- Typical flow: explorer -> normalized entities -> writer output.
- Common edit surface:
explorer.kt,entities.kt,writer.kt, then tests underbindings/src/test. - Pattern to preserve: single-namespace packs stay compact, multi-namespace packs become namespace-nested objects, and worldgen content is grouped under
Worldgen.
build-logic/
- Purpose: shared Gradle conventions, publishing logic, and project metadata.
- Common edit surface: convention plugins and
Project.kt. - Edit here when changing build behavior, publication rules, or project versioning.
generation/
- Purpose: source-data processing and generated Kotlin/resource output.
- Edit here when a generated enum, registry wrapper, or source-derived structure is wrong.
- Never fix a generation issue by editing
kore/src/main/generatedorbuild/generated/...directly.
helpers/
- Purpose: optional higher-level helpers built on the core DSL.
- Edit here only when the issue explicitly targets helper abstractions or reusable convenience APIs.
- Mirror core DSL patterns instead of creating a parallel architecture.
kore/
- Purpose: core DSL, typed arguments, command wrappers, serializers, worldgen builders, and data-driven resources.
- Common edit surface: feature classes,
DataPackregistration,Functionextensions, serializers, and tests underkore/src/test. - A typical change in this module touches one feature family end to end: model, registration, builder entry point, tests, and docs.
oop/
- Purpose: object-oriented abstractions layered on top of core Kore primitives.
- Edit here when the issue is specifically about that façade, not when the underlying DSL itself is wrong.
- Keep naming and behavior aligned with
kore/to avoid divergent APIs.
website/
- Purpose: documentation markdown, docs navigation, and frontend rendering.
- Docs live in
website/src/jsMain/resources/markdown/doc. - Edit this module in the same PR as any user-visible behavior change.
Core patterns to reuse
Argument wrappers and literals
Kore prefers typed wrappers over raw strings.
Argumentis the base abstraction for command-safe value types.- Generated argument wrappers are the preferred representation for registry references.
- Tag variants encode
#namespace:nameconventions. - Literal helpers such as
literal(),int(), andfloat()keep command assembly explicit and safe.
This improves autocomplete quality, reduces malformed command strings, and keeps serialization predictable.
Command wrappers
Command APIs generally live as Function extension functions.
- Build lines with
addLine(command("name", args...)). - Accept typed arguments directly when possible.
- Keep wrappers thin: syntax composition belongs here, domain state belongs in arguments or data classes.
This keeps generated commands deterministic and test-friendly.
Generator pattern
Most data-driven resources in kore follow one consistent model:
- A serializable feature class extends
Generator. - The class defines its
resourceFolder, transientfileName, andgenerateJson(dataPack)implementation. DataPackregisters a typed generator list throughregisterGenerator<T>().- A
DataPackextension function instantiates and registers the feature. - The extension returns a typed
*Argumentfor later references.
Concrete references:
For the procedural version of this pattern, use Contributing: Creating a New Generator.
Serializer strategy
Prefer an existing serializer before creating a new one.
Frequently reused serializers, in alphabetical order:
EitherInlineSerializerInlineAutoSerializerInlinableListSerializerLowercaseSerializerNamespacedPolymorphicSerializerNbtAsJsonSerializerProviderSerializerSinglePropertySimplifierSerializerToStringSerializer
Decision rule:
- Check whether an existing serializer already matches the target JSON shape.
- If not, see whether the model can be expressed with an existing pattern.
- Only then add a new serializer, with focused tests.
Fast heuristics when you are unsure where a change belongs
- Build, publishing, or versioning behavior ->
build-logic/and root Gradle metadata. - Contributor or user-facing explanation ->
website/in the same PR. - Datapack import output shape ->
bindings/pipeline and tests. - Generated enum or registry wrapper shape ->
generation/, then regenerate and updatekore/consumers. - New registry-backed DSL resource -> usually
kore/, plus generation if a new argument wrapper is required.
Testing patterns that speed up contribution
- Feature tests usually assert emitted JSON payloads and generated command/resource lines.
- Module-specific
testDataPack(...)helpers provide compact generation setups. - Serializer tests should focus on roundtrip behavior and JSON or SNBT shape checks.
When you touch generators, the usual path is: model -> DataPack registration -> builder entry point -> tests -> docs.
Documentation pages
Contributor pages are easier to maintain when they link to a single source of truth instead of restating the same rules.
Required frontmatter keys, in alphabetical order:
date-createddate-modifieddescriptionkeywordsnav-titlerootrouteOverridetitle
Keep routes stable, keep navigation intentional, and update entry pages when a new doc should become discoverable.
Why Kore uses these technical choices
- Centralized serializers keep JSON and NBT output stable across modules and make diffs easier to review.
- Generator-first data-driven features keep feature additions mechanical instead of bespoke.
- Tests close to feature families help catch schema drift when Minecraft updates resource definitions.
- Thin command wrappers preserve command predictability while still covering vanilla syntax broadly.
- Type-safe arguments over raw strings reduce command regressions and improve IDE discoverability.
