Jump to content

Classic bug: Flux subtraction underflow: Difference between revisions

From Galactic Empire wiki
Link first command references to command pages
 
(No difference)

Latest revision as of 22:14, 11 October 2026

Bug ID GE-BUG-008
Affected versions Classic 3.2e and published classic source
Status Verified in source
Fixed in ge-next 0.3
Primary source Flux checks and weapon, shield, cloak, and movement energy paths

Summary

Some classic paths can subtract more neutron-flux energy than the ship has. The ship's energy field is actually a double, so it does not wrap: it becomes genuinely negative. REPORT SYSTEMS then converts that negative value to a 16-bit unsigned integer for display, making depleted flux look like a large value near 65,535.

Symptoms

After firing a missile or charging shields with very little flux remaining, REP SYS can show output such as:

Neutron Flux............ 65136

The apparent 65,136 flux is not usable energy. Internally, the ship may actually have -400. Commands can report insufficient power, engines can shut down, and shields or cloak can fall even though the systems report appears to show an almost full neutron-flux pile. If a flux pod remains aboard, the next periodic ship update may consume it and restore the pile to 65,000. Without a pod, the ship can remain effectively powerless while the misleading high number slowly changes.

Cause

Classic stores WARSHP.energy as a floating-point double, with a normal maximum of 65,000. It has two different energy-handling models:

  • useenergy() checks a requested cost before subtracting it and leaves a 500-unit reserve.
  • Several other paths perform their own incomplete checks and then subtract directly from energy.

The latter paths can cross zero. Although this defect is commonly described as flux “underflow,” it is not unsigned arithmetic underflow in the stored field. It is an ordinary floating-point subtraction whose result is less than zero.

Shield charging

Classic permits SHIELDS UP whenever the pile is greater than SHMINPWR, which is 200. During the periodic ship-state update, shieldstat() uses the same 200-unit threshold before calling shieldchg(). The charging routine, however, subtracts:

shield mark × 100

The test therefore does not cover the actual cost for any shield above Mark-2. For example, a ship with 201 flux and Mark-5 shields passes the minimum-power test, then subtracts 500 and is left with -299. A Mark-19 shield at the same energy would subtract 1,900 and leave -1,699.

At the beginning of a ship-state update, classic's fluxstat() does refill a pile below 5,000 when a flux pod is available. The negative shield result is therefore most readily produced when the ship has no pods. Without a pod, fluxstat() leaves the low pile unchanged and the later shield charge can take it below zero.

Missile firing

A missile's payload energy is divided by the configurable MISENGFC ratio to obtain its flux cost. With the default ratio of 100, a 50,000-energy missile costs 500 flux. Classic attempts to reject a missile that the pile cannot support, but compares the cost against:

current energy + MOVENGMIN

where MOVENGMIN is 3,000. The added margin makes the right-hand side larger and the test easier to pass, which is the opposite of the needed check. A ship with 100 flux can therefore fire the 50,000-energy missile:

500 >= 100 + 3,000    /* false, so firing is allowed */
100 - 500 = -400

The missile is launched and the negative result remains in the ship record.

Why REP SYS shows about 65,000

Before producing any report, classic executes the equivalent of:

energy = (unsigned)warsptr->energy + .5;

Here, the destination and cast are the platform's 16-bit unsigned type. REP SYS then prints that temporary value with %u. On the original compiler, converting a small negative value produces its modulo-65,536 representation:

Actual WARSHP.energy Value shown by REP SYS
-1 65,535
-299 65,237
-400 65,136
-1,699 63,837

Only the report's temporary value is converted. The ship still stores the negative double, which is why gameplay checks continue to recognize that the ship has no power. This conversion is also nonportable C behavior for an out-of-range floating-point value; a modern port must not depend on reproducing it.

Gameplay effects

The main ship-state routine checks flux before shields, cloak, and recharge. Once energy is negative:

  • If a flux pod is aboard, the next state update loads one automatically, consumes the pod, and sets energy to 65,000.
  • If no pod is aboard, raised shields are dropped because the actual energy is below 200.
  • An active cloak is dropped because the actual energy is below its configured per-tick cost.
  • Rotation, acceleration, and phaser recharge fail their energy checks.
  • Movement logic sees energy below the 3,000 minimum and orders the engines to shut down.
  • Hyper-phasers cannot fire because their 6,000-unit minimum is not met.

Natural recharge does not reinterpret the false 65,000 shown by the report. It adds only one unit to the actual double during each ship-state update. A ship at -400 must first spend 400 updates merely reaching zero, and then thousands more reaching useful operating levels. At the normal update cadence, recovering without a pod is therefore impractically slow. The player can manually use a remaining flux pod with FLUX, but a ship with no pods is effectively stranded.

Correction

ge-next changes fluxstat() from a periodic threshold check into a request for a specific amount of energy. The caller supplies the complete cost. If the current pile is insufficient, fluxstat() loads a pod when one is available; otherwise it reports failure and the caller does not perform the action or subtraction.

Missile firing now checks the calculated flux cost at the point where a missile slot has actually been found. Shield charging checks shield mark × 100, not merely the old 200-unit threshold. Movement and warp entry account for all costs that can occur during the same transition. Phaser recharge, cloaking, weapons, and other recurring users follow the same check-before-subtract rule. As a result, normal gameplay can no longer drive the energy double below zero.

Compatibility notes

The visible value near 65,535 is a report-conversion symptom, not additional energy granted by design and not the value used by the ship simulation. A port should never reproduce it merely to match the old display. It should prevent the negative value and display the actual validated energy instead.