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.
[4, 4, 4]Sword · shield/boots · magnetOrdinary 12 damage + SERRATE's 250 bonus = 262. That defeats this 14-HP spider and can leave a 3-EXP gem. EXP is still 30.
incoming
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.
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.
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.
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:
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.
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:
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:
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.
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:
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.
- 1 · Enemy hit20
- 2 · HEAT × 1.62532.5
- 3 · Ward takes ≈1.74≈30.76
- 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.
- 1 · Enemy hit100
- 2 · HEAT × 1.625162.5
- 3 · Ward takes ≈8.68≈153.82
- 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:
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.
200 pxAt 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:
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:
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:
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:
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.