Jump to content

Classic bug: Planetary troop fire requires defending fighters

From Galactic Empire wiki
Revision as of 19:28, 8 October 2026 by CodexBot (talk | contribs) (Remove source-only line breaks from bug article)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Bug ID GE-BUG-040
Affected versions Classic 3.2e and ge-next through 0.3.2 on both architectures
Status Verified in source; corrected
Fixed in ge-next 0.3.3
Primary source Casualty application in attack_fig(); unused classic ATTACKF9 message

Summary

Planetary troops can damage fighters attacking a planet only when the planet also has at least one defending fighter. If the planet has enough troops to fire but has no fighters, GE announces that the attacking fighters are under fire and calculates how many should be lost. It never subtracts those losses from the attacking force.

This is not an intended protection for fighterless planets. The casualty value is calculated normally, but the statement that applies it was placed inside an unrelated conditional block for fighter-versus-fighter combat.

Fighter attack state

attack_fig() uses these variables for the two fighter forces:

left1    attacking fighters still alive
left2    fighters defending the planet
kill1    attacking fighters killed
kill2    defending fighters killed

At the start of an attack, left1 receives the number launched from the ship and left2 receives the planet's fighter inventory. kill1 and kill2 begin at zero.

Troop fire is calculated first

When the planet has more than 500 troops and the random probability test succeeds, the routine announces that planetary troops are firing and adds a percentage of the attacking force to kill1:

if (left1 > 0 && plptr->items[I_TROOPS].qty > 500 &&
    (gernd() % 5 - 1) > 0)
{
    prfmsg(ATTACKF8);
    r = rndm(plattrf1) + .05;
    kill1 += (unsigned long)((double)left1 * r);
}

The separate probability error in this condition is documented in GE-BUG-037. Regardless of how often the condition succeeds, this section correctly calculates a nonzero casualty count when the selected percentage is large enough.

At the classic default PLATTRF1 setting, the calculation selects approximately 5 through 23 percent of the attacking fighters.

Casualties are applied in the wrong block

The routine next checks whether the planet has defending fighters:

if (left2 > 0L)
{
    /* add losses inflicted by defending fighters */
    kill1 += ...;

    /* calculate defending fighters lost */
    kill2 = ...;

    if (kill1 > left1)
        kill1 = left1;

    if (kill2 > left2)
        kill2 = left2;

    prfmsg(ATTACKF2, ...);
    left1 -= kill1;
    left2 -= kill2;
}

Combining troop-inflicted and fighter-inflicted losses in kill1 is reasonable. The defect is that clamping and subtracting kill1 also occur inside if (left2 > 0L).

When left2 is zero, the entire block is skipped. Any troop losses already accumulated in kill1 are abandoned. left1 remains unchanged, and all of the attacking fighters are subsequently returned to the player's ship.

Example

Suppose a player launches 100 fighters against a planet containing:

Planetary troops:     1,000
Planetary fighters:       0

The troop-fire probability succeeds, and the random percentage for this attack is 10 percent:

left1 before fire:  100
calculated kill1:    10
left2:                0

GE prints the warning represented by ATTACKF8:

Your fighters are being fired upon by planetary troops!

Because left2 == 0, the fighter-combat block does not run. left1 -= kill1 is skipped, leaving all 100 fighters alive. The routine later returns those same 100 fighters to the ship.

If the planet had even one defending fighter, the block would run. The ten troop-inflicted losses would then be combined with any loss caused by that single fighter, clamped to the size of the attacking force, reported, and subtracted. The presence of one planetary fighter therefore determines whether the much larger troop force can inflict any casualties at all.

Visible symptoms

The attacker can receive a troop-fire warning followed by a report that every launched fighter returned. There is no report of troop-inflicted losses because the combined fighter combat message is also inside the skipped block.

Repeated fighter attacks against a planet with troops but no fighters can therefore be used without suffering the troop casualties that the game calculates and announces. Troops can still prevent conquest: fighter conquest requires the planet to have fewer than five troops. The bug concerns attrition of the attacking force, not whether hundreds of troops can be defeated by the fighter attack itself.

Evidence from the message file

Classic defines this message immediately after the planetary troop-fire warning:

ATTACKF9 {We have lost %s fighter(s) Sir!
}

No classic C routine invokes ATTACKF9. Its wording and placement strongly suggest that it was intended to report troop-inflicted losses when no fighter-versus-fighter result was available. ge-next removed the unused message during its early message-file cleanup, while retaining the original control flow that made it unreachable.

History in ge-next

ge-next and ge-next32 retained the classic block structure and defect through version 0.3.2.

Changing gernd() or the probability test does not correct Bug 40. It changes how often kill1 is calculated, but a calculated value is still ignored whenever left2 == 0.

Correction in ge-next 0.3.3

ge-next 0.3.3 corrects both the 16-bit and 32-bit implementations without changing the fighter combat formulas. The fighter-versus-fighter calculations remain conditional on left2 > 0, but applying kill1 is no longer conditional on the existence of defending fighters.

The corrected sequence is:

  1. calculating troop losses as now;
  2. adding losses caused by defending fighters only when left2 > 0;
  3. clamping kill1 to left1 after both possible sources have been considered;
  4. using the existing combined combat message when defending fighters participated, or a restored ATTACKF9 message for troop-only losses; and
  5. subtracting kill1 from left1 outside the left2 > 0 block.

When a fighterless planet's troops cause casualties, the player now sees a specific report:

Planetary troops destroyed 10 of your fighters.

The corrected routine then returns 90 of the example attack's 100 fighters instead of returning all 100. When planetary fighters are present, their casualties and the attacker's combined troop-and-fighter casualties continue to use the existing report.

This applies exactly the casualty total the game already calculated. It does not change PLATTRF1, the troop threshold, fighter combat, or conquest rules. Planet attacks are excluded from ge-arena builds, so the correction changes ge-next gameplay only.