<?xml version="1.0"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
	<id>https://manicpop.org/gewiki/index.php?action=history&amp;feed=atom&amp;title=Classic_bug%3A_NPC_class_slot_crowding</id>
	<title>Classic bug: NPC class slot crowding - Revision history</title>
	<link rel="self" type="application/atom+xml" href="https://manicpop.org/gewiki/index.php?action=history&amp;feed=atom&amp;title=Classic_bug%3A_NPC_class_slot_crowding"/>
	<link rel="alternate" type="text/html" href="https://manicpop.org/gewiki/index.php?title=Classic_bug:_NPC_class_slot_crowding&amp;action=history"/>
	<updated>2026-10-20T16:14:13Z</updated>
	<subtitle>Revision history for this page on the wiki</subtitle>
	<generator>MediaWiki 1.46.2</generator>
	<entry>
		<id>https://manicpop.org/gewiki/index.php?title=Classic_bug:_NPC_class_slot_crowding&amp;diff=1126&amp;oldid=prev</id>
		<title>CodexBot: Link glossary terms and clarify ship slot terminology</title>
		<link rel="alternate" type="text/html" href="https://manicpop.org/gewiki/index.php?title=Classic_bug:_NPC_class_slot_crowding&amp;diff=1126&amp;oldid=prev"/>
		<updated>2026-10-09T06:12:09Z</updated>

		<summary type="html">&lt;p&gt;Link glossary terms and clarify ship slot terminology&lt;/p&gt;
&lt;p&gt;&lt;b&gt;New page&lt;/b&gt;&lt;/p&gt;&lt;div&gt;{{Classic bug&lt;br /&gt;
|id=GE-BUG-006&lt;br /&gt;
|affected=Classic 3.2e and published classic source&lt;br /&gt;
|status=Verified in source&lt;br /&gt;
|fixed=ge-next 0.2&lt;br /&gt;
|source=GEMAIN.C: autortia()&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
== Summary ==&lt;br /&gt;
All automated classes compete for one pool of NPC slots. Classic neither reserves a range of slots for each class nor requires a newly created NPC to match the class previously associated with its vacant slot. Its selection algorithm also occasionally chooses a completely random automated class without enforcing that class&amp;#039;s configured limit. Together, these behaviors allow some classes to consume the capacity intended for others.&lt;br /&gt;
&lt;br /&gt;
== Symptoms ==&lt;br /&gt;
Some configured Cybertron or Droid classes become overrepresented while others rarely appear or disappear entirely. The distribution may drift as NPCs are destroyed and recreated.&lt;br /&gt;
&lt;br /&gt;
The important long-running result in the default configuration is that Droid classes can eventually stop appearing. A newly started game may contain the expected Lydorian Garbage Scows, Murdonian Transports, and Vakory Survey Drones. As automated ships are destroyed and their shared slots are reused, those Droid populations can shrink. Cybertron classes consume the vacated slots, and a mature game may be left with Cybertrons but few or no Droids.&lt;br /&gt;
&lt;br /&gt;
Cybertron Base Stars are especially prone to becoming overrepresented. A Base does not pursue players, cannot wander into an encounter, and is not easy to discover and destroy. If the random-class path creates an extra Base, that Base is therefore likely to survive while mobile NPCs continue being destroyed and replaced. More randomly selected Bases can accumulate over time, leaving the game with far more than the single Base requested by the default configuration and correspondingly fewer Droid classes.&lt;br /&gt;
&lt;br /&gt;
This is easy to mistake for intentional rarity or ordinary random chance. The Droid classes are still enabled in the configuration and their requested counts have not changed; the allocator simply has no slots left in which to create them.&lt;br /&gt;
&lt;br /&gt;
== The default 30-slot population ==&lt;br /&gt;
Classic allocates &amp;lt;code&amp;gt;NUMSHIPS&amp;lt;/code&amp;gt; interchangeable non-user [[Ship slot|ship slots]]. The default is 30. The default class table requests these automated ships:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Type !! Class !! Requested&lt;br /&gt;
|-&lt;br /&gt;
| Cybertron || Cybertron Scout || 10&lt;br /&gt;
|-&lt;br /&gt;
| Cybertron || Cybertron Battle Cruiser || 5&lt;br /&gt;
|-&lt;br /&gt;
| Cybertron || Cybertron Base Star || 1&lt;br /&gt;
|-&lt;br /&gt;
| Cybertron || Sarten Attack Drone || 6&lt;br /&gt;
|-&lt;br /&gt;
| Cybertron || Sarten Obliterator || 2&lt;br /&gt;
|-&lt;br /&gt;
| Droid || Lydorian Garbage Scow || 2&lt;br /&gt;
|-&lt;br /&gt;
| Droid || Murdonian Transport || 2&lt;br /&gt;
|-&lt;br /&gt;
| Droid || Vakory Survey Drone || 2&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The requested totals add up to exactly 30: 24 Cybertrons followed by six Droids. On an empty game, the ordinary allocation path scans classes in table order and can initially produce that intended mixture.&lt;br /&gt;
&lt;br /&gt;
== Cause ==&lt;br /&gt;
There are two related problems in &amp;lt;code&amp;gt;autortia()&amp;lt;/code&amp;gt;: the random class override ignores configured totals, and the ordinary replacement path treats every vacant NPC slot as interchangeable.&lt;br /&gt;
&lt;br /&gt;
=== The random class override ===&lt;br /&gt;
&amp;lt;code&amp;gt;autortia()&amp;lt;/code&amp;gt; normally scans automated classes in table order and selects the first class below its requested total. The source then deliberately overrides that choice on a 1-in-100 result:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
if (gernd()%100 == 0 || class == -1) {&lt;br /&gt;
    /* pick a random automated class */&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The random path verifies only that the selected class is a Cybertron or Droid. It does not check the selected class&amp;#039;s current population against its configured total. It can therefore create an eleventh Cybertron Scout even though the class is configured for ten, or another Cybertron Base Star even though only one was requested. Because the NPC pool has a fixed size, every such excess ship consumes capacity that some other class can no longer use.&lt;br /&gt;
&lt;br /&gt;
=== Vacant slots do not retain a class ===&lt;br /&gt;
The ordinary path also does not ask which class formerly occupied the empty slot. It counts every active member of every automated class, scans the class table from the beginning, and fills the vacancy with the first class below its configured &amp;lt;code&amp;gt;MAKE&amp;lt;/code&amp;gt; value. All default Cybertron classes appear in the table before all default Droid classes, so every Cybertron deficit has priority over every Droid deficit.&lt;br /&gt;
&lt;br /&gt;
This means a vacancy created by destroying one class can be filled by a different class. Under a perfectly balanced population, the only deficit is usually the class just destroyed, so the behavior initially appears correct. Once a random allocation has created an excess and a deficit, however, the next vacancy can be assigned to some earlier deficit rather than to the class that actually lost the ship. The identity of the vacancy migrates through the shared pool, while the first-under-limit search gives the classes near the start of the table repeated priority over the Droids at the end.&lt;br /&gt;
&lt;br /&gt;
The two problems therefore reinforce each other. Random selection creates an illegal class excess; filling arbitrary vacancies from the first deficit in table order moves the resulting shortage into later classes instead of reestablishing the intended slot-by-slot distribution.&lt;br /&gt;
&lt;br /&gt;
The configured &amp;lt;code&amp;gt;MAXDROID&amp;lt;/code&amp;gt; value does not repair this. Classic reads the option, but the published allocation code never uses the resulting &amp;lt;code&amp;gt;maxdroids&amp;lt;/code&amp;gt; variable.&lt;br /&gt;
&lt;br /&gt;
== How a Droid disappears ==&lt;br /&gt;
Consider a game that currently has the intended distribution, then follow one Lydorian Garbage Scow slot:&lt;br /&gt;
&lt;br /&gt;
# A player destroys one of the two Scows. One shared NPC slot becomes available, and the Lydorian class is now one below its requested total.&lt;br /&gt;
# Normally, the allocator would scan past the full Cybertron classes and recreate the missing Scow.&lt;br /&gt;
# On the 1-in-100 random override, it instead chooses Cybertron Scout, which is already full. The game now has 11 Scouts and only one Scow.&lt;br /&gt;
# Later, a Cybertron Battle Cruiser is destroyed. The normal allocator sees that earlier Cybertron class below its target and recreates it before ever reaching the missing Lydorian class. The Scow remains missing.&lt;br /&gt;
# Repeated random replacements can create more excess ships in earlier classes. The corresponding deficits are pushed toward classes later in the table, where all three Droid classes reside.&lt;br /&gt;
&lt;br /&gt;
Cybertron Bases make the same process more durable. If the random override uses a vacated Droid slot for an extra Base, that Base is unlikely to be found or destroyed. Mobile Scouts, combat ships, and Droids continue cycling through the remaining slots while the excess Base keeps its share of the fixed pool. Repetition can leave many persistent Bases and no capacity for one or more Droid classes.&lt;br /&gt;
&lt;br /&gt;
The drift is not strictly irreversible. If the excess Scout itself is destroyed, its class falls back to its requested count without creating a new Scout deficit, and a normal allocation can finally reach a missing Droid class. A random allocation can also happen to choose a Droid. Nevertheless, continued destruction and recreation repeatedly reintroduce the imbalance, and the ordering systematically makes the later Droid classes absorb the missing slots. Over a long-running game, they can all be crowded out.&lt;br /&gt;
&lt;br /&gt;
Rebuilding with a clean database can appear to fix the problem temporarily: filling a clean pool through the normal path restores the configured mixture, but the same random over-allocation begins drifting it again. Merely restarting an existing game does not guarantee even that temporary correction because saved Cybertron records introduce the separate persistence defect described in [[Classic bug: Stale NPC database records]].&lt;br /&gt;
&lt;br /&gt;
== Correction ==&lt;br /&gt;
ge-next replaces population counting and random class selection with a deterministic slot map. It starts with the NPC slot&amp;#039;s offset from the first non-user slot, then walks the automated classes in table order and subtracts each class&amp;#039;s configured total. The class whose range contains the remaining offset owns that slot.&lt;br /&gt;
&lt;br /&gt;
With the default table, offsets 0-23 in the NPC pool belong to the five Cybertron classes and offsets 24-29 belong to the three Droid classes. If the slot assigned to a Murdonian Transport becomes vacant, only a Murdonian Transport can be recreated there. No random Cybertron can permanently consume that capacity. Likewise, only the one offset assigned to the Cybertron Base Star can contain a Base under the default configuration.&lt;br /&gt;
&lt;br /&gt;
This removes both live-allocation failure modes. A random choice can no longer exceed a class&amp;#039;s total, because class selection is not random, and an empty slot cannot be filled by a different class merely because that class happens to be the first one below its global target. The slot mapping correction was first released in ge-next 0.2.&lt;br /&gt;
&lt;br /&gt;
== Related persistence defect ==&lt;br /&gt;
Deterministic slot ownership defines which class belongs in each slot, but it does not by itself guarantee that an old saved Cybertron record agrees with that assignment. Classic&amp;#039;s separate record-loading defect and ge-next&amp;#039;s later validation and pruning fixes are documented in [[Classic bug: Stale NPC database records]].&lt;br /&gt;
&lt;br /&gt;
== Compatibility notes ==&lt;br /&gt;
This correction preserves configured totals but may alter the observed mix on a long-running classic installation that had drifted.&lt;br /&gt;
&lt;br /&gt;
== Verification ==&lt;br /&gt;
Verified by source analysis of the live class-selection and replacement paths.&lt;br /&gt;
&lt;br /&gt;
[[Category:Classic bugs: NPCs]]&lt;br /&gt;
[[Category:Classic bugs: Configuration]]&lt;br /&gt;
&lt;br /&gt;
{{GE navigation}}&lt;/div&gt;</summary>
		<author><name>CodexBot</name></author>
	</entry>
</feed>