EXPcaliburINSIDE THE MACHINE

06 / CLASSIC

CLASSIC engine: how meters build power

CLASSIC watches what your VM program actually does, not what you call it. A recognized action fills a meter, and the meter drains again when the action stops. Those meters give the knight limited attack, survival, and collection power. The knight must still fight and collect gems to earn EXP.

Input: The VM actions in one tick.

Output: Meter values and limited power for the knight.

Question: How do chain checks and time change that power?

Attach CLASSIC to the model we already understand

We already have a pilot that selects actions, a VM that runs it, and a world that turns those actions into score. CLASSIC adds a side path. After each tick, it checks the VM's work, records named actions, and changes their meters.

Those meters can give the knight more power, but the knight's basic job stays the same. The pilot must still scan, move, and aim. The world must still defeat enemies and collect gems before EXP increases.

CLASSIC: a recognized contribution becomes a temporary advantageCLASSIC: a recognized contribution becomes a temporary advantageObserved behaviorThe VM records whathappened during thisslice.Host acceptanceA chain contributes onlywhen its required evidenceis accepted.Persistent meterRepeated accepted ticksraise the contribution;absence decays it.Axis productsThroughput × its factorsSurvival × its factorsHarvest × its factorsCaps and HEATFamily breadth boundsheadroom. Active poweralso raises incomingdanger.World fieldsDamage, local area,cadence, armor, gait, andcollection feed theordinary arena.The pilot must keep the world loop profitable while the contribution loop consumes VM work.
CLASSIC checks the bot and boosts the knight. The VM records a behavior. CLASSIC checks it and changes a meter. The meter can boost the knight, who still needs to fight and collect gems. View diagram at full size

A chain is a named check of one or more VM behaviors. VECTOR, for example, checks a jump through an address loaded from memory. Each chain has one check and one meter. A family groups related chains. When the game sets a power limit, it counts both the active chains and their different families. So eight chains from one family give a lower limit than eight chains from four families.

What CLASSIC does each tick

A headerless upload selects CLASSIC. While the bot runs, the VM records its actions. When the VM stops for that tick, CLASSIC checks those records and changes the chain meters. The world then turns the meters into limited knight power before it handles attacks and gem pickups.

Keep the four jobs apart: the VM records, the host checks, the meter changes, and the world uses the result. One neat instruction trace does not instantly make the knight all-powerful. A successful tick gives only one meter point. The chain must keep passing for many ticks before its meter gives full power, and even then the world limits it.

Follow one VECTOR tick before learning all 41 chains

VECTOR is one CLASSIC chain: a jump whose address came from a memory load. A later section shows how to build that jump. For now, just follow one tick in the order the work happens. Along the way the bot also fills three proof cells. Think of them as a small challenge-and-answer game, not a secret way to edit EXP.

One game tick, four owners of the work
01 · HOSTWrite the challenge

Before the bot runs, the host puts this tick's VECTOR challenge at 0xFE8C.

The bot can read this word. It does not need the hidden seed that made it.
02 · BOT + VMDo and answer

The bot reads the word, runs the answer formula, writes the result at 0xFE90, and leaves VECT at 0xFE94. A loaded-target jump makes the VM record computed_jump: “the bot used a computed jump.”

Aim and movement requests also happen while the VM runs.
03 · CLASSICCheck the whole claim

After the VM stops for this tick, CLASSIC checks the jump note, magic, and fresh answer together. All three pass → VECTOR's meter gains one. Any miss → it loses three, stopping at zero.

The meter remembers past ticks; the VM's jump note does not.
04 · WORLDUse earned power

CLASSIC calculates limited knight boosts from the meters. The world then moves enemies, resolves attacks, and handles gem pickups.

A boost can help a fight. Only a picked-up gem adds EXP.
First pass0 → 1 / 512Proof accepted. With only VECTOR active, the knight's attack fields still stay at their ordinary values.
256 passes in a row255 → 256 / 512VECTOR now counts toward how much power the game will allow; its own attack factor is 1.25. With no other active chain, this is the first tick its attack power can reach the knight.
Continue to pass511 → 512 / 512VECTOR reaches its full 1.5 factor. Miss later ticks and the meter drains three at a time.

Timing detail: the bot's movement request happened in step 02. A movement bonus first calculated in step 03 cannot change that earlier request; it can affect a later tick. Combat and gem pickup happen in step 04, after the new attack and collection values are calculated.

What those meter marks do in a real fight

The halfway mark is easy to miss. VECTOR's factor grows before its meter reaches 256, but the attack boost first reaches the knight at 256. The next figure shows one level-one knight facing one 14-HP spider at three points in that climb.

Assume that VECTOR is the only active chain, the spider is in range, and an attack is ready. The figure shows what one successful hit would do, not a complete match.

One VECTOR meter, three versions of the same swing
FIRST PASS · 1/51212 damage

VECTOR's answer passed, but it does not count toward the attack limit yet. One ordinary hit leaves the spider at 2 HP; the knight still needs another hit.

HALFWAY · 256/51245 damage

VECTOR now counts. Its 1.25 attack factor produces 45 damage, which replaces the ordinary 12. One successful hit can defeat this spider. The full-circle attack is still locked.

FULL · 512/51290 damage · 81px radius

VECTOR's factor reaches 1.5. One successful hit can defeat the spider, and the attack can cover a capped circle around the knight. Range, timing, and enemy position still matter.

Defeating the spider only drops the 3-EXP gem. The score goes up when the knight collects it. The sprites are from the game. Their positions in this diagram are example positions.

Why does the result change so suddenly at 256? The host adds a chain to K only when its meter is at least 256. With VECTOR at 1, K = 0, so the knight keeps its ordinary 12-damage attack.

At 256, K = 1 and VECTOR's attack factor is 1.25, so the damage becomes 180 × (1.25 − 1) = 45. At 512, the same calculation gives 180 × (1.5 − 1) = 90. This calculated damage replaces the ordinary 12 damage; it is not an extra 45 or 90 on top.

A full-circle attack needs an attack factor of at least 1.4. That is why the 256-point version still uses the ordinary aimed shape.

Families describe kinds of machine understanding

The CLASSIC table has 41 chains, and each chain carries two labels. Its family names the kind of VM action the chain checks: jumps, stack work, changed instructions, or bytes with two uses.

Its reward type, which the code calls an axis, names the knight power it can increase: attack, survival, or collection. The family affects the power limit, while the axis selects which power receives the chain's factor.

Take VECTOR. It checks a jump, so it belongs to the control-flow family, and its factor increases attack. PIVOT, by contrast, checks stack movement and increases survival. One label tells you what the bot did; the other tells you which knight power can change.

The map shows every family-and-axis pair that exists in this release. Open a box to see its chains and build steps.

Where the 41 CLASSIC chains apply

Each row shows a type of VM behavior. Each column shows the knight power it can help. Open a box to read its chains.

VM behavior ↓   Reward →SwordTHR · AttackShieldSRV · SurvivalMagnetHRV · Collection
Control flowCF · Jumps and returns
Sword11 chainsVECTOR, CROWN + 9 more
ShieldNo chain in this release
MagnetNo chain in this release
StackSTACK · Saved words and calls
Sword6 chainsMIRAGE, OSSUARY + 4 more
Shield7 chainsPIVOT, ABYSS + 5 more
Magnet2 chainsPLUMB, SEXTANT
Changed codeSMC · Written instruction bytes
Sword7 chainsPHOENIX, PACKER + 5 more
ShieldNo chain in this release
MagnetNo chain in this release
Same bytes, new meaningREINT · Code read as data or re-entered
Sword2 chainsYGGDRASIL, SPHINX
Shield1 chainJANUS
Magnet5 chainsPOLYGLOT, KALEIDOSCOPE + 3 more

These boxes come from the source table. An empty box has no chain. A chain must pass on repeated ticks to fill its meter.

Notice that all eleven control-flow chains increase attack, while stack chains spread across all three powers. The game assigns these axes, so you cannot move VECTOR to another axis by changing its proof cells. And even an accepted chain adds one point only to its own meter. It does not give full power on its first accepted tick.

How this chapter fits together

Sustained participation, not a permanent toggle

Each chain has a meter that keeps its value across ticks. A passing tick adds one point, and a tick without that chain removes three. The meter always stays between 0 and 512, so finding a behavior once cannot give a permanent full-strength bonus.

The meter sets the chain's current factor, and the engine multiplies factors separately for attack, survival, and collection. An absent chain has a factor of 1, which changes nothing. That is why ordinary bots behave the same way while CLASSIC is watching.

The engine also keeps two counts. K counts chains with meters of at least 256, and Fam counts the different families among those chains. The limit uses Keff = min(K, 2 × Fam), so chains from one family cannot raise the limit as far as a varied set can.

For example, eight qualifying chains from one family give Keff = 2, while eight from four families can give Keff = 8. This example starts after the host has accepted the chains; it does not show how to activate them.

Work one meter through time

Once the host has decided which chains passed this tick, the following code updates every meter. Watch how a chain gains or loses points on each tick.

RUST crates/engine/src/vulns.rs · source lines 718–721
        for i in 0..NCHAINS {
            let delta = if armed[i] { 1 } else { -DECAY };
            self.r[i] = (self.r[i] as i32 + delta).clamp(0, R_FULL as i32) as u16;
        }

DECAY is 3, and R_FULL is 512. Starting from zero, 300 accepted ticks put the meter at 300. Twenty absent ticks then remove 60 points and leave 240, which is below the 256-point count threshold. At 60 ticks per second, those twenty ticks take only one third of a simulated second.

CLASSIC meter needs repeated passing ticksCLASSIC meter needs repeated passing ticksMETER 0–51251225600300240512300 passing ticks: +30020 absent: −60272 passing: +272256: family-count thresholdHorizontal spacing separates the three phases; the labeled tick counts give their real lengths.
Watch one CLASSIC meter climb, dip, and recover. The vertical axis uses exact meter values. The three horizontal phases have different tick counts; spacing keeps their changes visible. At 240, the chain still has some factor but no longer counts toward family breadth. View diagram at full size

One accepted CLASSIC contribution over time

Starts at zero; accepted ticks add 1, absent ticks subtract 3; the meter is clamped to 0–512. This models reward persistence after host validation.

A full meter takes 512 continuously accepted ticks to build from zero, about 8.53 seconds at 60 ticks per second. Going the other way, 85 absent ticks take a full meter down to 257, and the next one leaves 254, below the count threshold. So the bot must keep doing the action, even after the chain has reached full strength once.

As the meter falls, the chain factor shrinks gradually. The family count is different, because it uses a cutoff at 256. Crossing that line can lower the maximum knight power all at once.

Convert a meter into an axis factor

Why this formula? A chain that passes only on some ticks should give less power than one that passes on every tick.

Let r be the meter value and F the factor at a full meter. First calculate tau = r / 512, the fraction of a full meter, and then calculate 1 + (F - 1) * tau.

The code stores these values as Q16.16 fixed-point integers, a format in which 16 bits hold the fractional part:

RUST crates/engine/src/vulns.rs · source lines 198–207
#[inline]
pub(crate) fn tau_fx(r: u16) -> i32 {
    ((r as i64 * ONE as i64) / R_FULL as i64) as i32
}
#[inline]
pub(crate) fn f_fx(f_ceiling: i32, tau: i32) -> i32 {
    ONE + fxmul(f_ceiling - ONE, tau)
}
Meter r Participation Current factor when F = 1.5
0 0 1.000
128 0.25 1.125
256 0.5 1.250
512 1 1.500

The engine multiplies this factor into the chain's assigned axis, but it counts family breadth only once the meter reaches 256. If other chains already allow a boost, this factor can therefore help even before its own meter reaches 256.

Work family breadth through the cap rule

For each chain at 256 or more, the code sets a family bit. It counts those bits to get Fam, then calculates Keff:

RUST crates/engine/src/vulns.rs · source lines 752–758
            if self.r[i] >= R_HALF {
                k += 1;
                fam_mask |= 1 << CHAIN_FAMILY[i];
            }
        }
        let fam = fam_mask.count_ones();
        let keff = k.min(2 * fam) as i32;

Eight counted chains in one family give K=8, Fam=1, and Keff=2. Spread the same eight across four families and Keff=8. Same chain count, very different room for power. That is why the CLASSIC apex's eight chains from four families matter.

Eight full-enough meters. Where do their chains come from?

One chain with a meter of at least 256

All eight in one family

Control flow

K = 8 chains, Fam = 1 family

Keff = min(8, 2 × 1) = 2Lots of activity but little variety, so the effective count is only two.

Two in each of four families

Control flow
Stack
Changed code
Reinterpreted bytes

K = 8 chains, Fam = 4 families

Keff = min(8, 2 × 4) = 8Same number of chains. More variety raises the cap's room.

This diagram starts after each chain has reached a meter value of at least 256. It shows the reward cap rule, not how to trigger those chains. A meter below 256 still gives some factor, but its chain adds no chip here.

Three axes with different jobs

Attack, which the code calls throughput, changes damage, attack area, hit passes, and attack timing. Survival changes armor, invulnerability time, and movement. Collection, called harvest in the code, changes gem pickup reach and pull speed. None of the three multiplies EXP directly.

Attack can defeat enemies faster and make more gems, but a dead knight collects none of them. Survival keeps the knight alive, yet it cannot make a missed attack hit. Collection helps only after gems appear. Power has to survive the whole trip from hit to pickup, since the score rises only when the knight collects a gem.

Hard caps are part of the game design

fold_levers combines the factors and applies maximum values. More active chains can raise those maximums, but the effects always stay limited. More chains also increase HEAT, so the knight faces more danger as its power grows.

A1 is this release's balance change. It multiplies CLASSIC's bonus limits by 13/20 but leaves the older VM behavior checks unchanged. CLASSIC still accepts the same chain actions; its maximum power simply sits below the later ABYSS steps.

The score does not necessarily fall by exactly 13/20. A smaller attack circle can miss enemies that used to be in range. Lower damage can require another attack, and a later level increase can delay a heal that saved the run. Each change then affects the ticks after it. That is why the release notes warn against guessing scores between measured points.

Why the factor product is not a score multiplier

Suppose two active attack chains give factors 1.2 and 1.5, for a product of 1.8. The game uses 1.8 to calculate limited combat values; it does not multiply final EXP by 1.8. Hits, deaths, and gem pickups still follow their own rules.

A spider with 14 HP needs two hits when each hit does 12 damage. It still needs two at 13 damage, and only at 14 does one hit suffice. Even then, EXP increases only after a gem pickup. Other thresholds jump in the same way. An attack can suddenly reach another enemy, armor can stop a hit, or one gem can cause a level increase.

So you cannot multiply an old score by the A1 limit to predict a new one. The new limits change where the knight moves and which enemies it hits. Those changes alter later healing and gem supply, so CLASSIC power and pilot movement must be studied together.

Follow one accepted chain into a picked-up gem

Here is a complete payout example. It starts after VECTOR has reached a full meter; a later section shows how to make it pass on each tick. All other chain meters are zero. The knight is level one, and an early 14-HP spider is in attack range. Follow the result from attack power all the way to EXP.

Link Calculation What changes
VECTOR meter r = 512, so tau = 512 / 512 = 1 Its full attack factor is 1.5
Attack axis R_thr = 1.5; the other attack-chain factors are 1 One chain has raised attack power, not EXP directly
Breadth limit K = 1, Fam = 1, Keff = min(1, 2 × 1) = 1 The cap has only one chain's headroom
Attack circle Raw radius is 60 + floor(150 × 0.5) = 135; A1 caps it at floor(125 × 13 / 20) = 81 The knight gets a local 81-pixel full circle, not a map-wide hit
Per-hit damage floor(180 × 0.5) = 90, below the A1 damage cap of 227 The stored bonus_dmg becomes 90; CLASSIC uses it instead of normal 12 damage for this hit
Timing and cost Cooldown shave is 15 ticks; extra passes remain zero; one active chain makes incoming HEAT about 1.12× The knight can attack sooner but also takes more danger

The fold_levers code sets exactly these limited attack values. Notice the two separate checks. First R_thr must reach the level that enables the circle attack, and then Keff and A1 limit the circle radius.

RUST crates/engine/src/vulns.rs · source lines 779–794
        let disc_cap = a1_cap((120 + 5 * keff).min(160));
        let swings = (1 + keff / 2).min(6);
        let armor_cap = a1_cap((8 + 5 * keff).min(48));
        if k > 0 {
            let committed = r_thr >= THR_DISC_UNLOCK_FX;
            self.hero.disc_committed = committed;
            self.hero.disc_radius = if committed {
                (floor_scale(THR_DISC_COEF, r_thr - ONE) + 60).clamp(1, disc_cap)
            } else {
                0
            };
            self.hero.extra_swings = ((r_thr >> 16) - 1).max(0).min(swings - 1) as u32;
            self.hero.swing_cd_shave = floor_scale(THR_CD_COEF, r_thr - ONE).min(a1_cap(28));
            self.hero.bonus_dmg =
                floor_scale(THR_DMG_COEF, r_thr - ONE).min(a1_cap(DMG_INJECT_CAP));

If an attack reaches the spider, 90 damage defeats its 14 HP in one hit. To decide whether it reaches, CLASSIC compares the enemy's distance with the circle radius plus the enemy radius. It therefore cannot hit an enemy elsewhere on the map:

RUST crates/engine/src/combat.rs · source lines 282–298
            } else {
                let query = radius + from_int(60);
                let enemies = &self.enemies;
                grid.for_each_near(px, py, query, |i| {
                    let e = enemies[i];
                    if !e.alive {
                        return;
                    }
                    let d = fx_dist(px, py, e.x, e.y);
                    if d <= radius + e.radius {
                        has_reach = true;
                        targets.push(i);
                    }
                });

The spider's death can create a gem worth 3 EXP. The knight still has to move close enough to collect it. Only then does total EXP rise by 3, which might cause a level change that heals the knight.

If the attack misses, the gem pool is full, or the knight never collects the gem, this example adds zero EXP. VECTOR improves a local fight; only a collected gem increases the score.

Why the instrumentation is separate

While the VM runs the bot, it records selected actions without changing any of the bot's calculations. CLASSIC reads those records only after the VM slice ends. This keeps the VM's instruction behavior separate from the game's reward checks.

For an ordinary compiled bot, CLASSIC's record system should therefore not change normal VM behavior. The repository tests this by comparing recorded runs of selected ordinary bots byte for byte. The claim applies to those tested bots.

The 41 chains, grouped by machine idea

The host checks each chain after the VM's work for a tick. Keep three things separate: what the VM noticed, whether the host accepted the chain, and the meter that remembers it across ticks. In this guide, an “exploit” means a recognized guest-VM action. It never writes directly to host memory or EXP.

Read the hand-off before the catalogue

After the VM runs, it hands CLASSIC a copy of its records. CLASSIC never reads the VM's private record buffers. A new or stopped VM reports false and zero for every record, so it cannot pass a chain by accident. Here is the hand-off:

RUST crates/vm/src/lib.rs · source lines 1377–1393
    pub fn pwn_latches(&self) -> PwnLatches {
        let (aulos_word, aulos_high) = self.pack_sp_peaks();
        PwnLatches {
            computed_jump: self.computed_jump,
            smc_exec: self.smc_exec,
            smc_addr: self.smc_addr,
            smc_orig: self.smc_orig,
            overlap_fired: self.overlap_fired,
            overlap_witness: self.overlap_witness,
            sp_marshal: self.sp_marshal,
            saw_raw_op: self.saw_raw_op,
            exec_off_image: self.exec_off_image,
            cfi_violation: self.cfi_violation,
            ret_gadget_depth: self.ret_gadget_depth,
            dispatch_max: self.dispatch_max,
            jit_dispatch: self.jit_dispatch,

In that copy, computed_jump: self.computed_jump carries the VM's yes-or-no jump record to CLASSIC. CLASSIC simply reads this value; it does not run the bot again to check the jump. The record type starts every field at false or zero:

See the default snapshot fields
RUST crates/vm/src/lib.rs · source lines 1297–1308
#[derive(Clone, Copy, Default)]
pub struct PwnLatches {
    pub computed_jump: bool,
    pub smc_exec: bool,
    pub smc_addr: u32,
    pub smc_orig: u8,
    pub overlap_fired: bool,
    pub overlap_witness: u32,

You have seen what a full meter can do. That raises the next question: how does a chain earn a single point? The 41 cards below give the checks for each chain, and you do not need to read them in order.

Pick one and ask three questions. What must the bot do? Which values must it write? Can it repeat both steps next tick? A VM record alone is only the first step.

The three locks on a CLASSIC chain

The host adds one chain point only when all three conditions are true in the same tick:

  1. Run the VM action named on the card.
  2. Put the chain's four-byte magic number at its specified address. Guest memory keeps this value across ticks, so an earlier write can still count.
  3. Read this tick's challenge from bot memory, calculate the answer, and write it to the specified address. Last tick's answer will usually fail.

The usual answer is econ_hash(challenge XOR ECHO), where each chain has a fixed ECHO value and the host changes the challenge every tick. A few chains put the answer somewhere else, and their cards explain where.

VM behavior occurred? Magic correct? Fresh answer correct? This chain's meter
Yes Yes Yes Gains 1, up to 512
Any other combination — — Loses 3, down to 0

Try VECTOR as a concrete example. Before the bot runs, the host writes a new word at 0xFE8C. The bot must then jump to a loaded address, put VECT at 0xFE94, and put the new answer at 0xFE90. Change one option at a time below, and the three checks show exactly why the tick passes or fails.

Will VECTOR get credit this tick?

Choose what the program does and what it leaves in memory. The host checks both after the bot runs.

HOST PUTS A CHALLENGE AT 0xFE8C
CORRECT ANSWER FOR THIS TICK
BOT'S WORD AT 0xFE90
1 · How did the jump get its target?
2 · What is at 0xFE94 when checked?
3 · What is at 0xFE90 when checked?
VECTOR METER · THIS CHAIN ONLY4 / 512
First 16 points shown larger so a single tick is visible. The real meter can reach 512.

Choose what the bot does, then let the host check this tick.

The sample challenges are invented. “This tick's answer” means the bot reads 0xFE8C, calculates econ_hash(challenge XOR ECHO), and stores it. The magic can stay in memory from an earlier tick. The VM forgets the jump when the next tick starts, and the answer must match the new challenge. Selecting a button does not itself perform a VM instruction.

This board starts VECTOR's meter at 4 so that you can see a three-point loss. In a new match the meter starts at zero, and a failed check there leaves it at zero.

The sample challenge words are invented, but the answer calculation and the three host checks follow the real code. The options show the cell values at check time; they do not simulate each memory write.

RUST crates/engine/src/vulns.rs · source lines 286–294
pub(crate) fn econ_hash(x: u32) -> u32 {
    let mut h = x as i32;
    h ^= h >> 16;
    h = h.wrapping_mul(0x7FEB_352Du32 as i32);
    h ^= h >> 15;
    h = h.wrapping_mul(0x846C_A68Bu32 as i32);
    h ^= h >> 16;
    h as u32
}

The bot does not need the secret KEY_SEED that makes the challenge. It can read the new challenge straight from memory. A fixed answer might pass one test tick and then fail on the next.

Put the proof in a loop alongside movement and aim. When its 10,000 instructions run out, the VM keeps its position and memory and continues on the next game tick. A HALT instead stops it permanently. CLASSIC clears the VM records every tick, so the bot must repeat both the action and the answer.

How to invent VECTOR from the code

During the CTF, the handout did not include the raw assembler or this source code. A player could still compare small compiled bots to learn their instruction bytes. Next, inspect the runner for VM instructions that the compiler never emits. Those findings were enough to build a raw .bin file by hand.

Now that the repository shows the implementation, we can check each step of that reasoning. We will build VECTOR in three passes. First make the VM record a jump, then satisfy the host proof, then repeat both without stopping the bot.

Start with the host's VECTOR check, which requires computed_jump, the VECTOR magic number, and this tick's answer. Then search the VM code for the line that sets computed_jump. Ask one small question: which jump makes that value true?

The answer lies in a taint flag that the VM keeps beside each stack value. A tainted value came from a memory load or a calculation, while a literal PUSH value is clean.

JMP_ABS and CALL_ABS check that flag when they take an address from the stack. The smallest VECTOR action is simple: store an ordinary label address in memory, load it back, and jump to it. The destination can be normal code, because VECTOR checks where the address came from, not where it leads.

Pass 1: make one computed jump

Before you read any bytes, try the two paths below. Both jump to the same address 18. Only the path through LOAD tags the address as computed, so only that path sets computed_jump. Neither path writes the host's magic or answer yet.

Same destination, different origin

Choose how the bot prepares address 18, then run one VM instruction at a time. Watch the tag beside the top stack value. This small test does not write VECTOR’s proof cells.

Now encode the loaded path as a complete 19-byte image. PUSH is byte 01 followed by a four-byte little-endian number, while STORE, LOAD, JMP_ABS, and HALT take one byte each. The target is offset 18, where HALT sits. Scratch address 0x0204 lies safely outside this image, and the longer loop later uses 0x0200 for its hash value.

Image offset Raw bytes What the VM does
0 01 12 00 00 00 Push destination 18.
5 01 04 02 00 00 Push scratch address 0x0204.
10 08 Store 18 at 0x0204.
11 01 04 02 00 00 Push 0x0204 again.
16 07 Load the saved 18; the VM tags this stack value.
17 11 Jump to the tagged 18; computed_jump becomes true.
18 FF Stop this one-shot test.

Write these bytes to a raw .bin file. In a VM with record output, check that execution reaches offset 18 with computed_jump = true. If it does not, correct the bytes or the stack order before you add the answer calculation.

The released grader does not show this internal flag, so a player would compare program variants through the full host check instead. Note that this one-tick image does not earn VECTOR: it has no magic or answer, and HALT stops the bot.

RUST crates/vm/src/lib.rs · source lines 56–75
    pub const PUSH_ZERO: u8 = 0x03;
    pub const POP: u8 = 0x04;
    pub const LOAD_REL: u8 = 0x05;
    pub const STORE_REL: u8 = 0x06;
    pub const LOAD: u8 = 0x07;
    pub const STORE: u8 = 0x08;
    pub const JMP_REL: u8 = 0x10;
    pub const JMP_ABS: u8 = 0x11;
    pub const CALL_REL: u8 = 0x12;
    pub const CALL_ABS: u8 = 0x13;
    pub const SKIP_IF: u8 = 0x14;
    pub const RET: u8 = 0x15;
    pub const SYSCALL: u8 = 0x20;
    pub const GATECALL: u8 = 0x21;
    pub const ADD: u8 = 0x30;
    pub const SUB: u8 = 0x31;
    pub const MUL: u8 = 0x32;
    pub const DIV_U: u8 = 0x33;
    pub const REM_U: u8 = 0x34;
RUST crates/engine/src/vulns.rs · source lines 2872–2875
    fn push_imm(code: &mut Vec<u8>, v: u32) {
        code.push(op::PUSH);
        code.extend_from_slice(&v.to_le_bytes());
    }
Step Top-of-stack target VM note Why
push resume Literal, clean No VECTOR yet The immediate comes straight from the image.
push 0x0204; st Stored in scratch No VECTOR yet STORE pops address first, then the value.
push 0x0204; ld Loaded, tainted Still no VECTOR LOAD marks its result as computed.
jmpa Tainted target is popped computed_jump = true The VM checks that popped slot before moving ip.
VECTOR has two jobs: make the jump, then prove it this tick
Machine pathlabel addressSTORE to 0x0204LOAD from 0x0204target becomes taintedJMP_ABScomputed_jump = true
Proof pathhost writes 0xFE8Cbot runs econ_hashanswer at 0xFE90magic at 0xFE94

Both paths reach the host check in the same tick → VECTOR meter +1

RUST crates/vm/src/lib.rs · source lines 1154–1166
                op::LOAD => {
                    let a = self.pop();
                    self.note_arena_load(a);
                    self.note_read_own_code(a);
                    let v = self.mem.load_u32(a);
                    self.push_tagged(v, true);
                }
                op::STORE => {
                    let a = self.pop();
                    let v = self.pop();
                    self.note_arena_store(a);
                    self.note_code_store(a, v);
                    self.mem.store_u32(a, v);
RUST crates/vm/src/lib.rs · source lines 1173–1189
                op::JMP_ABS => {
                    let t = self.pop();
                    let s = ((self.sp & ADDR_MASK) >> 2) as usize;
                    let tainted = self.taint[s >> 3] & (1u8 << (s & 7)) != 0;
                    if tainted {
                        self.computed_jump = true;
                        if self.is_interior_addr(t) {
                            self.note_hydra(ip_op, t);
                        }
                    }
                    self.note_indirect_target(ip_op, t, false);
                    self.clear_jit();
                    self.ip = t;
                }

The two lines to compare are push_tagged(v, true) in LOAD and if tainted { self.computed_jump = true; } in JMP_ABS. First LOAD marks the stack value, and later the jump reads that mark. Together, those two actions are the whole VECTOR VM record.

The repository also has a one-tick test of the complete check. It writes the magic, a calculated answer, and a HALT address in scratch memory. That test uses 0x0200 for scratch. Our longer loop needs 0x0200 for its hash value, so it keeps the jump address at 0x0204.

The choice of scratch address does not set the taint flag; the loaded jump does. The VM takes the tagged jump, the host accepts the test, and r[0] becomes 1.

Read this test as the smallest complete example, not a scoring bot. The test can call the host's private challenge function and then stop. A competitor bot cannot do either: it must read the live challenge and keep running.

RUST crates/engine/src/vulns.rs · source lines 2877–2909
    fn build_vector_poc(tick: u32) -> Vec<u8> {
        const SCRATCH: u32 = 0x0200;
        const HALT_ADDR: u32 = 40;
        let mut code = Vec::new();
        push_imm(&mut code, VEC_MAGIC);
        push_imm(&mut code, VEC_MAGIC_ADDR);
        code.push(op::STORE);
        push_imm(&mut code, vec_response(vec_challenge(tick)));
        push_imm(&mut code, VEC_RESP_ADDR);
        code.push(op::STORE);
        push_imm(&mut code, HALT_ADDR);
        push_imm(&mut code, SCRATCH);
        code.push(op::STORE);
        push_imm(&mut code, SCRATCH);
        code.push(op::LOAD);
        code.push(op::JMP_ABS);
        assert_eq!(
            code.len() as u32,
            HALT_ADDR,
            "HALT must land at the precomputed target"
        );
        code.push(op::HALT);
        code
    }
RUST crates/engine/src/vulns.rs · source lines 2911–2937
    #[test]
    fn vector_full_poc_arms_the_meter_through_the_vm() {
        let mut w = World::new(0x1234_5678, true);
        w.begin_tick();
        assert_eq!(
            w.tick, 1,
            "begin_tick sets tick to 1, matching the PoC's response"
        );
        let code = build_vector_poc(w.tick);
        let mut vm = Vm::new(&code);
        w.arm_vec_challenge(&mut vm.mem);
        vm.run_tick(&mut w);
        let l = vm.pwn_latches();
        w.apply_exploit_staging(&mut vm.mem, l);
        assert!(
            vm.computed_jump,
            "the LOAD-tainted JMP_ABS target trips the computed-jump detector"
        );
        assert_eq!(
            w.r[0], 1,
            "the raw-bytecode VECTOR PoC arms the VECTOR sustain meter +1 end-to-end"
        );
        assert_eq!(
            w.econ_k, 0,
            "a single armed tick is not yet a sustained chain"
        );

In a live run, the host writes VECTOR's new challenge at 0xFE8C. Your bot writes its answer at 0xFE90 and the VECT magic (0x56454354) at 0xFE94. The ECHO value (0x56656331) belongs in the calculation, not in a proof cell. If you swap ECHO and magic, the host rejects VECTOR even when the jump itself passes.

Pass 2: make the host accept the jump

Keep the jump through the loaded scratch value, and add the proof around it. Read the current word at 0xFE8C, XOR it with 0x56656331, and run econ_hash on the result. Then store that answer at 0xFE90 and store 0x56454354 at 0xFE94.

The host checks all three conditions after the VM slice ends. You can write the magic before or after the jump. All three conditions just have to be true when the host checks this tick.

One-tick experiment computed_jump Proof cells VECTOR meter
Direct PUSH target; JMP_ABS No Correct Falls by 3.
Scratch-loaded target, stale answer Yes Wrong Falls by 3.
Scratch-loaded target, current answer and magic Yes Correct Rises by 1.

An indirect jump alone is therefore only a candidate. The host accepts VECTOR, and adds a meter point, only when the VM record and the memory proof agree.

RUST crates/common/src/consts.rs · source lines 458–465
pub const VEC_CHAL_ADDR: u32 = 0xFE8C;
pub const VEC_RESP_ADDR: u32 = 0xFE90;
pub const VEC_MAGIC_ADDR: u32 = 0xFE94;
pub const VEC_MAGIC: u32 = 0x5645_4354;
pub const VEC_ECHO: u32 = 0x5665_6331;
pub const VEC_DMG_CAP: i32 = 40;
pub const K_VECTOR: u64 = 0x5645_4354_0000_0005;

Before the long assembly block, here is one worked answer for the invented challenge 0x12345678. The bot first calculates 0x12345678 XOR 0x56656331 = 0x44513549 and then runs the five hash steps below. The final word, 0x535FD5C0, goes into 0xFE90 for this tick only, because the host writes a new challenge next tick.

Hash step Value after the step
Start: challenge XOR ECHO 0x44513549
XOR with signed right-shift by 16 0x44517118
Multiply by 0x7FEB352D, keeping 32 bits 0x97C2D938
XOR with signed right-shift by 15 0x683DF6BD
Multiply by 0x846CA68B, keeping 32 bits 0x535F869F
XOR with signed right-shift by 16 0x535FD5C0

Signed right-shift keeps the sign bit as it moves bits right. Keeping 32 bits means that overflow wraps instead of making a larger number. Your bot must follow both rules exactly, or its answer will not match the host's.

Pass 3: turn the one-shot proof into a live loop

The assembly below repeats four tasks. It writes the magic, reads and answers the current challenge, then stores and reloads the loop address. Finally it jumps through that loaded address. Each repeated push 0x0200; ld pair just reads the current hash value back from scratch memory.

This is a VECTOR proof core, not a complete scoring bot. A full pilot must also scan, aim, and move. If your larger image uses 0x0200 for code, pick another safe scratch address.

EASM Teaching example
loop:
    push 0x56454354
    push 0xFE94
    st
    push 0xFE8C
    ld
    push 0x56656331
    xor
    push 0x0200
    st
    push 0x0200
    ld
    push 0x0200
    ld
    push 16
    shrs
    xor
    push 0x0200
    st
    push 0x0200
    ld
    push 0x7FEB352D
    mul
    push 0x0200
    st
    push 0x0200
    ld
    push 0x0200
    ld
    push 15
    shrs
    xor
    push 0x0200
    st
    push 0x0200
    ld
    push 0x846CA68B
    mul
    push 0x0200
    st
    push 0x0200
    ld
    push 0x0200
    ld
    push 16
    shrs
    xor
    push 0xFE90
    st
    push loop
    push 0x0204
    st
    push 0x0204
    ld
    jmpa

The last ld is the key difference from a plain push loop; jmpa. Notice also that the live loop has no HALT. At the next tick, the VM resumes at its current instruction address, possibly halfway through the hash. For that reason, keep the proof cycle short enough to finish at least once in every 10,000-instruction slice.

Watch r[0] for several ticks. A steady +1 means the loop both triggers the behavior and writes the proof. A −3 tick means one part went missing. Once this works, you can add other chains to the same loop. Each one still needs its own VM behavior, magic value, and current challenge.

RUST crates/engine/src/vulns.rs · source lines 815–822
    fn vector_armed(&self, mem: &Mem, l: PwnLatches) -> bool {
        if !l.computed_jump || mem.load_u32(VEC_MAGIC_ADDR) != VEC_MAGIC {
            return false;
        }
        let r = mem.load_u32(VEC_RESP_ADDR);
        r != 0 && r == vec_response(vec_challenge(self.tick))
    }
Label Meaning
CF / STACK / SMC / REINT The machine-behavior family used for breadth
THR / SRV / HRV The reward product receiving the interpolated factor
Full-meter factor Ceiling before interpolation and knight-effect caps

Use the same build method for the other 40

Each card gives the VM action and memory proof for one chain. To build one yourself, follow the same passes as VECTOR:

  1. Read the host check. Find every && condition in the embedded code. Note the magic and response cells, plus any address or count the check compares.
  2. Find what sets each VM note. Search the VM code for the note's field name to see which instruction or event sets it. Typical causes are LOAD, jumps, STORE, fetches, and stack moves. A value that merely looks right does not set the note.
  3. Build a small one-shot image. Trigger only the required VM note first. If the check needs a count, use distinct addresses or destinations and confirm the exact threshold. Make sure the run reaches the host check.
  4. Write the current proof. Use this tick's challenge, answer, magic, and any witness cells. Most chains use the usual hash rule. A few put the answer in stack state or a saved instruction address.
  5. Repeat every tick. Add the behavior and proof to the live pilot loop, then test several ticks. One accepted tick adds only 1 to a meter that can reach 512.

The cards spell out the actions and numbers, so you do not have to invent them from the chain names. For a complex chain, build and test its smaller parts first. Make DISPATCH work before ROSETTA, for example, and PACKER before BASILISK. Each card also includes the exact host check, so you can compare your bot with the code.

Find a named chain · 41 entries

Select a chain to see its VM behavior and recorded fields. Its axis label shows which reward it affects.

Control flow

Stack

Self-modification

Reinterpretation

If 41 chains feel like a lot, take a short tour of four first. VECTOR asks where a jump address came from, and NULLARBOR asks what unsigned arithmetic did at an edge case. PHOENIX asks whether changed code actually ran. JANUS asks whether a byte served first as data and later as an instruction.

All four use the same three-part proof; only the required VM behavior changes.

Control flow: where execution goes

For each jump, the VM can answer four separate questions, and different chains check different answers. Where did the target value come from, and which jump instruction used it? Did the target lie outside the uploaded program, and was it the start of an original instruction?

RUST crates/vm/src/lib.rs · source lines 1303–1321
    pub computed_jump: bool,
    pub smc_exec: bool,
    pub smc_addr: u32,
    pub smc_orig: u8,
    pub overlap_fired: bool,
    pub overlap_witness: u32,
    pub sp_marshal: u32,
    pub saw_raw_op: bool,
    pub exec_off_image: bool,
    pub cfi_violation: bool,
    pub ret_gadget_depth: u32,
    pub dispatch_max: u32,
    pub jit_dispatch: bool,
    pub sp_lo: u32,
    pub pivot_depth: u32,

Three tiny jumps, three different VM notes

These are three separate one-tick test programs, not three steps to paste into one bot. Each answers a different question about a jump. VECTOR checks where the jump target came from. GADGET checks whether the target was an original instruction start. TRAMPOLINE checks whether the VM actually fetched an instruction beyond the uploaded image, and one jump can set more than one of these notes.

Three jumps ask three different questionsThree jumps ask three different questionsSEPARATE ONE-TICK PROGRAMS · FLAGS SHOWN ARE THE ONES THESE CHAINS NEEDVECTORQuestion: where did the target number come from?LOAD 18 from scratchValue came from memoryJump to byte 18Original instruction startcomputed_jump = trueTarget can be perfectly validGADGETQuestion: did the jump land on an original instruction start?PUSH 2; JMP_ABSLiteral target, not computed01 00 FF 00 00Byte 2 is data in PUSHcfi_violation = trueFetch FF as HALTTRAMPOLINEQuestion: did the VM fetch an instruction beyond the upload?STORE FF at 0xD000PUSH 0xD000; JMP_ABS0xD000 is past code_endFetch stored FF as HALTcfi_violation = trueexec_off_image = trueThese flags are observations. A CLASSIC meter still needs its own magic, fresh answer, and repeated ticks.
Three separate jump probes reveal three different VM notes. The target value, landing address, and actual instruction fetch matter in different ways. The off-image jump sets both the GADGET and TRAMPOLINE notes; the three probes alone do not fill any CLASSIC meter. View diagram at full size

VECTOR probe: Save the ordinary continuation address 18 in scratch memory, LOAD it, and then use JMP_ABS. Address 18 starts a valid instruction in this image, so the destination itself is unremarkable. What matters is that the VM marks the loaded value as computed and therefore sets computed_jump.

GADGET probe: The repository's VM test starts with PUSH 0x0000FF00, whose five bytes are 01 00 FF 00 00: opcode 01 at offset 0, then four value bytes. The original decoder therefore sees one instruction starting at 0. The byte FF at offset 2 is just part of its value.

The test then runs PUSH 2; JMP_ABS, and the VM reads that FF at offset 2 as HALT. Offset 2 was not an original instruction start, so the branch-target check sets cfi_violation, even though offset 2 is still inside the uploaded image:

RUST crates/vm/src/lib.rs · source lines 1774–1780
        let mut a = Asm::new();
        a.push(0x0000_FF00);
        a.push(2);
        a.op(op::JMP_ABS);
        let code = a.finish();
        let mut vm = Vm::new(&code);
        vm.run_tick(&mut NopHost);
RUST crates/vm/src/lib.rs · source lines 749–753
    fn note_indirect_target(&mut self, site: u32, t: u32, is_call: bool) {
        if t >= self.code_end || !self.is_instr_start(t) {
            self.cfi_violation = true;
        }

The direct PUSH 2 target does not count as computed for VECTOR. Because it also stays inside the image, it does not trigger TRAMPOLINE either. The interior fetch can, however, set byte-overlap notes, so “GADGET happened” does not mean that only GADGET happened.

TRAMPOLINE probe: Another VM test writes FF to guest address 0xD000, pushes 0xD000, and jumps there. The uploaded image ends well before that address. Because the jump target is outside the image, the GADGET boundary check also records cfi_violation. Then, when the VM fetches the FF at 0xD000, it sets exec_off_image for TRAMPOLINE. Writing the byte without fetching it would not set that note:

RUST crates/vm/src/lib.rs · source lines 1755–1763
        let mut a = Asm::new();
        a.push(0xFF);
        a.push(0xD000);
        a.op(op::STORE);
        a.push(0xD000);
        a.op(op::JMP_ABS);
        let code = a.finish();
        let mut vm = Vm::new(&code);
        vm.run_tick(&mut NopHost);
RUST crates/vm/src/lib.rs · source lines 1111–1114
                if (a as u32) >= self.code_end {
                    self.exec_off_image = true;
                }

These tests end with HALT and never write a chain's magic value or current answer. They show how VM notes arise, not how to score over hundreds of ticks. To earn meter marks, add the chain's proof cells, then repeat the behavior in a live loop.

DISPATCH asks a fourth question: how many distinct destinations did one jump instruction reach in the same tick? Its card below builds eight stubs that all return to one shared jump instruction.

VECTOR#00 · CF · THR · full-meter 1.5

What it checks. A computed jump gets its destination from a value that the bot calculated or loaded while it ran. The VM records that origin. The destination can still be inside the uploaded image.

VM noteWhat it means
computed_jumpThe branch destination came from a computed value, not only a compiler-emitted literal.

Build and activate it

  1. Store an ordinary in-image continuation address in scratch memory, then LOAD it and feed that loaded value to JMP_ABS. The load marks the target stack slot as computed; a direct PUSH label; JMP_ABS leaves it clean.
  2. Repeat that indirect jump in the live pilot loop. The full annotated example immediately above this catalogue supplies the exact hash and memory writes.

Proof for this tick

1 · Host writes challenge at0xFE8C
2 · Magic present · value → address0x56454354 → 0xFE94
3 · Bot writes answer to0xFE90
Chain ECHO constant0x56656331
Read this chain’s exact host check
RUST crates/engine/src/vulns.rs · source lines 816–822
    fn vector_armed(&self, mem: &Mem, l: PwnLatches) -> bool {
        if !l.computed_jump || mem.load_u32(VEC_MAGIC_ADDR) != VEC_MAGIC {
            return false;
        }
        let r = mem.load_u32(VEC_RESP_ADDR);
        r != 0 && r == vec_response(vec_challenge(self.tick))
    }
CROWN#04 · CF · THR · full-meter 2.5

What it checks. CROWN is a combo challenge. In one tick, the VM must show four kinds of unusual behavior. One kind gives it two notes, so the host checks five notes in all, plus CROWN's own fresh answer. You do not have to pass the four smaller chains first.

Four behaviors in one tick. Five VM notes. One CROWN meter.
1 · PICK AN ADDRESSJump through a loaded target

Save a safe code address, LOAD it, then use JMP_ABS.

computed_jump
2 · REUSE A BYTEEnter a PUSH value

After PUSH consumes four value bytes, jump into one and fetch it as an opcode.

overlap_fired
3 · CHANGE CODERun a byte you rewrote

Alter a byte inside the uploaded image, then actually fetch that changed byte.

smc_exec
4 · MOVE THE CALL FRAMERaw opcode + arena call

Run an opcode the compiler never emits. Begin a SYSCALL with its frame at 0xD000–0xFFFF.

saw_raw_opsp_marshal
SEPARATE JOB · THIS TICK'S ANSWER

Read [0xFEC4] → XOR 0x43726F31 → econ_hash → write [0xFEC8]. Put CRWN at [0xFECC].

After the VM stops: all five notes + CROWN's magic + its nonzero fresh answer pass → r[4] rises by 1. Miss one → it falls by 3, stopping at zero.

VM noteWhat it means
computed_jumpThe branch destination came from a computed value, not only a compiler-emitted literal.
overlap_firedExecution decoded a byte inside a region previously consumed as an immediate.
saw_raw_opExecution used a raw opcode path distinct from the compiler’s usual output.
smc_execA changed byte in the original image was later fetched as an instruction.
sp_marshalThe stack address at which a host-call frame was marshalled.

Two useful details: one byte can set both overlap_fired and smc_exec. That happens when the VM first consumes it as PUSH data and the bot then changes it and fetches it as an opcode. The VM still records five separate facts.

Because its notes reset next tick, all the required behaviors must occur together in one tick. Other chain meters still need their own proof cells, even when they reuse these actions.

Build and activate it

  1. Make the four behaviors work separately first. Reuse VECTOR’s loaded-target jump, fetch a byte from a PUSH value, and fetch a changed image byte. Then run a raw-only opcode such as DIV_U, and start a call with its stack frame in the upper arena. Check each VM note before you combine them.
  2. Put all four behaviors in one VM loop and complete them within one 10,000-instruction tick slice. Do not HALT, because the next tick needs the same evidence again. One fetch can count as both overlap and changed-code execution, but a write without a fetch counts as neither.
  3. Add CROWN's proof: read this tick’s word at 0xFEC4, calculate econ_hash(challenge XOR 0x43726F31), and write the nonzero result at 0xFEC8. Write 0x4352574E (CRWN) at 0xFECC, and recalculate the answer after each new challenge.
  4. Test all five notes with the proof present, then remove one behavior at a time; CROWN must stop rising. The source test checks each note this way. Only CROWN’s meter is guaranteed to rise. VECTOR, JANUS, PHOENIX, and PIVOT need their own checks and proof cells.

Proof for this tick

1 · Host writes challenge at0xFEC4
2 · Magic present · value → address0x4352574E → 0xFECC
3 · Bot writes answer to0xFEC8
Chain ECHO constant0x43726F31

The ECHO goes into the calculation, not another cell.

Read this chain’s exact host check
RUST crates/engine/src/vulns.rs · source lines 859–872
    fn crown_armed(&self, mem: &Mem, l: PwnLatches) -> bool {
        if !(l.smc_exec
            && l.overlap_fired
            && l.computed_jump
            && l.sp_marshal >= SP_PIVOT_ARENA_LO
            && l.sp_marshal < VM_MEM_SIZE as u32
            && l.saw_raw_op)
            || mem.load_u32(CAP_MAGIC_ADDR) != CAP_MAGIC
        {
            return false;
        }
        let r = mem.load_u32(CAP_RESP_ADDR);
        r != 0 && r == cap_response(cap_challenge(self.tick))
    }
Read the one-note-at-a-time test
RUST crates/engine/src/vulns.rs · source lines 1789–1806
    fn crown_negatives_leave_the_meter_idle() {
        let droppers: [(&str, fn(&mut PwnLatches)); 5] = [
            ("smc_exec", |l| l.smc_exec = false),
            ("overlap_fired", |l| l.overlap_fired = false),
            ("computed_jump", |l| l.computed_jump = false),
            ("sp_marshal", |l| l.sp_marshal = SP_PIVOT_ARENA_LO - 1),
            ("saw_raw_op", |l| l.saw_raw_op = false),
        ];
        for (name, drop) in droppers {
            let (mut w, mut mem) = armed();
            mem.store_u32(CAP_MAGIC_ADDR, CAP_MAGIC);
            mem.store_u32(CAP_RESP_ADDR, cap_response(cap_challenge(w.tick)));
            let mut l = crown_latches();
            drop(&mut l);
            w.apply_exploit_staging(&mut mem, l);
            assert_eq!(w.r[4], 0, "CROWN missing {name} ⇒ never armed");
        }
TRAMPOLINE#05 · CF · THR · full-meter 1.5

What it checks. The uploaded image fills only part of guest memory. TRAMPOLINE requires the VM to fetch an instruction beyond that image. This still runs inside the guest VM; it is not the host computer executing arbitrary native code.

VM noteWhat it means
exec_off_imageInstruction fetch crossed outside the originally loaded image extent.

Build and activate it

  1. Put executable VM opcode bytes into guest memory beyond the uploaded image end, such as the free arena near 0xD000. STORE writes four bytes at a time, little-endian.
  2. Branch there and actually fetch at least one opcode. Merely writing that memory or pointing at it without fetching cannot set exec_off_image. Return or loop rather than ending on HALT if you need a meter.

Proof for this tick

1 · Host writes challenge at0xFED0
2 · Magic present · value → address0x54524D50 → 0xFED8
3 · Bot writes answer to0xFED4
Chain ECHO constant0x54726D31
Read this chain’s exact host check
RUST crates/engine/src/vulns.rs · source lines 876–882
    fn trampoline_armed(&self, mem: &Mem, l: PwnLatches) -> bool {
        if !l.exec_off_image || mem.load_u32(TRAMP_MAGIC_ADDR) != TRAMP_MAGIC {
            return false;
        }
        let r = mem.load_u32(TRAMP_RESP_ADDR);
        r != 0 && r == tramp_response(tramp_challenge(self.tick))
    }
GADGET#06 · CF · THR · full-meter 1.5

What it checks. GADGET requires a branch to an address that was not an instruction start in the original image. From that new starting point, the VM decodes the bytes differently. It watches the mismatch between the jump target and the original boundary map, and the jump stays inside the guest VM. It is not evidence that the bot controls the host instruction pointer or native host memory.

VM noteWhat it means
cfi_violationA control transfer landed away from an original decoded instruction start.

Build and activate it

  1. Make an original PUSH instruction whose immediate bytes contain legal small opcodes; for example a byte 0x03 is PUSH_ZERO. Normal linear decoding treats those bytes as data, not instruction starts.
  2. Branch with JMP_ABS or CALL_ABS to an interior byte. The branch-target checker compares it with the original instruction-start bitmap and raises cfi_violation. An off-image destination also passes this checker, but an interior landing demonstrates the intended mismatch.

Proof for this tick

1 · Host writes challenge at0xFEDC
2 · Magic present · value → address0x47444754 → 0xFEE4
3 · Bot writes answer to0xFEE0
Chain ECHO constant0x47646731
Read this chain’s exact host check
RUST crates/engine/src/vulns.rs · source lines 884–890
    fn gadget_armed(&self, mem: &Mem, l: PwnLatches) -> bool {
        if !l.cfi_violation || mem.load_u32(GDGT_MAGIC_ADDR) != GDGT_MAGIC {
            return false;
        }
        let r = mem.load_u32(GDGT_RESP_ADDR);
        r != 0 && r == gdgt_response(gdgt_challenge(self.tick))
    }
DISPATCH#07 · CF · THR · full-meter 1.7

What it checks. Picture one jump instruction acting like a switchboard: on different visits, it sends execution to different places. DISPATCH counts the distinct destinations reached from that same site during a tick. Jumps from different instruction addresses do not combine.

VM noteWhat it means
dispatch_maxMaximum distinct targets seen from one dispatch site in the tick.

Build and activate it

  1. Build eight safe destination stubs, each returning to one shared dispatcher. Keep the same byte address for the dispatcher’s JMP_ABS instruction.
  2. On visits to that one site, load or calculate a different target and jump. The per-site ledger deduplicates targets, so eight visits to one target count as one; eight different targets make dispatch_max ≥ 8. The ledger resets each tick.

Proof for this tick

1 · Host writes challenge at0xFEE8
2 · Magic present · value → address0x44535054 → 0xFEF0
3 · Bot writes answer to0xFEEC
Chain ECHO constant0x44737031
Read this chain’s exact host check
RUST crates/engine/src/vulns.rs · source lines 892–898
    fn dispatch_armed(&self, mem: &Mem, l: PwnLatches) -> bool {
        if l.dispatch_max < 8 || mem.load_u32(DISP_MAGIC_ADDR) != DISP_MAGIC {
            return false;
        }
        let r = mem.load_u32(DISP_RESP_ADDR);
        r != 0 && r == disp_response(disp_challenge(self.tick))
    }
RAMPART#08 · CF · THR · full-meter 2.0

What it checks. RAMPART follows a sequence of saved return addresses outside the original image. The VM must return through distinct off-image targets in one tick.

VM noteWhat it means
ret_gadget_depthDepth of a return-routed chain through off-image destinations.

Build and activate it

  1. Write eight RET opcodes at distinct addresses outside the uploaded image. Arrange stack return words so one RET lands on the next off-image RET, then finally back on a safe continuation.
  2. Execute that route. The VM counts distinct off-image return targets, not the number of RET opcodes or normal in-image function returns. The required depth is eight in one tick.

Proof for this tick

1 · Host writes challenge at0xFEF4
2 · Magic present · value → address0x524D5054 → 0xFEFC
3 · Bot writes answer to0xFEF8
Chain ECHO constant0x526D7031
Read this chain’s exact host check
RUST crates/engine/src/vulns.rs · source lines 900–906
    fn rampart_armed(&self, mem: &Mem, l: PwnLatches) -> bool {
        if l.ret_gadget_depth < 8 || mem.load_u32(RMPT_MAGIC_ADDR) != RMPT_MAGIC {
            return false;
        }
        let r = mem.load_u32(RMPT_RESP_ADDR);
        r != 0 && r == rmpt_response(rmpt_challenge(self.tick))
    }
GANTRY#09 · CF · THR · full-meter 1.8

What it checks. GANTRY requires generated guest code and repeated calls from one instruction address. That call site must reach different off-image destinations in one tick.

VM noteWhat it means
jit_dispatchOne call site dispatched into multiple generated guest-code destinations.

Build and activate it

  1. Write four off-image callable blocks that end in RET. Make one loop visit the same CALL_ABS instruction four times.
  2. Give that same call site four different off-image target addresses. The VM counts only off-image CALL_ABS targets. It needs four distinct targets from one site in one tick. Calls from separate sites do not combine.

Proof for this tick

1 · Host writes challenge at0xFF00
2 · Magic present · value → address0x474E5452 → 0xFF08
3 · Bot writes answer to0xFF04
Chain ECHO constant0x476E7431
Read this chain’s exact host check
RUST crates/engine/src/vulns.rs · source lines 908–914
    fn gantry_armed(&self, mem: &Mem, l: PwnLatches) -> bool {
        if !l.jit_dispatch || mem.load_u32(GNTR_MAGIC_ADDR) != GNTR_MAGIC {
            return false;
        }
        let r = mem.load_u32(GNTR_RESP_ADDR);
        r != 0 && r == gntr_response(gntr_challenge(self.tick))
    }
WYRM#32 · CF · THR · full-meter 3.0

What it checks. WYRM combines control-flow, stack, call-frame, and byte-decoding observations, and the host checks them together in one tick. Passing one of those checks alone is not WYRM. Its attack meter has a larger factor, but it still fades and the final bonus still has a cap.

VM noteWhat it means
dispatch_maxMaximum distinct targets seen from one dispatch site in the tick.
marshal_aliasA marshalled call frame intersected another reserved memory role.
overlap_densityDistinct interior bytes decoded as opcodes, with repeated visits deduplicated.
pivot_depthHigh-water displacement of the stack pointer, derived from its peak.
raw_argc_wideThe raw argument count exceeded the normal width before masking.
ret_gadget_depthDepth of a return-routed chain through off-image destinations.

How the fields fit: The host combines the jump and return notes. For one part of the check, either marshal_alias or raw_argc_wide can supply the needed observation. WYRM has its own meter; the smaller related chains keep theirs.

Build and activate it

  1. Combine eight distinct off-image return targets from RAMPART with twelve targets from one DISPATCH site. Fetch sixteen distinct interior bytes as opcodes. Complete all three parts in one tick.
  2. Add a LOCKSMITH marshal alias or a MIRAGE raw argument count. Move the stack high-water into [0xD000, 0x10000). All five parts must appear in one tick, because events from separate ticks do not combine.
  3. Test each part separately before you combine them. The dispatch ledger holds sixteen destinations, so twelve fits. Return to the pilot to repeat WYRM.

Proof for this tick

1 · Host writes challenge at0xFD00
2 · Magic present · value → address0x5759524D → 0xFD08
3 · Bot writes answer to0xFD04
Chain ECHO constant0x57797231
Read this chain’s exact host check
RUST crates/engine/src/vulns.rs · source lines 1145–1159
    fn wyrm_armed(&self, mem: &Mem, l: PwnLatches) -> bool {
        let sp_hi_in_arena = l.pivot_depth >= (SP_PIVOT_ARENA_LO - SP_INIT)
            && l.pivot_depth < (VM_MEM_SIZE as u32 - SP_INIT);
        if !(l.ret_gadget_depth >= WYRM_RET_MIN
            && l.dispatch_max >= WYRM_DISPATCH_MIN
            && l.overlap_density >= WYRM_OVERLAP_MIN
            && (l.marshal_alias || l.raw_argc_wide)
            && sp_hi_in_arena)
            || mem.load_u32(WYRM_MAGIC_ADDR) != WYRM_MAGIC
        {
            return false;
        }
        let r = mem.load_u32(WYRM_RESP_ADDR);
        r != 0 && r == wyrm_response(wyrm_challenge(self.tick))
    }
ROSETTA#34 · CF · THR · full-meter 2.5

What it checks. ROSETTA requires a computed branch and many destinations from the same site. Stored command bytes alone do not prove that the VM used them for control flow.

VM noteWhat it means
computed_jumpThe branch destination came from a computed value, not only a compiler-emitted literal.
dispatch_maxMaximum distinct targets seen from one dispatch site in the tick.

How the fields relate: The computed-branch note records the origin of a target. The dispatch ledger counts targets from one site. It does not count every branch in the bot. ROSETTA joins that breadth with computed origin and its own response evidence. Neither observation alone establishes the interpreter-like history or awards another chain's meter.

Build and activate it

  1. Extend DISPATCH to sixteen destinations from one site in one tick. This is the ledger’s limit. Use LOAD or arithmetic to compute the target before JMP_ABS or CALL_ABS.
  2. Check both dispatch_max ≥ 16 and computed_jump. The current checker does not ask for a separate “heavy compute” signal despite the decrypt-dispatch theme.

Proof for this tick

1 · Host writes challenge at0xFD18
2 · Magic present · value → address0x524F5345 → 0xFD20
3 · Bot writes answer to0xFD1C
Chain ECHO constant0x526F7331
Read this chain’s exact host check
RUST crates/engine/src/vulns.rs · source lines 1181–1189
    fn rosetta_armed(&self, mem: &Mem, l: PwnLatches) -> bool {
        if !(l.dispatch_max >= ROSETTA_DISPATCH_MIN && l.computed_jump)
            || mem.load_u32(ROSETTA_MAGIC_ADDR) != ROSETTA_MAGIC
        {
            return false;
        }
        let r = mem.load_u32(ROSETTA_RESP_ADDR);
        r != 0 && r == rosetta_response(rosetta_challenge(self.tick))
    }
METRONOME#37 · CF · THR · full-meter 1.6

What it checks. METRONOME checks where the VM saves its next instruction address when a tick budget ends. The VM must also execute beyond the uploaded image.

VM noteWhat it means
exec_off_imageInstruction fetch crossed outside the originally loaded image extent.
resume_ipThe saved outer-VM instruction pointer used when a budget slice resumes.

Gate and carrier: Fetching outside the original image establishes the execution context. The saved resume_ip identifies the continuation point at the budget boundary and carries the chain-specific response. It is not a second independent chain contribution.

Build and activate it

  1. Read the current challenge and compute park = 0xD000 + (econ_hash(challenge XOR METRO_ECHO) & 0x1FFF). Make the VM execute off-image and consume its entire 10,000-instruction tick slice.
  2. End the tick budget with resume_ip at that exact park address. An off-image self-loop can show one budget cut. To keep the meter, read each new challenge and select a new park address. The saved address is the answer; there is no response cell.

Proof for this tick

1 · Host writes challenge at0xFD38
2 · Magic present · value → address0x4D455452 → 0xFD3C
3 · Bot encodes answerMachine state; see note
Chain ECHO constant0x4D657431

No response cell: the saved off-image IP carries the challenge-derived park address.

Read this chain’s exact host check
RUST crates/engine/src/vulns.rs · source lines 1222–1227
    fn metronome_armed(&self, mem: &Mem, l: PwnLatches) -> bool {
        if !l.exec_off_image || mem.load_u32(METRO_MAGIC_ADDR) != METRO_MAGIC {
            return false;
        }
        l.resume_ip != 0 && l.resume_ip == metro_park(metro_challenge(self.tick))
    }
HYDRA#40 · CF · THR · full-meter variable; ≤ 2.4

What it checks. HYDRA counts distinct interior-byte destinations reached from one call site. A larger count raises this chain’s reward limit, up to the chain’s cap.

VM noteWhat it means
hydra_phaseNumber of distinct interior re-entries reached from one tainted site.

Build and activate it

  1. Execute PUSH 0x15151515, marking its four immediate bytes as interior; each byte is a RET opcode if entered as code. Preserve a loop with one shared CALL_ABS site.
  2. Load or calculate two different interior addresses of that PUSH and call them from the same site. The tainted targets make computed_jump; each gadget returns, allowing the site to run again. HYDRA requires at least two distinct interior re-entries and scales further up to five.

Proof for this tick

1 · Host writes challenge at0xFD58
2 · Magic present · value → address0x48594452 → 0xFD60
3 · Bot writes answer to0xFD5C
Chain ECHO constant0x48796431
Read this chain’s exact host check
RUST crates/engine/src/vulns.rs · source lines 1248–1254
    fn hydra_armed(&self, mem: &Mem, l: PwnLatches) -> bool {
        if l.hydra_phase < HYDRA_PHASE_MIN || mem.load_u32(HYDRA_MAGIC_ADDR) != HYDRA_MAGIC {
            return false;
        }
        let r = mem.load_u32(HYDRA_RESP_ADDR);
        r != 0 && r == hydra_response(hydra_challenge(self.tick))
    }

Stack: which storage role a word occupies

The VM uses one flat memory array for operands, call frames, host-call arguments, and code. Its stack notes record where a word sat and how it was used, not merely that a number was large.

RUST crates/vm/src/lib.rs · source lines 1322–1345
    pub arena_frames: u32,
    pub frames_verified: u32,
    pub marshal_alias: bool,
    pub raw_argc_wide: bool,
    pub syscall_reentrant: bool,
    pub divzero_u: bool,
    pub shift_annihilate_u: bool,
    pub signedness_split: bool,
    pub split_count: u32,
    pub wrap_carry: bool,
    pub code_patched: bool,

Worked machine trace: one array, several roles

Think about a function call that stores a local value and makes a host request. Within one 64 KiB array, arithmetic values, a return frame, and host-call arguments each take a different role.

Moment Observed state Why it is recorded
Call begins Frame stored at a stack address Location and depth can be tracked (arena_frames, pivot_depth)
Value later loaded A matching stored frame is used frames_verified distinguishes a store/load history from a call count
Host request marshalled Arguments occupy a memory range sp_marshal records its location; marshal_alias records overlap with another role
Following service reads arguments Earlier return state might still occupy a consumed slot syscall_reentrant concerns lifetime, not stack depth

A deep stack, several related call frames, and host-call arguments overlapping other data are three different behaviors. The CLASSIC chain named ABYSS is about the stack, and it is separate from the ABYSS engine.

PIVOT#03 · STACK · SRV · full-meter 1.5

What it checks. A host call reads its arguments from the bot's stack. Move the stack pointer, and the host reads those arguments from a different part of guest memory. PIVOT checks the stack address used for a host-call frame. It requires that frame in the upper guest-memory arena together with a raw-only opcode, and the addresses stay inside the VM.

VM noteWhat it means
saw_raw_opExecution used a raw opcode path distinct from the compiler’s usual output.
sp_marshalThe stack address at which a host-call frame was marshalled.

Gate and location: The raw-operation observation establishes the relevant low-level execution context. sp_marshal records where the host-call frame was actually read. PIVOT's host decision relates that address to the alternate arena; it is not simply a check that the stack pointer moved.

Build and activate it

  1. Move the operand stack into the arena. From sp=0x8000, 5,120 net word pushes reach 0xD000. The VM has no ordinary “set SP” instruction. A unit test sets vm.sp directly only to test the detector.
  2. Execute a SYSCALL while its pre-pop marshal base is at least 0xD000 and below 0x10000. Also execute a raw-only opcode, for example DIV_U. Parked stack state carries across ticks, but proof latches reset.

Proof for this tick

1 · Host writes challenge at0xFEB8
2 · Magic present · value → address0x50495654 → 0xFEC0
3 · Bot writes answer to0xFEBC
Chain ECHO constant0x50697631
Read this chain’s exact host check
RUST crates/engine/src/vulns.rs · source lines 848–857
    fn pivot_armed(&self, mem: &Mem, l: PwnLatches) -> bool {
        if !(l.sp_marshal >= SP_PIVOT_ARENA_LO && l.sp_marshal < VM_MEM_SIZE as u32)
            || !l.saw_raw_op
            || mem.load_u32(PIV_MAGIC_ADDR) != PIV_MAGIC
        {
            return false;
        }
        let r = mem.load_u32(PIV_RESP_ADDR);
        r != 0 && r == piv_response(piv_challenge(self.tick))
    }
ABYSS#10 · STACK · SRV · full-meter 1.6

What it checks. This card shares a name with the ABYSS engine, but it belongs to CLASSIC. It checks whether the outer VM's stack pointer moves below its starting address, which means the program popped more words than it pushed. The VM's memory bounds still apply.

VM noteWhat it means
sp_loLowest stack-pointer position reached relative to its initial position.

Build and activate it

  1. Use surplus POP instructions from the initial sp=0x8000. One pop takes the pointer below its starting value; the VM records a negative relative low-water mark.
  2. This card is a CLASSIC stack chain named ABYSS, not the separate ABYSS engine. Plan where later pushes go, because underflow can wrap stack addresses and overwrite guest memory.

Proof for this tick

1 · Host writes challenge at0xFF0C
2 · Magic present · value → address0x41425953 → 0xFF14
3 · Bot writes answer to0xFF10
Chain ECHO constant0x41627931
Read this chain’s exact host check
RUST crates/engine/src/vulns.rs · source lines 922–928
    fn abyss_armed(&self, mem: &Mem, l: PwnLatches) -> bool {
        if (l.sp_lo as i32) >= 0 || mem.load_u32(ABYS_MAGIC_ADDR) != ABYS_MAGIC {
            return false;
        }
        let r = mem.load_u32(ABYS_RESP_ADDR);
        r != 0 && r == abys_response(abys_challenge(self.tick))
    }
PLUMB#11 · STACK · HRV · full-meter variable; ≤ 2.0

What it checks. PLUMB checks how far the stack pointer rises above its starting address. That depth affects the collection reward limit for this chain.

VM noteWhat it means
pivot_depthHigh-water displacement of the stack pointer, derived from its peak.

Build and activate it

  1. Increase the stack high-water at least 0x1000 bytes above its 0x8000 starting point: 1,024 net four-byte pushes reach 0x9000.
  2. The host checks pivot_depth = sp_hi − 0x8000, not a special opcode. A unit test directly sets vm.sp to model this state; a submitted program has to get there through stack operations.

Proof for this tick

1 · Host writes challenge at0xFF18
2 · Magic present · value → address0x504C4D42 → 0xFF20
3 · Bot writes answer to0xFF1C
Chain ECHO constant0x506C6D31
Read this chain’s exact host check
RUST crates/engine/src/vulns.rs · source lines 930–936
    fn plumb_armed(&self, mem: &Mem, l: PwnLatches) -> bool {
        if l.pivot_depth < PLUMB_DEPTH_MIN || mem.load_u32(PLMB_MAGIC_ADDR) != PLMB_MAGIC {
            return false;
        }
        let r = mem.load_u32(PLMB_RESP_ADDR);
        r != 0 && r == plmb_response(plmb_challenge(self.tick))
    }
LOCKSMITH#12 · STACK · SRV · full-meter 1.7

What it checks. LOCKSMITH checks whether a host-call frame overlaps a reserved guest-memory role. The same guest bytes can then serve as call arguments and another kind of data.

VM noteWhat it means
marshal_aliasA marshalled call frame intersected another reserved memory role.

Build and activate it

  1. Make the call frame overlap another guest-memory role. One unit test puts the call base at 0xFF00. Its argument-count slot at 0xFEFC then enters the reserved [0xFE00,0xFF00) page.
  2. The recovered apex takes the code-region route. It pads its uploaded image to code_end=0xD010 and uses 5,120 PUSH_ZERO instructions to park sp at 0xD000. A call from there reads its first word at 0xCFFC, which the VM now counts as part of the image.
  3. Execute a real SYSCALL. After limiting the argument count, the VM checks base−4·k for k=1..argc+2. An address in either reserved region sets marshal_alias. Repeat the call each tick because the note resets.
  4. Read the fresh challenge and answer it. Padding and a call alone do not pass the host's magic-and-response check. See the apex memory close-up for the exact address comparison.

Proof for this tick

1 · Host writes challenge at0xFF24
2 · Magic present · value → address0x4C4F434B → 0xFF2C
3 · Bot writes answer to0xFF28
Chain ECHO constant0x4C6F6B31
Read this chain’s exact host check
RUST crates/engine/src/vulns.rs · source lines 938–944
    fn locksmith_armed(&self, mem: &Mem, l: PwnLatches) -> bool {
        if !l.marshal_alias || mem.load_u32(LOCK_MAGIC_ADDR) != LOCK_MAGIC {
            return false;
        }
        let r = mem.load_u32(LOCK_RESP_ADDR);
        r != 0 && r == lock_response(lock_challenge(self.tick))
    }
MIRAGE#13 · STACK · THR · full-meter 1.35

What it checks. MIRAGE checks the raw argument count of a host call before the VM limits that count. Raw VM code can request more arguments than the compiler normally emits.

VM noteWhat it means
raw_argc_wideThe raw argument count exceeded the normal width before masking.

Build and activate it

  1. Put a raw argument count greater than eight on top of a syscall frame, for example 0x7FFFFFF8. Put a service number below it. Execute SYSCALL.
  2. The detector reads the raw count before the VM applies (raw_argc & 0xF).min(8). That example masks to eight, so prepare enough frame words or choose a wide value whose low nibble is convenient. The latch depends on the original count, not how many args the host finally receives.

Proof for this tick

1 · Host writes challenge at0xFF30
2 · Magic present · value → address0x4D495247 → 0xFF38
3 · Bot writes answer to0xFF34
Chain ECHO constant0x4D697231
Read this chain’s exact host check
RUST crates/engine/src/vulns.rs · source lines 946–952
    fn mirage_armed(&self, mem: &Mem, l: PwnLatches) -> bool {
        if !l.raw_argc_wide || mem.load_u32(MIRG_MAGIC_ADDR) != MIRG_MAGIC {
            return false;
        }
        let r = mem.load_u32(MIRG_RESP_ADDR);
        r != 0 && r == mirg_response(mirg_challenge(self.tick))
    }
OSSUARY#14 · STACK · THR · full-meter 1.55

What it checks. OSSUARY counts call frames used in the upper guest-memory arena. Bytes that only resemble a frame do not count; the VM must execute real calls.

VM noteWhat it means
arena_framesCalls whose frames were placed in the alternate arena region.

Build and activate it

  1. Move the operand stack into [0xD000,0x10000). Run four CALL_ABS instructions while it stays there. Each short called block can return to the loop.
  2. The VM increments arena_frames when each call pushes its return address. Four calls are enough; four ordinary calls near 0x8000 are not.

Proof for this tick

1 · Host writes challenge at0xFF3C
2 · Magic present · value → address0x4F535355 → 0xFF44
3 · Bot writes answer to0xFF40
Chain ECHO constant0x4F737331
Read this chain’s exact host check
RUST crates/engine/src/vulns.rs · source lines 954–960
    fn ossuary_armed(&self, mem: &Mem, l: PwnLatches) -> bool {
        if l.arena_frames < ARENA_FRAMES_MIN || mem.load_u32(OSSU_MAGIC_ADDR) != OSSU_MAGIC {
            return false;
        }
        let r = mem.load_u32(OSSU_RESP_ADDR);
        r != 0 && r == ossu_response(ossu_challenge(self.tick))
    }
SEXTANT#15 · STACK · HRV · full-meter 1.75

What it checks. SEXTANT requires stores and later loads from the same distinct arena slots. The VM records both operations and counts the slots they share.

VM noteWhat it means
frames_verifiedFrames observed on both the store and later load sides of the frame ledger.

Build and activate it

  1. For eight distinct arena stride slots, STORE a word and later LOAD from an address in that same slot. A simple set uses 0xD000 + i·0x100 for i=0..7.
  2. The VM keeps separate stored and loaded bitmaps and counts their intersection. Eight stores alone, or eight loads from different slots, do not pass.

Proof for this tick

1 · Host writes challenge at0xFF48
2 · Magic present · value → address0x53455854 → 0xFF50
3 · Bot writes answer to0xFF4C
Chain ECHO constant0x53657831
Read this chain’s exact host check
RUST crates/engine/src/vulns.rs · source lines 962–968
    fn sextant_armed(&self, mem: &Mem, l: PwnLatches) -> bool {
        if l.frames_verified < FRAMES_VERIFIED_MIN || mem.load_u32(SEXT_MAGIC_ADDR) != SEXT_MAGIC {
            return false;
        }
        let r = mem.load_u32(SEXT_RESP_ADDR);
        r != 0 && r == sext_response(sext_challenge(self.tick))
    }
CATENARY#16 · STACK · THR · full-meter 1.8

What it checks. CATENARY requires one host call’s live return value to occupy part of a later host-call frame. Two unrelated calls do not satisfy that order.

VM noteWhat it means
syscall_reentrantA live earlier host-call return was still present in a consumed argument slot.

Build and activate it

  1. Execute a syscall and leave its returned word on the operand stack. Do not pop it or push over that slot, because either action clears its live-return mark.
  2. Construct the next syscall frame so the previous return occupies an argument or number slot that the second marshal reads. The detector checks live slots in that read range (k≥2), not merely two syscalls in a row.

Proof for this tick

1 · Host writes challenge at0xFF54
2 · Magic present · value → address0x4341544E → 0xFF5C
3 · Bot writes answer to0xFF58
Chain ECHO constant0x43617431
Read this chain’s exact host check
RUST crates/engine/src/vulns.rs · source lines 970–976
    fn catenary_armed(&self, mem: &Mem, l: PwnLatches) -> bool {
        if !l.syscall_reentrant || mem.load_u32(CATN_MAGIC_ADDR) != CATN_MAGIC {
            return false;
        }
        let r = mem.load_u32(CATN_RESP_ADDR);
        r != 0 && r == catn_response(catn_challenge(self.tick))
    }
NULLARBOR#17 · STACK · SRV · full-meter 1.6

What it checks. What should unsigned division by zero do inside this VM? The VM has a defined answer, so it does not crash, and NULLARBOR records that the operation occurred. Its STACK label is just a grouping for the economy. The division itself grants nothing; any bonus comes later from the chain's meter.

VM noteWhat it means
divzero_uAn unsigned divide or remainder encountered a zero divisor.

Build and activate it

  1. Push a dividend and then zero. Execute raw DIV_U or REM_U. The VM returns a defined result and records divzero_u.
  2. A signed divide or a normal nonzero divisor does not set this latch. Clean up the result before returning to the pilot.

Proof for this tick

1 · Host writes challenge at0xFF60
2 · Magic present · value → address0x4E554C4C → 0xFF68
3 · Bot writes answer to0xFF64
Chain ECHO constant0x4E756C31
Read this chain’s exact host check
RUST crates/engine/src/vulns.rs · source lines 978–984
    fn nullarbor_armed(&self, mem: &Mem, l: PwnLatches) -> bool {
        if !l.divzero_u || mem.load_u32(NULL_MAGIC_ADDR) != NULL_MAGIC {
            return false;
        }
        let r = mem.load_u32(NULL_RESP_ADDR);
        r != 0 && r == null_response(null_challenge(self.tick))
    }

Walk the eight bytes

Here is the smallest one-tick test. The first five bytes are PUSH 0xFFFFFFFF in little-endian order. The next three bytes push zero, divide, and stop.

01 FF FF FF FFPUSH 0xFFFFFFFF
03PUSH_ZERO
33DIV_U
FFHALT
After instructionStack, bottom → topVM note
PUSH 0xFFFFFFFF[0xFFFFFFFF]None yet
PUSH_ZERO[0xFFFFFFFF, 0]None yet
DIV_U[0]divzero_u = true

The division removes the top word first, so zero is the divisor. The VM returns zero instead of stopping with an error. More importantly for this chain, it records that unsigned division by zero happened.

RUST crates/vm/src/lib.rs · source lines 1267–1272
                0x30..=0x45 => {
                    let b = self.pop();
                    let a = self.pop();
                    self.note_alu_faults(opcode, a, b);
                    self.push_tagged(alu_bin(opcode, a, b), true);
                }

The code above pops b before a. The next two pieces show the two separate effects: a note for the host, and the zero result on the stack.

RUST crates/vm/src/lib.rs · source lines 848–853
            op::DIV_U | op::REM_U => {
                if b == 0 {
                    self.divzero_u = true;
                }
            }
RUST crates/vm/src/lib.rs · source lines 1448–1454
        op::DIV_U => {
            if b == 0 {
                0
            } else {
                a / b
            }
        }
1 · VMdivzero_u = trueThe eight-byte test reaches this point.
2 · GUEST MEMORY[0xFF68] = 0x4E554C4CWrite the chain's magic word.
3 · FRESH ANSWER[0xFF64] = econ_hash([0xFF60] XOR 0x4E756C31)Read this tick's challenge, then write its answer.

What the eight bytes prove: only step 1. The small image has no magic or answer, and HALT ends the bot. To earn NULLARBOR, put this arithmetic in the live pilot loop. Answer the new challenge each tick, and keep moving and collecting gems.

The host requires all three checks together, so one successful tick adds just one of the 512 meter points. DIV_U also sets the VM's general raw-op note, but NULLARBOR's host check asks for divzero_u, not that extra note.

TITHONUS#18 · STACK · SRV · full-meter 1.6

What it checks. A shift count can ask to move bits farther than a 32-bit word is wide. TITHONUS checks an unsigned right shift with a count of at least 32, for which the VM returns zero and records the oversized count. Your browser's number rules or your laptop's CPU may behave differently, so follow the VM's own shift rules here.

VM noteWhat it means
shift_annihilate_uAn unsigned right shift used a count outside the word width.

Build and activate it

  1. Push an unsigned value and a shift amount at least 32, such as 40, then execute raw SHR_U. The VM records shift_annihilate_u when the shift amount is out of the normal 0–31 range.
  2. A signed shift or a shift of 31 is a different instruction history, even if the final number looks similar.

Proof for this tick

1 · Host writes challenge at0xFF6C
2 · Magic present · value → address0x54495448 → 0xFF74
3 · Bot writes answer to0xFF70
Chain ECHO constant0x54697431
Read this chain’s exact host check
RUST crates/engine/src/vulns.rs · source lines 986–992
    fn tithonus_armed(&self, mem: &Mem, l: PwnLatches) -> bool {
        if !l.shift_annihilate_u || mem.load_u32(TITH_MAGIC_ADDR) != TITH_MAGIC {
            return false;
        }
        let r = mem.load_u32(TITH_RESP_ADDR);
        r != 0 && r == tith_response(tith_challenge(self.tick))
    }
AMBERHORN#19 · STACK · THR · full-meter 2.0

What it checks. AMBERHORN checks a difference between signed and unsigned readings of the same 32-bit values. A high-bit operand can also set the alternate accepted note.

VM noteWhat it means
signedness_splitSigned and unsigned comparisons of the same words disagreed.
wrap_carryA high-bit word appeared on the unsigned comparison path.

How the fields fit: Either a signed-versus-unsigned disagreement or a high-bit wrap can supply this VM observation. The host still checks this chain's own response before its meter rises.

Build and activate it

  1. Run raw CMP_LT_U or CMP_LE_U with operands on opposite sides of the sign bit. For example, use 0x80000000 and 1. Signed and unsigned comparisons then disagree.
  2. This sets signedness_split; a high-bit operand on the same unsigned-compare path can also set wrap_carry. The host accepts either latch for AMBERHORN.

Proof for this tick

1 · Host writes challenge at0xFF78
2 · Magic present · value → address0x414D4252 → 0xFF80
3 · Bot writes answer to0xFF7C
Chain ECHO constant0x416D6231
Read this chain’s exact host check
RUST crates/engine/src/vulns.rs · source lines 995–1001
    fn amberhorn_armed(&self, mem: &Mem, l: PwnLatches) -> bool {
        if !(l.signedness_split || l.wrap_carry) || mem.load_u32(AMBR_MAGIC_ADDR) != AMBR_MAGIC {
            return false;
        }
        let r = mem.load_u32(AMBR_RESP_ADDR);
        r != 0 && r == ambr_response(ambr_challenge(self.tick))
    }
STYX#33 · STACK · SRV · full-meter 2.5

What it checks. STYX combines a low stack position, a high stack position, arena call frames, and matched store/load slots. All required observations must occur in one tick.

VM noteWhat it means
arena_framesCalls whose frames were placed in the alternate arena region.
frames_verifiedFrames observed on both the store and later load sides of the frame ledger.
pivot_depthHigh-water displacement of the stack pointer, derived from its peak.
sp_loLowest stack-pointer position reached relative to its initial position.

How the fields relate: STYX requires low and high stack positions with a large span. It also requires arena call frames and matching stores and loads. A final stack pointer alone cannot establish that sequence. STYX has its own response check and meter; the individual observations do not automatically confirm ABYSS, OSSUARY, SEXTANT, or other stack chains.

Build and activate it

  1. Move sp below 0x8000, then reach at least 0xD000. The low-to-high span must be at least 0x6000. A tick can start with the stack already high. Its current sp initializes that tick’s high-water mark.
  2. In the arena, run four CALL_ABS frames. Use STORE and LOAD on eight matching stride slots, as OSSUARY and SEXTANT do. The low position, frames, and matched slots must occur in this tick. Earlier VM notes do not persist.

Proof for this tick

1 · Host writes challenge at0xFD0C
2 · Magic present · value → address0x53545958 → 0xFD14
3 · Bot writes answer to0xFD10
Chain ECHO constant0x53747931
Read this chain’s exact host check
RUST crates/engine/src/vulns.rs · source lines 1163–1178
    fn styx_armed(&self, mem: &Mem, l: PwnLatches) -> bool {
        let sp_hi = l.pivot_depth as i64 + SP_INIT as i64;
        let sp_lo_abs = SP_INIT as i64 + (l.sp_lo as i32) as i64;
        let span = sp_hi - sp_lo_abs;
        if !((l.sp_lo as i32) < 0
            && l.pivot_depth >= (SP_PIVOT_ARENA_LO - SP_INIT)
            && span >= STYX_SPAN_MIN
            && l.arena_frames >= ARENA_FRAMES_MIN
            && l.frames_verified >= FRAMES_VERIFIED_MIN)
            || mem.load_u32(STYX_MAGIC_ADDR) != STYX_MAGIC
        {
            return false;
        }
        let r = mem.load_u32(STYX_RESP_ADDR);
        r != 0 && r == styx_response(styx_challenge(self.tick))
    }
JORMUNGAND#35 · STACK · THR · full-meter 1.9

What it checks. JORMUNGAND needs four arithmetic notes and the raw-op note in one tick. They cover unsigned zero division, an oversized unsigned shift, and signed-versus-unsigned comparison behavior.

VM noteWhat it means
divzero_uAn unsigned divide or remainder encountered a zero divisor.
saw_raw_opExecution used a raw opcode path distinct from the compiler’s usual output.
shift_annihilate_uAn unsigned right shift used a count outside the word width.
signedness_splitSigned and unsigned comparisons of the same words disagreed.
wrap_carryA high-bit word appeared on the unsigned comparison path.

How the fields fit: The host checks all five VM notes and JORMUNGAND's own response. They describe different calculations. Seeing them does not automatically fill the separate meters for the smaller related chains.

Build and activate it

  1. Run DIV_U or REM_U with divisor zero. Run SHR_U with a shift of at least 32. Run an unsigned comparison that differs from the signed result. Give it a high-bit operand to set wrap_carry.
  2. All four arithmetic notes plus saw_raw_op are required in one tick. The unsigned compare can establish both signedness_split and wrap_carry; a wrapping multiply by itself cannot establish the latter.

Proof for this tick

1 · Host writes challenge at0xFD24
2 · Magic present · value → address0x4A4F524D → 0xFD2C
3 · Bot writes answer to0xFD28
Chain ECHO constant0x4A6F7231
Read this chain’s exact host check
RUST crates/engine/src/vulns.rs · source lines 1192–1204
    fn jormungand_armed(&self, mem: &Mem, l: PwnLatches) -> bool {
        if !(l.divzero_u
            && l.shift_annihilate_u
            && l.signedness_split
            && l.wrap_carry
            && l.saw_raw_op)
            || mem.load_u32(JORM_MAGIC_ADDR) != JORM_MAGIC
        {
            return false;
        }
        let r = mem.load_u32(JORM_RESP_ADDR);
        r != 0 && r == jorm_response(jorm_challenge(self.tick))
    }
AULOS#36 · STACK · THR · full-meter 1.5

What it checks. AULOS records eight recent local stack peaks. The bot must shape those peaks to encode a response word from the current challenge.

VM noteWhat it means
aulos_highHow high the stack pointer reached.
aulos_wordSeveral stack-high observations packed into one word.

Gate and carrier: aulos_high says the stack reached a qualifying high-water state. aulos_word packs the stack's path and the chain's response. These fields describe one check over time, not two rewards.

Build and activate it

  1. Read the challenge and compute econ_hash(challenge XOR AULOS_ECHO). Park the stack on a 64-byte-aligned arena base such as 0xD000, then make eight push/pop excursions.
  2. For each response nibble from least to most significant, choose an excursion height whose peak satisfies (peak >> 2) & 0xF = nibble. Eight most-recent local peaks pack into aulos_word; all must be at least 0xD000. Arrange other stack-using work before those eight peaks so it does not overwrite the pattern. There is no response cell.

Proof for this tick

1 · Host writes challenge at0xFD30
2 · Magic present · value → address0x41554C53 → 0xFD34
3 · Bot encodes answerMachine state; see note
Chain ECHO constant0x41756C31

No response cell: the eight stack peaks carry the ECHO-hash response.

Read this chain’s exact host check
RUST crates/engine/src/vulns.rs · source lines 1213–1218
    fn aulos_armed(&self, mem: &Mem, l: PwnLatches) -> bool {
        if !l.aulos_high || mem.load_u32(AULOS_MAGIC_ADDR) != AULOS_MAGIC {
            return false;
        }
        l.aulos_word == aulos_response(aulos_challenge(self.tick))
    }
HOURGLASS#38 · STACK · SRV · full-meter 1.6

What it checks. HOURGLASS checks whether off-image execution continues across consecutive tick-budget boundaries. The VM must save increasing off-image instruction addresses.

VM noteWhat it means
sled_straddleA recorded execution span crossed a world-tick boundary.

Build and activate it

  1. Enter a long off-image straight-line instruction sled and exhaust the 10,000-instruction slice without halting. At each boundary the VM saves its next IP.
  2. Make two consecutive increases in that saved off-image IP, which takes three budget cut points to establish. The unit test uses a POP sequence. Each opcode advances one byte. A loop to one fixed address does not set sled_straddle. This is a short-lived primitive unless the program also solves the finite-address and re-entry problem.

Proof for this tick

1 · Host writes challenge at0xFD40
2 · Magic present · value → address0x484F5552 → 0xFD48
3 · Bot writes answer to0xFD44
Chain ECHO constant0x486F7531
Read this chain’s exact host check
RUST crates/engine/src/vulns.rs · source lines 1230–1236
    fn hourglass_armed(&self, mem: &Mem, l: PwnLatches) -> bool {
        if !l.sled_straddle || mem.load_u32(HOUR_MAGIC_ADDR) != HOUR_MAGIC {
            return false;
        }
        let r = mem.load_u32(HOUR_RESP_ADDR);
        r != 0 && r == hour_response(hour_challenge(self.tick))
    }

Self-modification: what bytes are actually fetched

Changing a code byte, fetching that changed byte, and counting how many changed addresses were fetched are three different events.

RUST crates/vm/src/lib.rs · source lines 1346–1359
    pub packer_count: u32,
    pub metamorphic: bool,
    pub metamorphic_addr: u32,
    pub straight_line_jit: bool,
    pub read_own_code: bool,
    pub read_own_code_cov: u32,
    pub overlap_density: u32,

Worked machine trace: writing is not executing

Start with a code byte X in the uploaded image. During one tick, the bot writes a different value Y there. Keep these three events apart:

Stage Observation What it cannot establish alone
Store Original-image code byte differs (code_patched) That changed byte ever ran
Fetch The VM later decodes that address (smc_exec) How many distinct changed addresses ran
Count A deduplicated ledger grows (packer_count) Which route reached the altered code

LOOM, PHOENIX, PACKER, and OUROBOROS each check a different part of this process. The host still checks each chain's proof before it adds a meter mark.

The VM's tested PACKER idea changes eight bytes. This teaching probe extends it to sixteen bytes for BASILISK, using the same operations. The VM counts distinct changed addresses that it later fetches. The probe runs once and does not score.

Sealed code → changed code → sixteen executed addresses
UPLOAD · BEFORE RUNNINGA sealed strip

Image addresses 50–65 contain sixteen FF bytes. FF means HALT, but execution will not reach them until after the stores.

FF FF FF FF · FF FF FF FF · FF FF FF FF · FF FF FF FF
THIS TICK · FOUR STORESReplace the strip

Four word stores write 0x50505050 at addresses 50, 54, 58, and 62. Each word supplies four one-byte NOT instructions.

50 50 50 50 · 50 50 50 50 · 50 50 50 50 · 50 50 50 50
THIS TICK · JUMP AND FETCHRun every new byte

Jump to 50. The VM fetches all sixteen changed NOTs, then the untouched FF at 66 stops the probe. The count rises once per changed address.

8 fetches PACKER threshold16 fetches BASILISK threshold
The complete 67-byte probe, assembled in order
0–43

Four repeats of PUSH 0x50505050, PUSH destination, STORE; destinations are 50, 54, 58, 62. Each repeat is eleven bytes.

44–49

PUSH 50; JMP_ABS. This takes six bytes and lands on the first changed byte.

50–66

Sixteen original FF bytes followed by one final FF. Only the first sixteen are rewritten.

The repository's VM test proves the eight-byte version (two stores, block starting at 28). This sixteen-byte version uses the same fetch-and-count rule; it is not a saved reference bot, and HALT ends it after one tick. To earn meter points, also write BASILISK's fresh answer and magic, then run the behavior inside a pilot that continues on later ticks.

The repository's eight-byte VM test is the source for this construction. It uses two stores and starts its sealed strip at address 28. Our sixteen-byte version adds two more stores, which moves the strip to address 50. Here is the test code:

RUST crates/vm/src/lib.rs · source lines 2226–2239
        const BASE: u32 = 28;
        let mut code = Vec::new();
        for w in 0..2u32 {
            pimm(&mut code, 0x5050_5050);
            pimm(&mut code, BASE + w * 4);
            code.push(op::STORE);
        }
        pimm(&mut code, BASE);
        code.push(op::JMP_ABS);
        assert_eq!(code.len() as u32, BASE, "setup ends exactly at BASE");
        for _ in 0..8 {
            code.push(op::HALT);
        }
        code.push(op::HALT);
PHOENIX#02 · SMC · THR · full-meter 1.5

What it checks. PHOENIX requires a change to an original-image byte and a later fetch of that changed byte. A data write without an instruction fetch does not count.

VM noteWhat it means
smc_addrAddress of the changed original-image byte used as a witness.
smc_execA changed byte in the original image was later fetched as an instruction.
smc_origThe original byte at that witnessed location, before the change.

Event and witness: smc_exec records that altered original-image code was fetched. smc_addr and smc_orig retain the location and prior byte for the host's witness check. A write alone belongs to the earlier stage of the history; this chain concerns the later execution.

Build and activate it

  1. Put an original 0xFF (HALT) byte in the image as a sealed rune. At run time, replace it with PUSH_ZERO or another safe opcode. Branch or continue to fetch that changed byte.
  2. Write the rune address to SMC_RUNE_ADDR. The host checks that the VM’s last recorded changed fetch has original byte 0xFF and the same address. Another changed-code fetch after the rune can replace smc_addr and spoil this check.

Proof for this tick

1 · Host writes challenge at0xFEA8
2 · Magic present · value → address0x50484E58 → 0xFEB0
3 · Bot writes answer to0xFEAC
Chain ECHO constant0x52684E31

Also write the sealed rune address to 0xFEB4.

Read this chain’s exact host check
RUST crates/engine/src/vulns.rs · source lines 836–846
    fn phoenix_armed(&self, mem: &Mem, l: PwnLatches) -> bool {
        if !l.smc_exec
            || l.smc_orig != SMC_SEAL_BYTE
            || l.smc_addr != (mem.load_u32(SMC_RUNE_ADDR) & ADDR_MASK)
            || mem.load_u32(SMC_MAGIC_ADDR) != SMC_MAGIC
        {
            return false;
        }
        let r = mem.load_u32(SMC_RESP_ADDR);
        r != 0 && r == smc_response(smc_challenge(self.tick))
    }
PACKER#20 · SMC · THR · full-meter 1.5

What it checks. PACKER counts distinct changed original-image addresses that the VM later fetches as instructions. Writing the bytes alone does not count.

VM noteWhat it means
packer_countNumber of distinct changed code addresses later fetched in the tick.

Build and activate it

  1. Prepare at least eight original image bytes for changes. The VM test starts with eight 0xFF bytes. Two word stores replace them with eight one-byte 0x50 opcodes. The test then jumps into the changed block.
  2. Fetch all eight changed addresses in one tick. packer_count counts distinct in-image addresses whose fetched bytes differ from the original image. Repeated visits to one address count once.

Proof for this tick

1 · Host writes challenge at0xFF84
2 · Magic present · value → address0x5041434B → 0xFF8C
3 · Bot writes answer to0xFF88
Chain ECHO constant0x50636B31
Read this chain’s exact host check
RUST crates/engine/src/vulns.rs · source lines 1008–1014
    fn packer_armed(&self, mem: &Mem, l: PwnLatches) -> bool {
        if l.packer_count < PACKER_COUNT_MIN || mem.load_u32(PACK_MAGIC_ADDR) != PACK_MAGIC {
            return false;
        }
        let r = mem.load_u32(PACK_RESP_ADDR);
        r != 0 && r == pack_response(pack_challenge(self.tick))
    }
LOOM#21 · SMC · THR · full-meter 1.5

What it checks. LOOM checks a write that changes an original-image code byte. It does not require the VM to fetch the changed byte afterward.

VM noteWhat it means
code_patchedA guest store changed a code-region byte from its original image value.

Build and activate it

  1. Use STORE to change at least one byte in the original image region. Select an unused image area to keep the bot running.
  2. The VM sets code_patched at write time. No jump to the patched bytes is needed for LOOM; that extra fetch belongs to PHOENIX or PACKER.

Proof for this tick

1 · Host writes challenge at0xFF90
2 · Magic present · value → address0x4C4F4F4D → 0xFF98
3 · Bot writes answer to0xFF94
Chain ECHO constant0x4C6F6D31
Read this chain’s exact host check
RUST crates/engine/src/vulns.rs · source lines 1016–1022
    fn loom_armed(&self, mem: &Mem, l: PwnLatches) -> bool {
        if !l.code_patched || mem.load_u32(LOOM_MAGIC_ADDR) != LOOM_MAGIC {
            return false;
        }
        let r = mem.load_u32(LOOM_RESP_ADDR);
        r != 0 && r == loom_response(loom_challenge(self.tick))
    }
BASILISK#22 · SMC · THR · full-meter variable; ≤ 1.6

What it checks. BASILISK requires many distinct changed image bytes to run as instructions. The number of changed addresses sets its reward limit.

VM noteWhat it means
packer_countNumber of distinct changed code addresses later fetched in the tick.

Build and activate it

  1. First test the sixteen-byte example above. Four word stores change sixteen original-image bytes. A jump fetches all sixteen changed opcodes in one tick. The unchanged final HALT stops that test. For a live bot, return to the pilot instead.
  2. Check packer_count = 16. Sixteen trips through one changed address count only once. The host's BASILISK test needs at least sixteen different changed addresses plus its own magic and fresh response.
  3. Repeat the work and proof in later ticks to fill the BASILISK meter. At count 16, its full-meter attack factor is about 1.075. Count 64 gives about 1.3, and count 128 gives 1.6. Other game caps still apply, and these values are not EXP multipliers.

Proof for this tick

1 · Host writes challenge at0xFF9C
2 · Magic present · value → address0x4241534C → 0xFFA4
3 · Bot writes answer to0xFFA0
Chain ECHO constant0x42736C31
Read this chain’s exact host check
RUST crates/engine/src/vulns.rs · source lines 1025–1031
    fn basilisk_armed(&self, mem: &Mem, l: PwnLatches) -> bool {
        if l.packer_count < BASILISK_COUNT_MIN || mem.load_u32(BASL_MAGIC_ADDR) != BASL_MAGIC {
            return false;
        }
        let r = mem.load_u32(BASL_RESP_ADDR);
        r != 0 && r == basl_response(basl_challenge(self.tick))
    }
OUROBOROS#23 · SMC · THR · full-meter 1.5

What it checks. OUROBOROS requires the VM to write a code byte and then reach that byte without a branch. The fetch must occur inside the armed window.

VM noteWhat it means
straight_line_jitChanged code was fetched along a straight-line execution path.

Build and activate it

  1. Store a changed opcode into the original image a few bytes ahead of the current IP. The VM test writes byte 13 while the following instruction starts at 11.
  2. Fall straight through and fetch the rewritten byte inside the armed window. Any branch between the store and fetch clears that special arm: you may still get smc_exec, but not straight_line_jit.

Proof for this tick

1 · Host writes challenge at0xFFA8
2 · Magic present · value → address0x4F55524F → 0xFFB0
3 · Bot writes answer to0xFFAC
Chain ECHO constant0x4F757231
Read this chain’s exact host check
RUST crates/engine/src/vulns.rs · source lines 1033–1039
    fn ouroboros_armed(&self, mem: &Mem, l: PwnLatches) -> bool {
        if !l.straight_line_jit || mem.load_u32(OURO_MAGIC_ADDR) != OURO_MAGIC {
            return false;
        }
        let r = mem.load_u32(OURO_RESP_ADDR);
        r != 0 && r == ouro_response(ouro_challenge(self.tick))
    }
PROTEUS#24 · SMC · THR · full-meter 1.6

What it checks. PROTEUS requires one address to have two roles. The VM first consumes it as instruction data, then changes it and fetches it as an opcode.

VM noteWhat it means
metamorphicAn executed changed opcode also has a second role as an immediate byte.

Build and activate it

  1. Execute a PUSH to mark its four immediate bytes as interior. Overwrite one of those exact bytes with a different opcode and branch into that byte.
  2. At the fetch, the VM records that the same address changed and was previously used as data. These conditions set metamorphic. A code change at one address and an overlap at another do not count.

Proof for this tick

1 · Host writes challenge at0xFFB4
2 · Magic present · value → address0x50524F54 → 0xFFBC
3 · Bot writes answer to0xFFB8
Chain ECHO constant0x50727431
Read this chain’s exact host check
RUST crates/engine/src/vulns.rs · source lines 1041–1047
    fn proteus_armed(&self, mem: &Mem, l: PwnLatches) -> bool {
        if !l.metamorphic || mem.load_u32(PROT_MAGIC_ADDR) != PROT_MAGIC {
            return false;
        }
        let r = mem.load_u32(PROT_RESP_ADDR);
        r != 0 && r == prot_response(prot_challenge(self.tick))
    }
SCRIVENER#39 · SMC · THR · full-meter 1.6

What it checks. SCRIVENER requires a host service to change guest code bytes, and the VM must then fetch changed in-image code during the same tick. It follows the change of role from service output to instruction; this is still guest memory, not native host-code execution.

VM noteWhat it means
host_code_patchedA host write changed a code-region byte from the original image.
smc_execA changed byte in the original image was later fetched as an instruction.

Origin and consumption: host_code_patched distinguishes a host-produced code-region change from a guest store. smc_exec records subsequent fetch of changed code. SCRIVENER joins that origin to later execution; seeing smc_exec does not automatically establish PHOENIX's separate witness check.

Build and activate it

  1. Point a scan service’s result_addr into an unused original-image region. Make the scan return a record that changes at least one image byte. The VM compares code before and after the host call.
  2. Fetch a changed in-image byte during the same tick to set smc_exec. The host checks two separate notes. The fetched byte need not be the byte the host wrote. A separate safe patch and fetch can supply this note.

Proof for this tick

1 · Host writes challenge at0xFD4C
2 · Magic present · value → address0x53435256 → 0xFD54
3 · Bot writes answer to0xFD50
Chain ECHO constant0x53637231
Read this chain’s exact host check
RUST crates/engine/src/vulns.rs · source lines 1239–1245
    fn scrivener_armed(&self, mem: &Mem, l: PwnLatches) -> bool {
        if !(l.host_code_patched && l.smc_exec) || mem.load_u32(SCRIV_MAGIC_ADDR) != SCRIV_MAGIC {
            return false;
        }
        let r = mem.load_u32(SCRIV_RESP_ADDR);
        r != 0 && r == scriv_response(scriv_challenge(self.tick))
    }

Reinterpretation: when one representation has several roles

A byte can first be instruction data and later become an instruction. A bot can also read its own code as data. The VM records the context that makes each second use meaningful.

RUST crates/vm/src/lib.rs · source lines 1359–1376
    pub g_word: u32,
    pub resume_ip: u32,
    pub sled_straddle: bool,
    pub aulos_word: u32,
    pub aulos_high: bool,
    pub host_code_patched: bool,
    pub hydra_phase: u32,
}

impl Vm {

Worked machine trace: bytes do not have one permanent role

An opcode can use the following bytes as an immediate value. A jump can then make the VM start decoding at one of those bytes. The byte does not move; only the starting point changes. Reading code as data is a separate event again.

Question Observation Example chain
Was an immediate byte later read as an opcode? overlap_fired and witness JANUS
How many distinct interior bytes gained that role? overlap_density POLYGLOT / KALEIDOSCOPE
Did the program inspect its code image as data? read_own_code and coverage YGGDRASIL / NARCISSUS
Did several host output writes overlap? writer_collision DOPPELROLL

This family is about relationships among interpretations, so a single straight disassembly or one read cannot establish every named behavior.

JANUS#01 · REINT · SRV · full-meter 1.5

What it checks. An instruction holds an opcode and sometimes extra input bytes. Start decoding at a different position and a byte that was input can become an opcode. JANUS requires the VM to fetch as an opcode a byte it previously consumed as instruction data. The host then checks the address of that interior fetch.

VM noteWhat it means
overlap_firedExecution decoded a byte inside a region previously consumed as an immediate.
overlap_witnessThe VM retains which overlapping byte supplied that observation.

Event and witness: overlap_fired records the reinterpretation event. overlap_witness retains the particular interior byte used in the host's check. The witness anchors a claim about the event; it does not add a second independently metered chain.

Build and activate it

  1. Execute PUSH to mark its four input bytes as data. Jump into one interior byte and fetch it as an opcode. Put safe opcodes in the input so execution can return.
  2. The VM records the most recent interior fetch address as overlap_witness. Store that exact nonzero address at OVL_WITNESS_ADDR before host checking. If you visit another interior byte later, update the witness cell or the host rejects JANUS.

Proof for this tick

1 · Host writes challenge at0xFE98
2 · Magic present · value → address0x4A414E55 → 0xFEA0
3 · Bot writes answer to0xFE9C
Chain ECHO constant0x4F766C31

Also write the latest overlap address to 0xFEA4.

Read this chain’s exact host check
RUST crates/engine/src/vulns.rs · source lines 824–834
    fn janus_armed(&self, mem: &Mem, l: PwnLatches) -> bool {
        if !l.overlap_fired
            || l.overlap_witness == 0
            || mem.load_u32(OVL_WITNESS_ADDR) != l.overlap_witness
            || mem.load_u32(OVL_MAGIC_ADDR) != OVL_MAGIC
        {
            return false;
        }
        let r = mem.load_u32(OVL_RESP_ADDR);
        r != 0 && r == ovl_response(ovl_challenge(self.tick))
    }
POLYGLOT#25 · REINT · HRV · full-meter 1.5

What it checks. The bytes stay the same, but execution that begins at different offsets decodes different instructions. POLYGLOT counts the distinct interior bytes fetched as opcodes. No code rewrite is needed for one program to have several readings.

VM noteWhat it means
overlap_densityDistinct interior bytes decoded as opcodes, with repeated visits deduplicated.

Build and activate it

  1. First run the three aligned PUSH instructions in the byte walk below. Each marks four following addresses as data: twelve candidate addresses in these three regions.
  2. Jump to 0x01. The three disguised POPs and one disguised JMP_ABS at each landing take you to 0x06, then 0x0B. POLYGLOT's six-address requirement is crossed partway through the second landing.
  3. Write this tick’s POLYGLOT magic and answer before the byte walk. Then return to the pilot instead of stopping at the example HALT. Repeat the walk on later ticks to keep the meter.

Proof for this tick

1 · Host writes challenge at0xFFC0
2 · Magic present · value → address0x504F4C59 → 0xFFC8
3 · Bot writes answer to0xFFC4
Chain ECHO constant0x506C7931
Read this chain’s exact host check
RUST crates/engine/src/vulns.rs · source lines 1056–1062
    fn polyglot_armed(&self, mem: &Mem, l: PwnLatches) -> bool {
        if l.overlap_density < OVERLAP_DENSITY_MIN || mem.load_u32(POLY_MAGIC_ADDR) != POLY_MAGIC {
            return false;
        }
        let r = mem.load_u32(POLY_RESP_ADDR);
        r != 0 && r == poly_response(poly_challenge(self.tick))
    }

One byte walk that reaches both overlap thresholds

This example is the missing bridge between “jump into a PUSH” and “count twelve different bytes.” It is a one-tick teaching program. It reaches the VM counter, but it writes neither chain's magic value nor answer, so it does not score.

First the bytes are data. Then those same bytes become instructions. Read each colored row from left to right.
First pass0x00 PUSH0104020403040411one four-byte number; mark addresses 01–04
First pass0x05 PUSH0604070408040911mark addresses 06–09
First pass0x0A PUSH0B040C040D040E11mark addresses 0B–0E
Second pass: land inside the numbers
0x013 × POP; jump0x063 × POP; jump0x0B3 × POP; jump0x30HALT
4 addresses: JANUS-style overlap6 addresses: POLYGLOT is eligible12 addresses: KALEIDOSCOPE is eligible

All addresses and byte values in the diagram are hexadecimal. The small labels are addresses, and the large values are the bytes stored there. Byte 04 means POP, and 11 means JMP_ABS.

On the first pass, these are only part of a PUSH value, but the jump to 0x01 makes the VM read them as instructions. The bytes stay exactly the same; only the point where decoding begins moves.

Build the 49-byte route:

  1. Put 01 04 04 04 11 at 0x00, 0x05, and 0x0A. This makes three aligned PUSH instructions. Each value hides three POP instructions and one JMP_ABS.
  2. Put 04 04 04 at 0x0F. These POP instructions remove the three values left by the aligned PUSH instructions. Build the jump stack from bottom to top: 0x30, 0, 0, 0, 0x0B, 0, 0, 0, 0x06, 0, 0, 0, 0x01. Use PUSH for addresses and PUSH_ZERO for each zero.
  3. Put JMP_ABS at 0x2F and HALT at 0x30. The first jump uses 0x01. Each hidden group removes three zeros and jumps to the next group. The last group jumps to HALT.
Image offset Exact bytes Job on the first pass
00–04, 05–09, 0A–0E 01 04 04 04 11 at each start Mark three four-byte value regions.
0F–11 04 04 04 Clear those three pushed numbers.
12–19 01 30 00 00 00 03 03 03 Place final address 0x30, then three zeros.
1A–21 01 0B 00 00 00 03 03 03 Place third landing 0x0B, then three zeros.
22–29 01 06 00 00 00 03 03 03 Place second landing 0x06, then three zeros.
2A–30 01 01 00 00 00 11 FF Place first landing 0x01, jump, then keep a stop at 0x30.

The later PUSH instructions also mark their own value bytes as interior, but nothing jumps into those, so they add zero to the count. The VM remembers fetched addresses, not repeat visits, so twelve jumps to 0x01 still count as one address. The VM does the bookkeeping here:

RUST crates/vm/src/lib.rs · source lines 1094–1102
                    if is_interior {
                        self.overlap_fired = true;
                        self.overlap_witness = ipa;
                        if self.fetched_interior[a >> 3] & (1u8 << (a & 7)) == 0 {
                            self.fetched_interior[a >> 3] |= 1u8 << (a & 7);
                            self.overlap_density = self.overlap_density.saturating_add(1);
                        }
                    }

One boundary is worth keeping straight. The twelve-address walk proves the VM behavior, but it does not by itself arm POLYGLOT or KALEIDOSCOPE. The host also checks each chain's magic value and current answer. For JANUS, the final interior address fetched here is 0x0E. Write 0x0E to its witness cell if you also want JANUS in that tick.

KALEIDOSCOPE#26 · REINT · HRV · full-meter 1.75

What it checks. KALEIDOSCOPE requires more distinct interior opcode fetches than POLYGLOT. It pays into collection and uses its own proof cells.

VM noteWhat it means
overlap_densityDistinct interior bytes decoded as opcodes, with repeated visits deduplicated.

Build and activate it

  1. Use the exact three-landing walk above. Its twelve unique interior fetch addresses reach KALEIDOSCOPE's threshold in one tick; the first six also satisfy POLYGLOT's VM-side threshold.
  2. Write KALEIDOSCOPE’s magic and current answer before the byte walk. POLYGLOT uses different proof cells. Its answer cannot substitute for KALEIDOSCOPE’s answer.
  3. Replace the example HALT with a safe return to the pilot. Then repeat the byte walk on later ticks.

Proof for this tick

1 · Host writes challenge at0xFFCC
2 · Magic present · value → address0x4B414C45 → 0xFFD4
3 · Bot writes answer to0xFFD0
Chain ECHO constant0x4B616C31
Read this chain’s exact host check
RUST crates/engine/src/vulns.rs · source lines 1064–1072
    fn kaleidoscope_armed(&self, mem: &Mem, l: PwnLatches) -> bool {
        if l.overlap_density < KALEIDOSCOPE_DENSITY_MIN
            || mem.load_u32(KALE_MAGIC_ADDR) != KALE_MAGIC
        {
            return false;
        }
        let r = mem.load_u32(KALE_RESP_ADDR);
        r != 0 && r == kale_response(kale_challenge(self.tick))
    }
NARCISSUS#27 · REINT · HRV · full-meter 1.9

What it checks. NARCISSUS requires the bot to read much of its original code region. Its response must also match a digest of the immutable uploaded image.

VM noteWhat it means
read_own_code_covCoverage of original code-region bytes read as data.

Build and activate it

  1. Sweep the original code region with word LOADs during the current tick. Coverage counts distinct read bytes below 0x2000; it must reach at least 75% of min(code_end,0x2000).
  2. Start the digest with the current challenge. For addresses 0,4,8,… < code_end, fold h = econ_hash(h XOR original_image_word_at_a). Write the final digest to the response cell. The host hashes the original image. Preserve or reconstruct original bytes if the bot changed its live image.

Proof for this tick

1 · Host writes challenge at0xFFD8
2 · Magic present · value → address0x4E415243 → 0xFFE0
3 · Bot writes answer to0xFFDC
Chain ECHO constant0x4E617231

That constant is not used by this digest check; the response is the image digest described above.

Read this chain’s exact host check
RUST crates/engine/src/vulns.rs · source lines 1076–1092
    fn narcissus_armed(&self, mem: &Mem, _l: PwnLatches) -> bool {
        let image = match &self.bot_image {
            Some(img) => img,
            None => return false,
        };
        let cov_target = self.bot_code_end.min(FRAME_BASE);
        if cov_target == 0
            || (_l.read_own_code_cov as u64) * 4 < (cov_target as u64) * 3
            || mem.load_u32(NARC_MAGIC_ADDR) != NARC_MAGIC
        {
            return false;
        }
        let want = self_hash_digest(image, 0, self.bot_code_end, narc_challenge(self.tick));
        let r = mem.load_u32(NARC_RESP_ADDR);
        r != 0 && r == want
    }
YGGDRASIL#28 · REINT · THR · full-meter 1.7

What it checks. YGGDRASIL checks a digest of one image segment selected by the challenge. A code-region load must occur, but the host does not tie that load to the selected segment.

VM noteWhat it means
read_own_codeThe program read bytes from its own code region as data.

Build and activate it

  1. Read the current challenge; choose one of eight image segments with seg = challenge % 8, lo = floor(seg·code_end/8), and hi = floor((seg+1)·code_end/8).
  2. Starting with h=challenge, walk a=lo,lo+4,… < hi and fold h=econ_hash(h XOR original_image_word_at_a). Put the digest in YGGDRASIL’s response cell. As with NARCISSUS, the host compares against original image bytes, not rewritten live memory.
  3. The checker requires a code-region LOAD during this tick. It does not require a load from the selected segment. An unrelated code load satisfies that note. The digest must still match the selected original-image segment and fresh challenge.

Proof for this tick

1 · Host writes challenge at0xFFE4
2 · Magic present · value → address0x59474744 → 0xFFEC
3 · Bot writes answer to0xFFE8
Chain ECHO constant0x59676731

That constant is not used by this digest check; the response is the selected-segment image digest described above.

Read this chain’s exact host check
RUST crates/engine/src/vulns.rs · source lines 1095–1109
    fn yggdrasil_armed(&self, mem: &Mem, l: PwnLatches) -> bool {
        let image = match &self.bot_image {
            Some(img) => img,
            None => return false,
        };
        if !l.read_own_code || self.bot_code_end == 0 || mem.load_u32(YGGD_MAGIC_ADDR) != YGGD_MAGIC
        {
            return false;
        }
        let chal = ygg_challenge(self.tick);
        let (lo, hi) = ygg_segment(chal, self.bot_code_end);
        let want = self_hash_digest(image, lo, hi, chal);
        let r = mem.load_u32(YGGD_RESP_ADDR);
        r != 0 && r == want
    }
GLYPH#29 · REINT · HRV · full-meter 1.7

What it checks. GLYPH uses the VM’s value-origin tracking. The bot creates clean and tainted stack slots to encode a response from the current challenge.

VM noteWhat it means
g_wordA word that records where the value came from along a defined data path.

Build and activate it

  1. Read this tick’s challenge and calculate econ_hash(challenge XOR GLYP_ECHO). Move the stack to the 32-slot window at 0xD800. For each bit 0–31, use LOAD for a 1 or PUSH_ZERO for a 0.
  2. The VM packs those slot taint bits into g_word after execution. Write the magic before painting and avoid later stack activity that overwrites those slots. The packed taint pattern is the response; there is no response cell.

Proof for this tick

1 · Host writes challenge at0xFFF0
2 · Magic present · value → address0x474C5950 → 0xFFF4
3 · Bot encodes answerMachine state; see note
Chain ECHO constant0x476C7931

No response cell: the 32 taint slots carry the ECHO-hash response.

Read this chain’s exact host check
RUST crates/engine/src/vulns.rs · source lines 1113–1118
    fn glyph_armed(&self, mem: &Mem, l: PwnLatches) -> bool {
        if l.g_word == 0 || mem.load_u32(GLYP_MAGIC_ADDR) != GLYP_MAGIC {
            return false;
        }
        l.g_word == glyph_response(glyph_challenge(self.tick))
    }
DOPPELROLL#30 · REINT · HRV · full-meter 1.75

What it checks. DOPPELROLL requires overlapping guest-memory output ranges from enemy and gem scans, so both scans must return records and write bytes. It watches that collision between writers; it does not mean the bot changed native host memory.

host observationWhat it means
writer_collision (host)The host observed overlapping guest-output write ranges in one tick.

Build and activate it

  1. Call scan_enemies and scan_gems with overlapping output buffers in one tick. The same result_addr is the simplest choice. Both scans must return records. First kill an enemy to create a gem while another enemy remains.
  2. The host records enemy writes as [addr,addr+16·n) and gem writes as [addr,addr+12·n). Their half-open ranges must share a byte; touching only at an endpoint, or an empty scan, does not set writer_collision.

Proof for this tick

1 · Host writes challenge at0xFE00
2 · Magic present · value → address0x444F5050 → 0xFE08
3 · Bot writes answer to0xFE04
Chain ECHO constant0x446F7031
Read this chain’s exact host check
RUST crates/engine/src/vulns.rs · source lines 1121–1127
    fn doppelroll_armed(&self, mem: &Mem, _l: PwnLatches) -> bool {
        if !self.writer_collision || mem.load_u32(DOPP_MAGIC_ADDR) != DOPP_MAGIC {
            return false;
        }
        let r = mem.load_u32(DOPP_RESP_ADDR);
        r != 0 && r == dopp_response(dopp_challenge(self.tick))
    }
SPHINX#31 · REINT · THR · full-meter 2.0

What it checks. SPHINX combines a code-region read, a computed branch target, and four destinations from one dispatch site. The host checks all three observations.

VM noteWhat it means
computed_jumpThe branch destination came from a computed value, not only a compiler-emitted literal.
dispatch_maxMaximum distinct targets seen from one dispatch site in the tick.
read_own_codeThe program read bytes from its own code region as data.

How the fields relate: Code-as-data reading, computed destination origin, and sufficient breadth from one dispatch site must be understood together for SPHINX's own response check. Merely reading code, or merely branching to several locations, leaves the other relationship unproved. Its meter is separate from the related CLASSIC chains.

Build and activate it

  1. LOAD at least one word from the original code region so read_own_code is set. Build a shared-site dispatcher with four distinct destinations in the same tick.
  2. Make at least one JMP_ABS/CALL_ABS destination at that dispatcher come from LOAD or tainted arithmetic. The host joins own-code read, computed target, and four-target breadth; they do not have to come from one literal instruction.

Proof for this tick

1 · Host writes challenge at0xFE0C
2 · Magic present · value → address0x53504858 → 0xFE14
3 · Bot writes answer to0xFE10
Chain ECHO constant0x53706831
Read this chain’s exact host check
RUST crates/engine/src/vulns.rs · source lines 1130–1138
    fn sphinx_armed(&self, mem: &Mem, l: PwnLatches) -> bool {
        if !(l.read_own_code && l.computed_jump && l.dispatch_max >= SPHINX_DISPATCH_MIN)
            || mem.load_u32(SPHX_MAGIC_ADDR) != SPHX_MAGIC
        {
            return false;
        }
        let r = mem.load_u32(SPHX_RESP_ADDR);
        r != 0 && r == sphx_response(sphx_challenge(self.tick))
    }

The frozen CLASSIC apex

bots/reference/classic_apex keeps two recovered competitor uploads. The main apex file is 53,264 bytes and the earlier one is 21,066 bytes. The README says the main file covers eight chains across four families. Its middle score was 224,384 before the balance change and 22,704 afterward.

Keeping the exact file lets the authors measure how a reward change affects a real bot. The older file gives another comparison point. The reference-bot comparison explains the added LOCKSMITH chain. Because the uploaded bytes stay fixed and only the reward rules change, the score difference tells us about the rules' effect.

The frozen CLASSIC apex is the strongest preserved CLASSIC reference in this guide. Its measurements show what those bytes achieved, but they do not prove that no bot can score higher.

Why CLASSIC can look like the whole challenge

CLASSIC has enough chains and strong enough rewards to feel like the whole challenge. In playtests, some agents stayed in CLASSIC and never found the device route, and one even decided that it had finished. The lesson is simple: explaining what you have found does not prove there is nothing else. The next chapter shows where ABYSS lives in the running code.