Lua v4 GUI Evidence and Actions
Lua v4 exposes GUI state as immutable evidence and permits one guarded, irreversible operation at a time. Lua decides which observed slot is meaningful. Hexis revalidates the screen and selected slot evidence on the client thread before sending anything.
The source foundation implements hexis.gui.snapshot, hexis.gui.transact,
and filtered gui.changed subscriptions. They remain non-executable in these
docs until a named distributed build passes controlled live acceptance.
Observe one supported screen
hexis.gui.snapshot({}) requires gui.read. Its request must be empty. It
returns one of:
{status = "closed", observed_tick = 1042}
{status = "unsupported", observed_tick = 1042}
{
status = "open",
observed_tick = 1042,
screen = screen_snapshot,
}
closed means there is no open container screen. unsupported means a
container screen is open but its top container is not a rectangular 1-through-6
row inventory. An open result contains only the top container's 9 through 54
slots, never the player's 36 inventory slots:
screen_snapshot = {
generation = 7,
revision = 18,
title = "Chronomatron (3)",
rows = 6,
slot_count = 54,
slots = {
{
index = 0,
row = 0,
column = 0,
ref = {
screen_generation = 7,
observed_revision = 18,
index = 0,
fingerprint = "<64 lowercase hexadecimal characters>",
},
item = {
empty = false,
id = "minecraft:red_wool",
name = "Red",
count = 1,
foil = true,
lore = {"Remember this tile"},
lore_truncated = false,
},
},
},
}
Indexes, rows, and columns are zero-based. The title and item name are at most
256 characters; item IDs are at most 128 characters. A slot exposes at most 16
lore lines of at most 256 characters each. lore_truncated reports omitted
lines. An empty slot has ID minecraft:air, an empty name, and count zero.
The slot reference is opaque compare-and-submit evidence, not permission to click that index forever. A screen generation identifies one exact container menu instance. Its revision starts at 1 and advances for every observed title or slot fingerprint change. Opening a replacement menu creates a new generation. Screen generation is separate from the event envelope's world generation.
Submit one guarded operation
hexis.gui.transact requires gui.interact. An activate request is:
local result = hexis.await(hexis.gui.transact({
screen = {
generation = screen.generation,
revision = screen.revision,
},
operation = {
kind = "activate",
target = screen.slots[13].ref,
guards = {screen.slots[50].ref},
},
}), {timeout = 2})
screen.generation and screen.revision are positive integers. target has
exactly the four fields returned in a slot reference. guards is optional and
contains at most eight slot references. Target and guard indexes must be
unique, and every index is from 0 through 53.
On one client-thread turn, Hexis requires the same screen generation, the
target and every guard to still exist, and each fingerprint to still match.
The current screen revision may be newer than the request: unrelated slot
changes do not invalidate an activate when all named evidence is unchanged.
Immediately before the irreversible Minecraft call, the action atomically
claims ownership of its terminal result. Hexis then sends exactly one vanilla
left-button PICKUP click for the target.
There is no public button, click type, shift-click, drag, arbitrary packet,
repeat count, or batch operation. The source foundation spaces accepted click
submissions by at least 50 milliseconds.
Closing is the only other operation:
hexis.gui.transact({
screen = {generation = screen.generation, revision = screen.revision},
operation = {kind = "close"},
})
Unlike activate, close requires both the same generation and the exact current revision. It accepts no target or guards and makes the same terminal ownership claim immediately before closing the container.
This creates one deterministic boundary for cancellation and an irreversible
side effect. Cancellation or lifecycle invalidation wins if it claims terminal
ownership first, so the click or close is not submitted. If the GUI action
claims first, cancellation cannot replace its outcome: the submitted result
wins and is published after cleanup. There is no state where Lua receives
cancelled while the action knowingly proceeds with a later click.
The action atomically owns INVENTORY_SCREEN, MOVEMENT, CAMERA,
ATTACK_USE, and HOTBAR. Its action-specific data is:
{
operation = "activate", -- or "close"
applied = true,
submitted = true,
screen_generation = 7,
basis_revision = 18,
validated_revision = 19, -- present after validating an open screen
slot = 12, -- present for activate outcomes after screen validation
}
applied and submitted are both true only after the one operation was sent.
If the mechanism throws after claiming the irreversible operation but before
it can report whether application completed, Hexis returns non-retryable
internal_error with this narrower data instead:
{
operation = "activate", -- or "close"
screen_generation = 7,
basis_revision = 18,
application_uncertain = true,
}
Hexis quarantines the shared GUI resources after that indeterminate commit. Lua must not infer that the operation either did or did not apply, and a later GUI action fails closed until the quarantine is cleared by runtime recovery.
The action can return:
| Code | Retryable | Meaning |
|---|---|---|
ok | No | One requested operation was submitted |
screen_closed | No | No supported container remained open |
screen_changed | No | A different screen generation replaced the target |
gui_precondition_failed | Yes | The exact close revision, target, or a guard no longer matched |
busy | Yes | One of the five GUI control resources was unavailable |
cancelled | No | Cancellation won before the irreversible commit claim, so no operation was submitted |
internal_error | No | The transaction or safe cleanup failed; a post-claim exception reports application_uncertain = true and quarantines GUI resources |
Shared lifecycle invalidation can instead return world_changed or
authority_lost. Malformed tables, unknown fields, or a missing declared
capability are catchable Lua errors before dispatch. Lua must observe again
before considering any retry.
Subscribe from an exact revision
gui.changed shares the opaque subscription, events.next, hexis.select,
unsubscribe, timeout, and run-cleanup model with filtered chat. It requires the
events.gui.filtered capability:
local changes = hexis.events.subscribe({
type = "gui.changed",
filter = {
screen = {generation = screen.generation},
after_revision = screen.revision,
slots = {12, 49}, -- optional
},
})
The request has exactly type and filter. The filter requires one positive
screen generation and positive after_revision. Optional slots contains 1
through 54 unique indexes from 0 through 53. Omitting it admits changes from
every top-container slot. Lifecycle and title events have no slot and are
delivered even when slots is present.
The returned subscription is opaque, reusable, and owned by the current run.
A run may hold at most 16 active subscriptions total across GUI and chat. A
subscription created for a screen generation that is no longer current
receives a synthetic closed GUI record instead of attaching to another
screen.
Subscription uses the tracker's synchronized boundary for atomic cursor replay
and listener admission. That synchronized call may run on the Lua worker:
retained changes newer than after_revision are queued before the listener is
admitted for future delivery. Later screen mutations originate on the client
thread and enter only the subscription's thread-safe queue. Lua still consumes
them on its single owner lane; the client thread never invokes Lua. The tracker
retains at most 256 ordered mutations for the current screen. A stale cursor
produces resync_required rather than pretending the missed delta history is
complete.
Each envelope has the shared event fields and GUI-specific data:
{
type = "gui.changed",
sequence = 42,
observed_tick = 1043,
observed_at = {monotonic_seconds = 92.125},
world_generation = 3,
source = "minecraft_client",
dropped_before = 0,
data = {
kind = "slot_changed",
screen_generation = 7,
previous_revision = 18,
revision = 19,
slot = 12,
before = old_slot_snapshot,
after = new_slot_snapshot,
},
}
kind is exactly slot_changed, title_changed, closed, replaced, or
resync_required. A title change adds title. A slot change adds slot,
before, and after; their references use the previous and new revisions.
The three terminal or resync records contain only the common data fields.
Each GUI subscription queues at most 64 records and shares the 128-record run
limit with chat. Producers never block the Minecraft client. Overflow drops
old evidence, sets dropped_before, and turns the next deliverable GUI record
into resync_required. A script must not continue from partial deltas after
that result. It must take a fresh snapshot and subscribe from that snapshot's
generation and revision, or stop. The two reduced acceptance bundles stop.
Reduced official workloads
The embedded catalog contains two reduced acceptance bundles. Experiment Table has strict config schema version 2; Melody keeps an empty schema:
| Bundle | Source evidence | Explicit exclusions |
|---|---|---|
hexis:experiment-table-reduced-acceptance | Reacts to an enabled, already-open Chronomatron, Ultrasequencer, or Superpairs screen using revisioned snapshots, mutation events, target fingerprints, and a control-slot guard where timing state matters | Opening the table, selecting experiments, claiming rewards, auto_renew, currency or resource spending, and full relaunch parity |
hexis:melody-reduced-acceptance | Reacts to quartz-note changes on an already-open 54-slot Harp, derives repeated presses from the wool stack above a note, and resnapshots after every separately guarded click | Opening the Harp, song selection, navigation, recovery, latency compensation, and any perfect-score claim |
Experiment Table stops on close, replacement, stale-journal resync, event closure, or timeout. Its current policy records Chronomatron foil transitions, captures Ultrasequencer order from item counts, and remembers Superpairs cards from item name and lore. When a revealed card matches remembered evidence, it now selects the earlier mate slot instead of re-clicking the currently revealed slot.
Its config schema version 2 contains exactly three required booleans:
solve_chrono, solve_ultra, and solve_superpairs. All three default to
true. At run start the script takes one immutable config snapshot; if the
already-open screen's corresponding solver is false, it exits before creating
the GUI subscription or submitting a transaction. There is no auto_renew
field. The bundle does not invoke any purchase or renewal flow, and all
currency spending remains outside its contract.
Melody races screen mutations against a narrow score-chat subscription and
stops when either the screen becomes unusable or a matching % message
arrives. A stack can lead to multiple clicks, but each is a separate
gui.transact action followed by a fresh snapshot. There is no unsafe or
batched click escape hatch. Correct perfect-score behavior remains unproven.
Both workload packs are strict schema 1 data, not general GUI automation configuration. Source tests prove parsing, ordered event composition, and one submitted target in a headless harness. They do not establish real server timing or menu correctness.
Live acceptance gates
Before these calls or bundles become public and AI-runnable, a named distributed build must verify supported and unsupported containers, slot and title mutations, replacement and closure, cursor replay, journal and queue resync, stale target and guard rejection, unrelated-revision tolerance, contention, cancellation on both sides of the irreversible commit boundary, post-commit uncertainty and quarantine, and cleanup on a real client.
Experiment Table additionally needs controlled live runs of all three games with each solver enabled and disabled, across rounds, and without accidental reward, renewal, or currency clicks. Melody needs measured note-to-click latency, stacked-note behavior, score termination, lag and tick-rate variation, and song completion. Until that evidence exists, the bundle is a reduced reaction test, not a perfect-score product.