<?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_Skipped_planetary_updates</id>
	<title>Classic bug: Skipped planetary updates - 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_Skipped_planetary_updates"/>
	<link rel="alternate" type="text/html" href="https://manicpop.org/gewiki/index.php?title=Classic_bug:_Skipped_planetary_updates&amp;action=history"/>
	<updated>2026-10-20T16:49:41Z</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:_Skipped_planetary_updates&amp;diff=2161&amp;oldid=prev</id>
		<title>CodexBot: Link Btrieve explanation at first mention</title>
		<link rel="alternate" type="text/html" href="https://manicpop.org/gewiki/index.php?title=Classic_bug:_Skipped_planetary_updates&amp;diff=2161&amp;oldid=prev"/>
		<updated>2026-10-18T17:54:05Z</updated>

		<summary type="html">&lt;p&gt;Link Btrieve explanation at first mention&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-011&lt;br /&gt;
|affected=Classic 3.2e and published classic source&lt;br /&gt;
|status=Verified through source analysis and long-running games&lt;br /&gt;
|fixed=ge-next 0.2; startup calibration refined in 0.2.2&lt;br /&gt;
|source=Classic GEMAIN.C: plartia(); ge-next GEMAIN.C: plartia()&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
== Summary ==&lt;br /&gt;
Classic Galactic Empire does not record when an individual planet was last updated. Instead, one process-local cursor continuously sweeps through the entire planet database and updates planets as it encounters them. The routine tries to adjust its own callback interval so that one sweep takes the number of minutes configured by &amp;lt;code&amp;gt;PLANTOCK&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
This makes the configured period only a target for a complete database sweep, not a guarantee that every planet receives a fixed number of updates in a calendar day. With the recommended six-hour setting, some planets can receive three updates in a day while others receive five. Interrupted sweeps, server downtime, and restarts can make the difference more pronounced. As the database grows, the scheduler also spends increasing amounts of time walking [[Sector|sector]] and wormhole records that cannot receive economic updates.&lt;br /&gt;
&lt;br /&gt;
== Classic scheduling model ==&lt;br /&gt;
Classic describes &amp;lt;code&amp;gt;PLANTOCK&amp;lt;/code&amp;gt; as the amount of time in which to update all planets. Its default of 360 minutes is intended to produce four updates per day. The implementation does not divide the day into four named update periods, however. It continuously repeats this process:&lt;br /&gt;
&lt;br /&gt;
# Start at the beginning of the shared sector/planet/wormhole database.&lt;br /&gt;
# Run &amp;lt;code&amp;gt;plartia()&amp;lt;/code&amp;gt; after the current &amp;lt;code&amp;gt;plantime&amp;lt;/code&amp;gt; delay.&lt;br /&gt;
# Examine records until one planet is found or 20 records have been examined.&lt;br /&gt;
# If a planet was found, update at most that one planet.&lt;br /&gt;
# Save the [[Btrieve]] absolute position in the process-local &amp;lt;code&amp;gt;fpos&amp;lt;/code&amp;gt; variable and schedule another callback.&lt;br /&gt;
# At end of file, measure how long the sweep took, adjust &amp;lt;code&amp;gt;plantime&amp;lt;/code&amp;gt;, reset the cursor, and immediately begin another sweep.&lt;br /&gt;
&lt;br /&gt;
The updater therefore normally handles no more than one planet per callback. It may handle no planet at all: &amp;lt;code&amp;gt;MAXTIC&amp;lt;/code&amp;gt; limits one invocation to 20 database records, so an invocation that encounters 20 sector or wormhole records merely advances the cursor.&lt;br /&gt;
&lt;br /&gt;
Only an owned planet with population is passed to the normal economic &amp;lt;code&amp;gt;multiply()&amp;lt;/code&amp;gt; routine. The neutral-zone planets receive their own special replenishment treatment. Empty and unowned planets do not receive an economic update, but the scheduler still has to find and inspect their records.&lt;br /&gt;
&lt;br /&gt;
=== Adaptive timing ===&lt;br /&gt;
Classic starts with a delay of approximately 55 seconds between callbacks. At the end of a sweep it calculates approximately:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
measured sweep time = callback count * current callback delay&lt;br /&gt;
new callback delay  = current delay * (PLANTOCK / measured sweep time)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For example, if a sweep takes twice the configured target, the next callback delay is roughly halved. The result is never allowed below three seconds. This is feedback control based on the preceding sweep, not a schedule tied to the clock or to individual planets.&lt;br /&gt;
&lt;br /&gt;
== Why four updates per day is not guaranteed ==&lt;br /&gt;
A six-hour &amp;lt;code&amp;gt;PLANTOCK&amp;lt;/code&amp;gt; only asks the routine to make a complete pass approximately every six hours. There is no concept of &amp;quot;update 1,&amp;quot; &amp;quot;update 2,&amp;quot; &amp;quot;update 3,&amp;quot; and &amp;quot;update 4&amp;quot; for a particular day, and there is no check that stops updates after a planet has received four.&lt;br /&gt;
&lt;br /&gt;
Even when every sweep finishes normally, its boundaries are unrelated to midnight. Small timing differences accumulate from callback scheduling, database activity, and integer rounding. A calendar day can consequently contain only three visits to a particular planet or can catch five visits near the edges of successive sweeps. The routine simply begins another pass whenever the previous one ends.&lt;br /&gt;
&lt;br /&gt;
In an uninterrupted completed sweep, the cursor ordinarily reaches each eligible planet once. The larger problem is that classic has no durable way to know whether any particular planet has already received the update that should correspond to a given part of the day.&lt;br /&gt;
&lt;br /&gt;
== Database growth and wasted work ==&lt;br /&gt;
The file traversed by &amp;lt;code&amp;gt;plartia()&amp;lt;/code&amp;gt; is not a planet-only file. It contains the persistent records created as the galaxy is explored:&lt;br /&gt;
&lt;br /&gt;
* sector records;&lt;br /&gt;
* planet records; and&lt;br /&gt;
* wormhole records.&lt;br /&gt;
&lt;br /&gt;
Classic begins its scan at the sector-record portion of the index and counts sector and wormhole records while looking for planets. Those records cannot receive population, production, food-consumption, tax, or spy updates, so this work is scheduler overhead.&lt;br /&gt;
&lt;br /&gt;
This matters because scanning creates a sector record even when the sector has no planets. The database therefore tends to accumulate non-planet records faster than updateable colonies. As exploration continues, more callbacks are consumed merely advancing through the file. The adaptive calculation lowers the callback delay to compensate, but it cannot go below three seconds and each invocation still examines at most 20 records and updates at most one planet.&lt;br /&gt;
&lt;br /&gt;
Eventually the requested number of full passes may require more work than the routine can perform at those limits. Long before that hard ceiling, growth during one sweep and the use of the previous sweep as the timing estimate can make the actual cycle length lag behind its configured target. The classic documentation itself warns that CPU load grows with the galaxy and recommends increasing &amp;lt;code&amp;gt;PLANTOCK&amp;lt;/code&amp;gt; or reducing &amp;lt;code&amp;gt;UNIVMAX&amp;lt;/code&amp;gt; when performance becomes a problem.&lt;br /&gt;
&lt;br /&gt;
== Restarts and downtime ==&lt;br /&gt;
Classic stores its traversal cursor, callback count, and timing counters only in static variables. They are lost when the module or server restarts. The planet record contains no last-update day, pass number, or elapsed-time marker.&lt;br /&gt;
&lt;br /&gt;
This has several consequences:&lt;br /&gt;
&lt;br /&gt;
* A restart in the middle of a sweep starts the next run at the beginning of the database. Planets before the old cursor may be updated again before planets after it receive the interrupted pass.&lt;br /&gt;
* Hours or days of downtime produce no updates, and there is no catch-up when the game returns.&lt;br /&gt;
* A restart cannot distinguish a planet updated moments before shutdown from one that has not been updated for days.&lt;br /&gt;
* The adaptive interval also has to establish itself again instead of resuming from durable scheduler state.&lt;br /&gt;
&lt;br /&gt;
These failures are not recoverable from the classic records because the information needed to reconstruct the missed work was never recorded.&lt;br /&gt;
&lt;br /&gt;
== ge-next correction ==&lt;br /&gt;
ge-next 0.2 replaced the classic sweep with a day-and-pass scheduler. The configuration specifies the desired number of updates per day with &amp;lt;code&amp;gt;PLANUPD&amp;lt;/code&amp;gt; (four by default), rather than specifying an approximate duration for an endlessly repeated sweep.&lt;br /&gt;
&lt;br /&gt;
Each planet record has an unsigned-long &amp;lt;code&amp;gt;timestamp&amp;lt;/code&amp;gt;. ge-next packs two values into it:&lt;br /&gt;
&lt;br /&gt;
* the game day, represented as the number of days since 1980-01-01, in the upper bits; and&lt;br /&gt;
* the update-pass number in the low four bits.&lt;br /&gt;
&lt;br /&gt;
The low four bits accommodate the supported range of 1 through 15 updates per day. After processing a planet, ge-next records the current day and pass on that planet. Zygor&amp;#039;s record additionally stores the current scheduler state and the calibrated callback interval, allowing the scheduler to resume after a restart.&lt;br /&gt;
&lt;br /&gt;
Because every planet carries its own progress marker, encountering a record does not automatically mean applying another economic update. ge-next compares the stored day/pass with the current day/pass and computes exactly how many updates are due. Restarting midway through a pass may cause the scanner to revisit earlier records, but their timestamps prevent the same pass from being applied twice.&lt;br /&gt;
&lt;br /&gt;
If a planet is behind because of downtime or an interrupted run, ge-next replays the missing calls to &amp;lt;code&amp;gt;multiply()&amp;lt;/code&amp;gt;. Catch-up is limited to the previous seven days so a very old record cannot monopolize one callback with an unlimited amount of historical work. Intermediate catch-up updates run without generating repetitive player mail; only the final live update can send normal notices.&lt;br /&gt;
&lt;br /&gt;
ge-next also stops after completing the configured number of passes for the day. It does not immediately begin an unbounded fifth pass. The scheduler targets completion within 23 hours, leaving time for timing variation.&lt;br /&gt;
&lt;br /&gt;
=== Startup calibration refined in 0.2.2 ===&lt;br /&gt;
ge-next 0.2 initially estimated the delay between planet-update callbacks from the database size at startup. On a new game with very few records, that estimate could be about 1,000 seconds per callback at the default four updates per day. If players then explored and created many more planet records, the first pass could take longer than a day. The scheduler recalculated its callback delay only after finishing a pass, so it never reached the recalculation point; the new planets remained unvisited by that pass.&lt;br /&gt;
&lt;br /&gt;
ge-next 0.2.2 changed the startup estimate from abrupt database-size brackets to a gradual estimate based on the record count. More importantly, [[Game execution flow#Midnight routine|midnight cleanup]] now detects when the first pass has not finished (&amp;lt;code&amp;gt;passnum &amp;amp;lt;= 1&amp;lt;/code&amp;gt;) and clears the saved callback interval on Zygor. On the next startup, the scheduler estimates the interval again using the now-larger database instead of carrying forward the stale value. Once a pass does finish, the normal end-of-pass calibration can take over. This correction does not make the first day immune to rapid growth, but prevents an unfinished first pass from indefinitely blocking recalibration.&lt;br /&gt;
&lt;br /&gt;
== More efficient traversal ==&lt;br /&gt;
The revised routine starts at the planet-record key and remains within the planet portion of the index. It no longer walks every ordinary sector and wormhole record merely to locate the next planet. It can also skip empty or unowned planets while retaining explicit progress through the planet records.&lt;br /&gt;
&lt;br /&gt;
The result is that database growth from exploring empty sectors no longer consumes most of the planetary scheduler&amp;#039;s work. More importantly, the economic state of a planet is determined by its recorded update history, not by whether a process-local cursor happened to reach it often enough.&lt;br /&gt;
&lt;br /&gt;
== Porting guidance ==&lt;br /&gt;
A port should treat planetary production as a scheduled, idempotent job:&lt;br /&gt;
&lt;br /&gt;
* persist a day/period identifier or last-update time on every planet;&lt;br /&gt;
* calculate elapsed periods from that stored value;&lt;br /&gt;
* make revisiting an already processed record harmless;&lt;br /&gt;
* retain enough global state to resume an interrupted pass;&lt;br /&gt;
* impose a documented limit on catch-up work; and&lt;br /&gt;
* traverse a planet-only index or query instead of scanning unrelated sector and wormhole records.&lt;br /&gt;
&lt;br /&gt;
Database iteration order should determine only when a due update is processed, never whether the planet is considered to have experienced the pass at all.&lt;br /&gt;
&lt;br /&gt;
[[Category:Classic bugs: Planets]]&lt;br /&gt;
[[Category:Classic bugs: Database]]&lt;br /&gt;
[[Category:Classic bugs: Scheduler]]&lt;br /&gt;
&lt;br /&gt;
{{GE navigation}}&lt;/div&gt;</summary>
		<author><name>CodexBot</name></author>
	</entry>
</feed>