EXPcaliburINSIDE THE MACHINE

11 / ABYSS / ECONOMY

ABYSS engine: how power changes the arena

The proof work is done, so what does the knight actually get? Three kinds of power: SERRATE for attack, BASTION for survival and movement, and TRAWL for gem collection. Each one changes an ordinary game rule, but none of them has an “add EXP” button. EXP still comes only from collected gems.

Starts with: Three power levels the host has already allowed.

Ends with: Damage, armor, movement, and pickup settings the game can use.

Question: How does a level change a fight or gem pickup?

Bring the inner machine back to the knight

Suppose the host has allowed [4, 4, 4], level four on all three tracks. The proof example shows how the host grants those levels.

Now bring back the level-one knight and base spider from the first chapter. The knight has 30 EXP and deals 12 ordinary damage; the spider has 14 HP. We place the spider's eventual gem 80 pixels from the knight and hold everything still. That keeps the pickup rule easy to see. This is a teaching scene, not a captured match.

A level-four boost still has to travel through the game
Host allowed[4, 4, 4]Sword · shield/boots · magnet
01 / SERRATE · THE HIT
Aimed swing meets the spider

Ordinary 12 damage + SERRATE's 250 bonus = 262. That defeats this 14-HP spider and can leave a 3-EXP gem. EXP is still 30.

02 / BASTION + HEAT · IF STRUCK
1.625×
incoming
If another enemy hits on the way

HEAT raises the raw hit first. BASTION's roughly 5.34% ward and 39 flat armor then reduce it. Survival helps the knight reach the gem; it never awards EXP.

03 / TRAWL · THE ROUTE
80 px
The gem is close enough to pull

A move whose requested endpoint is within 12 pixels of a live gem sets the route marker. With that marker, the 180-pixel pickup radius lets the magnet pull this gem, 80 pixels away. One pull is not a pickup. EXP is still 30.

04 / WORLD · THE PICKUP
+3 EXP
Only the collected gem pays

After enough movement or pull, the gem reaches the 27-pixel collection distance. The world banks its value: 30 → 33 EXP, crossing the first level threshold. The knight becomes level two and heals to 118 HP.

Before hit 30After kill 30While pulling 30

The ordinary aimed swing must find a target before SERRATE's local disc expands the hit to eligible non-boss enemies. TRAWL's longer reach needs the gem route. If the knight dies or the gem disappears first, the last card never happens.

The note under the figure matters here. The level-four disc has a 72-pixel radius, but it does not replace the ordinary 94-pixel attack wedge. The game keeps the wedge and adds the disc for eligible non-boss enemies. But the disc cannot attack on its own. Aim, timing, target type, and the pilot's gem route all still matter.

Follow the boundary, not the field name

How does a level turn into a game rule? The Gate hands the engine a DriveEffects record of limited numbers, not pointers to the knight or enemies. The engine copies those numbers into its normal combat, survival, and pickup settings, so the Gate never edits a game object directly.

Here is the boundary record itself. Every field holds a value, not a reference to a world object:

RUST crates/gate/src/abyss/drive.rs · source lines 79–93
pub struct DriveEffects {
    pub applied: [u8; LINEAGE_COUNT],
    pub damage_bonus: i32,
    pub disc_radius: i32,
    pub total_passes: u32,
    pub cooldown_floor: u16,
    pub armor_bonus: i32,
    pub ward_q16: i32,
    pub iframe_bonus: i32,
    pub gait_bonus: i32,
    pub vacuum_radius: i32,
    pub magnet_speed: i32,
    pub lure_q8: u16,
    pub heat_q8: i32,
}

The fields fall into three groups. Everything from damage_bonus through cooldown_floor feeds attack, and armor_bonus through gait_bonus feed survival and movement. The fields from vacuum_radius through lure_q8 feed collection and supply. The last field, heat_q8, is the cost: more dangerous incoming hits. The engine reads them all during its normal tick.

SERRATE, BASTION, and TRAWL each range from level 0 to 16. They feed those groups in order: attack, survival and movement, then collection and supply. Higher levels also add HEAT.

Watch the two damage fields, because they work differently. The Gate's damage_bonus is added to ordinary damage. The older knight field bonus_dmg replaces ordinary damage whenever it is nonzero. So the ABYSS engine adds the two first and writes the total into bonus_dmg. Judge the fields by their names alone and you can easily drop the ordinary damage or count it twice.

The complete breakpoint tables

The next three tables work like a lookup menu: one for the sword, one for shield and boots, and one for the magnet. Use a row only after the host has allowed that level. Levels 0, 4, 8, 12, and 16 are exact breakpoints; the next section covers the levels between.

First, here is what each column does. A setting can change a fight, a move, or a spawn attempt. EXP still goes up only after the world creates a gem and the knight collects it.

The three table legends, translated into game actions

SERRATE · sword

Added damage
More damage when an eligible swing lands.
Nearby circle
An aimed swing can also catch non-boss enemies near the knight.
Damage passes
How many rounds of local damage one swing may make.
Wait between swings
Lower ticks let another swing start sooner.

BASTION · shield and boots

Armor
Subtract this many points after HEAT and ward.
Ward
Remove a percentage of the heated hit.
Protected ticks
Extra no-damage time after a hit that actually hurts.
Move speed
Extra speed available on a later tick.

TRAWL · magnet and lure

Pull reach
The widened pickup radius: how far out a live gem route lets the magnet start pulling.
Pull speed
How fast that gem moves toward the knight.
Spawn credit
Saved fractions can buy extra spawn-batch attempts over time.

Every track also adds HEAT: incoming hits grow. None of these fields is a direct EXP award.

SERRATE level Added damage Nearby circle (px) Damage passes Wait between swings, base 34 (ticks)
0 0 0 1 34
4 250 72 1 10
8 250 72 1 10
12 4,500 144 4 5
16 7,200 160 5 4
BASTION level Armor points Ward out of 65,536 Extra protected ticks Extra move speed (px/s)
0 0 0 0 0
4 39 3,500 ≈ 5.34% 16 64
8 119 13,750 ≈ 20.98% 43 160
12 126 15,500 ≈ 23.65% 47 260
16 165 29,491 ≈ 45.00% 64 360
TRAWL level Pull reach (px) Pull speed (px/s) Spawn credit out of 256
0 0 0, inactive 256 = 1.000×
4 180 320 269 ≈ 1.051×
8 180 320 269 ≈ 1.051×
12 780 1,025 410 ≈ 1.602×
16 890 1,185 527 ≈ 2.059×

The listed distances and speeds are the values handed to gameplay, which the engine then converts to its integer format. At level 0, SERRATE's radius formula would start from 48, but the code forces the radius to zero. That makes “off” a special case.

Why TRAWL's spawn credit looks like a fraction

At TRAWL level four, spawn credit is 269 / 256. That does not multiply each enemy's EXP by 1.051. Instead, picture a jar filled at each ordinary spawn opportunity. Each opportunity puts in enough for one batch attempt, plus 13 / 256 of a future attempt. After 20 opportunities, those small leftovers add up to one extra attempt:

Level-four TRAWL: how one extra spawn attempt builds up
EACH ORDINARY OPPORTUNITY269 / 2561 attempt now + 13 saved
AFTER 19 OPPORTUNITIES247 / 256Saved fractions; no extra attempt yet
SAVED FRACTIONS AT OPPORTUNITY 20260 / 2561 extra attempt + 4 saved for later

An attempt is not a guaranteed enemy. The spawn cap and free slots still decide what appears. A new enemy must then be defeated and its gem picked up to pay EXP.

In the code, the jar is a saved remainder. At each opportunity, the host adds new credit and spends the whole-number part on batch attempts. The remainder stays in the jar for later:

RUST crates/engine/src/abyss_entry.rs · source lines 35–41
impl LureCredit {
    pub(crate) fn take_attempts(&mut self, lure_q8: u16) -> u16 {
        let credit = u32::from(self.remainder_q8) + u32::from(lure_q8);
        self.remainder_q8 = (credit % 256) as u16;
        (credit / 256) as u16
    }
}

Explore the applied reward vector

These are already-certified levels, not a way to acquire authority. The widget implements the downstream curves in drive.rs; it does not predict a match score.

Interpolation and discrete jumps

The tables list only every fourth level. The game fills in the rest. BASTION armor, for example, goes from 39 at level 4 to 119 at level 8. That is an 80-point gap over four steps.

Levels 5, 6, and 7 therefore give 59, 79, and 99. In general, the game uses low + floor((high − low) × offset / 4), which rounds any fraction down.

RUST crates/gate/src/abyss/drive.rs · source lines 117–124
fn interpolate_i32(values: &[i32; 5], level: u8) -> i32 {
    let (lower, delta) = band(level);
    if delta == 0 || lower == values.len() - 1 {
        values[lower]
    } else {
        values[lower] + (values[lower + 1] - values[lower]).saturating_mul(delta as i32) / 4
    }
}

Cooldown runs the other way. Higher SERRATE levels reduce it. Here the formula rounds the reduction down before subtracting it, so a fractional step stays on the higher, less powerful side.

With an ordinary cooldown of 34, SERRATE levels 0–4 give 34, 28, 22, 16, and 10. The real ordinary cooldown depends on the knight's level, but this example and the widget hold it at 34 to isolate the curve.

Damage-pass count does not interpolate at all. It takes the current band's integer instead of filling the gap between one and four passes. As a result, attack output jumps at band boundaries instead of climbing smoothly. Between levels 4 and 8, every attack and collection value stays flat. Survival keeps improving, though, and HEAT keeps rising.

HEAT is an explicit cost

Q8 stores a multiplier as an integer in units of 1/256, so 256 means 1.0, and 512 means 2.0. In those units, the code calculates HEAT like this:

HEAT = min(1024, 256 + 10S + 8B + 10T + 48⌊T/4⌋)
Incoming multiplier = HEAT / 256

The extra TRAWL term rises at level-band boundaries. Equal vectors [4,4,4], [8,8,8], [12,12,12], and [16,16,16] give incoming multipliers of 1.625×, 2.25×, 2.875×, and 3.5×. The formula caps HEAT at 4×. Even the largest valid vector reaches only 3.5×.

More power comes with a bill. Extra enemies can mean more kills and gems, but also a crowded knight, HEAT-boosted hits, and drops left behind. So the highest setting is not automatically the best one for every pilot.

The saved full ABYSS bot uses high levels on all three tracks and scored well in tests. That does not prove that raising every level helps every bot on every seed.

Offense remains spatial

The attack circle is only part of a swing. First, the ordinary directional wedge must find a target. Only then can the swing hit non-boss enemies in either the wedge or the local circle. At low ABYSS levels the circle is smaller than the ordinary reach, yet it never removes that reach.

Extra passes can raise local damage, but only within the engine's caps and attack rules. The engine caps the disc at 160 pixels and the extra passes at four. That allows at most five passes in total. Bosses still keep their own wedge, damage, and timing rules. Neither the Gate nor the reward vector issues a map-wide kill command.

The codebase also holds a second, historical trap. Older Device code has a penetration tier that softens enemy health, but the current ABYSS Gate returns zero through that older interface. Do not add the old DD health reduction to ABYSS rewards just because both paths use DeviceV4 names.

What happens when the knight gets hit?

The boost has a cost: HEAT increases incoming damage. BASTION then reduces the heated hit in two steps. Its ward removes a percentage, and its armor then removes a fixed amount. Think of a percentage discount followed by a fixed coupon, except that the price is damage. ABYSS always uses this order: HEAT → ward → armor.

Keep the same [4,4,4] boost from the spider scene. HEAT multiplies damage by 1.625, and ward removes 3,500 / 65,536 ≈ 5.34%. Armor then removes up to 39 more. The two made-up hits below show why all three numbers matter.

Same knight, same boost; two very different hits
Small scrape · raw hit 200 HP lost
  1. 1 · Enemy hit20
  2. 2 · HEAT × 1.62532.5
  3. 3 · Ward takes ≈1.74≈30.76
  4. 4 · Armor takes up to 390

Armor erases what is left. ABYSS does not start the knight's whole-body protection timer for a hit that deals zero.

Heavy slam · raw hit 100≈114.82 HP lost
  1. 1 · Enemy hit100
  2. 2 · HEAT × 1.625162.5
  3. 3 · Ward takes ≈8.68≈153.82
  4. 4 · Armor takes 39≈114.82

Even after BASTION, this hit is worse than the original 100. A level-one knight at 46 HP would die if it was not already protected.

Teaching hit sizes, not a captured fight. Values are rounded for reading; the engine keeps fractions as fixed-point integers and rounds each multiplication down. An already protected knight ignores the new hit before this calculation.

BASTION is not “more HP”: it never raises the knight's HP at all. Instead, it changes how much of each hit gets through. Fixed armor can cancel small hits, while percentage ward removes more as a hit grows. HEAT can still make a large hit dangerous. If armor came before HEAT, the heavy example would start from (100 − 39) × 1.625 ≈ 99.13 before ward. ABYSS does not use that order.

The key engine lines follow that same order. Here, max(0) means armor can stop a hit but never turn it into healing. And because of grant_iframes = false, a zero-damage scrape cannot earn free protection from a later slam:

RUST crates/engine/src/ai.rs · source lines 291–299
            let heated = fxmul(amount, self.incoming_dmg_mult);
            let warded = apply_device_ward(heated, self.hero.ward_q16);
            let armor = (self.hero.guard_flat + self.hero.armor_hp).clamp(0, self.eff_armor_cap());
            amount = (warded - from_int(armor)).max(0);
            if amount == 0 {
                grant_iframes = false;
            } else {
                iframe_bonus = self.hero.iframe_bonus.clamp(0, self.eff_iframe_cap()) as u16;
            }

When a hit does deal damage, the knight gets a short period of protection from further hits. At BASTION level four, it lasts the ordinary 27 ticks plus 16 bonus ticks. The shared combat chapter shows the full damage routine and CLASSIC's different order.

BASTION's movement bonus has one more timing rule. A boost accepted during this tick prepares faster movement for the next tick. It cannot change a move the pilot has already requested. It is like buying faster boots after you have crossed the room: the speed helps on the next trip.

Collection is not an EXP multiplier

Start with an ordinary level-one knight. Its pickup radius is 74 pixels, so the magnet pulls any gem inside that distance toward the knight. Collection, which banks the EXP, happens only at the smaller collection distance: 27 pixels from the knight's center. In other words, there are two boundaries: one starts the pull, and one collects. Outside the pickup radius the magnet does nothing. A new gem can still drift under its own scatter motion, though.

At TRAWL level 4, a valid pickup route can widen the pickup radius to 180 pixels. The magnet's pull speed becomes 320 pixels per second. That replaces the ordinary 520; it does not add to it. The wider reach can help with a distant gem, but the slower pull does not collect it instantly. Try a gem 80 pixels away:

A nearby gem does not always add EXP

One teaching gem is worth 3 EXP. Place it 20–200 pixels from a stationary level-one knight, then choose which pickup rules are active. Its scatter speed is zero, and the boost stays on for this one update.

Gem from the game200 px
Bank within 27 pxMagnet starts within 74 px

At 80 pixels, the gem is outside the ordinary 74-pixel pickup radius. The magnet does not start. A level-4 TRAWL boost with no gem route falls back to that same ordinary rule. With a live route, the 180-pixel radius catches the gem. But one update moves it only about 5.33 pixels closer. It is still far outside the 27-pixel collection distance, so this update adds no EXP.

Where does the route come from? A public movement request. Its endpoint must lie within 12 pixels of a currently live gem. That request sets a pickup-route marker for later gem updates. Owning TRAWL proof or calling the Gate does not set the marker by itself:

RUST crates/engine/src/syscall.rs · source lines 453–467
            let pickup_route = self.gems.iter().any(|gem| {
                if !gem.alive {
                    return false;
                }
                let gx = to_int(gem.x) - to_int(hx) - i32::from(dx);
                let gy = to_int(gem.y) - to_int(hy) - i32::from(dy);
                gx.saturating_mul(gx) + gy.saturating_mul(gy) <= 12 * 12
            });
            if self.pilot_pickup_routing_ablated() && pickup_route {
                return packed(hx, hy);
            }
            self.device_move_lease = crate::world::DEVICE_PILOT_LEASE_TICKS;
            if pickup_route {
                self.device_pickup_route_lease = crate::world::DEVICE_PILOT_LEASE_TICKS;
            }

The gem update uses the wider radius and chosen speed only while that route marker is active. Otherwise, it falls back to the ordinary pickup radius and speed 520:

RUST crates/engine/src/combat.rs · source lines 484–500
        let pickup_route_live = !self.abyss_on() || self.device_pickup_route_lease > 0;
        let mut pr = self.hero.pickup_radius;
        if self.lever_effects_on() && pickup_route_live && self.hero.vacuum_radius > 0 {
            pr = pr.max(from_int(self.hero.vacuum_radius.min(self.eff_vacuum_cap())));
        }
        let magnet_speed =
            if self.lever_effects_on() && pickup_route_live && self.hero.magnet_speed > 0 {
                self.hero.magnet_speed.min(self.eff_magnet_cap())
            } else {
                GEM_MAGNET_SPEED
            };

The game then moves the gem and checks the smaller collection distance. Our teaching model holds the knight still and ignores scatter, so the pull is easy to see. A real gem also drifts at a slowing scatter speed. Obstacles and movement can change the knight's route, too:

RUST crates/engine/src/combat.rs · source lines 516–527
            let mut collected = false;
            if self.hero.alive {
                let d = fx_dist(g.x, g.y, px, py);
                if d <= pr {
                    let ang = fx_atan2(py - g.y, px - g.x);
                    let mv = mul_dt(from_int(magnet_speed));
                    g.x += fxmul(fx_cos(ang), mv);
                    g.y += fxmul(fx_sin(ang), mv);
                    let d2 = fx_dist(g.x, g.y, px, py);
                    if d2 <= from_int(PLAYER_RADIUS) + from_int(10) {
                        collected = true;
                    }

Only a successful pickup reaches the score. The amount is normally the gem's frozen value, set at creation and never changed. A separate CLASSIC chain can make the pickup pay less, but never more than that frozen amount. This is the call that actually adds EXP:

RUST crates/engine/src/combat.rs · source lines 565–568
                let kind = g.kind;
                let from_level = self.hero.level;
                let gained = self.tables.add_exp(&mut self.hero, value);
                self.events.push(Event::Pickup {

Gems also expire, and the 1,500-slot gem pool can be full when an enemy dies. Either way, that kill banks no EXP. A bigger magnet helps only while the pilot keeps finding gems and routing toward them. It is not an automatic collection command. The reference-bot chapter measures the score drop when gem-directed moves are blocked.

Lure works through the spawn economy instead. It adds credit at ordinary spawn opportunities. Saved credit can then pay for extra spawn attempts. But it adds no gem value, and pool limits can still block a spawn. Each extra enemy must still appear, die, drop a gem, and have that gem collected.

Follow value through every constraint

One useful check is banked EXP ≤ gem value the world made available. The gap can come from a full pool, gems left on the ground, or expired drops. Poor routes or the knight's death before pickup can widen it too. This check explains where value went; it does not predict the final score.

The highest-scoring reference combines allowed power with good attacks, survival, movement, aim, and gem routes. You can follow each step: boosts change local fights, the knight collects real gems, and the replay records the EXP. A large damage number alone cannot explain the score.