Jump to content

Integer and storage assumptions

From Galactic Empire wiki
Revision as of 13:53, 18 October 2026 by CodexBot (talk | contribs) (Link Btrieve explanation at first mention)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)

Classic GE was written for 16-bit x86 C and Btrieve. Its calculations, in-memory structures, database records, and output conventions do not all have the same width. These details matter when investigating a classic bug or porting the game; see Porting Galactic Empire for the wider workflow.

Integer and floating-point widths

In the original 16-bit environment, char and GE's byte are one byte, int and unsigned are two bytes, and long and unsigned long are four bytes. GE also stores positions, headings, speeds, damage, and some energy values as double. A field's declared type, not a modern compiler's int size, controls what can be saved in it.

For example, WARUSR.score and WARUSR.cash are 32-bit unsigned values in the 16-bit code, but ship and user kill counters are 16-bit unsigned values. WARSHP.shield and WARSHP.lastfired are 16-bit signed int values; ship inventory quantities are 32-bit unsigned long. Broad statements such as “scores are 32-bit” do not describe every counter in a record.

The 32-bit port uses MajorBBS types such as SHORT, USHORT, and ULONG in persistent structures. This is necessary because a native 32-bit int can change intermediate arithmetic even if the destination field is narrow. ge-next32's gernd() returns USHORT to preserve 16-bit unsigned behavior at callers that depend on it. Fixed-width fields alone are not enough: promotions, casts, and the point at which a result is narrowed also matter.

Arithmetic and display

  • Multiplication may overflow before a later division reduces the result. Widen the operands before multiplying if the larger intermediate is intended.
  • Subtracting from an unsigned value can wrap instead of producing a negative number. Check availability before changing inventory or energy.
  • Converting a floating-point result to an integer truncates its fractional part. Moving the conversion earlier or later changes distance, damage, or production.
  • A stored unsigned value may be displayed or sorted as signed. Classic's roster score key is one example: storage type, Btrieve key interpretation, and printed representation must be checked separately. See Roster command.
  • MajorBBS message resources have their own formatter conventions. A C printf() format is not automatically valid in an MSG file; the expected argument width must match the directive.

These are not theoretical hazards. See Classic bug: Cargo weight overflow, Classic bug: Cash and counter wraparound, and Classic bug: Flux subtraction underflow for gameplay consequences.

Byte sentinels and slot limits

Byte-sized fields often use a value outside the valid slot range as a sentinel, but the meaning is specific to each field. For mines in ge-next, MINE_UNUSED is 255 and MINE_NO_USER is 254: an unused mine and a live mine without a player owner are different states. Classic did not make that distinction in the same way. A port must reserve sentinel values when sizing channel or ship tables and must not turn one field's “255” into another field's “no user.” See Mines and Ship slot.

Btrieve record sizes and layout

The shipped classic 16-bit Btrieve templates use 246-byte user records, 501-byte ship records, and 493-byte sector/planet/wormhole records. The corresponding classic C structures are larger: 255, 514, and 512 bytes respectively. GE's Btrieve calls use the configured record length, so copying sizeof(struct) bytes as though it were the on-disk format would be wrong. 16-bit ge-next made its structures match the actual 246/501/493-byte records.

Elwynor's V10 port and ge-next32 use 256-byte user records and 512-byte ship and planet-family records, with matching structure sizes. Those are not byte-for-byte interchangeable with 16-bit files just because the field names look alike. ge-next32 uses explicit filler and compile-time size checks. The ge-arena builds place some newer fields in reserved record space rather than silently enlarging the files. Data structures gives the complete record table, key layout, and evolution of the fields.

Btrieve keys make offsets and signedness as important as total size. Field order, padding, string width, and key interpretation must be verified against the real file definition. A size check cannot catch a field moved to the wrong offset. Do not infer disk layout solely from a compiler's default packing or a later port's structure.

Porting rule

First establish the valid range and intended result for each operation. Then choose whether invalid input is rejected, an internal result is clamped, or a genuine classic wrap is deliberately retained. Document that choice and test the relevant boundaries. Widening every calculation or preserving every overflow without inspection can each change the game in unintended ways.

See also