Compukters Architecture
Product boundary
Compukters is an in-game Kotlin programming platform with two production authoring paths. Programs created inside a
computer use the Rust-owned /home filesystem and the packaged /rom/edit and /rom/kotlinc tools. The client IDE
owns bounded multi-file projects with compukter.toml, an optional compukter.lock, and Kotlin sources beneath src;
it can analyze and compile a project locally, attach to a computer, and deploy the resulting executable.
Both paths produce the same canonical Compukter artifact and execute through the same VM session boundary. A project
must declare exactly one supported top-level entry point: fun main() with either no parameters or one
Array<String> parameter, returning Unit. The standalone playground uses the same compiler artifact and VM session
contracts without the Minecraft carrier.
Platform and K2 tooling
Built-in Guest Kotlin declarations are authored in guest-platform as separately versioned modules. Its build
produces only the canonical base platform bundle consumed by both compilation and IDE analysis. Optional integrations
own and build their Guest declarations as addon bundles against that base; addons/create owns the first such bundle.
The public ru.lazyhat.compukters.addon Gradle plugin resolves an isolated builder, base bundle, Minecraft-independent
host API, and thin target adapter from one independently versioned SDK release by Maven coordinate. The SDK version
changes with its public compatibility boundary rather than every Compukters release. The plugin derives the wire
schema and bindings from Guest Kotlin declarations, validates a checked-in selector lock, generates the typed JVM host
contract, and packages the resulting .cagb in the independent addon JAR.
platform-bundle owns the base bundle model, codec, module graph, and default imports; platform-k2 exposes that
metadata to K2 without making the K2 implementation part of the platform format.
Constant Int and qualified enum-entry defaults cross this bundle explicitly; compiler lowering materializes an
omitted platform argument without a JVM-style mask dispatcher. Other platform default expressions remain unsupported.
Canonical IntRange and IntProgression declarations let K2 resolve ascending, descending, and stepped Int loops.
The compiler recognizes only their canonical for shape and evaluates start, end, and step once into scalar registers;
no range or iterator object enters the VM. It validates a positive step before iteration, calculates the next index in
Long to avoid Int overflow, and retains a loop-header quota safepoint. Stored progressions are outside this subset.
Direct IntArray for loops follow the same canonical-shape rule: the compiler snapshots the array reference, loads
its length, and reads each element with existing array instructions at the current index. The loop header retains its
quota safepoint; no iterator object is created. A source variable may be reassigned without changing the traversed
array, while writes to that array’s elements remain visible to later iterations.
Compiler and analysis workers are pinned, isolated JVM processes. Their payloads are assembled into one bounded
k2-tooling-workers.zip.xz: nested runtime JARs and the carrier ZIP use canonical stored entries, then the complete
ZIP is compressed as one checksummed XZ stream. The outer runtime uses XZ for Java’s pure-Java streaming decoder with
a fixed memory limit and feeds the decoded ZIP directly into bounded, hash-verified, atomic publication.
An external copy of tooling.bundle identifies the expected content-addressed cache directory before the carrier is
opened. A cache hit validates the complete file tree and hashes without decompressing XZ; a corrupt hit is
retained until a replacement has been fully decoded and verified, then replaced with the valid tree.
Kotlin compiler and Analysis API internals stay inside the workers and do not enter the mod runtime classpath.
worker-client owns the generic payload publication, process, framing, deadline, and immutable-byte machinery shared
by both worker clients.
The production mod archive embeds Tomlj and its ANTLR runtime under a private relocated Compukters namespace. They do
not enter NeoForge’s module layer as separate automatic modules and therefore cannot collide with loader-provided
versions; Checker Qual remains compile-only.
The two execution-producing paths are:
In-computer source
-> Rust captures source and output preconditions
-> server-global ServerCompilerService
-> isolated compiler K2 worker
-> server-global persistent artifact cache
-> Rust re-verifies and atomically installs the artifact
Client IDE project
-> bounded project snapshot + manifest + lock + resolved platform profile
-> client compilation service and client-local artifact cache
-> isolated compiler K2 worker
-> attach and verify against a server target
-> revision-checked deployment into the Rust filesystem
-> optional canonical terminal submission to run the executable
Both compilation services send ordered project sources, target settings, worker identity, platform-module identities,
and limits through the same bounded compiler protocol. compiler-k2-engine owns the shared FIR-to-IR and Compukter
lowering implementation; compiler-k2 supplies the isolated compiler-worker entry point and payload.
The client IDE has a separate analysis path. ide-analysis-client owns the bounded protocol, scheduling,
cancellation, and worker lifetime without depending on K2. ide-analysis-k2 owns the isolated incremental K2
workspace and answers diagnostics, completion, symbol, reference, expression, and semantic-token queries. Compilation
and analysis use the same resolved platform bundle and source-snapshot identities, but have separate worker sessions
and result contracts.
Analysis protocol v8 also carries explicit Kotlin format and parameter-information requests. Parameter information resolves the innermost call at a UTF-16 caret into a bounded, deterministically ordered set of K2-substituted callable signatures with active-parameter spans; client-side snapshot, revision, path, caret, and call-range checks reject stale popup results. The format request includes the exact editor text and UTF-16 caret captured by the Reformat Code action, so formatting does not depend on whether the semantic snapshot has caught up. The worker runs ktlint standard rules from a compiler-free nested runtime whose shaded IntelliJ references are relocated back to the ordinary namespace. Its child classloader reloads the worker’s existing Kotlin compiler JAR files with a platform parent: the distribution stores one compiler copy, while formatter and Analysis API retain separate IntelliJ global state. Formatted text is bounded by the negotiated source-file limit; the client applies a current result as one undoable edit and leaves the document dirty for a separate save. Stale results are discarded, and formatter failures warn without changing or saving the source. Ctrl+S, autosave, and implicit saves do not format.
Server compilation
The trusted server JVM owns compiler scheduling, diagnostics, artifact production, and the server-global content-addressed cache. The Rust machine validates guest paths, captures an immutable source snapshot plus filesystem preconditions, and suspends only the requesting foreground process. The server tick submits and polls bounded compiler work without waiting on the worker or cache. On completion Rust re-verifies the complete artifact and atomically installs it only if the source and output preconditions still match. Native buffers remain caller-owned and no Rust pointer enters Kotlin.
The packaged tooling payload is validated and published beneath <world>/compukters/compiler-worker; temporary
worker state is kept separately beneath <world>/compukters/compiler-temp. Successful server artifacts are stored
beneath <world>/compukters/compiler-cache/v1, shared by every computer and dimension in that server world, and reused
after restart. Cache keys cover the ordered source snapshot, compiler and payload identity, target, selected trusted
platform modules, and compilation limits. Cache publication and cache hits both pass the stateless Rust artifact
verifier over FFM; runtime admission quotas are deliberately not part of cache validity.
ServerCompilerService performs bounded asynchronous preparation, persistent-cache lookup, and single-flight
deduplication by compilation identity. Minecraft-facing code submits requests and drains completions; worker and cache
I/O run outside the server tick thread.
Runtime ownership
The asynchronous actor migration (#603) has a server-scoped service registered with NeoForge. Server startup records
its lifetime; workers are allocated on first use. Each pre-tick drains at most 1024 actor results and runs reply
callbacks on the server thread before world and block-entity ticking. Work submitted during one tick therefore has the
full inter-tick interval before the next deterministic delivery boundary. Stopping removes the service before closing
it, so late callbacks cannot reopen it.
Production computers attach one actor identified by ComputerId and a machine epoch. A full scheduler rejects a new
attachment without blocking or failing the server tick; the block remains powered off and retries on a later tick.
The server config exposes vm.workers, vm.maximum_actors, vm.mailbox_capacity, vm.messages_per_turn, and
vm.result_capacity_per_worker; defaults are half of the available processors clamped to 2..8, 4096 actors, 64
commands, 4 commands per turn, and 256 replies per worker. Operators may explicitly configure up to 64 workers.
Increasing queue limits trades bounded memory for burst tolerance; increasing messages per turn trades inter-actor
latency for locality.
Scheduler snapshots read atomic counters and bounded lane sizes without scanning registered actors. Every five seconds the debug log reports registered actors together with configured capacity, runnable actors, mailbox and result depths, worker occupancy, average/maximum command-queue latency, average/maximum execution time, completed-result latency, the last server pump size and duration, deferred world requests, host-completion-to-next-advance delay in server ticks, and rejected or capacity-coalesced redstone input submissions. Pending redstone transitions retain their sampled order while a VM turn is in flight, up to the configured mailbox capacity; overflow coalesces only the newest retained packet and increments the diagnostic counter. These are lifetime counters and gauges for the current server service rather than an equal-CPU or delivery-latency contract.
ActorProgramComputer is the asynchronous carrier implementation for that migration. Its server-side state is an
observation from actor replies, and terminal, filesystem, deployment, and input requests return futures. The scheduler
admits at most one pending or executing tick permit per actor, while completed replies may remain queued for the next
server-thread pump without blocking admission of a later tick. The carrier suppresses obsolete lifecycle replies and
performs redstone and sound world actions on its owning server thread. On a later server tick it submits one typed
continuation containing the immutable world-action result. The actor validates and applies that completion before using
the same command to perform the next bounded advance; its single reply both completes the old deferred request and may
publish the next one. A full actor mailbox retains the exact continuation for retry without repeating the world mutation.
Its close future reports the final filesystem generation after accepted work drains and native resources close; this
barrier does not depend on server result pumping and may complete on a worker thread.
Third-party world capabilities use the same continuation boundary. Loader integrations register bounded
ProgramAddonHost factories together with deterministic AddonGuestApiBundle values. Each bundle contains one
versioned Kotlin platform module, its exact external-call bindings, the matching typed capability schema, and optional
sources; it contains no executable addon JVM classes. Registration rejects duplicate addon/module identities, and a
created host must expose the exact registered capability schema. The actor transfers immutable typed requests to
the server thread, where the host may complete immediately or retain a bounded wait; completions resume the exact VM
task on a later turn. The Minecraft 1.21.1 Create adapter and its Guest Kotlin declarations live in the standalone
addons/create Gradle root. It declares the public plugin, tooling, platform bundle, common host API, target adapter,
and development mod through their external coordinates. For local co-development its composite build substitutes the
adjacent Compukters projects, selecting the self-contained namedElements development mod instead of a republished
Maven Local runtime. The API and adapter share the independent SDK version; the runtime-only development mod retains
the product version. The Compukters root does not include or invoke Create tasks, so the base platform, shared
Minecraft code, and 26.1 code have no direct Create ownership. The adapter resolves adjacent loaded positions or
named devices across loaded peripheral cables and binds handles to exact block-entity identities, preventing replacement
from silently rebinding a running Guest program. Stock Ticker snapshots and package requests remain inside that addon
and use the same bounded server-thread host boundary.
ProgramRuntimeHost owns one current Rust ComputerMachine, advances it with bounded guest and maintenance budgets,
commits terminal changes once per active server tick, and exposes typed full/delta states and failures through JDK 25
FFM on the 26.1 product line. The Java 21 runtime API owns the same typed session boundary independently of its native
transport. It does not own a second grid or output transcript. It is loader-independent and confined to its actor worker.
Within one process, the Rust VM may schedule several bounded cooperative Guest tasks. Exactly one task executes
instructions at once; suspension on a host request or Task.join() transfers execution to the next runnable task in
FIFO order. Pending requests retain their (TaskId, RequestId) owner, so independent reads and writes may remain in
flight and complete out of order without running Guest code re-entrantly. Returning from the root task ends the process
and cancels its remaining task work.
The built-in compukter:timer capability at ABI 1.0 backs Tasks.sleepTicks: the loader-independent runtime retains
the bounded request and resumes its owning task at the requested server-tick boundary. It does not introduce a VM
clock, background timer, wall-clock dependency, or work while the computer is not receiving tick permits.
Guest Kotlin exposes this as transparent stackful blocking through ordinary functions: suspend declarations are
outside the supported source subset, while legacy suspend-call artifact instructions remain decodable and executable.
The production execution profile reserves a 256 KiB managed heap for each active foreground process; child-process
capacity is charged independently while its parent is suspended. Heap arenas are released with their owning machine.
An explicit actor request can also compose one immutable resource snapshot from host lifecycle/configuration and the
native machine’s semantic work, Guest heap, admitted mutable execution-resident, and filesystem quota counters. The
host counts Guest and maintenance budgets only when it actually invokes native advancement; both host and native
counters saturate explicitly instead of overflowing. These values describe VM work and granted capacity, not host CPU
percentage. No sampling loop, history buffer, heap-content scan, or unsolicited FFM call runs for unobserved computers.
Published Runtime 0.15.0 platform bundles use manifest schema 2 and contain the FFI and JNI native libraries for one
operating-system target under a single Runtime, VM commit, and C ABI identity. Bundle validation covers both transport
entries before staging; the Java 25 runtime module packages only FFI, while the Java 21 runtime module packages only
JNI. The two Minecraft release artifacts therefore share one pinned native release without carrying an unusable
transport. FFI ABI 15 carries host failures as a stable category plus a non-empty, bounded UTF-8 detail instead of an
opaque numeric producer code; the VM retains that detail without allocating while resuming and exposes it in terminal
process diagnostics.
Runtime ABI 1.3 adds exact decimal materialization for the existing I64 scalar form; artifacts require it only when
an I64 value is converted to String, while purely numeric Long programs remain compatible with Runtime ABI 1.0.
Runtime ABI 1.4 does the same for F32 using Kotlin/JVM-compatible spellings, including signed zero, infinities, NaN,
and the smallest subnormal values; purely numeric Float programs likewise remain compatible with Runtime ABI 1.0.
The Minecraft carrier owns exactly one actor endpoint and submits at most one ordinary advance or host continuation
for each server tick. Rust starts
/rom/boot, compiled from system/programs/boot.kt; boot delegates to /rom/shell, compiled from
system/programs/shell.kt. A foreground child suspends its parent until it exits or fails. There is one active
foreground lane today, while the runtime contract leaves room for later parallel execution. Reboot replaces the
complete machine stack and clears the terminal. Minecraft sends full state to a new viewer and ordered deltas
thereafter. Terminal viewers submit bounded asynchronous key, text, state, resync, and resource-snapshot operations,
which merge in server-arrival order without client-side echo or a terminal input lease. A valid standalone-terminal
viewer samples resources immediately and then every ten server ticks through the same single-poll admission. Its
viewer-local window retains one prior sample to derive semantic consumed/granted Guest-unit usage; it is discarded on
close, invalidation, machine replacement, or server stop. Replies are published only after returning to the server
thread and only while the viewer still refers to the same machine epoch.
The raw terminal is a synchronous Rust device: cell writes, positional writes, rectangular fills, colors, cursor changes, and input polling never cross into Minecraft. The authoritative fixed 51x19 cell grid and its replication journal live in the machine; Minecraft only renders full states or ordered deltas. Waiting for an absent input event and foreground process execution are explicit suspension points. Kotlin guests consume ordered raw Text and Key events; there is no compatibility line buffer or second input protocol. A program must finish its active event before starting an interactive child, so event ownership never leaks across foreground process frames. The server host never blocks the Minecraft thread.
Minecraft owns the persistent 30-bit redstone output register and samples dirty local faces before VM advancement. Rust owns the complete input snapshot, predicate waiters, and confirmed output mirror. Input crosses FFI as one scalar changed-mask-plus-levels packet; output requests are reduced in publication order and committed through one loader-independent host port at most once per computer per tick. A successful physical commit is confirmed to Rust before every original blocking request resumes. Completion and the next bounded advance share one actor command and one result without allowing multiple advances in a server tick. VM halt, fault, shutdown, reboot, and replacement never synthesize a zero output.
One-shot sound requests cross the same actor boundary as immutable (note, volume) batches. Minecraft emits the
vanilla note-block pling in the block sound category, using equal-temperament pitch around neutral note 12, before the
VM task receives its Boolean admission result. Each loaded computer has a four-tick cooldown, and one server-wide
counter admits at most 64 computer sounds per tick. Rejected sounds return false immediately and are never queued;
unloaded block entities retain no sound state or background work.
The Minecraft carrier also owns a direct-touch peripheral fabric. Passive orthogonal cables form bounded components across loaded chunks without forcing chunk loads. Addon providers map a touched block or multiblock part to a canonical logical identity; a versioned world directory stores normalized names against that identity independently of computers and cable topology. Lookup is scoped to one cable component and addon ID, rejects duplicate reachable names, and never returns a partial component after a traversal bound is exceeded. Typed addon host handles bind to the resolved device identity, so later rewiring or renaming cannot redirect an existing handle.
The base text display uses the same bounded world-request path with an internal compukters:display capability. Its
20x10 buffer and exclusive writer lease belong to the display block entity on the server, outside the VM terminal.
The block tick checks its writer and cable reachability even while Guest code is idle. Stopping or disconnecting that
writer clears the screen, and no display text is saved in world NBT. Clients receive at most one full-grid block-entity
update per changed server tick and render text on the oriented front face.
The client renders the fixed 51x19 grid in a centered compact panel while the world remains visible through a
translucent dim layer. A separate footer presents rolling CPU utilization, current Guest heap and virtual-disk
usage, and concise lifecycle activity. Here CPU is the virtual computer’s consumed/granted semantic Guest-unit
budget, not physical host timing. Users can select the packaged Cozette 6x13, Dina 6x10, or ProggyTiny 6x10 terminal
font without changing terminal coordinates or creating a second grid.
The terminal screen can suspend its observation and open the IDE, whose target terminal view consumes the same
replicated terminal state but does not yet display these standalone-terminal gauges. Returning from the IDE reopens
the standalone observation without reopening the screen and receives a fresh authoritative terminal state.
The Rust VM owns verification, the Tier 0 interpreter, managed memory and collection, quotas, traps and faults, capability suspension, and host-neutral sessions. Future JIT or AOT tiers must remain behind the same verified artifact and session contract.
Filesystem and machine lifetime
The Rust runtime owns the guest filesystem and its persistence. Minecraft stores only a stable 128-bit ComputerId;
guest paths and bytes never enter block-entity NBT or a JVM-side mirror. Every computer sees an immutable packaged
/rom and a private persistent /home. The world store lives under <world>/compukters/filesystems, performs bounded
I/O on its own worker, flushes active generations on world saves, and drains, flushes, and closes before server shutdown
completes. A permanent lock anchor carries a process-lifetime exclusive OS file lock: a live second server is
rejected, while orderly close or process termination releases ownership without deleting the anchor. Removing a
computer through the player destruction lifecycle closes its machine before creating a
recoverable tombstone; ordinary block-entity removal during chunk unload only closes the current machine and preserves
its filesystem.
The Minecraft filesystem registry accepts asynchronous machine-close barriers. An unloading computer retains its attachment until the barrier completes and the final generation is flushed; destruction defers its tombstone until that point. Store shutdown starts every outstanding drain before waiting for the combined barrier. Ordinary release does not wait for VM completion; the server-stopping hook alone permits a bounded ten-second wait. A failed close barrier keeps the store unavailable rather than closing storage underneath a possibly live native machine. Native flush, tombstone, recovery, and store close run on one lazy persistence executor per world, outside the registry monitor and outside both the server tick and VM actor workers. Each world captures the configured VM actor capacity when its store opens and admits at most that many computer identities, including pending removal/recovery; each identity owns at most one queued or running token. Repeated saves coalesce to the latest requested generation. The identity remains unavailable until final persistence completes, so a quickly reloaded block retries attachment instead of opening a second machine. Failed background carrier admission is retried once every 20 block-entity ticks; explicit terminal access may retry immediately. Persistence failures are logged once, fail outstanding lifecycle futures, and prevent unsafe reattachment or store close. Initial store opening happens during server startup rather than an ordinary computer tick. Calls sharing a native world-store handle are serialized by a fair lock across VM actors and persistence work, while ordinary VM execution stays on actor workers.
The versioned C ABI v16 exposes opaque world-store lifecycle operations, machine creation inside a store, stateless
artifact verification, dedicated bounded compilation request and completion calls, and typed Unit, Int, raw-bit
Float, Boolean, String, or categorized and human-readable failure host-request completion. Kotlin can select a world
store, identify a computer, request flush, tombstone, or recovery, and route compiler results, but it cannot perform
arbitrary guest file operations. Guest code reaches Rust-owned state only through declared capabilities. The guest
machine-creation calls carry a bounded, versioned schema for optional host capabilities; Rust validates and owns that
schema before admitting the executable, so addon operations use the same typed verifier contract as built-in devices.
ABI v16 also adds an instruction-limited advance that returns retired instruction count separately from the existing
weighted guest and maintenance budgets. The earlier advance wire result and its meaning remain unchanged.
Server tick requests enter actor mailboxes with a deferred permit. After block entities and benchmark carriers submit
their requests, the NeoForge post-tick hook divides aggregate instruction capacity among the submitted runnable
computers and releases their permits. World effects remain ahead of each permit, while later actor commands stay behind
it. A deferred permit releases its worker token while waiting for the post-tick allocation.
The guest filesystem facade exposes bounded stat, list, readText, and writeText; Rust validates paths, UTF-8, permissions,
quotas, and atomic replacement while /rom remains immutable. The shell and editor map stable failures to user-facing
diagnostics. Executable installation remains a Rust-owned filesystem transaction and never accepts a host path.
The IDE target protocol is a separate bounded host interface rather than direct filesystem ownership. After attaching to a target, the client may inspect supported filesystem metadata and content, upload an artifact for verification, observe the destination revision, and deploy using a verification ticket plus the expected revision. Heartbeats and detach bound the target attachment lifetime; revision conflicts require an explicit retry or user confirmation.
Server-side IDE verification, deployment, canonical input, and filesystem operations may suspend until the runtime answers. The request transport admits at most 256 pending operations per server and four per player, including suspended work. Verification retains its upload staging reservation while awaiting the runtime, and a candidate returned after its target lease ends is closed instead of becoming a ticket. The IDE terminal also supports suspended open, resync, input, and polling operations. Each viewer has at most one pending poll; replies are checked against the current viewer session and machine before publication. The standalone terminal uses the same bounded asynchronous transport as the IDE terminal, and its viewer state is discarded when the server stops.
Guest programs and APIs
Boot, shell, kotlinc, edit, and vmbench are ordinary no-std Kotlin programs packaged as extensionless executables in /rom.
Shell owns line editing, authoritative echo, prompts, and direct built-ins. A non-absolute external command resolves
first to /home/<name> and, only when that path is absent, falls back to /rom/<name>; an absolute command is used as
given.
Process.run(path) and Process.run(path, args) execute one verified extensionless artifact as a foreground child and
return ProcessResult.Exited(code) or ProcessResult.Failed(reason, diagnostic). Process.exit(code) terminates the
current process. Guest code does not select an explicit capability mask when starting a child.
Tasks.launch starts a supported () -> Unit value in a bounded cooperative task. The compiler keeps direct
top-level references as static spawns; other function values cross the existing spawn boundary as one managed
reference into a private Guest trampoline, which invokes Function0<Unit> through normal dynamic dispatch. The child
frame retains that reference after the launching function returns, and shared capture cells remain VM-owned.
Ordinary higher-order Guest calls admit non-null function values whose parameter and result types are supported by
Guest Kotlin. The compiler creates one managed interface per concrete signature and lowers calls through the same
interface-call instruction, preserving unboxed primitive values and ordinary reference ownership. There is no special
arity transition at 22 or 23 arguments in the Guest artifact.
Nested closures capture free values through their enclosing closure objects. Mutable captured locals retain a single
VM-owned typed cell shared across nesting levels and sibling closures, including after the enclosing call returns.
An unbound top-level Guest function reference uses the same managed function-value interface and a capture-free
closure whose invoke method calls the referenced project function. An inferred KFunction value has the same Guest
call behavior; reflection is not part of the Guest function-value contract.
A reference to an admitted Guest primary constructor uses a capture-free managed closure. Its invoke method allocates
the object, calls its constructor function with that receiver, and returns the object, without a constructor-specific
VM type or instruction. Direct construction follows the same path. The constructor calls its superclass constructor
on the same receiver before evaluating backed properties and init blocks in declaration order.
Constructor-backed Guest var properties use the same managed instance fields as val properties. Default setter
calls lower to verified field_set instructions after evaluating the receiver and assigned value in source order.
For primary constructors, the compiler evaluates explicitly supplied arguments once in call-site order, then omitted
default expressions in parameter order. Earlier constructor parameters are available while evaluating later defaults.
The existing full-arity constructor function receives the resolved values, so field initialization and init blocks
observe the same values and object identity. A constructor reference retains its full arity when the expected function
type supplies every parameter. When the expected type omits supported trailing defaults, K2 supplies an adapter
function whose body calls that constructor. The compiler lowers the adapter as a managed closure, so each invocation
evaluates defaults and allocates a new object without another VM instruction or ABI change.
Source-defined class getters and setters lower as ordinary non-suspending instance methods, so overridden accessors
use the existing virtual dispatch table. A computed property has no managed field; field inside a backed accessor
lowers to verified field_get or field_set on its receiver. Non-overriding final default accessors retain direct
field access.
Abstract class and interface properties contribute getter and setter method signatures without managed fields.
Implementing classes provide backed or computed accessors, selected through the existing virtual or interface call
instruction when the property is used through a base reference.
Interface method and computed property bodies use the same function records and bytecode as class methods. During
artifact admission the VM first searches the class inheritance chain, then chooses the most specific matching
interface declaration. An abstract redeclaration suppresses an inherited default; missing or ambiguous concrete
targets reject admission. Dispatch entries are shared by all VMs using the admitted execution image.
An explicit super<Interface> call in an override resolves the concrete interface body at compile time and emits a
direct call. If the named interface only inherits the body, K2’s fake override resolves to that concrete ancestor.
Abstract or ambiguous targets publish no artifact; the direct call retains the original receiver and skips the
override’s dynamic dispatch entry.
A bound Guest instance-method reference evaluates its receiver once and stores that object in the closure. Its invoke
method loads the stored receiver and uses the target method’s static, virtual, or interface dispatch mode.
An unbound instance-method reference has no receiver field; its first function parameter supplies the receiver for
the same dispatch path.
/rom/kotlinc source.kt [-o output] accepts exactly one source file today; its default output is the source basename
without .kt. This single-file in-computer command is distinct from the IDE and compiler protocol, which support
bounded multi-file project snapshots.
/rom/edit <path> is a nano-like 51x19 editor backed by one managed 4096-unit CharArray gap buffer. Cursor motion and
deletion preserve UTF-16 surrogate pairs, CRLF input is normalized to LF, Tab inserts four spaces, Enter inherits
leading indentation, and the viewport scrolls in both axes. Ctrl+S writes through Rust-owned FileSystem.writeText;
Ctrl+X exits directly when clean or opens a Y/N/Escape save prompt when dirty. The buffer, source, and compiled artifact
belong to the computer filesystem, while terminal state belongs only to the current VM lifetime. The playable
in-computer loop is edit demo.kt -> kotlinc demo.kt -> demo; source and artifact survive machine reload and remain
isolated by ComputerId.
/rom/vmbench cpu <rounds> runs the documented deterministic, allocation-free integer/branch workload through the
ordinary foreground process and VM quota path. /rom/vmbench redstone <rounds> normalizes the local top output to zero
and then emits one acknowledged weak-power 15 -> 0 pulse per round; every transition suspends through the ordinary
Guest redstone API until its physical server-thread commit is confirmed. These workloads exist to measure aggregate
in-world runtime cost and do not own a timing capability, privileged execution budget, or benchmark-only host path.
See the
in-world VM benchmark guide for the controlled scaling procedure.
An operator-only harness can run up to 4096 internal headless artifacts through the same verified session and actor
scheduler, including a phased capacity run that settles actors at terminal input, observes 100 idle ticks, samples
their existing resource counters once, and wakes them with an ordinary Text event. It can also dispatch either ordinary
/rom/vmbench workload to at most 1000 loaded physical computers in a bounded area. The harness owns no persistent
computer identity, never loads chunks, and does not provide a guest-visible fleet protocol.
Terminal, standard output and error, redstone, sound, process, filesystem, and compiler declarations live in the base
guest-platform bundle as separately identifiable modules. Optional integrations publish bounded addon Guest API
bundles instead. The server admits those bundles, includes their content hashes in the target and compilation-cache
identity, and sends their metadata and optional sources to the attached IDE. Analysis indexes the bounded catalog of
available addons for completion, while K2 semantics and compilation activate only the modules selected in the project
lock. Compiler and analysis workers decode only this data format: they never load or execute addon code. Exact admitted
bindings extend intrinsic lowering, while a
same-named Guest declaration remains ordinary Guest code. General stream handles, pipes, and process redirection remain
later layers.
Addon host handlers report failures with a broad HostFailureKind and a non-empty UTF-8 detail of at most 256 bytes.
The kind remains suitable for runtime classification; the producer-owned detail is diagnostic text and is not a stable
programmatic identifier.
Module ownership
Minecraft-independent Gradle modules are physically grouped under modules/common; all shared and version-specific
Minecraft integration is grouped under modules/minecraft. These directories express source ownership only: the
existing Gradle project paths and artifact names remain flat and stable.
| Module | Purpose |
|---|---|
native-runtime-api |
Java 21 Kotlin-facing VM session, wire validation, opaque world-store lifecycle, and trusted host capabilities |
native-runtime-ffm |
Explicit JDK 25 FFM transport, native resource loading, and FFM integration evidence |
native-runtime-jni |
Explicit Java 21 JNI transport, native resource loading, and JNI-to-C-ABI integration evidence |
platform-bundle |
Canonical platform bundle model, codec, module graph, identities, and default imports |
addon-api |
Minecraft-independent public host contracts and self-contained compile-only SDK artifact |
addon-guest-api |
Loader-independent addon bundle model, codec, capability schemas, bindings, and admission limits |
addon-gradle-plugin |
Public external-build DSL, generated-source wiring, ABI-lock workflow, and Maven-coordinate SDK boundary |
addon-guest-api-fixture |
Test-only neutral addon bundle used by common compiler and IDE verification |
platform-k2 |
Shared K2 metadata and FIR integration for the Compukters platform |
compiler-artifact |
Canonical executable artifact model, validation, and encoding |
worker-client |
Generic bounded JVM worker processes, payload publication, framing, deadlines, and immutable values |
tooling-runtime |
Packaged shared runtime containing the pinned compiler and analysis workers |
compiler-client |
Compiler protocol, controller, project snapshots, compilation identities, and persistent cache |
compiler-runtime |
Server-global scheduling, single-flight compilation, persistent cache, and compiler backend lifecycle |
compiler-k2-engine |
Shared K2 FIR-to-IR pipeline, platform linking, trusted intrinsics, and Compukter lowering |
compiler-k2 |
Isolated compiler worker entry point and packaged compiler payload |
guest-platform |
Built-in Guest Kotlin declarations and canonical base platform-bundle inputs |
ide-core |
Minecraft-independent project, editor, profile resolution, client compilation, and analysis models |
ide-analysis-client |
K2-free analysis protocol, controller, scheduling, cancellation, and worker lifetime |
ide-analysis-k2 |
Isolated K2 Analysis API worker and incremental project workspace |
ide-client |
Minecraft-independent IDE workspace, controller, analysis coordination, target, and file-transfer logic |
playground |
Standalone compile-and-run entry point with stdin and stdout |
core |
Loader-independent server behavior and ProgramRuntimeHost |
minecraft/shared/common |
Canonical loader-independent Minecraft sources, resources, and tests compiled against every supported game target |
minecraft/shared/addon-neoforge-api |
Canonical public Minecraft registration adapter compiled into each supported target artifact |
minecraft/shared/neoforge |
Canonical NeoForge integration sources, resources, and tests compiled against every supported loader target |
v1_21_1-common |
Minecraft 1.21.1 compatibility adapters over the shared computer carrier |
v1_21_1-addon-neoforge-api |
Thin NeoForge 1.21.1 addon SDK adapter |
addons/create |
Standalone Gradle build for the independently packaged Create 6.0.x Guest API bundle, kinetic-device adapter, bounded host state, and focused tests |
v1_21_1-neoforge |
NeoForge 1.21.1 compatibility adapters, Java 21 JNI packaging, and production archive |
v26_1-common |
Minecraft 26.1 compatibility adapters over the shared computer carrier |
v26_1-addon-neoforge-api |
Thin NeoForge 26.1.2 addon SDK adapter |
v26_1-neoforge |
NeoForge 26.1 compatibility adapters, client UI, GameTests, resources, and production archive |
host/compukter-vm |
Artifact verification, managed Rust execution runtime, and VM-owned versioned C ABI in its ffi workspace member |
Ownership rules:
- Every module under
modules/commonmust remain independent ofnet.minecraft.*. - Common compiler and IDE modules must use the neutral addon fixture and must not depend on optional loader integrations.
- Version modules must consume neutral
modules/minecraft/sharedroots rather than another version module’s source tree. - Compatibility declarations must remain in the Compukters namespace and must not emit classes beneath
net.minecraft.*. - Kotlin modules must not implement another interpreter or mutable guest machine model.
worker-client,ide-core,ide-analysis-client, andide-clientmust not acquire K2 implementation dependencies.- K2 compiler internals belong to
compiler-k2-engineandcompiler-k2; K2 Analysis API internals belong toide-analysis-k2. - Minecraft protocol, UI, and assets require deliberate feature designs and live next to their owning feature.
- Compiler-internal FIR and IR types must not leak into the platform bundle, artifact, worker protocol, FFM, or native runtime contracts.
LegacyImplementationRemovalTestprevents removed product contours and old package identities from returning.