v1 means an application built on it needs no major refactor for the foreseeable future, and later releases add to the abstractions it already has. It also carries no legacy: nothing stays for backwards compatibility. By that bar the framework is not close: one of seven surfaces is ready to freeze, and the concurrency model's declaration side is half built — components declare their mailboxes and what they send, and supervision watches every lane, but the copier, capture and the rest of the channel seam are not there. Most of what the dimensions below grade is built well, somewhere else.
v1 is a promise that an application built on it needs no major refactor for the foreseeable
future, and later releases add to the abstractions it already has. That is a question about
whether a shape is final, which is not the same as whether the capability is built. The
five dimensions below grade the second; this ledger grades the first. Six surfaces are frozen at
v1, and a seventh, the sibling types Shell hands over, bounds them all.
| Surface | Ready to freeze? | What stands between it and the freeze |
|---|---|---|
| The wire protocol | Ready | Framing, ok/err and twenty-three verbs, exercised by a real window on every acceptance run; zoom and resize landed. What is still asked for (keydown/keyup, await on existence) is additive. |
| Shell and the phases | Close | Phases carry the stack's own constraints, three ported applications ran on them, and every generated one does now. Open: what Shell exposes of the siblings, and whether a new Phase can ever be additive. |
| The launch surface | Close | FRAMEWORK_KEYS and the dropped-capability reports are settled. --profile is reserved and nothing honours it, and the module behind it has no name yet. |
| The annotations and the processor's semantics | Decisions open | Exact-type matching, what a null provider does, and the two promises that need a method body are unargued. Every new check is a way to break a build that was accepted, so the checks that exist must be the checks v1 keeps. |
| The component model | Declaration half built | Threads and mailboxes are the container's; @Subscribe and @Publishes declare a component's mailboxes and sends, the blocking-cycle and immutable-payload checks run, and supervision (Watchdog, LivenessPolicy) watches every lane. Missing: @Subscribe off a component, capture (T2.5, undecided), and the generated copier. It reshapes how every component is written. |
| The build shape | One instance | One starter, no BOM, no aggregated reachability metadata — the suite's applications build Windows native executables in two editions, and each carries its own metadata and flags, which the template now copies into every project — and a composition-root question (GuiApp or VexelEngine) that decides what an application inherits. |
| The sibling boundary | Undecided | Shell returns Gui, GuiApp, KronoGui, TitleBar, Settings, Atchung, ComputeQueue and dev.vexelray.os.Icon, and the framework's Windows, Window and Dialogs hand over Gui, TitleBar and Modal. v1 is only as stable as they are. Gui is ruled frozen and its methods are not yet classified; memory() is deleted, app() is ruled the device, and the windows and the dialogs are roots of their own. One bus also carries one tree, upstream. |
What v1 leaves out is fine if the seam it will use already exists. One of these still has none, and it is the rest of the channel seam, which is why the concurrency work still comes first.
| Left out of v1 | The seam it attaches to | Exists? |
|---|---|---|
Diagnostics, --profile | A starter and @ConditionalOnType; atchung-probe | Yes the name is undecided |
| A second automation socket | Driver.open(shell, gui, controls, offset) | Yes |
| Framework SDF vocabulary | A @Provides and a placed component | When the component model is final |
| Simulation vocabulary | FrameStage.APP and a placed component | Partly |
| Supervision policies, restart | LivenessPolicy, handed back, acting through a Context (exit, interrupt) | Yes restart is a new Context method |
| elektro-Q, cross-process actors | The channel declaration: @Subscribe, @Publishes | Partly on components only |
| Reactive automation, macros, extensions | A @Subscribe that is not on a component | No |
The five dimensions below are graded by where the capability lives, because that is the question this framework exists to answer. It is not the question v1 asks, which is whether the shape is final — see the ledger above. The end state for almost everything here is the rightmost stop.
Shell accessor. No hunting.
The pixels path is real end to end and has been pressure-tested by a design tool: Surface → SurfaceCompiler → SdfScene → SdfComposer → SPIR-V → SampledColorTarget → Node.image. What is missing is a compositing vocabulary and any framework-level presence at all — not one mention of SDF across the six modules' sources.
GuiApp are two
Specified
ClipDepth still has one author, one consumer
Absent
Implicit with forward-mode symbolic AD.ShaderCache keyed on structural equality of the whole
SdfScene: records all the way down, so caching needed no bespoke fingerprinting.ConeField reads geometry from a storage buffer — one pipeline for every
scene, after a few hundred segments were measured at 5 s of pipeline build on the frame
loop.VkQueue is not thread-safe, so a drag handler
raises a flag and all GPU work happens in one pump() in FrameStage.APP.
This dimension has started. The plan is unchanged — one component, one thread, one
mailbox, with the mapping decided in the wiring and never at runtime — but it is no longer
only a plan. Lanes in -core owns the application's handler, offload
and component threads; Placement in -shell owns a component's thread,
its mailboxes and the drain-then-stop around it; the generated wiring places each @Component on both, and
no application code can, and @Subscribe and @Publishes put each mailbox and
each send on a declaration. The grep this page prints as its own check used to return zero and now
returns 60 hits across eight files — which is the intended shape rather than a leak: 36
are Lanes, 12 are Watchdog, 5 are Placement, one each in
Stalls, Shell and PointerLock, and the last four are words: two
in the template's list of Java keywords and a method name in the processor's Channels.
Twenty of threading.md's thirty-three rules are held, and four still say processor.
Eight came from Lanes and Placement, which needed something to be the
rule rather than a processor; most of the rest are the processor's checks — placement, colour, the
immutable payload, the declared channel graph and its blocking cycles — and supervision came from
Watchdog and LivenessPolicy.
Pump, State, Fold, Backpressure
In the stack
Actor, Conduit, four transports
In the stack
KronBridge folds a topic into a cell on the baton
In the stack
Kron.sleepTimeout() already registered
In the framework
Lanes: handler, offload, one per component
In the framework
Lanes.offload(), bounded in threads and never virtual
In the framework
@Component and @Subscribe, placed by the generated wiring; drain then stop on a timer
In the framework
Pump.drain(timeout), parked without holding a lock
In the stack
@MainThread actually enforced — a compile error, the framework's GuiApp included
In the framework
@Subscribe(overflow = …); an edge and a sample on one topic is a compile error
In the framework
Stalls, 250 ms, always on
In the framework
Watchdog names a lane stuck inside one delivery; LivenessPolicy decides, report then exit by default
In the framework
Rate → mailbox → component thread → Topic → Cell, and the one drain period of
latency is priced in advance rather than discovered.FrameStage vs Rate settled: the frame is the main thread's
recipe and its order is causal; a Rate is everyone else's grid.Gui takes both lanes now and closes
only the ones it built itself, which turned two upstream rules into one
change. What replaced it is smaller and less certain: the one application checked for
blocking handlers turned out to route everything through GuiApp.post instead,
so nobody yet knows what does block one. -Dvexelray.stall.ms=16 is the
setting that answers it by running the applications rather than reading them.ThreadGroup with a busy gauge;
Watchdog, on no lane, reports a stalled delivery once by name and halts past a
deadline; close has a total bound; and the policy is the application's to replace. What is left:
the main thread is not watched (a modal dialog legitimately blocks it), the handler and offload
pools are not grouped and have no cancellation, Context has no restart,
and W2 has no wedge that ignores interrupts, so the halt backstop has not fired in a witness.@Component is a DI contract not an actor one, @MainThread is
two-coloured, FrameHooks is the barrier's N=1 case.
The app edge now has one language and the measurement is real: three ported wirings run
186 / 296 / 556 lines against a ~350-line scaffold that each application used to carry
for itself, and two of the three import four or fewer sibling types. The app fabric
is half there: Shell.bus() is the application's own and a component speaks on it in
@Subscribe and @Publishes, but Shell.krono() hands over a clock
and then you are in Kronometer's language, and graphics is still vexelray's.
@Provides — the look, input, clipboard, liveness policy and dialogs; the icon is named in @VexelApp(icon); pacing not yet
In the framework
Wiring a lambda can no longer satisfy
In the framework
applyTo
In the framework
Rate, Settlement, Overrun, Moment in no signature
In the stack
Shell.bus(), the application's own; components declare their side of it
In the framework
run(() -> myAppInfo, args) compiled and produced a blank window that
refused nothing.Shell's accessors refuse to hand over what does not exist yet, so
work in the wrong phase is a throw rather than a null.Settings per application, which was the singleton invariant two
repos maintained by a comment.Launch.FRAMEWORK_KEYS means --automation is asked for the same
way on every application on the desk.@Provides method" was true of the look alone. A
provided input backend or clipboard is now handed back to the shell and the framework's is never
opened — and one built too late for the framework to use it is a compile error naming the phase.--profile parses and no module
honours it. The processor evaluates @ConditionalOnType now, so a part can be
present or absent by what is on the classpath — but nothing uses that to honour a profile.shell.wake(gui::onWork) is redundant — GuiApp already
wires every tree it presents — and reads like the line that makes the wake happen.Shell to ask.
The no-reflection thesis holds, and three applications ran on it before being deleted on
purpose — the witnesses are generated now: the template's project, the two-window (W1), component (W2) and
long-running (W3) witnesses, and the suite's calculator and text editor, Vexplore, Pix and the
simulation demos. Of the three things that make Boot
Boot, the generator is built: vocabulary is enforcement, and the template's wiring is
generated. The starter has its first instance — the driving socket, named in
@VexelApp — but no BOM makes one dependency enough. And the instrument that answers
"why did we miss a frame" is still only specified.
-diagnostics, the Actuator analogue — blocked on a name
Specified
@Subscribe — the actor substrate, on a component; the extension API and reactive automation want it off one, which is unbuilt
In the framework
AutomationStarter, a @Default an application names and may back off
In the framework
CLASS deliberately; no
Class.forName on the startup path.-api and -core stay JDK-only, so every decision the
container makes is testable with no GPU — which is why all 314 tests run anywhere, a
real GuiApp and a placed component among them.@Component, @MainThread
or @Setting, and a generated wiring cannot put a part in the wrong phase — but a
hand-written Wiring is still a method body, and a backwards Phase
dependency in one is still a startup error.FrameStage.APP is an empty
slot, by design.-diagnostics may be mostly free: atchung-probe already has
spans, counters and stall-hunting, and Automation already imports it. The
always-on half was taken without waiting for the module — Stalls reports a
stage that held the main thread past 250 ms, which is deliberately the narrow
question did somebody block this thread rather than the profiler's.
The strongest area on this page, and the only capability where the framework's share is already
finished. A running desktop application — real window, real Vulkan device, real frame loop —
is driven over a loopback line protocol: one command in, one reply out, terminated by a lone
., replies beginning ok or err so a script branches on
two characters. Twenty-three verbs, a real cursor behind them, and a CLI whose exit status is the
verdict.
--automation=7654 parses in every application
In the framework
find, click, drag, type, go, settle, await, shot
In the stack
ottermate, empty dependency block
In the stack
settle that consults the clock — the framework's driver hands it the application's
In the framework
zoom, resize, dpi, windows — on WindowControls; per-app --capture is retired
In the stack
port() + 1
Specified
ok. Now the
layout model publishes what is actually on screen and the target is revealed or the command
is refused.Driver.open reports the drop by name
and the application runs undriven — one of the six dropped-capability reports.settle used to answer as soon as
nothing was owed, and photographed a panel three seconds before its fade finished. It waits for
the clock now — including a fade a click handler has only just posted — and the acceptance loop
times a real pulse to keep it that way.key is
press-and-release, so Shift-click is untestable from outside, and await waits on
text, so a script that wants "the dialog appeared" needs the application to print something —
a witness flaked on exactly that. keydown/keyup and a bare
await <landmark> are both additive.settle reports it after ten seconds and names
await instead — honest, and a real limit for a clock face or a spinner.@Subscribe seam is what
would let a macro wait on something the application says.The framework's promise is that a developer never goes hunting. This is the current honest answer, tool by tool.
| Tool | What it holds | Presence here | How an application reaches it today |
|---|---|---|---|
| vexelray-gui | Frame loop, widgets, windows | Absorbed | The framework is its edge. Shell is the whole surface. |
| tactroller | Every device event on the desk | Absorbed | InputBackend — a default, and a named report when a platform module is missing. PointerLock carries the GUI's lock intent onto the device. |
| …-gui-automation | Driving a live application end to end | Absorbed | A module of its own, so the socket is linked only into an application that wants to be driven, and built only in one that names AutomationStarter. --automation=7654, then ottermate from outside. |
| kronometer | Timing, animation, latency compensation | Lifecycle only | Constructed, ticked, parked on and woken by the framework — then Shell.krono() hands over the vocabulary whole. |
| vexelray | Vulkan runtime, canvas, MSDF, SDF | None | Through GuiApp's device, or directly; Shell.computeQueue() and Shell.windowMark() hand over a queue and the window's icon. On Windows it presents through DXGI only, carried by vexelray-gui-core. Viewports and scenes are the application's. |
| supirvast | SPIR-V generation | None | Transitively, behind vexelray-shader's byte[] boundary. |
| atchung | The typed bus | Fabric and mailboxes | Shell.bus() is the application's own, and a @Component is placed on it by the generated wiring, with a Pump for an inbox. A second Gui still may not join it — see dimension 02. |
| elektroq | Transport-agnostic messaging, actors | None | Not at all. Named only as the processor's precedent, and in no application. |
Steps 01–06, in the order they were taken. Each decided a vocabulary the next one baked in. What is left is under the road to v1, below.
Shell.bus(), built in the constructor and available in every phase; the
framework's Gui is on it, so an application is one
fabric rather than one per tree.
The half that could not be added later without moving everything built
on top of it. What is left of this step is the handler executor, which needs
Gui's worker pool to stop being a field initializer — an upstream change, not
another argument.
A bus fault is the framework's to answer and still halts — saving first is what turns a
crash into a hang. A wedged component is a different event, answered by the
Backpressure of its channel: a sample coalesces, an edge must not be lost, and
a channel carrying both gets split.
What it left open was supervision, since built: Watchdog names a
lane that has stopped draining, and LivenessPolicy decides what happens next.
The designer's shader composition, on its own platform thread with a capacity-one
latest-wins mailbox and a WakeSource of its own. It found a real defect on the
way in: the hand-rolled version checked for a newer edit before composing rather
than around it, so a superseded design could finish last and land on screen.
Nothing extracted into the framework yet, deliberately — one component is
not a census, and what two of them share is what the container should own.
Lanes owns the application's threads and the generated wiring puts each
@Component on one with its mailboxes, its wake and its drain-then-stop; upstream,
Gui takes both lanes rather than minting a pool per tree. A component's
concurrency is now a property of what the wiring placed rather than of how many windows an
application happens to hold.
Eight of threading.md's rules moved to held and none of them
needed the processor — they needed something to be the rule.
mvn clean install -Pacceptance drives the builder API, asserts the
Blueprint in memory, writes it, builds it — its own tests and the framework's
processor included — and runs it with mvn compile exec:exec under automation:
the template's text editor opens a folder, switches files by click and by key, edits and saves
one, takes a screenshot, then resizes, zooms and changes density and checks each picture is the
size asked for.
The three ported applications were the witness and they are gone on
purpose. A generated project has no conventions of its own to defend, and the automation
step is what stops the builder and the framework agreeing with each other about being
wrong.
Checks, then the wiring: every rule a declaration can decide is a compile error, and an
application's Wiring is generated from its @VexelApp and its
@Provides methods — each part built once, in the phase its parameters put it in.
The template ships a Recipes configuration rather than a hand-written wiring, and
-Pacceptance builds and drives what it generates.
Four of the thirty-three threading rules still say
processor, and none can be decided from a declaration: they want a method body,
a send nobody declared, or the component tree. The framework's own defaults turned out to want handing
back rather than backing off, because a hand-written wiring still has to start with input; that
made overriding the input backend and the clipboard real, and what generation left is in the
TODO.
What is left, in the order that is load-bearing: each step decides a vocabulary the next one bakes in. The detail, and why each entry blocks v1, is in TODO.md.
@Subscribe off a component, the capture rule (undecided), the generated copier, the
@Subscribe defaults, the rest of supervision (the main thread, the handler and offload
pools, restart), and the way an offloaded result gets back onto the frame loop. The
component mailboxes, the declared channel graph, its checks and the lane watchdog have landed.
It reshapes how every component is written and is where new compile
errors come from. It is also the seam elektro-Q, reactive automation and extensions are
waiting for, which is why nothing else can be judged final before it.
Exact-type matching, what a null provider does, the two promises that need a
method body, which defaults are replaceable, whether a Phase can ever be added
without breaking, and how a new processor check is allowed to land.
Cheap while nothing depends on the answer and breaking afterwards. An
application relies on these without ever writing them.
What Shell hands over of vexelray-gui and atchung;
the upstream bus that admits one tree; then whether GuiApp or
VexelEngine is the composition root.
v1 is only as stable as the types an application holds, and every side of
this edge is the author's own.
Two or three generated applications that differ where it matters: several windows on one
device, component-heavy with real placement, long-running with nothing to settle.
The component witness (W2) and the long-running one (W3) are built and pass,
and the suite's applications are generated from the template. W1, several windows on one device, is
built too, and shaped the Window seam and the dialogs. Each new shape was expected to
find something, and what it finds is written up as the ports were.
A second starter, the BOM, and aggregated reachability metadata; and a name for the diagnostics module, since a module name is Maven coordinates an application writes down. After the component model, because what an application inherits depends on what the stack turns out to reach. The single highest-leverage item on the open-questions list.
Delete what exists only for backwards compatibility: kept constructors, redundant seams, reserved names nothing honours, test seams in public API, dead API, and prose that describes how something used to work. The second v1 condition: there is no legacy in v1. It comes after the component model, which makes some seams redundant, and before the freeze, which would otherwise make them permanent.
Mark each public type frozen or experimental, walk the whole surface for anything that
would be renamed today, fix it, and cut 1.0. Releases are 0.x until then.
The marking exists and does not reflect: @Stability, on every
public type in -api, -core, -shell and
-automation, and every one says EXPERIMENTAL. Nothing is frozen yet, and
vexelray-gui wants a marker of its own. The pass is meaningless while any step above is
open.
The five dimensions were regraded on 10 October 2026 at 777d496 against TODO.md,
threading.md's matrix, components.md and the code, not inferred from the docs alone. The hash is
when this page was read, not what it describes. The threading grep reads
src/main/java only, because a template's resources are a generated project's source
rather than the framework's. The v1 ledger and the road to v1 were first assembled on 28 September 2026 and re-read on the same day as the dimensions, with one read of Shell's public signatures for the sibling types it returns. Nothing checked them against the whole public surface; that walk is step 13.
# 314 green, offline: core 58, shell 106, template 55, processor 90, automation 5 mvn -o test # 60 hits in eight files: Lanes 36, Watchdog 12, Placement 5, one each in Stalls, Shell and # PointerLock, two keywords in the template's Checks, two words in the processor's Channels. # A ninth file is a new thread seam grep -rn "Thread\|Executor\|java.util.concurrent\|volatile\|synchronized\|Atomic" \ --include=*.java vexelray-framework-*/src/main/java | grep -v MainThread # 41 hits, four names: atchung 17, tactroller 14 and probe 8 in the framework's Java, # and kronometer 2 in the template's Motion.java. A fifth name is a new dependency edge grep -rn "import sibarum" --include=*.java vexelray-framework-*/src/main # dimension 5, against a real window: exit status is the verdict printf 'find Save\nclick save\nsettle\nshot after.png\n' | ottermate --script -