Archie is a Kotlin-first library mod for Fabric and NeoForge, built on Architectury.
It carries the parts every mod ends up writing for itself — screens, config, packets, saved state, storage capabilities — as one set of Kotlin APIs that behave the same on both loaders.
Here because another mod asked for it? Install it alongside and it does its work out of sight.
Archie runs a real Compose runtime inside Minecraft, so a screen is a function of its state and redraws itself when that state changes.
class MyScreen(menu: MyMenu, inventory: Inventory, title: Component) :
ComposeContainerScreen<MyMenu>(menu, inventory, title) {
init { start { content() } }
@Composable
fun content() {
var amount by remember { mutableStateOf(0) }
ContainerPanel(contentWidth = 18 * 9) {
Column(verticalArrangement = Arrangement.spacedBy(6)) {
Label(Component.literal("Amount: $amount"))
Button(onClick = { amount++ }) { Text(Component.literal("More")) }
Slots("input")
}
}
}
}The widget set covers what a machine GUI needs: Text, Icon, Texture, Divider, Spacer, ProgressBar, FluidTank, EnergyBar; Button, Checkbox, Switch, Radio, Slider, Dropdown, ColorPicker, TextField; and containers from Panel and TabContainer through Scrollable, Collapsible, Table, PannableCanvas and a NodeTreeView for graph-shaped data. Dialogs and multi-step Wizard flows come as primitives to build from.
Modifiers chain the way they do in Compose proper — Modifier.size(...).tooltip(...).onScroll { } —
and layout is Row/Column/Box with real arrangement and alignment. Anything drawn inside a
layer — a modal, a popup editor — participates fully: it takes input first, and it declares its
own tooltip without the host screen knowing it exists.
Themes are data. A ComposableTheme names the textures and colours each widget draws with, loaded
from resource packs, so a pack restyles every Archie screen at once and a mod ships its own look
without forking a widget.
ComposeBlockContainerMenu pairs a block entity with its screen, and BlockEntityStateManager
keeps every field marked @Sync current on the client as it changes. Slots come from real storages
— ArchieItemSlot, ArchieFluidSlot, and menu slots backed by Common Storage Lib — so a slot shows
what the storage holds.
NBTHolder gives a block entity or an item stack typed, delegated fields that read and write NBT on
their own:
class MyBlockEntity(pos: BlockPos, state: BlockState) : NBTBlockEntity(TYPE, pos, state) {
@Sync var progress: Int by field(Int.serializer()) { 0 }
val inventory: ArchieItemStorage by itemField(9)
val jobs: ObservableList<Job> by listField(Job.serializer()) { emptyList() }
}Fields nest, so a whole sub-object round-trips as one field. ObservableList and ObservableMap
persist every mutation made through them. @Sync marks what the client needs. Data attachments
cover state belonging to something you do not own.
A config is a Kotlin object of typed fields. Archie writes the file, generates the Cloth Config screen, and — for a server spec — pushes the values to each client as it joins.
object MyConfig : ConfigContainer(MOD) {
object Gameplay : ConfigSpec.Server(MOD, Component.literal("Gameplay")) {
object Machines : CategorySpec(Component.literal("Machines")) {
var speed by intSlider(
Component.literal("Speed"),
Component.literal("Ticks per operation."),
min = 1, max = 200, default = 20,
)
}
}
}Fields cover booleans, numbers with or without sliders, strings, enums and arbitrary selectors, colours, keybinds, lists, maps, registry entries, and nested specs.
NetworkChannel registers @Serializable Kotlin classes, so a packet is its own wire format.
object MyChannel : NetworkChannel(MOD % "main") {
fun init() {
serverbound(SetSpeedPacket::class) { packet, context -> packet.handleOnServer(context) }
onClient { clientbound(StatusPacket::class) { packet, _ -> packet.handleOnClient() } }
}
}One set of item, fluid and energy storages that expose themselves to whichever capability system the
loader has: exposeItemStorage, exposeFluidStorage and their siblings take a block entity type
and a selector, and the platform difference ends there. Registries, creative tabs and custom model
registration work the same way — declare once, get both loaders.
Two further mods, dev-time only, never on a player's classpath:
archie-datagen— a Kotlin DSL for models, blockstates, lang, tags, recipes, loot tables and advancements, with builders that read as the JSON they produce.archie-gametest— a GameTest harness with assertions, a JUnit bridge, a client-side harness for screens, and screenshot comparison for testing what a GUI actually draws.
repositories {
maven("https://maven.kernelpanicsoft.net/releases")
maven("https://maven.kernelpanicsoft.net/snapshots")
}
dependencies {
// per loader module
modApi("net.kernelpanicsoft.archie:archie-core-fabric:<version>")
// dev-time only
modCompileOnly("net.kernelpanicsoft.archie:archie-core-gametest-fabric:<version>")
modLocalRuntime("net.kernelpanicsoft.archie:archie-core-gametest-fabric:<version>")
}Artifacts are archie-core-{common,fabric,neoforge}, with archie-core-datagen-* and
archie-core-gametest-* alongside them.
Full guides live at docs.kernelpanicsoft.net/Archie and
in docs/.
- Minecraft 1.21.1
- Fabric or NeoForge
- Architectury API, Cloth Config — plus Fabric API and Fabric Language Kotlin on Fabric
Pre-release. Everything above is built and in use by Boilerplate, which is what the API is exercised against — so expect it to keep moving where that use finds rough edges.
Bug reports and design arguments are equally welcome on the issue tracker.
One Gradle build at the repo root. Four products - core/ is the library, datagen/ and
gametest/ are the dev-time mods above, and test/ is a playground used to exercise Archie during
development - each split common/fabric/neoforge and versioned by
Stonecutter, so a project path carries the Minecraft version it
is for:
./gradlew build
./gradlew :core:fabric:1.21.1:runClient
./gradlew :datagen:neoforge:1.21.1:runDatagen
./gradlew :gametest:fabric:1.21.1:runGametest # server-side suite
./gradlew :gametest:fabric:1.21.1:runGametestClient # client GUI harnessPublished artifacts keep the flat names the project paths no longer have:
archie-core-fabric, archie-core-gametest-neoforge, and so on.
Licensed under GPL-3.0-or-later.
