Skip to content

Gun Heat Waves & Bullet Shadows

Origins

Gun Heat Waves and Bullet Shadows were developed by the RoboWiki community to improve wave-based movement systems. These techniques help bots distinguish between actual enemy bullets and false alarms caused by energy drops from other sources.

Gun Heat Waves and Bullet Shadows are complementary techniques used in advanced movement systems, particularly Wave Surfing. They solve two related problems:

  1. When can the enemy fire again? (Gun Heat Waves)
  2. Where are bullets definitely not located? (Bullet Shadows)

Together, they improve wave detection accuracy and enable more precise bullet dodging.

Gun Heat Waves

The Problem

You detect enemy fire by monitoring energy drops. But sometimes the enemy's energy drops due to:

  • Ramming damage
  • Bullet hits from other bots (melee)
  • Death
  • Wall collisions (in some game modes)

This creates false wave detections. Your movement system dodges bullets that don't exist, wasting positioning and potentially moving into real danger.

The Solution

Track the enemy's gun heat. A bot can only fire when gun heat reaches zero. By modeling when the enemy's gun cools down, you can confirm whether an energy drop corresponds to an actual shot.

Detecting Energy Drops

The first step is observing the enemy's energy level each turn and detecting changes:

pseudocode
previousEnergy = 100.0  // Track enemy energy

on enemy scan:
  energyDrop = previousEnergy - currentEnergy
  
  if energyDrop > 0:
    classifyEnergyDrop(energyDrop)
  
  previousEnergy = currentEnergy

Classifying Energy Drops

Not every energy drop is a bullet! Here's how to tell them apart:

Color coding: 🔫 Green = Real bullet (valid), 💥 Red = Ram damage (ignore), 🎯 Orange = Hit by bullet (ignore)

TurnEnergy DropCauseHow to identify
301.0🔫 Fired bulletDrop in [0.1, 3.0] + gun heat check
421.0🔫 Fired bulletDrop in [0.1, 3.0] + gun heat check
460.6💥 Ram damageDrop = 0.6 exactly
502.8🎯 Hit by bulletDrop > 3.0
541.0🔫 Fired bulletDrop in [0.1, 3.0] + gun heat check

Your bot tracks Gun heat

You cannot read the enemy's gun heat directly—you must model it based on when they fire. See Validating with Gun Heat below for the implementation.

Energy Drop Signatures

Use these patterns to classify energy drops:

CauseEnergy Drop RangeNotes
🔫 Fired bullet0.1 – 3.0Must also check gun heat = 0
💥 Ram damageexactly 0.6Fixed value; easy to filter
🎯 Hit by bullet0.4 – 16.0Damage = 4p + 2(p-1) if p>1
🧱 Wall collision0 – ~3.5= max(0, abs(velocity) × 0.5 - 1)
☠️ Inactivity penalty0.1/turnRare; only if bot is idle

Validating with Gun Heat

The energy drop range [0.1, 3.0] overlaps with other damage sources. To confirm a real bullet, track the enemy's gun heat. A bot can only fire when gun heat = 0.

pseudocode
enemyGunHeat = 3.0  // Bots start with gun heat = 3.0

on enemy scan:
  // Gun heat cools every turn
  coolingRate = 0.1  // Default cooling rate
  enemyGunHeat = max(0, enemyGunHeat - coolingRate)
  
  // Detect and classify energy drop
  energyDrop = previousEnergy - currentEnergy
  
  if energyDrop == 0.6:
    // Ram damage - ignore
    logFalseDetection("ram")
  else if energyDrop > 3.0:
    // Hit by bullet - ignore  
    logFalseDetection("bullet hit")
  else if 0.1 <= energyDrop <= 3.0:
    // Possible bullet - validate with gun heat
    if enemyGunHeat <= 0.001:
      bulletPower = energyDrop
      heatGenerated = 1 + bulletPower / 5
      createWave(bulletPower)
      enemyGunHeat = heatGenerated
    else:
      logFalseDetection("gun not ready")

This eliminates most false waves, particularly in melee where multiple bots are shooting and ramming.

Visualizing Gun Heat

The gun heat pattern is a sawtooth wave: linear cooling interrupted by instant spikes when firing.

PhaseTurnsWhat happens
❄️ Initial cooldown0 → 30Heat cools from 3.0 to 0.0 (30 turns × 0.1)
🔫 First shot30Heat = 0 ✅ → Fire power 1.0 → Heat instantly becomes 1.2
❄️ Cooldown30 → 42Heat cools from 1.2 to 0.0 (12 turns × 0.1)
🔫 Second shot42Heat = 0 ✅ → Fire power 1.0 → Heat instantly becomes 1.2
❄️ Cooldown42 → 54Heat cools from 1.2 to 0.0
🔫 Third shot54Heat = 0 ✅ → Fire again

Reading "30+"

The notation "30+" means "immediately after the action on turn 30". The gun heat is 0 at the start of turn 30, then instantly jumps to 1.2 when the bot fires.

Decision Flowchart

Complete Example Timeline

Here's a full battle sequence showing how the detection → classification → validation flow works:

TurnEnemy EnergyDropClassificationGun HeatAction
0100.03.0Round start
3099.01.0In [0.1, 3.0] → check heat0.0 ✅✅ Create wave (power 1.0)
3199.00No drop1.2
4298.01.0In [0.1, 3.0] → check heat0.0 ✅✅ Create wave (power 1.0)
4697.40.6= 0.6 → ram damage0.8❌ Ignore
5094.62.8> 3.0 → hit by bullet0.4❌ Ignore
5493.61.0In [0.1, 3.0] → check heat0.0 ✅✅ Create wave (power 1.0)

Bullet Shadows

The Problem

When you dodge a bullet, you move perpendicular to the enemy. But what if you move toward the enemy? Could you walk into a bullet you're trying to dodge?

Bullet Shadows answer: "Where can bullets not be?"

The Core Insight

Once you detect a bullet's position (either by being hit or by seeing it pass), you know:

  1. The bullet's current location
  2. The bullet's trajectory (straight line from enemy to initial bearing)
  3. All points behind the bullet (closer to the enemy) are safe zones—no bullet can exist there

This creates a "shadow" region where you're guaranteed not to encounter that specific bullet.

Once a bullet is detected, the region behind it forms a safe shadow

Implementation

pseudocode
bulletShadows = []

on bullet detected:
  shadow = {
    origin: enemyFirePosition,
    bulletPosition: currentBulletPosition,
    trajectory: angle(origin -> bulletPosition),
    speed: bulletSpeed
  }
  bulletShadows.append(shadow)

function isInShadow(position):
  for shadow in bulletShadows:
    vectorToPosition = position - shadow.origin
    vectorToBullet = shadow.bulletPosition - shadow.origin
    
    if length(vectorToPosition) < length(vectorToBullet):
      if angle(vectorToPosition) ≈ shadow.trajectory:
        return true  // Position is behind the bullet
  return false

Use Cases

1. Aggressive Movement

Move toward the enemy immediately after their bullet passes:

pseudocode
if mostDangerousWave.hasPassedMe() or isInShadow(myPosition):
  moveTowardEnemy()
else:
  continueEvading()

2. Wave Surfing Refinement

Exclude shadow regions from danger calculations:

pseudocode
for guessFactor in reachableGFs:
  position = positionAtGF(guessFactor)
  if isInShadow(position):
    danger[guessFactor] = 0  // Perfectly safe
  else:
    danger[guessFactor] = calculateDanger(position)

3. Melee Survival

In melee, bullets come from all directions. Bullet Shadows help identify temporary safe zones:

pseudocode
destinations = generatePossibleMoves()
safeDestinations = [d for d in destinations if isInShadow(d)]
if safeDestinations:
  moveTo(bestOf(safeDestinations))

Combining Gun Heat Waves and Bullet Shadows

Advanced bots use both techniques together:

Wave Detection

pseudocode
on enemy energy drop:
  if gunHeatAllowsFire():
    wave = createWave()
    trackedWaves.append(wave)

Wave Validation

pseudocode
on tick:
  for wave in trackedWaves:
    if wave.shouldHaveHitMe():
      if wasHitByBullet():
        confirmWave(wave)  // Real bullet
      else:
        createBulletShadow(wave)  // Bullet missed; shadow region is safe
      trackedWaves.remove(wave)

Movement Decision

pseudocode
safestGF = findSafestGuessFactor(dangerProfile, bulletShadows)
destination = positionAtGF(safestGF)
if isInShadow(destination):
  useAggressiveMovement(destination)
else:
  useDefensiveMovement(destination)

Practical Tips

Gun heat tracking:

  • Initialize gun heat to 3.0 when you first scan an enemy (both platforms start bots with 3.0 gun heat).
  • With default cooling rate (0.1/turn), bots can first fire on turn 30.
  • In melee, track gun heat separately for each enemy.

Bullet shadows:

  • Clear shadows after bullets exit the battlefield or after a timeout (50-100 turns).
  • Use shadows primarily for confirming wave misses, not for proactive movement (too narrow to rely on).
  • Shadows work best in one-on-one; in melee, overlapping bullets complicate shadow geometry.

Performance:

  • Gun heat tracking adds minimal overhead (one floating-point variable per enemy).
  • Bullet shadow calculations can be expensive—limit to 5-10 active shadows maximum.

Common Mistakes

  • Forgetting gun heat decay: Not subtracting cooling rate every turn leads to false positives.
  • Rounding errors: Gun heat checks should use <= 0.001, not == 0, to handle floating-point precision.
  • Shadow geometry bugs: Off-by-one errors in angle calculations create false safe zones.
  • Over-reliance on shadows: Shadows confirm past bullets, but don't predict future danger.

When These Techniques Matter Most

Gun Heat Waves:

  • Essential in melee (many false energy drops)
  • Useful in one-on-one against ramming bots
  • Critical for Wave Surfing accuracy

Bullet Shadows:

  • Most impactful in aggressive/close-range strategies
  • Helpful for post-dodge positioning
  • Less useful against slow-firing or pattern-matching enemies

Next Steps

Further Reading

Based on RoboWiki content (CC BY-SA 3.0) for classic Robocode and the official Robocode Tank Royale documentation.