Tooling workers and IDE ownership
Worker isolation and analysis
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 under a private relocated Compukters namespace. Tomlj carries its own
relocated ANTLR runtime, so no standalone ANTLR or Checker Qual dependency is needed. These libraries do not enter
NeoForge’s module layer as separate automatic modules and therefore cannot collide with loader-provided versions.
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 v15 adds a peripheral-provider role to semantic tokens and completion kinds. The worker identifies providers through inheritance of PeripheralProvider, including companion values; type references retain their ordinary class role. The editor renders provider values in the dedicated palette color with a P completion badge.
The protocol also carries explicit Kotlin format, rename-admission, and parameter-information requests.
Completion records carry immutable ordered UTF-16 label-relative name-match ranges, derived by the same worker-owned
contiguous word-fragment matcher used for filtering and ranking. The client renders these ranges without repeating
the matching algorithm. Expression information carries optional bounded plain-text KDoc from the resolved project
declaration or its exact attached platform source. Both fields are validated at the worker/client boundary, including
Unicode boundaries, nonoverlapping ranges and negotiated UTF-8 documentation limits. Hover wraps documentation and
bounds its visible height; it does not execute HTML or fetch external content.
Callable completions carry bounded independent presentation fields for the declared extension receiver, package and
specialized return type. Type-scope member signatures retain K2 substitutions; extension applicability supplies
receiver substitutions through the pinned K2 completion checker. Explicit receiver checks use a query-only contextual
Kotlin expression fragment with a synthetic selector, so literals and incomplete safe-calls still receive K2 inference
without mutating admitted source PSI or changing diagnostic scheduling. Safe-call lookup uses the non-null receiver
type; unknown callable type parameters remain symbolic.
Member functions have a distinct completion kind. Presentation and insertion metadata share the same resolved
signature without making the client parse rendered Kotlin declarations.
Presentation requests explicitly choose whether to include diagnostics. During an automatic or manual completion
session the client cancels diagnostic work and requests semantic-only presentation; edited diagnostics are rebased
outside the changed range rather than discarded wholesale. Empty completion results, dismissal, insertion and
parameter-information transitions resume diagnostics for the latest snapshot. Semantic-only results cannot erase
previous diagnostics, and the protocol rejects mismatched diagnostics policies.
Completion items include an optional semantic call shape: presence of parameters, required arguments before a
trailing lambda, and a final non-null function parameter. K2 derives this from resolved symbols (including type
aliases), not rendered signatures; imports, callable references, type positions and shorthand string templates
receive name-only insertions. The client uses that shape to add or reuse call delimiters and records the primary
caret together with autoimports in one atomic editor history entry. Newly generated delimiters join smart typing’s
tracked closers. Structural Enter expands an empty Kotlin brace pair even when it was inserted by completion or
loaded from source, removing inner padding and preserving the document’s line endings and leading indentation.
Protocol v13 workers are rejected at handshake;
the platform bundle, compiler protocol and VM artifact ABI are unchanged. Rename
returns bounded editable project locations after validating a single Kotlin identifier and speculatively checking
project diagnostics and reference bindings. The worker restores its original PSI, source identity, and completion
state even on cancellation; a failed restoration invalidates the workspace and requires a fresh snapshot open.
Rename currently rejects projects with analysis errors, read-only library symbols, the main entry point,
and inheritance or convention-based declarations rather than attempting partial refactoring.
Presentation results
include bounded method-name ranges with non-negative project usage counts. K2 gathers declarations in the active
source and resolves references in one project traversal under the presentation session, keeping overloads and
same-named methods separate. Counts share the semantic-token item bound, are checked against the correlated source,
and appear as clickable same-line labels to the right of declarations; zero counts are hidden. During pending
analysis, exact edits rebase the last known counts for untouched method names, avoiding flicker. Edits touching
or extending a name discard its label; a fresh accepted presentation replaces provisional counts, while stale
responses remain rejected. The previous worker protocol is intentionally incompatible and rejected at handshake.
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.
Client editor and terminal presentation
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. Terminal windows, IDE terminal overlays and in-world text displays use
packaged JetBrains Mono NL with 6x13 cells and no font selector. The former terminal font preference is no longer
part of client configuration; old values are removed by NeoForge configuration correction without affecting IDE layout.
The IDE code editor, completion list and hover information use the same bundled TrueType face without ligatures,
with 6x12 editor cells (size 10 and line spacing 1.2); terminal/display cells remain 6x13.
Editor and terminal drawing use the same font resource ID directly, without a reference alias: Minecraft 1.21
warms font IDs in parallel and its FreeType provider does not synchronize access to a shared native face.
The editor metrics drive glyph placement, caret and selection geometry, and hit testing. The IDE chrome retains Minecraft’s UI font.
The Minecraft 1.21.1 adapter opts these explicit JetBrains Mono draws into linear texture filtering. A bounded
render-type wrapper delegates vanilla shader, blend, depth and geometry state, applies filtering after vanilla’s
nearest-filter setup, and restores the previous texture filters, binding and active unit after drawing. Other fonts
remain unchanged. The 26.1 adapter submits prepared glyphs with a cached linear GUI sampler and uses NeoForge’s
linear text render types for in-world displays, preserving clipping, transforms and polygon offset without global
font or texture-state changes. Visual quality still requires an in-client comparison at the chosen UI scale.
The IDE controller owns a bounded project-document cache (128 editor-limited documents by default), so returning to
a file preserves its caret, viewport and undo history. Dirty background buffers participate in autosave and are
drained before a build, project transition or ordinary close. Polling invalidates clean changed buffers and retains
dirty conflicts. Analysis snapshots overlay unsaved project buffers under the admitted source limits and recompute
their source identity; they never analyze a mix of renamed active text and stale background disk text.
Explicit Find Usages reuses the serialized reference/declaration analysis lane and requires a single resolved
declaration. The controller publishes bounded immutable source-context rows in the lower panel, navigates through
the existing project navigation history, and rejects queued results invalidated by source edits or project changes.
The controller also owns one deduplicated diagnostic view for the problems panel, gutter, counters and F2 navigation.
Navigable rows carry exact source evidence; build diagnostics retain bounded admitted source text. Opening a target
checks that evidence before applying a UTF-16 offset, including after an asynchronous open or implicit save. Stale
build messages remain visible but do not contribute clickable locations or gutter markers.
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.