Classic bug: Stale NPC database records

Revision as of 00:44, 17 October 2026 by CodexBot (talk | contribs) (Link nightly cleanup to the midnight routine overview)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Bug ID GE-BUG-013
Affected versions Classic 3.2e and published classic source
Status Verified in source
Fixed in ge-next 0.3
Primary source GECYBS.C: cyb_init(); saved Cybertron ship/user records

Summary

Classic assigns each persistent Cybertron a synthetic user ID derived from its live ship slot, such as @Cybrg-23. When that slot is initialized, classic loads the saved ship associated with the ID but does not verify that the saved ship's class is the class currently expected in that slot.

A configuration change can therefore assign a slot to a different Cybertron class while classic continues loading the old class from the database. Invalid, duplicate, and no-longer-configured @Cybrg-N records are not reliably removed either. The persistent database can override the intended NPC layout every time the game starts.

This is separate from Classic bug: NPC class slot crowding, which causes the live NPC population to drift while ships are destroyed and replaced. The two defects compound each other: runtime crowding can save an unintended Cybertron in a slot, and weak persistent-record validation can restore that assignment after a restart.

Cybertron persistence in classic

Droids are created as transient automated ships, but Cybertrons have saved user and ship records. For live slot N, cyb_init() builds the ID @Cybrg-N. It creates the synthetic user record if necessary, then searches the ship database by that user ID.

The function is called with a class argument representing the Cybertron class the allocator wants in the slot. That argument is used when there is no saved ship and a new one must be created. If a saved ship is found, however, classic loads it directly and largely ignores the requested class.

Classic's only class-related sanity check is effectively:

if (shipclass[saved_ship.shpclass].max_type != CLASSTYPE_CYBORG)
    log an error

That check is insufficient:

  • It accepts any valid Cybertron class, not the specific class requested for this slot.
  • It only logs an error for a non-Cybertron class; it does not reject or delete the record.
  • It does not first prove that shpclass is within the current class table, so a stale class number can index outside the configured table.
  • It does not verify that the numeric suffix in @Cybrg-N agrees with the live slot being initialized.
  • It does not require a valid positive ship number.
  • If several ships exist under the same synthetic user ID, it loads one matching record and does not remove the duplicates.

Classic then marks the loaded ship active and resets selected runtime fields, but it does not reconstruct the ship from the currently configured class. Other saved class-dependent state can therefore survive from an obsolete layout.

How configuration changes expose the bug

Automated class totals define boundaries within the fixed NPC slot pool. If an administrator changes those totals, the class expected at a particular slot can move.

For example, assume @Cybrg-23 was previously assigned to a Cybertron Base Star. The sysop changes the class table so slot 23 should now contain a Cybertron Battle Cruiser. On the next initialization:

  1. The allocator requests a Battle Cruiser for slot 23.
  2. cyb_init() constructs @Cybrg-23.
  3. Classic finds the old Base Star ship record.
  4. The sanity check sees that Base Star is still a Cybertron class and accepts it.
  5. Slot 23 returns as a Base Star, despite the new configuration.

The configured Battle Cruiser remains absent and the obsolete Base survives. Changing more than one class total can shift every later class boundary, so a single configuration edit can leave many slots populated with their former classes.

If the total Cybertron population is reduced, some old @Cybrg-N IDs no longer correspond to any configured Cybertron slot. Classic has no general cleanup that recognizes and removes those ship and user records. They remain in the databases even when they are no longer loaded into the active NPC pool.

Renumbering, removing, or changing the type of a class is more dangerous. A saved numeric shpclass may now identify a Droid, user ship, unused entry, or no valid entry at all. Classic can retain malformed state and can index the class table with a stale out-of-range value while performing its sanity check.

Player and administrator consequences

The defect can produce:

  • Cybertron classes appearing in different quantities than the current MAKE values request;
  • an old class repeatedly returning after every restart;
  • extra long-lived Cybertron Bases occupying capacity intended for mobile ships;
  • expected Cybertron classes never appearing because their slots load old classes instead;
  • class-table changes appearing ineffective unless the game databases are reset manually;
  • duplicate synthetic ships accumulating under one @Cybrg-N identity; and
  • obsolete synthetic ship and user records remaining after NPC totals are reduced.

This can resemble the live slot-crowding defect, but the diagnostic pattern is different. Crowding develops as NPCs are destroyed and recreated. Persistent mismatch appears immediately when saved Cybertrons are loaded and can restore the same wrong class in the same slot on every restart.

Why ge-next 0.2 did not completely fix this

ge-next 0.2 replaced classic's shared, partly random allocator with a deterministic slot map. Given a live NPC slot, ge-next can derive exactly which configured class owns it. This fixed runtime class crowding and made a correct persistent validation rule possible.

The slot map alone did not make old database records valid. A saved ship could still contain a different Cybertron class and override the class derived from the slot. Persistent loading therefore required its own correction.

Initial ge-next validation

During 0.3 development, ge-next first hardened Cybertron loading against obviously invalid class values. Before indexing the class table, it checked that the saved class number was in range and that the entry was a Cybertron class. A record that failed those checks was deleted and replaced with a new Cybertron.

This closed the out-of-range access and prevented user, Droid, or inactive classes from being loaded as Cybertrons. It was still incomplete for this specific defect: a saved Base Star remained a valid Cybertron record even if the deterministic slot map now required a Battle Cruiser.

Complete ge-next correction

The completed ge-next 0.3 loader validates a saved Cybertron against both the database identity and the deterministic slot assignment. A record is accepted only when all of these conditions hold:

  • the record's user ID exactly matches the expected @Cybrg-N ID;
  • the ID suffix is numeric, in range, and resolves back to the current slot;
  • the ship number is positive;
  • the class number is inside the loaded class table;
  • the class is a Cybertron class; and
  • the saved class exactly equals the class currently assigned to that slot.

The loader examines all ship records belonging to the synthetic user. It keeps one valid ship, deletes invalid records, and removes additional valid ships with different ship numbers as duplicates. If no acceptable record remains, it creates a fresh ship using the slot's expected class.

The initialization path also verifies that the class supplied to cyb_init() agrees with the class independently derived from the slot. This prevents a caller error from bypassing the same invariant.

Cleanup of obsolete identities

Validation during initialization handles records for Cybertron slots that still exist. It cannot naturally encounter an old identity whose slot is no longer configured as a Cybertron slot.

ge-next therefore added midnight cleanup for synthetic automaton records. It parses every saved user ID beginning with @, verifies that the suffix identifies a currently configured Cybertron slot, and removes ship and user records that cannot belong to the current layout.

Together, loading validation and cleanup allow an administrator to change Cybertron class totals without resetting the entire game:

  • records in unchanged slots and classes are retained;
  • records whose slots now expect another Cybertron class are deleted and recreated correctly;
  • duplicates and malformed records are removed; and
  • identities beyond the newly configured Cybertron ranges are pruned during cleanup.

Relationship to Bug 6

Classic bug: NPC class slot crowding concerns selection of a class for a vacant live slot. This bug concerns whether a persistent ship loaded into that slot agrees with the selected class.

Either defect can exist without the other. Fixing only the allocator leaves old records able to override it; fixing only record loading leaves live random replacement able to distort the population again. ge-next uses the same deterministic slot map for both creation and validation so the two paths agree.

Porting guidance

Persistent NPC identity should encode or reference a stable logical slot, but the saved class must still be validated against the current configuration. Loading code should never assume that a record is valid merely because its general type is "NPC" or "Cybertron."

When configuration can change, a port should:

  • derive the expected class from the current slot map;
  • validate identifiers, class bounds, type, and class equality before use;
  • define which one of several duplicate records is canonical and delete the others;
  • recreate an NPC when no valid saved record remains; and
  • prune persistent identities whose slots no longer exist.