Skip to main content

Lua v4 Combat Actions

Lua v4 exposes two deliberately different combat actions:

  • hexis.combat.fire issues one bounded stationary ranged shot. Success proves that Hexis fired, not that the shot hit or defeated the target.
  • hexis.combat.engage pursues and fights one exact target with the implemented melee_kite behavior. Success proves that target was defeated.

Both actions are implemented in the source foundation. They remain non-executable in these docs until a named distributed build passes controlled live acceptance.

Shared boundary

Both actions require the combat capability. Their potential resources are exactly MOVEMENT, CAMERA, ATTACK_USE, and HOTBAR, acquired atomically. Contention returns retryable busy. A cleanup failure returns non-retryable internal_error and quarantines the shared controls.

The target shape for both actions is exactly:

target = {
ref = {
id = "f81d4fae-7dec-11d0-a765-00a0c91e6bf6",
world_generation = 8,
},
}

id is a UUID and world_generation is a non-negative integer copied from bounded entity evidence. The target must resolve to the same live living entity in the current resident world and be no farther than 128 blocks at preflight. A stale generation returns non-retryable world_changed; a missing, dead, or too-distant entity returns retryable target_lost. Both happen before controls or a combat mechanism are acquired. Neither action falls back to coordinates, substitutes another entity, or chooses a replacement target.

Unknown, missing, or malformed request fields are catchable Lua errors before dispatch. Action results become visible only after owned controls and inputs have been safely released. Host timeout, explicit cancellation, world change, authority loss, or runtime invalidation can terminate either action through the shared lifecycle with empty action-specific data.

Fire one ranged shot

hexis.combat.fire accepts exactly these fields:

local shot = hexis.combat.fire({
target = {ref = observed_target.ref},
held = {
name_patterns = {"Frozen Scythe"},
},
projectile = "direct",
timing = {
reaction_ms = 100,
dwell_ms = 100,
cooldown_ms = 500,
},
})
local result = hexis.await(shot, {timeout = 5})

The held selector contains 1 through 16 nonblank name substrings of at most 128 characters. Matching is trimmed and case-insensitive. This action does not select a weapon: the current main-hand item must match at the start and again immediately before firing.

projectile is exactly direct or arrow. direct aims at the target; arrow adds the implemented vertical gravity compensation. It does not infer the projectile from the held item. Timing is strict and bounded:

FieldBounds
reaction_msinteger 0 through 750
dwell_msinteger 50 through 500
cooldown_msinteger 250 through 2,000

The action takes exclusive movement control and clears movement input. It waits for the manager-owned minimum interval since the previous accepted fire, applies the requested reaction delay, then requires line of sight and aim within 1 degree continuously for dwell_ms. Aim is bounded to 3 seconds. It sends one vanilla right-click and stops. cooldown_ms is a lower bound before this shot, not an extra sleep after it.

A mechanism result has exactly:

{
outcome = "fired", -- or "not_fired"
target = {ref = observed_target.ref},
projectile = "direct",
elapsed_ms = 284,
fired_tick = 93012,
}

fired_tick is 0 when no shot was sent. The mechanism outcomes are:

CodeRetryableMeaning
okNoOne right-click was accepted; outcome is fired
item_not_foundNoThe held item did not match or changed before firing
target_lostYesThe exact living target disappeared or exceeded 128 blocks
aim_failedYesVisibility and settled aim were not achieved within 3 seconds
busyYesPhysical combat control could not issue the shot
cancelledNoThe bounded shot was cancelled

Those mechanism outcomes include the exact data above. Preflight and shared lifecycle failures can instead carry empty data. combat.fire does not confirm damage, health change, death, drops, or a kill. It does not emit player_dead.

Engage one melee target

hexis.combat.engage accepts required target, behavior, and timeout_seconds, plus optional held and stop_below_health_percent:

local fight = hexis.combat.engage({
target = {ref = observed_target.ref},
held = {name_patterns = {"Melee Weapon"}},
behavior = {
kind = "melee_kite",
attack_range = 3,
minimum_distance = 1.5,
preferred_distance = 2,
maximum_distance = 2.5,
maximum_pursuit_distance = 64,
attacks_per_second = 7,
},
timeout_seconds = 15,
stop_below_health_percent = 25,
})
local result = hexis.await(fight, {timeout = 20})

behavior has exactly the seven fields shown. kind is exactly melee_kite. All distances must be finite and satisfy:

0.75 <= minimum_distance <= preferred_distance <= maximum_distance
<= attack_range <= 3.0

maximum_pursuit_distance is from 4 through 128, attacks_per_second is an integer from 1 through 12, and timeout_seconds is an integer from 1 through 30. An optional held uses the same 1-through-16, 128-character selector bounds as combat.fire. stop_below_health_percent, when present, must be a finite number from 1 through 100 inclusive. Omitting it disables this threshold and preserves the engagement's behavior before the field was added; there is no implicit default.

The action never selects a weapon. When held is present, the current item must match it. When omitted, the current selected slot, display name, and item ID are accepted as the weapon. Either way, any later slot or item change returns non-retryable item_not_found.

Outside maximum_distance, Hexis pursues the target's observed position with the Pathfinder. This action explicitly disallows drops and Ether Transmission. It returns retryable unreachable only when the target exceeds the explicit maximum_pursuit_distance policy bound or the typed navigator proves complete frontier exhaustion. Two failed navigation attempts and 4 seconds without meaningful pursuit progress instead return retryable navigation_failed. Unlike the standalone navigator's non-retryable unreachable, combat keeps this code retryable because the moving target and a later observation can change the pursuit decision.

Inside maximum_distance, Hexis aims at the exact target. Below preferred_distance, it moves backward only when loaded local collision checks prove solid, dry support and clear body space. Otherwise it holds position. minimum_distance selects precision retreat input; it is not a guaranteed hard separation distance. Hexis attacks only within attack_range, with line of sight, the exact entity under the live crosshair, and the requested attack-rate limit. Three seconds without visibility while in the engagement range returns retryable aim_failed.

When stop_below_health_percent is present, the combat mechanism checks the player before it begins physical combat and on every later engagement loop. Health at or below the threshold returns non-retryable player_low_health. The mechanism stops active navigation and safely clears and releases its movement, camera, attack/use, and hotbar controls before publishing that result. Lua owns any later retreat, healing, or stop policy; the action does not perform one automatically.

A mechanism result has exactly:

{
outcome = "defeated", -- or "not_defeated"
target = {ref = observed_target.ref},
attacks = 3,
elapsed_ms = 2810,
stage = "attacking", -- "pursuing" or "retreating"
}

A low-health result adds exactly the sampled percentage and accepted threshold to that common mechanism data:

{
outcome = "not_defeated",
target = {ref = observed_target.ref},
attacks = 2,
elapsed_ms = 30,
stage = "pursuing",
health_percent = 20.0,
threshold = 25.0,
}

health_percent is the mechanism's finite player-health percentage sample, clamped to 0 through 100. threshold repeats the exact accepted request value.

The mechanism outcomes are:

CodeRetryableMeaning
okNoThe exact target was observed dead or dying; outcome is defeated
item_not_foundNoThe guarded main-hand item changed or failed its selector
target_lostYesThe exact target disappeared without proof of defeat
player_dead (ActionCode.PLAYER_DEAD)NoThe local player died during combat
player_low_healthNoThe sampled player health was at or below the requested stop threshold
timeoutYestimeout_seconds elapsed before a terminal target outcome
unreachableYesThe target exceeded the maximum-pursuit policy or a complete search frontier was exhausted
navigation_failedYesNavigation failed twice or pursuit made no meaningful progress for 4 seconds without proof of unreachability
aim_failedYesVisibility could not be established for 3 seconds in range
busyYesRequired physical combat control was unavailable
cancelledNoThe engagement was cancelled

Those mechanism outcomes include outcome, target, attacks, elapsed_ms, and the final stage. Only player_low_health also includes health_percent and threshold. player_dead and player_low_health are distinct from target_lost and never claim the target was defeated. Preflight, internal, safe-stop, and shared lifecycle failures can instead carry empty data.

Policy belongs in Lua

Lua owns bounded observation, candidate filtering and ranking, the decision to fire or engage, server-specific item selectors, retries after fresh evidence, quest and world state, and when to stop. Hexis owns UUID revalidation, resource arbitration, path pursuit, safe retreat, camera and line-of-sight checks, exact crosshair attacks, cadence, held-item guarding, deadlines, and cleanup.

There is no v4 hexis.combat.hunt. A hunt is a Lua policy loop that repeatedly observes, chooses one entity, invokes one of these actions, handles its typed result, and observes again before retrying. A retryable target_lost is not permission to reuse stale identity or assume defeat.

Official reduced acceptance bundles

The embedded v4 catalog now contains two maintained combat acceptance bundles. They are deliberately small test workloads, not renamed versions of the old products:

BundleWhat its policy provesWhat it does not prove
hexis:zealot-one-shot-acceptanceEnters The End, equips a Frozen or Glacial Scythe, chooses one resident Enderman using exact carried-block evidence before label evidence, fires one direct shot, and temporarily avoids that UUID after a confirmed fired outcomeZealot Farmer parity, movement or chase, hit or kill confirmation, loot, player or staff safety, lag handling, inventory recovery, or a complete farming loop
hexis:sven-engage-acceptanceEnters the Park, chooses exactly one self-labelled Sven boss when the bounded heuristic is unambiguous, otherwise chooses a regular wolf, and runs one bounded melee_kite engagement with configured low-health stop and an explicit retry allowlistSven Slayer parity, quest start or phases, definitive boss ownership, weapon selection, healing, abilities, pups, drops, inventory policy, or world recovery

The Zealot policy prefers an Enderman carrying minecraft:end_portal_frame, then a label containing Special Zealot, then the first other eligible Enderman. A successful action means only that the right-click was sent. The policy re-observes before choosing again and stops on every non-retryable result.

The Sven policy treats labels_contain_self_name as bounded heuristic evidence, not proof of ownership. It skips a boss carrying another player's label and refuses to choose when more than one self-labelled boss is visible. Its current retry allowlist contains target_lost, timeout, unreachable, navigation_failed, aim_failed, and busy when the result is also retryable. Its required flee_health integer setting has default 10, minimum 5, maximum 20, and step 5. The entrypoint passes that snapshot value as stop_below_health_percent and stops on player_low_health. player_dead and every other non-retryable result also stop it. The threshold does not select a healing item, heal the player, or implement a retreat route.

Zealot has an empty configuration schema; Sven has only the required flee_health field. Both use strict bundle-local workload packs plus hidden official area catalogs. They are UI-visible preview workloads. Their parser and headless composition tests are source evidence; neither bundle nor combat action has passed a named distributed-build live acceptance gate.

Acceptance gates and adjacent gaps

The source foundation has strict parser tests, action/result tests, and a Lua composition test that observes a Zealot, fires once, re-observes after a lost Sven target, then proves defeat through a second engagement. That is headless composition evidence, not live combat acceptance.

Before either action becomes public or AI-runnable, a named distributed build must verify at least direct and arrow aim, shot cadence, movement neutralization, melee pursuit, blocked and unsafe retreat, item changes, PLAYER_DEAD, an initially low player, a mid-engagement health-threshold crossing, omission of the optional threshold, target loss versus defeat, cancellation, world change, control contention, and safe-stop on a real client.

There is no maintained v4 Mob Killer source in this checkout or the embedded v4 catalog. The two maintained combat bundles are reduced acceptance workloads, not Zealot Farmer or Sven Slayer controllers. Their strict packs and policies do not establish accepted handling for player or staff safety, lag, loot, inventory, quest phases, boss mechanics, healing, abilities, or world recovery. The two actions make those products possible to compose; the acceptance bundles do not prove those products complete.