Classic bug: MAXPLSE is off by one
| Bug ID | GE-BUG-045 |
|---|---|
| Affected versions | Classic 3.2e and published classic source |
| Status | Verified in source; corrected in ge-next |
| Fixed in | ge-next 0.1 |
| Primary source | MAXPLSE configuration and xgetsector() in GEPLANET.C
|
Summary
Classic's MAXPLSE option claims to set the maximum number of planets in a sector, but random sector generation can never create the configured number. The actual maximum is always one less than the setting.
At the classic default of MAXPLSE=5, ordinary sectors can contain at most four planetary objects. Setting the option to its minimum accepted value of one is more severe: it makes every randomly generated ordinary sector empty.
Configuration
Classic defines the option as:
MAXPLSE {Maximum planets in a sector? 5} N 1 9
The configuration system accepts values from 1 through 9, matching the nine entries available in a sector's fixed ptab array. The wording and range both indicate that a setting of N should permit as many as N objects.
Despite the option's name, the generated count includes both planets and wormholes. The routine first chooses the total number of planetary objects and then independently decides whether each slot will be a planet or wormhole. A sector with four objects might therefore contain three planets and one wormhole rather than four planets.
Faulty count calculation
Sectors are generated lazily when GE first needs a sector that does not yet have a database record. For an ordinary sector, classic calculates its object count as follows:
if (gernd() % plodds == 0)
p = gernd() % maxplanets;
else
p = 0;
PLODDS controls whether the sector receives an opportunity to contain objects. If that first test succeeds, the second random value chooses the count stored in p.
For any positive integer N, a remainder produced by value % N can range only from zero through N - 1. It can never equal N. Therefore:
| MAXPLSE setting | Counts classic can generate | Actual maximum |
|---|---|---|
| 1 | 0 | 0 |
| 2 | 0–1 | 1 |
| 4 | 0–3 | 3 |
| 5 (default) | 0–4 | 4 |
| 9 | 0–8 | 8 |
The subsequent loops use i < p, correctly creating exactly p entries. The error is not in those loops; it occurs when the upper bound is excluded from the random count.
The MAXPLSE=1 failure
The minimum configuration value makes the defect especially clear:
p = gernd() % 1;
Every integer modulo one is zero. Even when the PLODDS test succeeds, p is still zero. Outside the predefined neutral sector, GE consequently creates no random planets and no random wormholes at all.
An administrator might reasonably choose one to request sparse sectors with a maximum of one object. Instead, the setting disables random planetary-object creation.
Default distribution
With classic's defaults of PLODDS=3 and MAXPLSE=5, the opportunity test succeeds approximately one-third of the time. On success, the second expression selects 0, 1, 2, 3, or 4.
Ignoring the small modulo bias of the classic random generator, each nonzero count therefore occurs in approximately 1/15 of newly generated ordinary sectors:
| Object count | Approximate probability |
|---|---|
| 0 | 11/15, or 73.3% |
| 1 | 1/15, or 6.7% |
| 2 | 1/15, or 6.7% |
| 3 | 1/15, or 6.7% |
| 4 | 1/15, or 6.7% |
| 5 | 0 |
Zero is possible through two paths: the initial opportunity test can fail, or the test can succeed and the count roll can select zero.
Persistent effect on a galaxy
Once generated, the sector record and its planetary records are stored in the database. Revisiting or rescanning that sector loads the existing records rather than rolling a new count.
Changing MAXPLSE therefore affects only sectors that have not yet been generated. It does not add objects to existing sectors. A long-running classic galaxy can permanently contain the lower-density sectors created under the off-by-one interpretation unless its planet database is rebuilt or otherwise migrated.
Sector 0 0 is not affected. Its predefined planets and wormholes are built from the separate neutral-sector table and its count is set from s00plnum, not the random value p.
Correction in ge-next 0.1
ge-next changed the count calculation to include the configured upper bound:
p = gernd() % (maxplanets + 1);
A setting of N now produces values from zero through N. MAXPLSE=1 can produce zero or one, and MAXPLSE=9 can use all nine available sector slots.
Simply making that correction while retaining the default value of five would have changed the established default distribution from 0–4 to 0–5. ge-next therefore lowered the default to four at the same time:
MAXPLSE {Maximum planets in a sector? 4} N 1 9
The corrected default calculation is gernd() % 5, which retains the classic default's effective 0–4 count and approximate density. The difference is that the configuration now tells the truth: four is both the stated and attainable maximum.
Custom settings also behave according to their documented meaning, although, as in classic, they apply only when previously unknown sectors are generated.
Home · Play · Reference · Versions & history · Sysop · Development · Classic bugs