Classic bug: Planetary ion cannon off-by-one
| Bug ID | GE-BUG-004 |
|---|---|
| Affected versions | Classic 3.2e and published classic source |
| Status | Verified in source |
| Fixed in | ge-next 0.2.6 |
| Primary source | GEFUNCS.C: checkdist() |
Summary
The range check that clears a ship's hostility toward a planet looks at the planet after the one that was attacked.
Symptoms
Planetary ion cannons may stop firing as soon as the attacker leaves orbit, or continue firing throughout the sector instead of stopping at the intended 1,000-distance limit. The exact result depends on the next planet-table slot.
Intended behavior
A ship can establish orbit when it is within 250 displayed distance units of a planet. Attacking troops or fighters records that planet in the ship's hostile field. On subsequent ship-state updates, fireion() loads the attacked planet and lets its ion cannons fire at the hostile ship.
Leaving orbit is not supposed to end the defense immediately. The ship should remain hostile, and the attacked planet should continue firing, until the ship moves more than 1,000 distance units from that planet. This gives the ion cannons a defensive area extending well beyond the 250-unit orbit radius.
Cause
Planet location values are encoded as displayed planet number plus 10, while the cached planet array is zero-based. Classic converts the hostile location with:
i = ptr->hostile - 10;
For displayed planet N this produces index N, not N − 1. The correct conversion is hostile - 11.
If the following slot contains a planet, its unrelated distance controls the hostile flag. If it is empty, the flag may not be cleared at all.
The firing code does not have the same indexing error. Given an encoded hostile value of N + 10, fireion() subtracts 10 and passes the resulting displayed planet number N to the planet-record loader. The attacked planet therefore fires correctly. It is only the routine deciding when that fire should stop that measures the wrong planet.
Result when the following slot contains a planet
Suppose a player attacks displayed planet 1:
where = hostile = 11 attacked planet = displayed planet 1 = planets[0] classic checkdist index = 11 - 10 = planets[1]
fireion() continues to load displayed planet 1, but checkdist() measures the ship's distance from displayed planet 2. Planets in a sector are normally much more than 1,000 units apart. On the first movement update after the attacker engages its engines and leaves orbit, planet 2 is therefore usually already more than 1,000 units away. checkdist() immediately clears hostile, and planet 1's ion cannons stop firing even though the ship may still be only a few hundred units from planet 1.
From the player's perspective, the ion cannons appear to work only while the ship remains in orbit. This is not an explicit orbit restriction: it is the distance to the unrelated next planet clearing hostility as soon as movement causes checkdist() to run.
If the two planets happen to be within 1,000 units of one another, the result is less immediate but still wrong. Planet 1 keeps firing only while the ship is within 1,000 units of planet 2. The defensive boundary is centered on the wrong planet.
Result when the following slot is empty
Now suppose the player attacks the last populated planet in a sector and the next cached slot has type == 0. Classic wraps the distance test in this condition:
if (ptab[usrn].planets[i].type != 0) {
/* measure distance and possibly clear hostile */
}
Because the wrong next slot is empty, the entire distance calculation is skipped. Nothing clears hostile, so fireion() keeps loading the actual attacked planet and firing its cannons regardless of the ship's distance. Crossing a sector boundary clears the hostility field in the movement routine, but merely crossing the intended 1,000-unit limit does not. The practical result is ion-cannon fire throughout the entire sector.
Attacking displayed planet 9 creates an additional memory-safety edge case: the faulty calculation selects planets[9], one entry beyond the nine-element cache. Whether hostility clears then depends on unrelated memory following the table.
Correction
ge-next uses the correct array index, so ion fire continues only until the ship is more than 1,000 distance units from the planet it attacked. The fix was committed after ge-next 0.2.5 and first released in ge-next 0.2.6.
Verification
Verified by source analysis and gameplay testing against different local planet-table layouts.
Home · Play · Reference · Versions & history · Sysop · Development · Classic bugs