<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
    <id>https://rets.dassheep.tech/devblog</id>
    <title>reTS Blog</title>
    <updated>2026-07-25T19:30:00.000Z</updated>
    <generator>https://github.com/jpmonette/feed</generator>
    <link rel="alternate" href="https://rets.dassheep.tech/devblog"/>
    <subtitle>reTS Blog</subtitle>
    <icon>https://rets.dassheep.tech/img/brand/rets-wordmark-compact.svg</icon>
    <entry>
        <title type="html"><![CDATA[The Rounding Mode Nobody Restored — and Three More Things That Weren't Missing]]></title>
        <id>https://rets.dassheep.tech/devblog/the-rounding-mode-nobody-restored</id>
        <link href="https://rets.dassheep.tech/devblog/the-rounding-mode-nobody-restored"/>
        <updated>2026-07-25T19:30:00.000Z</updated>
        <summary type="html"><![CDATA[Deep in Tiberian Sun's executable there is a helper that converts a]]></summary>
        <content type="html"><![CDATA[<p>Deep in Tiberian Sun's executable there is a helper that converts a
floating-point number to an integer. Every game does this thousands of times a
second. This one begins by loading a specific x87 control word — the register
that tells the floating-point unit how to round — and then it never puts the old
one back.</p>
<p>That is not a bug you would ever notice. The control word it loads says <em>round
toward zero</em>, and a function whose entire job is truncation wants exactly that.
The helper works perfectly. It just leaves the machine in that state
afterwards, forever, for every other calculation the game does.</p>
<p>We spent two sessions finding out how much that costs.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="building-an-oracle-we-didnt-have">Building an oracle we didn't have<a href="https://rets.dassheep.tech/devblog/the-rounding-mode-nobody-restored#building-an-oracle-we-didnt-have" class="hash-link" aria-label="Direct link to Building an oracle we didn't have" title="Direct link to Building an oracle we didn't have" translate="no">​</a></h2>
<p>The subterranean units — the Devil's Tongue, the Subterranean APC — burrow out
of the world, travel underground, and surface somewhere else. Reversing the
state machine that drives them was straightforward. Verifying it was not.</p>
<p>The underground travel step chains an arctangent, a truncation to sixteen bits,
an off-by-one angle offset, a sine, a cosine, a multiply and two
float-to-integer conversions. Every link in that chain is individually plausible
and the whole thing is collectively unverifiable by reading. Our own workflow
has a rule for this: <em>if the port and the study could share the same misreading
and still agree, confirm it against the real thing.</em></p>
<p>The route we'd built for that — driving the retail game live under emulation —
was unavailable. The host was down, and the tooling targeted a different
executable in the family anyway. So instead of waiting, we went one level down
and generalized something smaller: a harness that maps <strong>the whole retail
executable into an emulator at its real addresses</strong> and runs an arbitrary range
of its actual instructions. No virtual machine, no game process, no Wine. The
shipped constants are the shipped constants because they are being read out of
the shipped file.</p>
<p>Two things had to be true for that to be an oracle rather than a convincing
imitation, and both took a while to learn.</p>
<p><strong>First, the emulator's floating-point state has to be set by making the guest
execute the instruction that sets it.</strong> Writing the control-word register
directly through the emulator's API sets the register field without
reconfiguring the emulator's actual float behaviour. It reads back exactly what
you wrote, while every result stays silently mis-rounded. We caught this only
because a value that should have been 256 × √2 came back at single-precision
grade.</p>
<p><strong>Second, you have to check that the range you're running is one the emulator
computes exactly.</strong> The emulator implements the transcendental floating-point
instructions with host doubles rather than true 80-bit arithmetic. Anything
executing one is being approximated — plausibly, silently. So every capture now
sweeps its reachable code for those instructions first and refuses to proceed if
it finds any.</p>
<p>The engine turns out to dodge this entirely, for a reason that matters more than
the tooling.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="their-sine-is-not-your-sine">Their sine is not your sine<a href="https://rets.dassheep.tech/devblog/the-rounding-mode-nobody-restored#their-sine-is-not-your-sine" class="hash-link" aria-label="Direct link to Their sine is not your sine" title="Direct link to Their sine is not your sine" translate="no">​</a></h2>
<p>The four math routines this code calls look exactly like the C runtime's
<code>atan2</code>, <code>sin</code>, <code>cos</code> and <code>sqrt</code>. They are not. They are the engine's own
table-driven approximations — routines we had already reversed months earlier
for an unrelated system, sitting one folder away in our own tree, and nobody
connected them.</p>
<p>The gap is not small and not uniform. Their <code>atan2(1, 1)</code> is <code>0.7735</code> where the
real answer is <code>0.7854</code>. Their <code>sqrt(65025)</code> is <code>254.998</code>, not <code>255</code>.</p>
<p>We measured what a port built on the standard library's trigonometry would do:
across 720 evenly spaced headings, it produces <strong>a different integer coordinate
on 118 of them — 16.4%</strong> — always by exactly one unit. Coordinates are
synchronized state in a lockstep multiplayer game. That is a desync generator.</p>
<p>And it is nearly invisible. The same substitution agrees with the binary on
<strong>eleven of the twelve</strong> cases we'd picked by hand before running the sweep. A
reasonable test suite, written carefully by someone who understood the system,
would have certified the wrong implementation.</p>
<p>That is the argument for sweeping rather than spot-checking, in one number.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="what-the-rounding-mode-actually-costs">What the rounding mode actually costs<a href="https://rets.dassheep.tech/devblog/the-rounding-mode-nobody-restored#what-the-rounding-mode-actually-costs" class="hash-link" aria-label="Direct link to What the rounding mode actually costs" title="Direct link to What the rounding mode actually costs" translate="no">​</a></h2>
<p>Now back to that control word.</p>
<p>Because the truncation helper never restores it, <em>round toward zero</em> is the
state every floating-point instruction in the game runs under. It applies to the
arithmetic, not just to the conversions. And it changes answers.</p>
<p>For a unit heading due south, one term of the travel step works out to
<code>-0.00000000000000465</code>. Added to a coordinate of <code>32896</code>, rounding to nearest
gives back exactly <code>32896</code>. Rounding toward zero does not — it lands a hair
below, and truncation then takes the whole unit off. <strong>The four compass
directions are asymmetric</strong>: north and east step cleanly, south and west each
lose one unit sideways.</p>
<p>Nine of our 732 captured vectors disagreed with the port until that was
modelled. With it, all 732 agree.</p>
<p>This session we found the same hazard in a second place. The dig-in duration
divides a constant by a speed value and by a rules key. At a modded speed of
<code>0.1</code>, the hardware produces <strong>639</strong> where round-to-nearest arithmetic produces
exactly <code>640.0</code> and truncates to <strong>640</strong>. One frame, on a synchronized timer.</p>
<p>Rust gives you no control over rounding mode, so the fix is arithmetic rather
than architectural: compute the ordinary quotient, recover the exact leftover
with a fused multiply-add, and step one representable value toward zero when the
leftover says the ordinary rounding overshot. Our lint configuration had already
gone out of its way <em>not</em> to ban fused multiply-add, with a note explaining it's
exactly specified and therefore safe across platforms. That note was written for
a portability reason. It paid for itself here.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="divide-by-zero-get-zero">Divide by zero, get zero<a href="https://rets.dassheep.tech/devblog/the-rounding-mode-nobody-restored#divide-by-zero-get-zero" class="hash-link" aria-label="Direct link to Divide by zero, get zero" title="Direct link to Divide by zero, get zero" translate="no">​</a></h2>
<p>While sweeping the duration formula across its inputs, we tried the degenerate
one: what if a mod sets the speed field to zero?</p>
<p>The division produces infinity. The engine's conversion helper is a bare
64-bit store instruction with no software clamp, so the floating-point unit's
masked error path writes its "integer indefinite" pattern — and the calling code
reads only the <strong>low half of that pattern, which is zero</strong>. The timer expires
instantly. No crash, no trap, no enormous number.</p>
<p>Rust's float-to-integer conversion <em>saturates</em>. A natural implementation returns
the maximum integer here and strands the unit underground permanently.</p>
<p>The part worth recording: our codebase already contained a helper built for
precisely this "the caller reads only the low half" shape, with a doc comment
explaining the mechanism correctly. That helper is <strong>also wrong for this input</strong>,
because the language's saturating conversion gives one extreme for positive
infinity and the other for negative, and only one of the two happens to have a
zero low half. A helper that models the right mechanism can still be wrong on
the input you actually have. We wrote a third one.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="reading-the-notes-before-trusting-the-notes">Reading the notes before trusting the notes<a href="https://rets.dassheep.tech/devblog/the-rounding-mode-nobody-restored#reading-the-notes-before-trusting-the-notes" class="hash-link" aria-label="Direct link to Reading the notes before trusting the notes" title="Direct link to Reading the notes before trusting the notes" translate="no">​</a></h2>
<p>With the numeric half confirmed, the integer half of the port — seven states,
the movement and abort entry points, the tunnel-network object, six cell
predicates — was ready to be written straight from our own reverse-engineering
notes.</p>
<p>We re-read those notes against the disassembly first. Four things were wrong.</p>
<p>The worst concerned the descent. Our notes said the descent checks a cached
height value against the burrow floor. It does — and then, on the other branch,
it <strong>re-reads the coordinate fresh and checks that too</strong>, against the same
number but with a <em>different comparison</em>: the cached value is tested for
equality, the fresh one for less-than-or-equal, and both jump into the same
transition. A port written from the notes keeps descending for one extra frame
whenever the two disagree, or whenever the unit is already below the floor
rather than exactly on it.</p>
<p>That correction also settled a question we'd logged as open for two sessions —
whether two different height accessors agree with each other. They don't need
to. The live code consults both.</p>
<p>Two of the cell predicates turned out to be misdescribed in a more interesting
way. We had them recorded as near-mirrors: one sweeps north and west, the other
sweeps southeast. In fact the first is a <strong>pairwise edge detector</strong> — it reports
true only where a tunnel cell is immediately followed by a non-tunnel one, which
means three consecutive tunnel cells in a row match <em>nothing</em>. It isn't asking
"is there a tunnel near me". It's asking "where does the tunnel end". And the
two functions disagree about map edges: in one, running off the map ends the
current search direction; in the other, it abandons the entire query and
answers no.</p>
<p>Neither of those is the kind of thing you find by reading a summary. Both are
the kind of thing that produces a port which looks right and drifts.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="two-loose-ends-tied">Two loose ends, tied<a href="https://rets.dassheep.tech/devblog/the-rounding-mode-nobody-restored#two-loose-ends-tied" class="hash-link" aria-label="Direct link to Two loose ends, tied" title="Direct link to Two loose ends, tied" translate="no">​</a></h2>
<p>The direction tables that all of this walks — the eight compass offsets — live
in a section of the executable that has no contents in the file. They're
zero-filled at load and populated by an initializer at startup, which means they
<strong>cannot be compared across the three games statically</strong>. We'd flagged that as
unresolvable without a live read.</p>
<p>The same whole-image emulation technique handles it: find the initializer, run
it, read the memory back. Identical in all three games, entry for entry. The
detail that mattered was that the two related tables <strong>don't share a layout</strong> —
one packs its pairs into four bytes, the other uses eight — and both had to be
determined from how consumers index them rather than assumed. A port that
guessed one layout for both would have read the second table as noise.</p>
<p>Then the recovered values turned out to match a table already in our tree, ported
from a completely unrelated piece of work. Two reverses from opposite directions,
same eight pairs.</p>
<p>The last loose end was a genuine risk to a conclusion we'd published to
ourselves. We'd established that every <em>consumer</em> of tunnel state is identical
across Tiberian Sun, Red Alert 2 and Yuri's Revenge — but never checked the
function that <em>produces</em> it. Identical consumers over a divergent producer still
diverge.</p>
<p>Two surprises there. The addresses in our own tracking issue pointed at a
basic block <em>inside</em> the function rather than the function; and the function
turns out to carry <strong>two different names in two different reverse-engineering
communities</strong>, which is why nobody had noticed it was already partially ported
from the other end of the family. Tiberian Sun's own debug symbols settle it.</p>
<p>The verdict needed scoping, and the scoping is the finding. The tunnel-specific
block is <strong>47 instructions with zero differences across all three games</strong>. The
enclosing function is <em>not</em> — the sequels diverge from Tiberian Sun in about 15%
of their instructions, in an overlay path, a vegetation special case, and a
drawing offset. All upstream or downstream of the tunnel logic; none of it on
that path. "The function is identical" would have been false. "The tunnel path
is" is true, and that's what discharges the risk.</p>
<p>The nicest corroboration of the two sessions came free: that block allocates an
object of one size in Tiberian Sun and a larger one in both sequels, and the
difference is <em>exactly</em> the growth we'd derived last session from an entirely
unrelated argument about object headers. Two independent routes, same number.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="sixteen-functions-that-were-always-one-function">Sixteen functions that were always one function<a href="https://rets.dassheep.tech/devblog/the-rounding-mode-nobody-restored#sixteen-functions-that-were-always-one-function" class="hash-link" aria-label="Direct link to Sixteen functions that were always one function" title="Direct link to Sixteen functions that were always one function" translate="no">​</a></h2>
<p>The second half of the day went at bridges — the biggest system in Tiberian Sun
we hadn't touched. Thirty-nine functions, and the original developers named them
so explicitly that the whole design reads straight off the list: damage and
destroy, times two bridge kinds, times two orientations, times two corners, all
hand-expanded into separate routines. Plus a repair for each kind.</p>
<p>Our own notes said this made it "one of the cheapest large systems still
unreversed". Three of the things those notes asserted turned out to be wrong.</p>
<p><strong>The first was the shape of the call graph.</strong> The two public entry points were
described as the dispatchers over that matrix. They aren't. There are two <em>more</em>
dispatchers in between — neither of which has a name in any symbol database we
have — and the sixteen matrix routines are never reached directly from either
public entry point. The recon had every address right and the call graph wrong,
which is the recurring failure mode of working from a symbol list: it tells you
what exists, never what calls what.</p>
<p><strong>The second was whether the matrix is real.</strong> The interesting question about
sixteen hand-expanded functions is whether they're genuinely different logic or
the same code pasted with different constants. That deserves a number, not an
impression. So: the two damage routines for the two bridge kinds are both the
same byte length, both ninety-five instructions, and differ in <strong>exactly one
instruction</strong> — which tileset they read from. The two destroy routines are both
the same length, both two hundred and eighty-five instructions, and differ in
<strong>three</strong>. Ninety-nine percent identical.</p>
<p>So the port is one routine parameterised by kind, not two implementations — and
that claim is licensed by a diff rather than by taste. The corner axis collapses
the same way: nineteen differing instructions out of ninety-three, every one of
them a constant, no new branches anywhere.</p>
<p>The axis that does <em>not</em> collapse is orientation. East-west and north-south are
a genuine shape difference: a different tileset for the middle sections, an
extra guard on every north-south routine, and — the one that would really hurt a
port — <strong>the same byte read as a bit test on one axis and a magnitude comparison
on the other</strong>. Same field, different question. Testing at one orientation would
never reveal it.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="two-numbers-that-were-the-entire-design">Two numbers that were the entire design<a href="https://rets.dassheep.tech/devblog/the-rounding-mode-nobody-restored#two-numbers-that-were-the-entire-design" class="hash-link" aria-label="Direct link to Two numbers that were the entire design" title="Direct link to Two numbers that were the entire design" translate="no">​</a></h2>
<p>The third wrong assumption was about an asymmetry our notes had flagged as
"itself a finding to pin". Two of the three bridge kinds get nine functions
each. The third gets two. Why?</p>
<p>Three hypotheses, all testable. <em>They're single-tile, so there's no orientation
to expand over</em> — refuted; the code walks up to three cells. <em>They're destroyed
by conversion rather than a state machine</em> — confirmed. <em>They aren't repairable</em>
— confirmed, and <strong>actively</strong> so: the repair scan marks a cell eligible on a
tileset match and then writes the flag <strong>back to zero</strong> when the cell turns out
to be one of these. It isn't an omission. It's a rejection.</p>
<p>Then the state machine itself fell out of a discrepancy we'd nearly filed as
noise. The <em>shape query</em> for these bridges accepts twenty-eight values. Every
<em>damage</em> path gates on twenty-six. We spent a while assuming one of the two
readings was a mistake — and the two extra values turned out to be exactly the
literals the destroy helpers write when they finish. So the lower range means
<em>damageable</em> and the top two mean <em>already destroyed</em>: re-damaging a wrecked
bridge is a no-op at the caller's gate, while a geometry query still sees a
bridge-shaped cell. Twenty-six versus twenty-eight <strong>is</strong> the design.</p>
<p>And the real answer to the asymmetry arrived last, from a completely different
direction. A field we'd been calling "the tile number" turned out — once we
found the code that <em>writes</em> it rather than the code that reads it — to be an
overlay id: an index into a separate type table entirely. Which means the small
bridges and the large ones <strong>aren't stored in the same system at all</strong>. One is a
flat per-cell overlay. The other is a placed multi-cell tile object carrying
sub-tile geometry, a height level, corner identity and a damage stage.</p>
<p>That is why the machinery can't be shared. Corner expansion, the end-cap state
propagation, the middle-section flood fill — all of those are operations on
placed tile objects, and an overlay simply has none of that structure. The
asymmetry isn't a design shortcut. It's two different data models meeting at one
function.</p>
<p>That field had been described wrongly in <em>two</em> places, in opposite directions:
an older piece of research called it an owner field, and this session's own
first pass called it a tile number. Both are now fixed, and the correct owner
field — one slot further into the structure — is pinned from the function that
writes it.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-train-bridges-nobody-thought-were-still-there">The train bridges nobody thought were still there<a href="https://rets.dassheep.tech/devblog/the-rounding-mode-nobody-restored#the-train-bridges-nobody-thought-were-still-there" class="hash-link" aria-label="Direct link to The train bridges nobody thought were still there" title="Direct link to The train bridges nobody thought were still there" translate="no">​</a></h2>
<p>Tiberian Sun has trains. Red Alert 2 and Yuri's Revenge don't. It is settled
community knowledge that the sequels kept the ordinary bridges and dropped the
rail ones, and our own tracking issue said so.</p>
<p>Both sequels contain the complete rail-bridge matrix. All eight routines, the
dedicated dispatcher, and the supporting cell function — compiled in and
<strong>correctly wired</strong>, byte-identical in control flow to Tiberian Sun's.</p>
<p>Finding them was the interesting part, because neither sequel's symbol data
names a single bridge function. For Yuri's Revenge there was one thread to pull:
a named function that we suspected — from nothing more than a plausible
alternative name — was the same routine. It diffs identically over sixty-nine
instructions. Then the corroboration that made it certain: the original's
reference count on that function is eighty, the sequel's is eighty, and the
eighty decomposes the same way in both — eight callers from one place,
twenty-four from another, twelve from a third, and so on. The remaining twelve
functions were then located by fingerprinting the <em>spacing between calls</em> inside
each routine, and every prediction was confirmed on first check.</p>
<p>Red Alert 2 gave up nothing at all by name, so that one was pure signature work.
A twenty-byte prologue pattern hit <strong>exactly eight times</strong> in four megabytes of
code, clustering into two groups of four with the same internal size gaps the
original has. And then the call graph confirmed the split without our help: one
cluster calls the rail-specific cell function, the other calls the ordinary one.
That wiring only exists if this is a working rail-bridge implementation.</p>
<p>This is the third time on this project that something "everyone knows" is
exclusive to Tiberian Sun has turned out to be present and live in all three
games. Three for three. Each time the <em>engine</em> was shared and only the <em>content</em>
was removed — and each time the received wisdom was actually about the unit
roster, not the code. It has stopped being a surprise and started being a rule:
when a system looks exclusive, go and look for the code before believing it.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-number-in-the-file-is-not-the-number-in-the-engine">The number in the file is not the number in the engine<a href="https://rets.dassheep.tech/devblog/the-rounding-mode-nobody-restored#the-number-in-the-file-is-not-the-number-in-the-engine" class="hash-link" aria-label="Direct link to The number in the file is not the number in the engine" title="Direct link to The number in the file is not the number in the engine" translate="no">​</a></h2>
<p>The bridge work left one loose thread that turned into the day's third piece of
work. Low bridges, it turned out, aren't tiles at all — they're <em>overlays</em>, the
flat one-byte-per-cell decorations that also carry walls, fences, train tracks,
tiberium, veins and crates. So we went after the overlay system itself.</p>
<p>An overlay has no object identity. A cell stores a single number — the overlay
type — and a single state byte, and that's the entire per-cell representation.
Everything else lives in a shared type object looked up by that number. The
numbers themselves come from a list in the rules file, and the list is
<em>numbered</em>, so the obvious reading is that the number in the file is the number
in the engine.</p>
<p>It isn't. The engine's id is the <strong>position</strong> at which a type gets appended to
its array, and Tiberian Sun's list skips two keys — 40 and 41 are simply absent.
So the id is the key minus one for the first thirty-nine entries and the key
minus <strong>three</strong> for the remaining hundred and forty-one.</p>
<p>What makes that dangerous is that it half-works. Read the key as the id and the
first block is off by one — wrong, but wrong in a way that still lands you
inside the right general neighbourhood. Past the gap it silently becomes three.</p>
<p>It nearly cost us a real result, too. One line of investigation reported that
entry 74 was a crate prop, sitting right in the middle of the range we'd
previously established — from the assembly, in an entirely separate piece of
work — as the destructible low bridges. That reads like a refutation. It was the
same numbering confusion: that check had been done in key space. In position
space, 74 is the first low bridge and 101 is the twenty-eighth, and the range is
<em>exactly</em> the twenty-eight low-bridge entries, no more and no less.</p>
<p>The nicest part came from the shipped data saying something on its own. The
assembly work had concluded that two specific ids mean "this span is already
destroyed". Reading the rules file with no knowledge of that conclusion, those
two ids turn out to be the only entries in the whole run that stop forcing their
terrain type and turn invisible on radar — which is exactly what a collapsed
bridge section should do. Two independent lines of evidence, neither able to see
the other, landing on the same two numbers.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-default-nobody-passed">The default nobody passed<a href="https://rets.dassheep.tech/devblog/the-rounding-mode-nobody-restored#the-default-nobody-passed" class="hash-link" aria-label="Direct link to The default nobody passed" title="Direct link to The default nobody passed" translate="no">​</a></h2>
<p>The parser that reads each overlay's settings looked like the most routine code
in the system, and we wrote down what we thought it did: for each setting, ask
the INI file for a value, and if it isn't there, use <code>false</code>.</p>
<p>That would mean two flags which the <em>constructor</em> deliberately sets to <code>true</code>
get quietly flipped to <code>false</code> for any type whose section doesn't mention them.</p>
<p>Here is what the code actually does, for each of eighteen settings in turn: load
the field's <strong>current</strong> value, push that as the lookup's default, call the
getter, store the answer back. The default isn't a constant at all — it's
whatever the field already holds. An absent setting <strong>preserves</strong> the
constructor's value rather than zeroing it. And there's a second guard in front
of the whole thing: if the type has no section in the rules file, the parser
exits before touching anything. Which matters more than it sounds, because
twenty-four of the hundred and eighty shipped entries have no section — including
four of the seven wall types.</p>
<p>The wrong reading had survived a first pass <em>and</em> an adversarial second pass. It
was an entire column of a table, uniformly wrong and uniformly plausible, and
the only way to catch it was to read the pushed argument rather than the call.</p>
<p>Then the same shape appeared again, in the opposite direction. Both passes over
the wall-damage routine described its final branch as <em>"returns zero, with the
owner, the overlay and the damage state all untouched."</em> The instruction in
question sets the <strong>return value</strong> to zero. The damage advance had already been
written to the cell, several instructions earlier, and the destroy test reads it
back afterwards.</p>
<p>Take the return literally and the whole <code>DamageLevels</code> setting becomes inert:
walls would only ever be destroyed outright or completely unaffected, never chip
down through stages the way they visibly do in the game. That build would look
almost right. Almost right is the worst kind of wrong, because nothing about it
prompts you to look.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="one-byte-three-meanings">One byte, three meanings<a href="https://rets.dassheep.tech/devblog/the-rounding-mode-nobody-restored#one-byte-three-meanings" class="hash-link" aria-label="Direct link to One byte, three meanings" title="Direct link to One byte, three meanings" translate="no">​</a></h2>
<p>Three separate lines of work converged on the same byte of cell state. The wall
code packs a damage stage and a four-way connection mask into it. The vein code
tests it against a threshold. The bridge work had already named it the bridge
damage stage.</p>
<p>A cell can't be a wall and a high bridge at the same time, so it isn't three
fields — it's one byte whose meaning is chosen by whatever happens to occupy the
cell. Modelling it per-system, which is what you'd do if each investigation
wrote up its own findings in isolation, would have produced three mutually
incompatible pictures of the same storage.</p>
<p>The wall machinery sitting on top of it is more characterful than we expected.
Connections are a four-bit cardinal mask — and walls connect to <em>buildings</em>, not
just to other walls, but only two of the seven wall types can reach the GDI gate
branch and only one can reach the Nod one. Wire fences, barbed wire and wooden
fences connect to no building at all. Damage carries a probabilistic roll off
the <strong>synchronised</strong> random number generator, which puts walls on the
determinism contract alongside bridge rubble. And the destroy rule has a real
asymmetry: at the second-to-last damage stage, an isolated section dies while a
connected one survives.</p>
<p>One wall type is also excluded, alone, from the "isolated sections vanish"
behaviour the other six get. We don't know why yet. It's in the binary.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="when-the-tools-silence-isnt-evidence">When the tool's silence isn't evidence<a href="https://rets.dassheep.tech/devblog/the-rounding-mode-nobody-restored#when-the-tools-silence-isnt-evidence" class="hash-link" aria-label="Direct link to When the tool's silence isn't evidence" title="Direct link to When the tool's silence isn't evidence" translate="no">​</a></h2>
<p>Nine functions in this system — including the settings parser, the single most
important function in it — have no entry in our disassembler's function
database. They're only ever reached through virtual dispatch, so the automatic
analysis never created records for them, and the standard query answers
"function not found" for addresses whose names we know and whose bytes are
sitting right there in the file.</p>
<p>This has now cost us five separate sessions, which is why there's a purpose-built
tool that reads the executable directly and ignores the analysis database
entirely. Taking the tool's silence as evidence of absence would have deleted
about a third of this class's behaviour from the write-up.</p>
<p>It is the same mistake as the rest of the day, wearing different clothes. A
control word that was never restored. Bridge machinery assumed removed because
the vehicles were. A setting absent from a file, read as a setting set to zero. A
tool that couldn't find something, read as the something not being there.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-number-we-had-to-make-smaller">The number we had to make smaller<a href="https://rets.dassheep.tech/devblog/the-rounding-mode-nobody-restored#the-number-we-had-to-make-smaller" class="hash-link" aria-label="Direct link to The number we had to make smaller" title="Direct link to The number we had to make smaller" translate="no">​</a></h2>
<p>One last note, on honesty about progress — and it got sharper as the day went
on.</p>
<p>Our coverage tracker counts references to binary addresses inside ported files.
The morning's port made it report a jump of thirty functions, which was wrong:
a port cites addresses for the <em>seams</em> it deliberately doesn't implement — the
animation system, the sound call, the checksum engine — and the tracker counted
every one as implemented. The real figure was eighteen. Twelve phantoms.</p>
<p>The bridge port then made it report thirty-two. We went through all thirty-two
one address at a time. <strong>Fifteen are real. Seventeen are seams.</strong></p>
<p>That's worse than a rounding error, and the reason is structural rather than
accidental: the over-count scales with how <em>well</em> a module documents its own
boundaries. The bridge port is a well-behaved one — it lists what it models, and
then it lists the eight things it deliberately doesn't, with addresses, because
our own conventions require exactly that. Every one of those became a phantom.
The worst case isn't a sloppy port; it's a conscientious <em>partial</em> port of a
large system, because a partial port is precisely the one that has to name a lot
of seams.</p>
<p>The tempting fix — delete the addresses — would buy an accurate percentage by
destroying the traceability the addresses exist for. Those citations are how a
reader finds the <em>next</em> piece of work. So the number stays wrong in the tool for
now, the correction is recorded against it, and the real fix is a tag the
tracker can honour.</p>
<p>The overlay port, written later the same day with that lesson in hand, cites
addresses only for behaviour it actually implements and names its seams without
them. It contributed <strong>five</strong> newly counted functions and <strong>zero</strong> phantoms. The
problem was never that we were porting too little. It was where the addresses
were being written down.</p>
<hr>
<p>The three parts of this day were about completely unrelated things — a
floating-point register, a bridge, a fence — and they failed in the same
direction every time. Every capture we ran to <em>confirm</em> something corrected it
instead. Every premise we inherited about the bridge system was right about the
addresses and wrong about the structure. Twenty-six claims in the second part
needed fixing before a word of the write-up was written, and two more in the
third survived both a first pass and an adversarial second one.</p>
<p>Most of those were caught not by going back to the binary but by reading a
report against its own cited evidence — which remains the cheapest verification
available and the one that keeps paying. The ones that <em>did</em> need the binary
needed it for a specific reason: they were claims about what an instruction
means, not about whether two claims agree. "The default is <code>false</code>" and "the
function returns without effect" are both perfectly self-consistent. They're
just not what the bytes say.</p>
<p>If there's a single thread here, it's that <strong>absence is the hardest thing to
read correctly</strong>. A control word that was never restored looks like a control
word that doesn't matter. Bridge code compiled into a game with no trains looks
like code that was removed. A setting missing from a file looks like a setting
set to zero. A function the tool can't find looks like a function that isn't
there. In each case the wrong reading is the comfortable one, and in each case
it produces something that runs, looks plausible, and is quietly wrong.</p>
<p>Three of the morning's five findings are rounding-mode findings, and not one of
them is visible in an unmodified game. The rules key involved ships as <code>1</code>, so
the scaling asymmetry, the truncation hazard and the divide-by-zero path are all
dormant in retail and all live the moment a mod touches it. An engine tested
only against shipped values would carry three latent desync generators and pass
everything.</p>
<p>Which suggests the next piece of tooling: not "does this match the original at
the shipped value" but "does it match across the whole reachable range of the
knob". Today that sweep was done by hand. It shouldn't be.</p>]]></content>
        <author>
            <name>The reTS project</name>
            <uri>https://github.com/DasSheep/reTS</uri>
        </author>
        <category label="devblog" term="devblog"/>
        <category label="movement" term="movement"/>
        <category label="subterranean" term="subterranean"/>
        <category label="tunnels" term="tunnels"/>
        <category label="bridges" term="bridges"/>
        <category label="overlays" term="overlays"/>
        <category label="walls" term="walls"/>
        <category label="floating-point" term="floating-point"/>
        <category label="determinism" term="determinism"/>
        <category label="faithful-port" term="faithful-port"/>
        <category label="verification" term="verification"/>
        <category label="multi-game" term="multi-game"/>
        <category label="tiberian-sun" term="tiberian-sun"/>
        <category label="tooling" term="tooling"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[Compiled, But Never Called]]></title>
        <id>https://rets.dassheep.tech/devblog/compiled-but-never-called</id>
        <link href="https://rets.dassheep.tech/devblog/compiled-but-never-called"/>
        <updated>2026-07-24T18:00:00.000Z</updated>
        <summary type="html"><![CDATA[Somewhere in Tiberian Sun's executable there is a function that does exactly]]></summary>
        <content type="html"><![CDATA[<p>Somewhere in Tiberian Sun's executable there is a function that does exactly
what you'd want it to do if you were trying to find out whether the Firestorm
expansion is installed. It checks for one of Firestorm's files, and it hands
back yes or no. It has the shape of an answer.</p>
<p>Nothing in an unmodified game ever calls it.</p>
<p>That turned out to be the shape of practically everything this week. A
campaign's win screen quietly picks between three endings depending on the
order two flags get checked, not the order you'd guess. A weather event's own
settings ship almost entirely switched off in the retail data — present, but
functionally dormant. Code built for one purpose gets recompiled, unrenamed,
into a completely different one. A tracking script we wrote ourselves was
quietly hiding more than half of our own finished work from the backlog it
reported. That Firestorm-detection question really does have an answer —
just not the one the perfectly-named function seemed to promise. A wall
we'd already reverse-engineered down to a tidy, four-function port contract
turned out to have a second, undocumented way of killing you. A tool we
were about to build, to read the game's sealed data archives, had already
been finished months ago and forgotten. And a burrowing-unit mechanic that
everyone including us assumed was Tiberian Sun's alone is sitting fully
alive in both sequels, which simply never shipped a unit that uses it.</p>
<p>One thread runs under all of it: the plausible-looking thing — a name, a
flag, a summary, a task description, our own inventory of our own work — is
not always the thing actually running, and the only way to tell is to read
the raw instructions instead of the label.</p>
<!-- -->
<p><em>Last verified against the project oracle: 2026-07-25.</em></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="three-ways-a-win-can-go">Three ways a win can go<a href="https://rets.dassheep.tech/devblog/compiled-but-never-called#three-ways-a-win-can-go" class="hash-link" aria-label="Direct link to Three ways a win can go" title="Direct link to Three ways a win can go" translate="no">​</a></h2>
<p>When you win a campaign mission, the engine reads a handful of flags on the
scenario and takes one of a few paths. The campaign can simply end — silently,
or with a credits screen. It can hand you the interactive map room to pick your
next target. Or it can resolve the next mission automatically, with no screen at
all.</p>
<p>The order those flags are checked turns out to matter: one of them
short-circuits <em>before</em> another, and reading the two checks in the wrong order
inverts the whole meaning. This is the sort of detail intuition gets backwards,
so we read the raw instructions rather than trusting the plausible-looking
guess — the "end silently" flag wins, quietly skipping the screen the next flag
would have shown.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-branch-graph">The branch graph<a href="https://rets.dassheep.tech/devblog/compiled-but-never-called#the-branch-graph" class="hash-link" aria-label="Direct link to The branch graph" title="Direct link to The branch graph" translate="no">​</a></h2>
<p>The automatic path is the interesting one. Each side's campaign is a small graph
of <em>stages</em>. A stage knows the map it represents and carries a list of outgoing
options — edges to other stages. To advance, the engine takes a "next mission"
name, finds your current stage, and walks its options looking for one whose
target map matches that name. On a match, it writes the target's map name into
the field the loader reads and moves your position to that stage.</p>
<p>There's a sharp edge worth preserving: before it scans, the resolver blanks the
next-mission name. So if nothing matches, the name is left <em>empty</em> rather than
silently reusing whatever was there before. A naive reimplementation would skip
that reset and quietly carry the old value forward — a subtle divergence. We kept
the faithful behavior, and made it toggleable for anyone building on top who'd
rather it not.</p>
<p>That "next mission" name comes in two flavors — a primary and an alternate,
chosen by yet another flag. For a while we'd assumed those fields were
vestigial: data the map editor round-tripped but the engine never actually
read. Reading the win path settled it. They're live, and the alternate is a
real branch key the engine consults every time it resolves a mission
automatically.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-myth-about-the-map-room">The myth about the map room<a href="https://rets.dassheep.tech/devblog/compiled-but-never-called#the-myth-about-the-map-room" class="hash-link" aria-label="Direct link to The myth about the map room" title="Direct link to The myth about the map room" translate="no">​</a></h2>
<p>Here's the part that surprised us. Going in, the working theory was that the
cinematic map room — choose-your-next-mission on a painted campaign map — was a
<em>Tiberian Sun</em> luxury, and that Red Alert 2 and Yuri's Revenge had trimmed it
down to a plain linear "just load the next map."</p>
<p>The binaries disagree. The same branch machinery — the same map room, the same
stage-and-option graph, the same primary-and-alternate keys in the same places —
is compiled into all three games. It isn't a Tiberian Sun feature the later
games removed; it's shared engine code all three inherited. What actually differs
is how much the <em>shipped campaigns</em> use it — how many missions author real
branches — and how lavish the presentation is, not whether the mechanism exists.</p>
<p>That distinction matters for a faithful reimplementation: the bytes say the
capability is universal, and the linearity, where it exists, is a <em>content</em>
choice, not an engine limit. So the reimplementation models one branch engine
shared by all three, and treats the presentation — the movie, the painted
map, the voiceover — as a separate layer on top of it.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="what-carries-across">What carries across<a href="https://rets.dassheep.tech/devblog/compiled-but-never-called#what-carries-across" class="hash-link" aria-label="Direct link to What carries across" title="Direct link to What carries across" translate="no">​</a></h2>
<p>Choosing the next map is only half the story. The other half is what follows you
into it. Just before the game tears down the finished mission and loads the next,
it takes a small snapshot: the credits you finished with, the story flags you
tripped during the mission, the mission timer if one is running, and your
difficulty. It loads the new map — which wipes the world clean — and then pours
that snapshot back in. That's why your money and your progress don't reset between
missions of a campaign.</p>
<p>The money is the fussy part, and it's a tidy example of why we read the raw
instructions instead of the friendly decompiled version. Your carried-over credits
aren't handed back whole: they're scaled by the mission's carry-over percentage and
then clamped to a cap (unless the mission opts out of the cap entirely). The
decompiled code shows a bland "convert to integer" call and hides <em>how</em> it rounds.
The actual instructions round by <strong>truncating toward zero</strong> — always down. Reproduce
that with ordinary rounding and you'd quietly hand the player a credit too many on
any mission that scales or caps the carry-over. So we checked the arithmetic against
the real bytes and matched it exactly, down to the direction of the rounding.</p>
<p>Two smaller things fell out of the same read. The snapshot is written into
save games too, so a saved campaign remembers exactly what you were
carrying. And, echoing the map-room finding, the snapshot-and-restore code is
again the <em>same</em> in all three games — in two of them identical down to the
individual instruction, only the addresses shifted. One mechanism, three
engines, reversed once.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="a-different-kind-of-decision">A different kind of decision<a href="https://rets.dassheep.tech/devblog/compiled-but-never-called#a-different-kind-of-decision" class="hash-link" aria-label="Direct link to A different kind of decision" title="Direct link to A different kind of decision" translate="no">​</a></h2>
<p>Missions and carried-over credits are about state that <em>persists</em> — it survives
a level, a save, a reload. Not every decision the engine makes works that way.
Tiberian Sun's ion storm is one of those: decisions made and thrown away
within a single frame, in a system most players never consciously register.</p>
<p>The ion storm is a Tiberian Sun weather event with no counterpart in Red Alert 2
or Yuri's Revenge — or so it first appears. It darkens the map, cuts power to
the movement systems of units sensitive to it, grounds anything flying, and
blacks out radar. A countdown pings the EVA announcer roughly every fifteen
seconds while the storm builds, and once it hits, it runs for a duration the
mission itself sets — and periodically, while it's active, it strikes with
lightning.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-dice-the-storm-rolls">The dice the storm rolls<a href="https://rets.dassheep.tech/devblog/compiled-but-never-called#the-dice-the-storm-rolls" class="hash-link" aria-label="Direct link to The dice the storm rolls" title="Direct link to The dice the storm rolls" translate="no">​</a></h2>
<p>Per frame, while the storm is active, the strike logic is a two-stage gamble.
First roll: does anything get struck at all this frame? Second, only if the
first says yes, at odds modders will recognize as <code>IonLightningRandomness</code>:
hit a random visible-on-radar cell, or survey the whole map instead? The
survey path walks every object in play, skips anything flying (already
grounded, so off the target list entirely), and scores what's left — a
baseline weight for most things, higher for a ground unit of a
storm-favored type that still has power, and highest for a storm-favored
building. Then it rolls once for every object that survived the skip.</p>
<p>That last detail is the one worth sitting with. The number of random draws the
storm consumes in a given frame isn't fixed — it depends on how many objects
happen to be on the map when the frame runs. Get every formula in this system
exactly right — the weights, the odds, the rounding — and a reimplementation
can still end up out of sync with the original, for a reason that has nothing
to do with any formula being wrong: it drew a different number of times from
the same shared random sequence. So the port doesn't just reproduce the
outcome; it keeps an ordered ledger of every draw the storm takes in a frame —
what was drawn, the bounds it was drawn from, and why — and the tests check the
ledger, not just what got hit.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="symmetry-that-isnt-there">Symmetry that isn't there<a href="https://rets.dassheep.tech/devblog/compiled-but-never-called#symmetry-that-isnt-there" class="hash-link" aria-label="Direct link to Symmetry that isn't there" title="Direct link to Symmetry that isn't there" translate="no">​</a></h2>
<p>Starting a storm and ending one turn out to be handled by two routines that
look like they should mirror each other and, on inspection, deliberately
don't. They walk the list of affected objects in opposite directions. One of
them skips a whole category of object the other doesn't bother skipping. Only
the start routine special-cases aircraft. Only the stop routine checks whether
there's still a player around to check against. Six differences like that, in
a pair of routines a tidier design would have made into reflections of each
other. A reimplementation that "cleaned that up" — made start and stop mirror
images because that's obviously how it should work — would be faithfully wrong
in six different places at once.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-weapon-that-used-to-be-weather">The weapon that used to be weather<a href="https://rets.dassheep.tech/devblog/compiled-but-never-called#the-weapon-that-used-to-be-weather" class="hash-link" aria-label="Direct link to The weapon that used to be weather" title="Direct link to The weapon that used to be weather" translate="no">​</a></h2>
<p>The most striking find of the week showed up almost by accident, while
confirming that Red Alert 2 and Yuri's Revenge really don't have an ion storm.
They don't — there's no weather event, no countdown, no EVA warning. But both
executables still contain the compiled source file path of the code that
became their Lightning Storm superweapon, and it's the ion storm's own file.
Lightning Storm isn't a new system inspired by the ion storm; it's the ion
storm's code, recompiled and repurposed, and it was never even renamed on the
way over.</p>
<p>The difference between the two versions shows exactly what changed: what
calls what. The Tiberian Sun version answers only to its own internal clock
and scripted map triggers — a mission's script decided the moment was right,
or a timer ran out. The sequel version answers to the generic "a player or
the AI fired a superweapon" pipeline, the same machinery every other
superweapon uses. An autonomous weather event, something that happened <em>to</em>
the map, got turned into a weapon a side <em>owns and fires</em>. Same code,
opposite relationship to the player.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="what-actually-ships">What actually ships<a href="https://rets.dassheep.tech/devblog/compiled-but-never-called#what-actually-ships" class="hash-link" aria-label="Direct link to What actually ships" title="Direct link to What actually ships" translate="no">​</a></h2>
<p>There's a retail surprise buried in the storm's own settings, too. In an
unmodified copy of Tiberian Sun, <code>IonStorms</code> ships set to off — and we haven't
found any code that reads it, so whether that setting is what suppresses random
storms, or whether the random-storm path simply isn't there to begin with, is
still open. Either way, no storm starts on its own. The crate pickup that's
supposed to summon one has a pick weight of zero, so it can never actually be
drawn. And <code>IonLightningRandomness</code>
is left at its default of 90, so nine times out of ten the storm just hits
empty ground instead of an object. Put those together and the ion storm, as
most players experience it, only ever happens because a mission's script
explicitly summons it — the "random weather" half of the system is present in
the shipped data and functionally switched off. <code>IonStormDuration</code> itself is
parsed out of the data file and then never consulted anywhere the storm
actually runs; the real duration comes from whatever the triggering mission set
instead.</p>
<p>Two claims made about this system during the week turned out to be wrong when
checked against the actual machine code — one of them would have "corrected"
code that was already right. Checking the raw instructions instead of the
plausible-looking read settled both, and it wasn't only new claims that
needed correcting; a couple of the fixes landed on assumptions from earlier
work, not just this week's.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="a-backlog-three-times-the-size-the-list-said">A backlog three times the size the list said<a href="https://rets.dassheep.tech/devblog/compiled-but-never-called#a-backlog-three-times-the-size-the-list-said" class="hash-link" aria-label="Direct link to A backlog three times the size the list said" title="Direct link to A backlog three times the size the list said" translate="no">​</a></h2>
<p>All of that — the mission graph above, the carryover snapshot, the storm's
own dice-roll ledger — plus three more systems this blog already covered,
needed writing down properly for a reader without a disassembler. That's
what the <a class="" href="https://rets.dassheep.tech/reference">Engine Reference</a> is for, and this week five entries
went live: the mission-and-carryover mechanics above, the ion storm, the AI
difficulty-behavior switchboard, the house-alliance system, and native
observer mode — the last three narrated here days ago, now given formal,
binary-verified write-ups. Catching up on that debt meant catching a
mistake in the tool meant to measure how much debt there was.</p>
<p>We keep a running list of finished, tested systems and cross-check it
against what's actually published. This week that list said fifty-three
systems were eligible. Looking closer said the real number was much bigger
— the list is generated by a small script matching a one-line confidence
rating against the exact wording used when the tool was built, and that
wording had since drifted: newer write-ups phrased the same judgment
slightly differently, and a literal text match didn't recognize it.
Fifty-one finished, fully verified
studies had been quietly excluded — more than half of everything on the
books — and thirty-eight of those already clear the reference section's own
bar: verified across all three games, or verified with an explicit
divergence. Publish-ready, and the tracker never said so.</p>
<p>The fix went in additively, not by loosening the check: a new section
listing exactly what the old wording missed, with its original confidence
text attached. It deliberately does <em>not</em> wave those fifty-one onto the
publish list — some genuinely need more scrutiny, and a strict filter's
whole point is to keep that visible rather than relax the pattern until
everything passes. All one hundred and twelve completed studies now sort
into three clean groups: fifty-three already correctly flagged as ready,
fifty-one the old check had been hiding, and eight that honestly aren't
ready yet.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="one-author-two-skeptics-twenty-three-fixes">One author, two skeptics, twenty-three fixes<a href="https://rets.dassheep.tech/devblog/compiled-but-never-called#one-author-two-skeptics-twenty-three-fixes" class="hash-link" aria-label="Direct link to One author, two skeptics, twenty-three fixes" title="Direct link to One author, two skeptics, twenty-three fixes" translate="no">​</a></h2>
<p>Every entry goes through the same gauntlet: one person writes the page fresh
from the research, then two independent readers go over it with different
jobs — one a pedant checking every number and rules-file key, the other a
skeptic hunting for any claim that doesn't hold across all three games.
Twenty-three required fixes came back across the five pages. All
twenty-three went in.</p>
<p>The single biggest category wasn't a wrong fact — it was the <em>scope</em> of a
true one. A finding earned by checking one game kept drifting, in drafts,
into language that reads as if it applies to all three. Observer mode was
the worst offender: the research fully verifies it for Yuri's Revenge and
separately confirms the identity mechanism and two statistics feeds for Red
Alert 2, and nothing more — Tiberian Sun has no such mode at all. An early
draft still described several consumers as though the whole family had been
checked everywhere. The fix isn't dramatic — narrower sentences, an explicit
version line — but it's the difference between an encyclopedia entry and
folklore with citations.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="two-corrections-and-one-near-miss">Two corrections, and one near miss<a href="https://rets.dassheep.tech/devblog/compiled-but-never-called#two-corrections-and-one-near-miss" class="hash-link" aria-label="Direct link to Two corrections, and one near miss" title="Direct link to Two corrections, and one near miss" translate="no">​</a></h2>
<p>Two of the twenty-three were real errors, not scope creep. The first lands
on ground this post already covered: when the mission resolver finds no
match, does the pending name come back blank, or keep whatever was already
there? A draft treated those as the same failure. They aren't. Miss because
nothing in the branch graph matches, and the reset that runs <em>before</em> the
scan leaves the name genuinely empty — as described earlier. Miss instead
because the current stage can't be located at all, and an earlier safety
check returns before that reset ever runs, leaving the stale old name in
place. Blank and stale are opposite failures with the same symptom.</p>
<p>The second was a claim repeated for weeks unchecked: that the campaign's
story-flag arrays are "never cleared when a new mission loads." Checked
against the code that sets up a fresh mission, that's false — both arrays
get unconditionally zeroed at that point. What's true, and narrower, is that
loading the mission's own data file doesn't clear them itself; they only
<em>look</em> untouched because the carryover step described earlier writes the
saved values straight back in immediately after the zeroing.</p>
<p>One more thing didn't survive the skeptic pass. A draft of the ion storm
entry cited Firestorm's own rules values under a filename that turned out to
belong to nothing but our own internal research notes — not a confirmed name
in any retail install. Firestorm's rule data ships sealed inside its own
game archive, not as a loose text file, and nobody had actually confirmed
what name, if any, it carries on disk. A modder chasing that citation would
have gone looking for a file that doesn't exist. We caught it before
publishing and pulled the invented filename rather than let it stand. The
risk was never the number. It was the name attached to it.</p>
<p>One of those five entries was the ion storm, correctly billed as exclusive to
Tiberian Sun and Firestorm — which raised a question none of the underlying
studies had answered: how does the game <em>know</em> Firestorm is there? That's
the question the function from the top of this post was supposed to answer.
It doesn't. So we went and found what actually does.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-predicate-that-never-runs">The predicate that never runs<a href="https://rets.dassheep.tech/devblog/compiled-but-never-called#the-predicate-that-never-runs" class="hash-link" aria-label="Direct link to The predicate that never runs" title="Direct link to The predicate that never runs" translate="no">​</a></h2>
<p>The function is a plain, ordinary one: it builds a handle to a specific
Firestorm file, asks whether it exists, and hands back yes or no — exactly
the mechanism a curious modder would go looking for. A search through the
entire executable — every relative call, every literal reference to its own
address, anywhere a table of function pointers might quietly be pointing at
it — turns up nothing. Nothing calls it. The one specific filename it checks
for is referenced exactly once anywhere in the whole image: from inside that
same dead function. Its neighbor in memory is blunter still — three bytes of
machine code that unconditionally answer "yes," whose one caller throws the
answer away without even looking at it.</p>
<p>Both were written for a real question, and neither one is how the game
actually answers it.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-bitmask-that-does-the-job">The bitmask that does the job<a href="https://rets.dassheep.tech/devblog/compiled-but-never-called#the-bitmask-that-does-the-job" class="hash-link" aria-label="Direct link to The bitmask that does the job" title="Direct link to The bitmask that does the job" translate="no">​</a></h2>
<p>The real mechanism is smaller than either of those functions, and has no
name attached to it at all — just one bit inside a general-purpose flags
value. At startup, the game sweeps two separate families of numbered archive
files looking for expansion content, checking each family from slot
ninety-nine down to slot zero, a hundred slots apiece, mounting whatever it
finds along the way. Only after that sweep does one routine decide what it
found: it sets the Firestorm bit if, and only if, <em>both</em> the first numbered
Firestorm archive, <code>EXPAND01.MIX</code>, and Firestorm's own <code>FIRESTRM.INI</code> are
present together. Either alone isn't enough.</p>
<p>That distinction matters: this bit tracks whether Firestorm is <em>installed</em>,
not whether you're currently playing a Firestorm campaign. Boot a base copy
of Tiberian Sun with the expansion files sitting alongside it, and the bit is
set the moment the game starts — whether or not you've ever launched a
Firestorm mission that session.</p>
<p>A handful of small accessor functions read and flip that bit, and the
shipped game checks it in seventy-three separate places: rules loading,
scenario parsing, the campaign-selection screen, side and speech
preparation, the random map generator, the shell's own menus, modem
multiplayer, even Westwood Online. World Domination Tour turns out to be
gated on this same bit.</p>
<p>Of those seventy-three, only two sit inside the simulation itself — the part
of the engine that has to produce identical results, frame for frame.
Everywhere else the bit governs presentation, data loading, and menus:
right, but not deterministic. And one of the two simulation branches is a
genuine hazard. When Firestorm is enabled, the routine that lays out a
house's starting base draws an extra number from the map's shared random
sequence to pick one of three possible buildings. With Firestorm off, that
draw never happens — so a Firestorm game and a base game pull from
different positions in the same shuffled sequence from the first building
placement onward, even with everything else identical. Same fix as the
storm's own dice: pin the ordered record of what got drawn, not just the
outcome.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="six-dead-functions-one-live-kill">Six dead functions, one live kill<a href="https://rets.dassheep.tech/devblog/compiled-but-never-called#six-dead-functions-one-live-kill" class="hash-link" aria-label="Direct link to Six dead functions, one live kill" title="Direct link to Six dead functions, one live kill" translate="no">​</a></h2>
<p>The Firestorm Wall — the barrier the rules file calls <code>FirestormWall</code> —
turned up the densest patch of finished, uncalled code we've found in the
project: a frame lookup, an off-switch that treats it like a superweapon,
both handlers for the mission-scripting actions that turn it on and off, and
two menu wrappers around those actions. Six complete functions, and the
retail build never reaches one of them. The compiler had also placed the
same logic inline wherever it's actually called from, leaving the
free-standing copy stranded — and reference material built independently of
this project, years ago, points its labels straight at those stranded
copies.</p>
<p>The wall itself isn't dead code, and its kill test deserves precision,
because the honest version is less dramatic than the myth. It isn't a huge
damage number overwhelming a health bar. The call that kills you hands over
a pointer to your own current remaining health as the damage amount, and
marks the hit in a way that tells the game's general damage-resolution
code — the same code every attack funnels through — to skip the armor step
entirely. Armor is never consulted. It still plays a burn animation, split
by altitude: something flying burns in place, in the air; anything on the
ground burns wherever the wall is standing. And — worth saying plainly,
since it'd be easy to assume otherwise — neither the wall's flag nor the
code that makes it block projectiles is exclusive to Tiberian Sun. Both
carry into Yuri's Revenge as live options, not fossils.</p>
<p>The kill test's simplicity made it look useful beyond just this wall, too:
integer arithmetic throughout, no randomness anywhere in it, and seemingly
only one point of contact with anything outside the wall itself — the cell
it occupies. That made it look like a clean early candidate for proving out
running the simulation across multiple threads, the kind of small,
self-contained system you'd reach for first. Two of those three claims turned
out to be wrong, in ways worth the rest of this section to explain.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-walls-second-sweep">The wall's second sweep<a href="https://rets.dassheep.tech/devblog/compiled-but-never-called#the-walls-second-sweep" class="hash-link" aria-label="Direct link to The wall's second sweep" title="Direct link to The wall's second sweep" translate="no">​</a></h2>
<p>The Firestorm Wall already had a finished reverse-engineering write-up, and
that write-up carried something better than most: an explicit port
contract, distilled down to exactly what an implementation has to
reproduce. Two bits of state. No randomness. No floating point. Four short
functions. On paper it was the cheapest item left on the list — read the
contract, write the port, pin the tests, done by the end of the afternoon.</p>
<p>Before writing any of it, the raw instructions got read again anyway,
function by function, straight from the disassembly rather than the
write-up's summary of it. Not because the write-up was suspected of
anything — because an implementation built from a summary can agree with
the summary and both still be wrong, and there's no way to catch that from
inside the summary itself.</p>
<p>The wall does not have one lethal sweep. It has two, and they use different
warheads.</p>
<p>The write-up's version is the one that's easy to picture: the wall's own
cell has an occupant list, the wall walks it, and whatever it finds dies to
the Firestorm warhead with armor skipped entirely. That much is correct.
What the write-up never describes is the second half of the same routine.
After that walk finishes, the wall looks outward — a five-by-five block of
cells centered on itself — and checks every walking unit it finds there.
For each one, it asks that unit's own movement controller a direct
question: are you, right now, moving through my cell? Every unit that
answers yes takes a forced, full-strength hit — not from the Firestorm
warhead, but from the demolition-charge warhead, the same one used for
planted explosives. And unlike the kill described earlier, this one is
completely silent: no burn animation, no flash, none of the visual feedback
the wall's own cell produces. The unit simply dies.</p>
<p>That second sweep is the wall players actually experience. It doesn't only
kill what's standing inside it the moment it switches on; it reaches out
and kills what's walking into it from up to two cells away. A port built
strictly to the contract would have shipped a wall roughly half as lethal
as the retail one — and every test for it would have passed, because the
tests would have been written from the same description that left the
second sweep out.</p>
<p>Three smaller things came out of the same pass. The routine opens with a
guard the write-up doesn't mention: hand it a building that isn't actually
a firestorm wall, and it returns immediately, before it computes anything.
The sweep of the wall's own cell only kills units — vehicles, infantry, and
aircraft — never a building that happens to share the cell. And the spark
animation has a shape that looks wrong until you know the context: a
corner segment that's already sparking, when the triggering event fires
again, destroys its own spark instead of leaving it alone, so repeated
events alternate rather than settle into a steady state. That's tolerable
only because the whole routine fires on scripted events rather than running
every frame — which is exactly why pinning down that it wasn't a per-tick
system mattered earlier in the reverse.</p>
<p>One piece of the contract turned out simpler than advertised. The write-up
had flagged a coordinate conversion inside the routine as an unusual
rounding trick, worth a caution. It isn't unusual — it's exactly what the
compiler produces for an ordinary signed divide by 256. The port reuses the
conversion already sitting elsewhere in the codebase instead of carrying a
second, bespoke version, and pins the two as equivalent across the sign
boundary as a test.</p>
<p>That leaves two things to walk back, and the second one stings more.</p>
<p>A few sections ago, this same wall's simplicity got called a clean early
candidate for proving out multi-threaded simulation, on the strength of it
having "only one point of contact with anything outside the wall itself."
The five-by-five sweep makes that wrong. The wall reaches into up to
twenty-five neighboring cells and interrogates the movement controller of
every unit inside them — a single-cell occupant walk was never an accurate
description of what the function does, only of the half of it the port
contract described.</p>
<p>The same paragraph also called the wall free of randomness. That was
checked, and it's true of the four functions themselves — every instruction
in all four was scanned for floating-point math and for calls into the
random-number generator, and there are none. But "this function contains no
randomness" and "this behavior involves no randomness" are different
claims, and the gap between them is one call deep. Every animation these
functions spawn — the spark on a corner segment, the flash when something
dies crossing the wall — is built by a shared animation constructor, and
that constructor pulls several numbers from the game's synchronized random
sequence, varying by animation type.</p>
<p>That matters more than it sounds. In a lockstep multiplayer game every
machine has to draw the same random numbers in the same order forever, so
anything that consumes one is game state, not decoration. A port that
treated the spark as cosmetic and skipped it would pull one fewer number
than the original — and from that instant every subsequent random decision
in the match, on that machine only, would differ. The same trap was already
documented earlier this month for a weather effect's lightning bolts,
where the visual had to be modeled as a <em>count</em> of draws for exactly this
reason. It got missed here anyway. The implementation now pins how many
animations each path creates, with a test that fails loudly if anyone
decides they're just graphics.</p>
<p>The cheap-to-verify half of the original claim does survive: the wall's own
arithmetic is whole numbers, so its expected values can be worked out by
hand with no floating-point emulation. The narrow-interaction half and the
no-randomness half don't.</p>
<p>The general shape of the mistake is the same one this whole post keeps
running into, just one layer further removed each time. A port contract is a
summary of a study, and a study is a summary of a function. Distill twice
and whatever the first read missed stays missed, however carefully each
distillation was done — that's how the second sweep went unnoticed. And a
function is itself a summary of everything it calls, which is how the
randomness went unnoticed one level below that. What caught the first was
reading the function through to its last instruction instead of stopping
where the existing description stopped. What caught the second was someone
refusing to stop at the function's edge either. The rule that actually falls
out of both isn't "read the function" — it's read until you reach something
you've already verified, not until you reach something that looks finished.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="two-more-near-misses">Two more near misses<a href="https://rets.dassheep.tech/devblog/compiled-but-never-called#two-more-near-misses" class="hash-link" aria-label="Direct link to Two more near misses" title="Direct link to Two more near misses" translate="no">​</a></h2>
<p>Two more things almost went wrong this week — not in the game, but in our
own tools and our own claims, caught before they became part of the record.</p>
<p>The first: a small search tool built this week, meant to scan the
executable for anything that might call a given function, has a flag meant
to widen its search across every section. As a side effect nobody had
noticed, that flag silently switched off the part of the search that
follows ordinary direct calls — the most common kind there is. Run against a
function that plainly is called, the tool cheerfully reports back that
nothing calls it. It came within one command of reporting the
addon-detection routine's own bit-setter as dead code, right alongside the
genuinely dead predicate this post opened with. The fix made the flag
strictly additive — it can only widen a search now, never narrow it — and
every earlier search that used it was re-run against something already
known to be alive. Same species of mistake as the backlog script above:
when a tool encodes a judgment call, what it silently excludes needs to stay
visible.</p>
<p>The second: an early draft of this very story overreached, describing the
startup archive-mounting routine as "present and live in all three
engines" — true only in the weakest sense. All three executables reference
the same internal format string, but a reference and a reachable function
are different claims. Red Alert 2's own copy has nothing anywhere in its
image that calls it — compiled in, permanently unreachable. The same code
in Yuri's Revenge is very much alive. Finding a string proves a function
exists; it says nothing about whether the game ever runs it.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-reader-we-already-had">The reader we already had<a href="https://rets.dassheep.tech/devblog/compiled-but-never-called#the-reader-we-already-had" class="hash-link" aria-label="Direct link to The reader we already had" title="Direct link to The reader we already had" translate="no">​</a></h2>
<p>One item on the list was a small tool: Tiberian Sun keeps its rules data
sealed inside its own archive format, and without a reader for those
archives, a write-up can prove what a setting is <em>called</em> — by watching the
game parse that exact name and store it in that exact place — but not what
value the shipped game actually gives it. Every claim about a value had to
be hedged on outside sources.</p>
<p>Writing that reader took no time at all, because it already existed. It had
been built months earlier as part of the archive-format work, it already
handled the encryption those archives use, and it read the retail files
without a single change. Archives nested inside other archives, which
looked like the hard part, needed nothing special either — every command
already took a plain file path, so reaching a file two archives deep is
just doing it twice. The only new code was one lookup command. The gap was
never a missing tool. It was a missing look.</p>
<p>What it bought was worth the embarrassment. The wall's second sweep — the
one described above, the one that kills things moving through the wall's
neighbourhood — turns out to use the game's ordinary high-explosive
warhead, while the wall's own occupants die to a dedicated Firestorm one.
Two different weapons for two different ways of being in the wrong place.
That was visible as two adjacent settings before; now it's a fact about
what the game does.</p>
<p>And then the detail that quietly settled an argument. The Firestorm
expansion ships its own complete copy of the rules file. Compared against
the base game's copy, it differs by <strong>three lines</strong> — a guard-range value
on the GDI wall, the Nod wall, and the Firestorm defence structure.
Everything else is identical. Every Firestorm setting the wall needs is
already sitting in the unmodified base game's rules, shipped to everyone
who ever bought Tiberian Sun without the expansion.</p>
<p>That matters because of what it rules out. The earlier work concluded, from
the instructions alone, that the expansion gate is a bit in a bitmask and
nothing else — not a check for whether the data is present. Reading the
instructions could never have distinguished those two possibilities; both
would compile to the same thing. The data can. The content was always
there; only the switch was missing.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="one-field-two-doors">One field, two doors<a href="https://rets.dassheep.tech/devblog/compiled-but-never-called#one-field-two-doors" class="hash-link" aria-label="Direct link to One field, two doors" title="Direct link to One field, two doors" translate="no">​</a></h2>
<p>Two loose ends were left dangling on the wall write-up: both of its lethal
sweeps skip a candidate whose <em>type</em> has a particular flag set, and each
sweep reaches that flag through a different entry in the object's function
table. Different entries strongly suggest different questions being asked.
The obvious guess — recorded as a guess — was that one of them asked about
the owning player, since a nearby field on the player object tracks whether
Firestorm is currently active.</p>
<p>Both entries turn out to be the same accessor. One of them is a
three-instruction forwarder whose entire body is "look up this object's
function table and jump to the other entry." Two doors, one room. The flag
they both read is a per-unit-type setting the game parses out of the rules
under the name <code>IgnoresFirestorm</code> — a documented modder-facing switch, sitting
in plain sight the whole time, and the guess about the owning player was
simply wrong.</p>
<p>The other loose end was more interesting, because naming it changed what
the wall does. The sweep asks each nearby mover's movement system a
question, and the write-up had described that question as "are you headed
through my cell." It isn't. It's "are you <em>in</em> my cell" — the mover's
current destination-in-progress gets converted to a cell and compared
against the query. Present tense, not intent. A small correction to one
sentence, and it makes the next finding possible.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-tunnellers">The tunnellers<a href="https://rets.dassheep.tech/devblog/compiled-but-never-called#the-tunnellers" class="hash-link" aria-label="Direct link to The tunnellers" title="Direct link to The tunnellers" translate="no">​</a></h2>
<p>The last item was subterranean movement: the Subterranean APC, the Devil's
Tongue, units that vanish into the ground and come up somewhere else. The
task description laid it out clearly — one system, tying together a
burrowing movement mode, a tunnel object a map can declare, and a handful
of terrain checks. It named the function that hands a cell its tunnel, and
reasoned from there that the burrowing units travel through those declared
tunnels.</p>
<p>They don't. Not once, anywhere. Across the entire burrowing movement
system there is not a single reference to the tunnel object, to the terrain
checks, or to the pair of fields that tunnel traversal actually runs on.
There are <strong>two</strong> systems here, and they never touch:</p>
<p>One is a movement mode. A unit dives out of the world, travels to any
coordinate you like, and comes back up. Free-form; no tunnel required, none
consulted.</p>
<p>The other is a map feature. A scenario author declares an entrance cell, a
direction to enter from, and an ordered list of hops — and units walk that
fixed path one waypoint per tick. It is a scripted route, not a place a
burrowing unit can go.</p>
<p>They meet in exactly one place, and it's cosmetic: an object standing near
a declared tunnel gets its draw depth nudged so it doesn't look wrong.
Building an implementation on the task description's model would have
produced a game where you cannot burrow unless the mapmaker drew you a
tunnel first — which is not the game.</p>
<p>The movement mode itself is a seven-state cycle, and the symbol names
gave that away before a single instruction was read: seven separately-named
state handlers sitting in a row. Turn to face the destination. Play the dig
sound and start burrowing. Sink until you are exactly one cell-height below
the surface. Travel. Rise until you meet the terrain. Finish emerging. Plus
one more state that exists only to unwind a cancelled trip.</p>
<p>A few things about it that a player would recognise but never be able to
articulate. You cannot fire at any point in the cycle — including the very
first state, where the unit is still standing on the surface turning
around, having done nothing yet. The unit only counts as underground during
the travel state proper; while sinking and rising it is still, as far as the
rest of the engine is concerned, on the ground layer. It casts no shadow
while sinking, travelling, or rising, and casts one again the instant it
finishes. And the dig sound plays on the way down <em>and</em> on the way up, at
the exact frame the unit crosses fifty units of depth.</p>
<p>The dispatcher that picks between those seven states decrements its state
value before checking whether it's in range. Which means the state numbers
and the table positions are off by one from each other — the same trap the
trigger-action work hit earlier this week, caught the same way: by reading
the arithmetic rather than trusting a table of names to line up.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="nineteen-units-at-a-time-and-one-unit-of-angle">Nineteen units at a time, and one unit of angle<a href="https://rets.dassheep.tech/devblog/compiled-but-never-called#nineteen-units-at-a-time-and-one-unit-of-angle" class="hash-link" aria-label="Direct link to Nineteen units at a time, and one unit of angle" title="Direct link to Nineteen units at a time, and one unit of angle" translate="no">​</a></h2>
<p>The travel step is the part an implementation has to get exactly right, and
it is more delicate than it looks. Each tick, the game computes the bearing
from where the unit is to where it's going, converts that angle into the
compact integer form the engine uses for facings, converts it <em>back</em> to a
real angle, and steps nineteen units along it. Arrival is declared at
twenty units — which is why the step is nineteen and not twenty; the unit
can never overshoot the band.</p>
<p>Two details in that round trip are not incidental. The angle gets truncated
to sixteen bits partway through, so it wraps rather than clamping. And when
it's converted back, the value subtracted is one unit short of a perfect
quarter turn. Not a quarter turn. A quarter turn minus one.</p>
<p>The error that introduces is about five thousandths of a degree. Nobody
will ever see it. But the coordinates it feeds are integers, and over a
nineteen-unit step, five thousandths of a degree is occasionally enough to
land on a different integer — and integers are exactly what a lockstep
multiplayer game and a replay file agree on. Reproducing the <em>intent</em> here
would be wrong; the only correct implementation is the one that also
subtracts one unit short.</p>
<p>This is why that step doesn't get hand-computed. A chain of trigonometry,
a truncation, an off-by-one, and two round-toward-zero conversions is the
textbook case where a careful implementation and a careful reading of the
original can share a misunderstanding and agree with each other perfectly.
The expected values will come from the original binary running under
instrumentation, not from anyone's arithmetic.</p>
<p>Two more things worth knowing about tunnellers, both of which a player has
probably experienced without explanation. <strong>A burrowing unit can be killed
by having nowhere to surface.</strong> If it arrives underneath a spot where the
map offers no legal place to come up — after two separate attempts to find
one nearby — it takes lethal damage on the spot, from the ordinary
high-explosive warhead. And the check the game supposedly uses to decide
whether a unit may enter a tunnel is five bytes long: it ignores the cell,
ignores the unit, and returns yes. Whatever gating exists, it isn't there.</p>
<p>The other one connects back to the wall. Because a burrowing unit's
movement mode never implements the question the wall's sweep asks — it
inherits a default that always answers "no" — <strong>a burrowing unit is never
killed by a Firestorm Wall's outer sweep.</strong> It walks under the wall's
neighbourhood untouched. Neither piece of work could have found that alone;
it took naming one function in one system and reading the vacancy in the
other.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="four-more-that-are-never-called">Four more that are never called<a href="https://rets.dassheep.tech/devblog/compiled-but-never-called#four-more-that-are-never-called" class="hash-link" aria-label="Direct link to Four more that are never called" title="Direct link to Four more that are never called" translate="no">​</a></h2>
<p>The dead-code theme did not let up. Four of those seven state handlers are
never called by anything. The dispatcher handles those four states with
inlined copies of their code, and the compiler left the standalone versions
behind — named, complete, unreachable.</p>
<p>That claim was checked the way this week taught: the three handlers that
<em>are</em> reached were run through the identical scan first, and each showed
exactly one caller. Three live, one caller each; four dead, none. Without
the live three as a control the four zeros would have proved nothing about
the code and everything about the scan.</p>
<p>Three of the four orphans match their inlined twins exactly. One doesn't,
and the difference is a small window into how the original was written.
Where the live inlined version reads a unit's height once per tick before
deciding which state to run, the orphan reads it fresh, inside the state,
where the logic actually needs it. The orphan is the shape the source was
written in; the live copy is the shape the optimiser left behind after
hoisting that read out of the switch. The abandoned function is the more
faithful record of the programmer's intent, and the unreachable one is the
only place that intent survives.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="expected-exclusive-found-everywhere">Expected exclusive, found everywhere<a href="https://rets.dassheep.tech/devblog/compiled-but-never-called#expected-exclusive-found-everywhere" class="hash-link" aria-label="Direct link to Expected exclusive, found everywhere" title="Direct link to Expected exclusive, found everywhere" translate="no">​</a></h2>
<p>The task description called subterranean movement a Tiberian Sun mechanism
that the later games don't carry in this form. It seemed safe. Nothing in
Red Alert 2 or Yuri's Revenge burrows.</p>
<p>The engine is in all three, alive. The identifier the movement mode
registers itself under is byte-for-byte identical across the three
executables, and all three actually perform that registration at startup —
checked against the other movement modes as a control, so "it's in there" is
not being mistaken for "it runs." Red Alert 2 has a complete function table
for it and a factory that allocates it at the same size Yuri's Revenge
does. All three parse the map section that declares tunnels. The routine
that hands a cell its tunnel is byte-identical between Red Alert 2 and
Yuri's Revenge and structurally identical in Tiberian Sun — differing only
in <em>where</em> the field sits in the cell, which grew by a fixed amount between
the games. Same instructions, shifted field: the signature of one codebase
compiled three times.</p>
<p>So what's Tiberian Sun-exclusive isn't the machinery. It's the content —
the units that use it, and the tunnel artwork. The later games kept the
whole mechanism and shipped nothing that employs it.</p>
<p>That's the second time this has happened. Earlier work expected a Tiberian
Sun campaign feature to have been demoted in the sequels and found the same
live code in all three there too. Two for two, in the same direction: a
feature that <em>looks</em> exclusive because only one game shipped anything using
it. The absence of a unit is not the absence of an engine, and the only way
to tell them apart is to go read the other two executables instead of
noticing what isn't in the box.</p>
<p>One honest limit, written down rather than glossed: what's been shown is
that the machinery exists and runs in all three, not that it <em>behaves</em>
identically. The state handlers themselves were never compared across the
three games. Until they are, "present in all three" is the whole claim.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="why-its-worth-the-care">Why it's worth the care<a href="https://rets.dassheep.tech/devblog/compiled-but-never-called#why-its-worth-the-care" class="hash-link" aria-label="Direct link to Why it's worth the care" title="Direct link to Why it's worth the care" translate="no">​</a></h2>
<p>None of this is behavior a player consciously tracks. Nobody counts which
flag decided whether a campaign ending was silent or came with a credits
screen, or notices a Firestorm game drawing from a different point in the
random sequence than a base game would. That's exactly why it has to be
exact — faithfulness isn't measured by the moments people notice, it's
measured by the ones they don't, where a small deviation compounds quietly
until a saved game or a replay stops matching the game it came from.</p>
<p>Every story this week shared the same shape underneath. Something that
looked like the answer — a well-named function, a rounding call, a routine
that mirrors its own opposite, a port contract, a checklist, a filename we
half-remembered — wasn't quite it, or wasn't all of it. The fix was never
cleverness, just going back to the raw instructions or the raw list and
checking what was actually there instead of what the label promised. That
held for the game's own code and, four times this week, for our own tools
and our own claims in exactly the same way — a search flag that quietly
narrowed what it looked at, a checklist that quietly excluded finished
work, a claim about how narrowly a wall's effects reached, and a claim that
the same wall involved no randomness at all. The last two were both offered
before we'd read far enough to know better, and the last one had already
been written into working code by the time it was caught. All four were
caught the same way: by making the exclusion visible, or by rereading the
thing itself, instead of trusting the summary. A mission resolver, a storm's
dice, a bit that never got a name of its own, a wall that kills from two
cells away instead of one and quietly rolls dice while it does — none of it
is worth documenting accurately if the tools and the claims doing the
documenting are the next thing that needs auditing.</p>
<p>By the end of the week the shape had turned up one more time, and from the
opposite direction. Twice now, a feature we expected to be exclusive to one
game turned out to be running in all three, with only the content that uses
it missing from the sequels. And twice, a task description we wrote ourselves
turned out to describe a system that doesn't exist — one mechanic where there
are really two, tied together by a connection the code never makes. Both were
settled the same way as everything else here: not by arguing about the
description, but by scanning the actual code for whether the connection was
there at all, and by opening the other two executables instead of trusting
what wasn't in the box. The label is the hypothesis. The instructions are the
answer.</p>]]></content>
        <author>
            <name>The reTS project</name>
            <uri>https://github.com/DasSheep/reTS</uri>
        </author>
        <category label="devblog" term="devblog"/>
        <category label="campaign" term="campaign"/>
        <category label="carryover" term="carryover"/>
        <category label="ion-storm" term="ion-storm"/>
        <category label="firestorm" term="firestorm"/>
        <category label="randomness" term="randomness"/>
        <category label="tiberian-sun" term="tiberian-sun"/>
        <category label="multi-game" term="multi-game"/>
        <category label="faithful-port" term="faithful-port"/>
        <category label="verification" term="verification"/>
        <category label="reference" term="reference"/>
        <category label="tooling" term="tooling"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[What the Engine Already Knew]]></title>
        <id>https://rets.dassheep.tech/devblog/what-the-engine-already-knew</id>
        <link href="https://rets.dassheep.tech/devblog/what-the-engine-already-knew"/>
        <updated>2026-07-24T15:00:00.000Z</updated>
        <summary type="html"><![CDATA[A lot of what a twenty-five-year-old game "does" lives in community memory as]]></summary>
        <content type="html"><![CDATA[<p>A lot of what a twenty-five-year-old game "does" lives in community memory as
much as in the executable — house rules, remembered quirks, features everyone is
sure were mods. Spend a day reading the binary of that game and a pattern
emerges: the engine already knew. The rule you half-remember is there, usually
more precise and stranger than the memory of it. Today was seven passes over the
selection, command, alliance, AI, spectator, and campaign-scripting layers, and
the throughline was the same each time — the machinery was already present, and
our job was to state exactly what it does rather than what we assumed.</p>
<!-- -->
<p><em>Last verified against the project oracle: 2026-07-24.</em></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-cursor-already-knows-what-you-mean">The cursor already knows what you mean<a href="https://rets.dassheep.tech/devblog/what-the-engine-already-knew#the-cursor-already-knows-what-you-mean" class="hash-link" aria-label="Direct link to The cursor already knows what you mean" title="Direct link to The cursor already knows what you mean" translate="no">​</a></h2>
<p>Two of the day's passes were about the moment a pointer position becomes an
<em>intention</em>. The first: when you drag a selection box over a mixed group and one
unit becomes the "primary" — the one the command cursor and voice lines answer
to — which unit wins? The intuitive guess is "the one you own" or "the first one
clicked." The binary says neither. The tie-breaker is whether a unit's <em>type</em>
has a usable primary weapon. An armed unit leads a mixed box; unarmed units just
come along. It is the classic behavior every player has felt and few could state,
and the field it reads is not a house flag at all but a computed property of the
unit type — an identity we pinned four independent ways before letting the name
stand.</p>
<p>The second pass reversed the function that decides, as you sweep the pointer
across the map, which action the cursor is offering — attack, move, sell, repair,
place a beacon, plan a waypoint. This is the exact seam a standalone engine needs
to get right, because it is where input stops being a mouse and becomes an
abstract command — the same seam a touch or gamepad control scheme has to drive
without a mouse existing at all. The surprise was structural: a value we had
pencilled in as an exotic "mode" flag is simply <em>how many units are selected</em>.
That reframes the whole thing. With units selected, the cursor is decided by the
selected unit itself; only on an <em>empty</em> selection does the engine fall through to
the sticky command modes. A whole ladder of behavior collapses into one early
branch — and the fall-through even still emits one action code the community
headers literally name after a Tiberian-Sun-era bug, a fossil the retail build
never tidied away. We preserved it rather than fixing it; faithful means faithful.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-opponents-real-rules-and-the-ones-it-doesnt-have">The opponent's real rules, and the ones it doesn't have<a href="https://rets.dassheep.tech/devblog/what-the-engine-already-knew#the-opponents-real-rules-and-the-ones-it-doesnt-have" class="hash-link" aria-label="Direct link to The opponent's real rules, and the ones it doesn't have" title="Direct link to The opponent's real rules, and the ones it doesn't have" translate="no">​</a></h2>
<p>Three passes went at the computer opponent, and each replaced a piece of folklore
with a smaller, sharper fact.</p>
<p>Alliances first. The whole system is a single row of bits living in the <em>asking</em>
player's memory — which is why the relationship is one-directional under the
hood. Forming an alliance sets only your own bit toward the other side; breaking
one tears down both directions at once. Buried in the "can I ally this player"
check was a rule intuition gets backwards: you may not form an alliance that would
leave you allied to <em>everyone</em>. You cannot ally your last enemy — and, as a
corollary, in a one-on-one you cannot ally at all. Our first oracle test caught
this the honest way: it tried to ally in a two-player world and the port correctly
refused. An adversarial re-read then turned up a genuine defect — the alliance
break clears a secondary mask with an idiom that wipes the whole thing instead of
one bit — and the three-engine reconcile found that same defect sitting
byte-identically in all three games. Not a late regression; an inherited flaw
that shipped in every version.</p>
<p>Then the difficulty ladder — the little block of numbers that decides which
things the AI is <em>allowed</em> to do on its own: fire a superweapon, build a base,
crush infantry, scatter from grenades. Each behavior unlocks when the house's
difficulty rating clears that behavior's rung. The catch a naive rebuild gets
wrong: ask for a rating <em>above</em> the maximum and the engine does not clamp it to
the top — it resets it to nearly the lowest setting. Write the value expecting a
genius and you get a near-idiot. And one rung in the list is read by nothing at
all — a dead rule, faithfully dead in all three engines.</p>
<p>The third AI pass was a sweep of the "cheats" — the structural advantages the
engine grants a computer house that a human never gets. The reconcile came back
with thirty-seven genuine ones and, just as usefully, four popular "cheats" the
binary does not actually gate to the AI. The flagship correction was vision. The
folklore says the AI sees through the fog of war. The bytes say something
stranger: there is no fog <em>for</em> the AI to see through. Shroud and discovery exist
only for the one local viewing player; there is nowhere to store a per-house fog
even in principle. The AI isn't defeating a visibility check — no such check was
ever placed on its path, while the human's identical scan is gated to skip what it
hasn't scouted. The most iconic advantage — the coalition that force-allies every
computer house against the humans in one sweep — now ships as a toggle you can
switch off, so the AI has to build its alliances one relationship at a time, like
a person.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="machinery-that-was-always-there">Machinery that was always there<a href="https://rets.dassheep.tech/devblog/what-the-engine-already-knew#machinery-that-was-always-there" class="hash-link" aria-label="Direct link to Machinery that was always there" title="Direct link to Machinery that was always there" translate="no">​</a></h2>
<p>The last two passes were the purest archaeology. Spectator mode has been treated
as mod territory for twenty years; the retail game ships it — a dedicated
observer slot, a spectator sidebar that draws a roster instead of a build queue,
an elapsed-game clock where the credits counter goes, and a scoreboard that
quietly excludes the watching player. The compiler even left the original
observer source filename sitting in the string table. The mechanism decomposed
into three <em>different</em> "is this an observer" tests that the community patch layer
usually flattens into one — and keeping them apart mattered, because two of the
flattened claims tested false against the retail image. The end-of-match
scorecard that tools scrape is <em>computed</em> by the retail build and then handed to a
logging function whose body is empty — the release compiled the logging out, so
those famous stats lines only exist because patches re-enable the sink. Present
in one sibling engine, provably absent in the older one: the observer is a later
addition, and we said so with a search thorough enough to earn the word "absent"
rather than "not found."</p>
<p>The final pass reversed the store behind every campaign that ever <em>remembered</em>
something — the named 0/1 flags a mission's triggers set and test to branch the
story. Two little arrays on the scenario: a set of <em>global</em> flags that persist
across a whole campaign, and a larger set of <em>local</em> flags that reset every map.
Three earlier studies had each touched a corner of this from their own side — the
trigger that writes a flag, the trigger that reads one, the loader that first
parses them — but nobody had built the thing they all operate on. Consolidating it
turned up how persistence actually splits. The flags are not in the save file at
all. Local flags round-trip only within one map's own text. And the global flags
— the ones that survive from mission two into mission five — persist by an
<em>explicit</em> hand-off: a routine at the win/lose boundary bulk-copies all of them
into the next mission before it pays out carryover money and starts the clock.
They persist because something deliberately carries them, not by accident. The
reconcile pinned one clean divergence — two of the three engines cap the local
flags at fifty; the last doubled them — and we captured a faithful sharp edge
along the way: an unguarded name copy that a pathological map could overflow,
documented as a quirk rather than silently hardened.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-method-under-all-of-it">The method under all of it<a href="https://rets.dassheep.tech/devblog/what-the-engine-already-knew#the-method-under-all-of-it" class="hash-link" aria-label="Direct link to The method under all of it" title="Direct link to The method under all of it" translate="no">​</a></h2>
<p>Seven passes, one discipline. The disassembly is the only oracle; community lore,
reference headers, and even our own earlier notes are corroboration that has to
survive an adversarial re-read whose only job is to <em>disprove</em> the claim. Every
finding is reconciled across all three engines, because a behavior that is
identical in three binaries is a shared-source fact and a behavior that differs is
a documented divergence — and today's differences were small and precise: an
inherited alliance defect, a doubled flag array, an observer feature that arrived
one engine later. The parts of a game that live in memory are worth checking
against the parts that live in the executable. Most of the time, the engine
already knew.</p>]]></content>
        <author>
            <name>The reTS project</name>
            <uri>https://github.com/DasSheep/reTS</uri>
        </author>
        <category label="devblog" term="devblog"/>
        <category label="faithful-port" term="faithful-port"/>
        <category label="ai" term="ai"/>
        <category label="campaign" term="campaign"/>
        <category label="multi-game" term="multi-game"/>
        <category label="verification" term="verification"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[Before the First Tank Moves]]></title>
        <id>https://rets.dassheep.tech/devblog/before-the-first-tank-moves</id>
        <link href="https://rets.dassheep.tech/devblog/before-the-first-tank-moves"/>
        <updated>2026-07-22T16:00:00.000Z</updated>
        <summary type="html"><![CDATA[A skirmish match seems to begin when the battlefield appears. The engine has a]]></summary>
        <content type="html"><![CDATA[<p>A skirmish match seems to begin when the battlefield appears. The engine has a
different answer. Long before the first tank accepts an order, it has already
turned dialog controls into a match contract, raw map records into terrain,
and terrain into connectivity rules that decide where every ground unit may
go. This week we followed that preparation chain from both ends.</p>
<!-- -->
<p><em>Last verified against the project oracle: 2026-07-22.</em></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-match-behind-the-dialog">The match behind the dialog<a href="https://rets.dassheep.tech/devblog/before-the-first-tank-moves#the-match-behind-the-dialog" class="hash-link" aria-label="Direct link to The match behind the dialog" title="Direct link to The match behind the dialog" translate="no">​</a></h2>
<p>The Skirmish screen is not merely presentation. Its commit handler writes the
live match-options structure and the player's saved preferences from the same
set of controls. Those are parallel destinations: the preferences do not get
loaded back and transformed to create the live match. This distinction matters
for a standalone client, because reproducing the visible dialog without the
exact commit contract would produce a match that looks configured correctly
but behaves differently.</p>
<p>One initialization routine gave us the backbone of that contract. It copies
seventeen defaults from the rules into the live options in a straight pass.
The dialog then overwrites the fields it owns. Two choices are more opinionated
than the UI suggests: bridge destruction is committed on, while the
multi-engineer option is committed off. The dialog is not simply preserving
whatever value an INI file supplied.</p>
<p>The game-mode catalog is similarly concrete. The engine probes six named INI
sections in a fixed order and instantiates one of several multiplayer-mode
classes. A previously undocumented cooperative class is present alongside the
better-known modes. The Siege machinery survives too, but its shipped section
is empty in both Red Alert 2 and Yuri's Revenge — working engine support for a
piece of cut content.</p>
<p>There is also a boundary worth stating plainly: the unmodified retail
executables do not read <code>spawn.ini</code>. That launch surface belongs to the later
community ecosystem. The faithful engine's own mode lookup is therefore the
right seam for supporting both the retail dialog and modern headless launch
configuration without pretending they came from the same original parser.</p>
<p>Difficulty finishes the configuration story. Assigning a handicap copies one
of three rule rows into a house's runtime multipliers. Computer houses use a
mirrored index — effectively <code>2 - difficulty</code> — rather than the player's row
directly. The original also carries a real uninitialized-stack write in this
path, shared by Tiberian Sun and Yuri's Revenge. It is documented as a retail
quirk rather than quietly rationalized away.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="a-map-cell-is-a-decision-not-a-file-record">A map cell is a decision, not a file record<a href="https://rets.dassheep.tech/devblog/before-the-first-tank-moves#a-map-cell-is-a-decision-not-a-file-record" class="hash-link" aria-label="Direct link to A map cell is a decision, not a file record" title="Direct link to A map cell is a decision, not a file record" translate="no">​</a></h2>
<p>Once the match options exist, the map still is not ready for movement. A cell
starts as a tile index, a sub-tile, a height, and perhaps an overlay. The setup
pass turns those ingredients into the <code>LandType</code> used by passability and
pathfinding.</p>
<p>The order of that decision is surprisingly consequential:</p>
<ul>
<li class="">An overlay first imprints its own land type. Walls, railroads, and specially
flagged overlays are authoritative; the underlying tile does not override
them.</li>
<li class="">Ordinary cells validate their sub-tile and derive land and slope from the
loaded TMP tile record. Invalid sub-tiles are blanked rather than trusted.</li>
<li class="">Tiberium is conditional terrain. On a shallow clear slope it becomes
Tiberium land; on a steep slope the engine removes the overlay.</li>
<li class="">Tunnel mouths can request a tube link as part of setup.</li>
<li class="">A cliff safety pass examines six neighbors in a fixed, asymmetric pattern.
With the shipped <code>CliffBackImpassability=2</code>, a sufficiently high cliff behind
a cell forces that cell to Rock, making the back of the cliff impassable.</li>
</ul>
<p>That last rule was hiding in plain sight. It had once looked like an editor
safety measure; tracing the rules loader showed it is a normal, enabled
gameplay option in every shipped rules set. It changes where units can stand,
so it belongs in the simulation contract.</p>
<p>Here is the preparation pipeline as the engine effectively sees it:</p>
<!-- -->
<p>Each arrow is now an explicit implementation seam rather than an assumption
that “the map loader probably handles it.”</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-ring-around-the-playable-map">The ring around the playable map<a href="https://rets.dassheep.tech/devblog/before-the-first-tank-moves#the-ring-around-the-playable-map" class="hash-link" aria-label="Direct link to The ring around the playable map" title="Direct link to The ring around the playable map" translate="no">​</a></h2>
<p>The next question above those connectivity tables is named exactly as you
would expect: are two cells in the same zone? But the retail routine does more
than compare two integers.</p>
<p>It distinguishes the scenario's usable rectangle from the full isometric map
diamond. A source cell in the rim between those boundaries is accepted before
the engine consults either zone table. With a separate leave-map flag, a unit
starting in the usable area may also target that rim. Only the ordinary case
resolves both cells — including their independent bridge-deck rules — and
compares the resulting zone ids.</p>
<p>That permissive rim is deliberate boundary policy. Flattening the function to
<code>zone(source) == zone(destination)</code> would make transitions at the edge of the
playable area disagree with the original even if every zone id were otherwise
perfect.</p>
<p>The same control-flow shape appears in Tiberian Sun, Red Alert 2, and Yuri's
Revenge. The work also located Red Alert 2's previously missing cell-zone
resolver, completing the three-game lineage for this part of the map contract.
The port records which of the four exits accepted or rejected the query, and
its tests cover the boundary rim, the leave-map gate, equal and unequal zones,
and bridge handling at each endpoint.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="three-engines-one-preparation-chain">Three engines, one preparation chain<a href="https://rets.dassheep.tech/devblog/before-the-first-tank-moves#three-engines-one-preparation-chain" class="hash-link" aria-label="Direct link to Three engines, one preparation chain" title="Direct link to Three engines, one preparation chain" translate="no">​</a></h2>
<p>Reading the three generations together is more useful than stamping everything
“shared” or “different.” The terrain-tag table is byte-identical across all
three. Red Alert 2's cell-setup pass closely matches Yuri's Revenge. Tiberian
Sun has the recognizable ancestor, but keeps a veins-era case and different
height-step behavior. The same-zone policy, on the other hand, is structurally
the same in every binary.</p>
<p>That is the shape reTS needs: shared code where the instructions support it,
versioned behavior where the engines genuinely diverge, and no modernization
layer allowed to blur the distinction. A modern lobby may feed the match
contract. A modern renderer may draw the resulting world. Neither gets to
silently change how a steep tiberium slope is cleared or how a unit crosses the
edge of the usable map.</p>
<p>Before the first tank moves, the engine has already made thousands of small,
ordered decisions. Faithfulness means those decisions are part of the game —
not invisible setup we can replace with something merely plausible.</p>]]></content>
        <author>
            <name>The reTS project</name>
            <uri>https://github.com/DasSheep/reTS</uri>
        </author>
        <category label="devblog" term="devblog"/>
        <category label="faithful-port" term="faithful-port"/>
        <category label="map" term="map"/>
        <category label="skirmish" term="skirmish"/>
        <category label="multi-game" term="multi-game"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[A Bridge Over Water]]></title>
        <id>https://rets.dassheep.tech/devblog/a-bridge-over-water</id>
        <link href="https://rets.dassheep.tech/devblog/a-bridge-over-water"/>
        <updated>2026-07-21T13:00:00.000Z</updated>
        <summary type="html"><![CDATA[March a column of tanks across a bridge in Red Alert 2 and they cross without]]></summary>
        <content type="html"><![CDATA[<p>March a column of tanks across a bridge in Red Alert 2 and they cross without
a second thought. That "without a second thought" is doing a lot of work.
Underneath the bridge is water — impassable to a tank — and yet the two banks
behave as if they were one continuous stretch of land. This week we reversed
the routine that makes that true, and then spent the back half of the session
arguing with ourselves about whether we'd broken something along the way.</p>
<!-- -->
<p><em>Last verified against the project oracle: 2026-07-21.</em></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="zones-and-the-question-they-answer">Zones, and the question they answer<a href="https://rets.dassheep.tech/devblog/a-bridge-over-water#zones-and-the-question-they-answer" class="hash-link" aria-label="Direct link to Zones, and the question they answer" title="Direct link to Zones, and the question they answer" translate="no">​</a></h2>
<p>Pathfinding in this engine doesn't ask "is there a route from A to B?" every
time something wants to move — that would be ruinously expensive on a big map.
Instead the map is pre-divided into <strong>connectivity zones</strong>: contiguous regions
a given kind of mover can travel within. Two cells in the same zone are
reachable from each other; two cells in different zones are not, at least not
without a special crossing. So the fast question a unit asks before it bothers
to plan a path is simply "are we in the same zone?" — one lookup, no search.</p>
<p>Every cell carries its zone id, indexed per movement type (what's one connected
zone for a hovercraft is several disconnected islands for a tank). The routine
we reversed this week is the one that answers "which zone is this cell in?" — the
lookup that gates every reachability check in the game.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-bridge-deck-problem">The bridge deck problem<a href="https://rets.dassheep.tech/devblog/a-bridge-over-water#the-bridge-deck-problem" class="hash-link" aria-label="Direct link to The bridge deck problem" title="Direct link to The bridge deck problem" translate="no">​</a></h2>
<p>For an ordinary cell the answer is a straight table read. Bridges are where it
gets interesting. A bridge deck sits <em>over</em> water. Taken at face value its cell
belongs to the water — impassable, disconnected, an island of one. If the lookup
returned that, no ground unit would ever agree to step onto a bridge, because the
deck would read as unreachable from either bank.</p>
<p>So the engine does something craftier. When the query lands on a bridge deck, it
doesn't return the deck's own zone. It consults a table of <strong>zone connections</strong> —
records that tie a bridge span to the land it joins — and, unless the connection
is already flagged as resolved, it <strong>walks across the span</strong>: east if the bridge
runs horizontally, south if it runs vertically, one cell at a time, until it
steps off the far end onto solid ground. It checks that the landing cell is real
terrain — a bridge or cliff tile, not bare rock or open water — and then borrows
<em>that</em> cell's zone. The deck inherits the connectivity of the shore it reaches.
Both banks, and the span between them, resolve to a single zone, and the tank
crosses without a second thought.</p>
<p>It's a small, self-contained piece of cleverness, and it's the kind of thing
that only survives a faithful port if you copy the <em>exact</em> rules: which direction
the walk goes, what counts as a valid landing, and — a detail the summary we
started from had quietly smoothed over — that the endpoint search is
axis-asymmetric. A vertical span measures distance one way; a horizontal span
measures it the other. Copy the summary and you swap them; copy the instructions
and you don't.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="two-grids-that-looked-like-one">Two grids that looked like one<a href="https://rets.dassheep.tech/devblog/a-bridge-over-water#two-grids-that-looked-like-one" class="hash-link" aria-label="Direct link to Two grids that looked like one" title="Direct link to Two grids that looked like one" translate="no">​</a></h2>
<p>Having landed the <em>reader</em>, we went looking for the <em>writer</em> — the routine that
fills in all those zone ids in the first place. We found the map's
pathfinding-rebuild code, and with it a genuinely alarming coincidence.</p>
<p>The rebuild pass walks a per-cell grid using the <strong>same</strong> indexing math the
reader uses — the same stride, and the same slightly-unusual "clamp to the last
valid slot" rule we'd carefully matched last week. Same formula, down to the
off-by-one. But the records it writes are <em>ten bytes wide</em>, and the records the
reader reads are <em>four</em>. Same index, different record size, on what appeared to
be the same array. If it really was the same array, then one of the two record
sizes was wrong — and the one we'd shipped was the reader's.</p>
<p>It wasn't the same array. The map keeps <strong>two parallel grids</strong>, one entry per
cell in each, sharing only a count and an index formula: a compact four-byte grid
holding the connectivity zone the reader returns, and a wider ten-byte grid the
pathfinding rebuild uses as scratch space. The writer zeroes and refills the
scratch grid; it never touches the field the reader reads. Two structures that
are trivial to conflate precisely because they're indexed identically — and that
share nothing else.</p>
<p>We didn't want to take our own word for that, because "the code you shipped is
fine, actually" is exactly the conclusion a tired reviewer wants to reach. So the
reconciliation ran as an adversarial exercise: independent passes re-derived both
record layouts from the raw instructions, from different starting assumptions,
each trying to prove a bug existed — cross-checked against the original engine's
own published structure names. They converged: last week's port is faithful, the
two grids are distinct, no bug. The review <em>confirmed</em> the earlier work instead
of overturning it, and named a new structure on the way through.</p>
<p>There was a bonus in the confusion. It corrected our roadmap. We'd assumed one
builder fed the zone reader; in fact the pathfinding chain we found builds the
<em>scratch</em> grid, and the reader's grid is filled by a different routine we haven't
located yet. That's two clean next steps instead of one tangled one — and the
shared indexing math is now a single tested primitive both grids, and both future
builders, will call.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="why-the-fuss">Why the fuss<a href="https://rets.dassheep.tech/devblog/a-bridge-over-water#why-the-fuss" class="hash-link" aria-label="Direct link to Why the fuss" title="Direct link to Why the fuss" translate="no">​</a></h2>
<p>None of this changes what a bridge looks like on screen. It changes whether a
faithful reimplementation <em>agrees</em> with the original about which cells can reach
which — the invisible substrate under every unit that has ever pathed around a
lake. Getting the bridge-borrow rule exactly right, and proving the grid
underneath it is modeled exactly right, is the difference between "our
pathfinding looks about the same" and "our pathfinding is the same." The second
one is the whole point.</p>]]></content>
        <author>
            <name>The reTS project</name>
            <uri>https://github.com/DasSheep/reTS</uri>
        </author>
        <category label="devblog" term="devblog"/>
        <category label="faithful-port" term="faithful-port"/>
        <category label="pathfinding" term="pathfinding"/>
        <category label="map" term="map"/>
        <category label="adversarial-verify" term="adversarial-verify"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[Where the Turrets Go]]></title>
        <id>https://rets.dassheep.tech/devblog/where-the-turrets-go</id>
        <link href="https://rets.dassheep.tech/devblog/where-the-turrets-go"/>
        <updated>2026-07-20T14:30:00.000Z</updated>
        <summary type="html"><![CDATA[If you've ever watched a skirmish AI in Red Alert 2 quietly ring its base]]></summary>
        <content type="html"><![CDATA[<p>If you've ever watched a skirmish AI in Red Alert 2 quietly ring its base
with prism towers on exactly the side you were massing tanks, you've seen
this week's subject in action. We spent the day dissecting the routines that
decide <em>where defensive structures go</em>, <em>when</em> the AI is even allowed to want
them, and the ancient trigonometry humming underneath both — the last unmapped
pieces of the AI's base-building brain, and the functions standing between us
and a bit-exact port of that entire decision path.</p>
<!-- -->
<p><em>Last verified against the project oracle: 2026-07-20.</em></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="a-compass-around-the-base">A compass around the base<a href="https://rets.dassheep.tech/devblog/where-the-turrets-go#a-compass-around-the-base" class="hash-link" aria-label="Direct link to A compass around the base" title="Direct link to A compass around the base" translate="no">​</a></h2>
<p>The placement engine thinks in compass quadrants. Every candidate cell gets
classified into one of four directions around the base center — the engine
computes a bearing with its own table-driven arctangent (much more on that
below), squashes it to a 16-bit "binary angle," and keeps just the top three
bits' worth: north-ish, east-ish, south-ish, west-ish.</p>
<p>Then it asks: which direction am I weakest? Every building the AI owns
reports three numbers — how good it is against infantry, against armor, and
against aircraft — and those get stamped into three threat heat-maps with a
gentle falloff: full value on the building's cell, fading over a six-cell
radius. Sum each quadrant, pick the smallest total, and that's where the
next gun goes. There's a subtle eligibility rule our adversarial review pass
caught: a numerically weaker direction is <em>skipped</em> if no candidate cells
were actually offered on that side. The math says "build north," but if
north has nowhere to build, the second-weakest side wins.</p>
<p>Here is the whole decision, including the fallback we'll come to next:</p>
<!-- -->
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="counting-the-dice-rolls">Counting the dice rolls<a href="https://rets.dassheep.tech/devblog/where-the-turrets-go#counting-the-dice-rolls" class="hash-link" aria-label="Direct link to Counting the dice rolls" title="Direct link to Counting the dice rolls" translate="no">​</a></h2>
<p>Deterministic lockstep multiplayer means every random number matters. Both
players' machines run the same simulation and only exchange inputs, so if
one machine draws three random numbers where the other draws four, the game
desyncs — not crashes, just quietly diverges until the "reconnection error"
box appears. That's why an AI routine with a <em>variable</em> number of internal
dice rolls was a blocker: we couldn't port anything downstream of it until
every roll was accounted for.</p>
<p>The full census turned out to be beautifully finite. In Yuri's Revenge the
routine makes zero to four draws per call: up to three to add fuzz to the
AI's "what mix of defenses do I want" targets (skipped entirely when the AI
has no elected enemy — and, in a path our verification pass flagged, still
<em>burned</em> even when the fuzz drives the totals negative and the result gets
thrown away), plus at most one for a weighted lottery over eligible defense
types (skipped when there's only one candidate).</p>
<p>Then came the genuine surprise: <strong>Red Alert 2 doesn't make the fuzz draws at
all.</strong> Its version of the same routine hardcodes the target mix to a flat
one-third each and never touches the random stream for it. Same function,
same shape, three fewer dice rolls — the second confirmed case where the two
sibling engines disagree about <em>when randomness happens</em>, after the
dispatch-cadence difference we wrote about earlier. For anyone dreaming of
cross-game compatibility layers: this is why you can't just swap rules files
and hope.</p>
<p>Two smaller routines fell alongside the main event, and both matter for the
same census. One is the AI's base-plan maintenance pass, which has a charming
permanent side effect: every time it runs, it grows the AI's notion of its own
base footprint by one cell in every direction, then walks the perimeter
looking for stretches of five or more buildable cells to reserve as future
expansion nodes. Run it enough times and the AI's "base" is most of the map.
The other seeds placeholder nodes for power plants around an existing one — and
our review pass proved it draws no random numbers anywhere in its call tree,
upgrading an earlier "probably clean" into a certainty. The adversarial wave
earned its keep beyond that: it overturned six claims from the first-pass
analysis, including one where a dispatch helper's call into the
base-construction brain — the only path to the random stream in that
neighborhood — turned out to be <em>conditional</em>, not unconditional as first read.
A ported version built on the first reading would have drawn dice on ticks
where the original didn't. That's the whole reason every finding gets a hostile
second pass before it becomes code.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="when-the-candidate-list-runs-dry">When the candidate list runs dry<a href="https://rets.dassheep.tech/devblog/where-the-turrets-go#when-the-candidate-list-runs-dry" class="hash-link" aria-label="Direct link to When the candidate list runs dry" title="Direct link to When the candidate list runs dry" translate="no">​</a></h2>
<p>So the compass tells the AI which <em>side</em> is weakest. But what happens when
that side has no cells to offer — when the candidate list is simply empty?
That job belongs to a second, larger routine, the map-wide placement scanner,
and it turned out to have three personalities.</p>
<p>If the building being placed is <strong>naval</strong>, the scanner doesn't scan at all: it
calls the game's actual pathfinder with the floating movement class, asks it
for open water near the base center sized to a shipyard footprint, and then
rejects anything farther from the house's first Construction Yard than a
rules-configurable adjacency distance — the naval-yard adjacency knob modders
know from the INI. If the base has no center yet, it just returns the base
anchor. Otherwise it scores a per-house list of base-perimeter cells — through
the threat grid, or by plain distance-to-base for cloak generators (support
structures don't chase threat) — sorts, and walks the winners through a
placement dance: look at all eight neighbors of a candidate, sum up the
compass directions of the ones already reserved by this house's base, convert
that <em>vector sum</em> into one of eight octants, and try to tuck the building on
the corresponding side, offset by its foundation plus the AI base-spacing
margin (plus one extra cell for buildings that want walls or elbow room — the
rule names literally explain the +1).</p>
<p>The hostile-review pass earned its keep three times over. Our own first
reading said the neighbor scan "keeps the last match" — refuted; it's a
running sum, which means a candidate with owned cells on <em>opposite</em> sides
cancels to zero and gets skipped exactly as if it were isolated. A claimed
uninitialized-memory argument turned out to be a deterministic zero hiding
behind two disguised half-word writes. And the strangest find: the whole
attempt phase runs <strong>twice</strong>, and an exhaustive trace proved the second pass
is byte-for-byte dead logic — a fossil the compiler faithfully preserved for
twenty-five years. Our version runs it once and documents why.</p>
<p>Cross-game, the scanner sharpened the picture too. Red Alert 2's copy drops a
safety check Yuri's Revenge keeps — a null guard on the picked type that, if
you ported the newer engine naively, is a reachable crash. Tiberian Sun's
defense-placement brain passed its own exhaustive audit: one gated random draw
is its <em>entire</em> relationship with the dice, but its pick weighting quietly adds
a cost bonus that neither sibling has — cheap defenses get a leg up in Tiberian
Sun. Three engines, one function, three personalities, and the test suite now
knows all three.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-math-that-time-forgot">The math that time forgot<a href="https://rets.dassheep.tech/devblog/where-the-turrets-go#the-math-that-time-forgot" class="hash-link" aria-label="Direct link to The math that time forgot" title="Direct link to The math that time forgot" translate="no">​</a></h2>
<p>Underneath all of this — every bearing, every distance, every angle — sits
something delightful: the engine doesn't compute square roots or trigonometry
the way any modern program would. It ships lookup tables and does a few bit
tricks on the floating-point representation to index them. Classic 1990s speed
engineering, shared byte-for-byte across Tiberian Sun, Red Alert 2, and Yuri's
Revenge, and used by hundreds of call sites across the whole engine: bullets,
pathfinding, rendering, everything.</p>
<p>For our port this is a gift and a trap. The square-root table (sixteen
thousand entries) we managed to regenerate <em>bit-exactly</em> from a closed-form
formula — every one of 16,384 entries matches — because it only needs
operations the IEEE floating-point standard guarantees to the last bit on
every platform. The arctangent, sine, and cosine tables are the trap: they
were baked decades ago by whatever C runtime the original compiler shipped, and
modern math libraries land up to fourteen units-in-the-last-place away from
them. No formula we can responsibly write reproduces them; the shipped data
<em>is</em> the specification. So the port carries those tables embedded verbatim,
with witness values pinned in tests — 4,094 of the arctangent table's 4,097
entries disagree with a modern library, which settles the argument. The sine
and cosine tables (one shared ten-thousand-entry table, one full period plus a
quarter-period tail so cosine's phase shift never wraps) carry a faithful
imperfection we now reproduce <em>on purpose</em>: negative angles round <em>away</em> from
the nearest entry, so the sine of −90° lands two slots off from where a clean
implementation would put it — returning not −1.0 but −0.9999988. The game has
been slightly wrong, identically, on every machine, for twenty-five years — and
now so are we.</p>
<p>The sharpest catch in this whole family was a single number. The constant that
converts angles into the engine's 16-bit compass has been described — by us and
by community lore alike — with a tidy formula: negative 65536 over 2π. A
reviewer pulled the actual eight bytes out of the executable and did the
arithmetic: the real value is <strong>negative 32767 over π</strong>, off from the folklore
by exactly 1/π. Our code already carried the correct extracted value, but any
future cleanup that "simplified" the literal into the pretty formula would have
quietly bent every angle computation in the game. The lesson is a running theme
here: the extracted bytes are the oracle, and the elegant closed form is a
hypothesis until it matches them.</p>
<p>One more entry for the "one percent is never one percent" file, in the same
spirit: the fuzz magnitude scales a difficulty setting by a floating-point
0.01 — which, as binary fractions go, doesn't exist. The nearest representable
value is 0.00999999977..., so a difficulty of 50 against a base of 3000 fuzzes
by ±1499, not the ±1500 you'd compute on paper. Our first hand-derived test
said 1500; the port said 1499; the port was right. We keep catching our own
arithmetic with this class of bug, which is exactly why every expected value
gets computed twice, two different ways, before it becomes an oracle.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-when-not-just-the-where">The "when," not just the where<a href="https://rets.dassheep.tech/devblog/where-the-turrets-go#the-when-not-just-the-where" class="hash-link" aria-label="Direct link to The &quot;when,&quot; not just the where" title="Direct link to The &quot;when,&quot; not just the where" translate="no">​</a></h2>
<p>Knowing <em>where</em> and <em>what</em> the AI builds only answers half of it. Every AI
team in the game is born from a trigger list, and each trigger runs a predicate
— a cascade of gates and a nine-way condition check — before it's even allowed
a lottery ticket. That predicate is now fully reversed, adversarially reviewed,
and ported with a spec test on every gate. It rolls no dice at all, which is
good news for lockstep. The gates have opinions: while a house hasn't met its
per-difficulty quota of base-defense teams, <em>only</em> base-defense triggers may
fire; once the team builder signals there's enough defense, they're excluded
instead. The skirmish difficulty toggles are indexed by an enum that counts
<em>backwards</em> (hardest = 0), and a difficulty value out of range passes the gate
entirely.</p>
<p>The sharpest finds were two errors in the community's own reference headers,
trusted for years: a comparator struct whose two field names are swapped
relative to the real binary layout (the threshold and the operator code live in
each other's slots), and a condition enum whose first two labels are crossed
(the first inspects the <em>enemy's</em> arsenal, the second your own — the opposite
of what the names say). Port from the headers alone and you'd compare the wrong
number with the wrong operator against the wrong player. The binary remains the
only witness that never misremembers. Two curios rode along: the "superweapon
charged" conditions never actually check <em>charged</em> — they check <em>granted</em>,
within a tunable slack of readiness — and they inspect the asking player's
superweapons rather than the enemy's, thanks to a one-instruction stack quirk.
Cross-game, Red Alert 2's copy is instruction-identical, while Tiberian Sun's
is the visible ancestor: fewer condition kinds, a smaller class, and no
base-reachability gate at all — that geography check (can my base even <em>reach</em>
yours, amphibiously if need be) arrived with Red Alert 2.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-placement-finder-that-never-was">The placement finder that never was<a href="https://rets.dassheep.tech/devblog/where-the-turrets-go#the-placement-finder-that-never-was" class="hash-link" aria-label="Direct link to The placement finder that never was" title="Direct link to The placement finder that never was" translate="no">​</a></h2>
<p>There's a function whose community-known name says <em>pathfinding</em> — and it has
nothing to do with routes. It's the <strong>placement finder</strong>: the routine every
scattered infantryman, deploying vehicle, rallying factory, and teleport
landing asks the same question — <em>"find me a valid cell near here."</em> It's now
fully reversed, along with all five of the helper predicates it composes, and
it's the primitive the naval branch of the scanner above actually calls.</p>
<p>Its search is a square ring expanding outward from the start cell, each
candidate run through a gauntlet: is the cell inside the playable diamond, can
this unit's whole footprint stand there, is the ground close enough in height,
is it clear to build on if asked. Collect up to twenty-four survivors, then
pick. The pick is the fun part. If the caller named a target to be near, it's a
straight nearest-wins scan through the engine's table square root. If the
caller <em>didn't</em> — and here's the quirk — the engine takes the current <strong>frame
number modulo the candidate count</strong>. Not the synchronized dice everything else
uses: the frame counter. Every client computes the same frame, so lockstep
survives, but the choice depends on <em>when</em> you ask, and the sentinel for "no
preference" is cell (0,0) itself — the engine literally cannot express "put me
near the map's origin corner." Better still: at ring radius zero, the scan's
top and bottom edge generators both collapse onto the start cell, so the origin
enters the candidate list <em>twice</em> and gets double weight in that modulo pick.
Faithful means reproducing that too.</p>
<p>The day's sharpest catch, though, was ours, not Westwood's. Re-deriving the
playable-diamond bounds check from a fresh disassembly showed its first
inequality is a <em>lower</em> bound — the pass condition points the other way from
what our five-day-old notes said, and our shipped port had faithfully inherited
the inverted reading. The port's tests never caught it because their expected
values were hand-computed from the same wrong formula — a perfect specimen of
the shared-misread failure mode our adversarial-verify process exists to kill.
Fresh eyes on the raw instructions caught it; doc, port, and tests are all
corrected. The lesson generalizes: a test is only as honest as the
independence of the person who computed its answer.</p>
<p>One tidy loose end fell with it. Three mysterious team-configuration fields the
AI's reachability gate reads turn out not to be map-author data at all — the
engine <em>derives</em> them from a team's unit composition (what movement class the
group shares, whether it's naval, whether it's a base-defense team, which skips
the geography check entirely). "Can my base reach yours" answers itself from
what units the team wants to field. And the finder's cross-game story is a neat
nesting: Yuri's Revenge has the fullest version, Red Alert 2 lacks one option,
Tiberian Sun lacks two — three nested generations of the same machine,
identical at the core.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="how-bridges-actually-work">How bridges actually work<a href="https://rets.dassheep.tech/devblog/where-the-turrets-go#how-bridges-actually-work" class="hash-link" aria-label="Direct link to How bridges actually work" title="Direct link to How bridges actually work" translate="no">​</a></h2>
<p>Two threads closed the day where they had to: at the very bottom of the stack,
the per-cell question everything above eventually asks — <em>"can this unit stand
here?"</em> The passability predicate is now fully reversed, fresh-eyes and
adversarially verified, and its three long-mysterious argument slots finally
have names. The best of them explains bridges. The engine keeps <em>two</em> occupancy
ledgers per cell: one for the ground, one for the bridge deck above it. With
the default arguments every retail caller passes, a bridge cell automatically
switches to the deck ledger — and <em>waives</em> the impassability of the terrain
underneath. That one four-instruction waiver is the entire reason infantry can
walk across water on a bridge.</p>
<p>Getting there on the Tiberian Sun side first meant solving a disassembler
mystery that had blocked walking its per-house AI tick for a week: an
"unresolved jump table" that fused three functions into one unreadable blob.
It turned out not to live in the tick body at all — it's a small island of
<em>data</em>, one jump table cleverly shared by two different switch statements (the
second indexes seven entries deep into the first one's table through a
byte-compression map), sitting between functions. A linear disassembler eats
those bytes as instructions and never recovers. Resolved from raw bytes, the
layout is clean: a buildability checker, a tiny owned-building lookup helper
nobody had documented, and the real tick body, each padded apart. The switch's
bounds check is an <em>unsigned</em> compare, so a sufficiently malformed prerequisite
value would wrap around it and index memory far out of bounds — a dormant hazard
the original compiler left in, cousin to one we'd already documented on the
Yuri's Revenge side.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="where-this-leaves-us">Where this leaves us<a href="https://rets.dassheep.tech/devblog/where-the-turrets-go#where-this-leaves-us" class="hash-link" aria-label="Direct link to Where this leaves us" title="Direct link to Where this leaves us" translate="no">​</a></h2>
<p>The AI's build-order brain is now decision-complete for Yuri's Revenge — <em>what</em>
to build, <em>where</em> to put it, and exactly <em>when</em> the dice get rolled — all the
way down through the trigonometric tables and the single passability predicate
the whole engine leans on, and all pinned by tests whose expected values were
hand-computed from the original machine code and, where a port and its study
could have shared a blind spot, cross-checked against the running retail game
itself. The Tiberian Sun counterparts we'd been hunting are located too, its
copies of every one of these functions read side by side with the newer engine
so each <code>identical</code> / <code>divergent</code> / <code>TS-exclusive</code> verdict is on the record.</p>
<p>The through-line of the whole day is a single discipline: the extracted bytes
are the only witness that never misremembers. A constant that community lore
had "simplified," a comparator struct whose header field names were swapped, a
diamond bounds check our own five-day-old notes had inverted — every one of
them agreed with a plausible, tidy story, and every one of them was wrong until
the raw instructions said otherwise. That's why a hostile second reader gets
the last word before anything becomes code.</p>]]></content>
        <author>
            <name>The reTS project</name>
            <uri>https://github.com/DasSheep/reTS</uri>
        </author>
        <category label="devblog" term="devblog"/>
        <category label="ai" term="ai"/>
        <category label="determinism" term="determinism"/>
        <category label="multi-game" term="multi-game"/>
        <category label="mathematics" term="mathematics"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[Anatomy of a Frame]]></title>
        <id>https://rets.dassheep.tech/devblog/anatomy-of-a-frame</id>
        <link href="https://rets.dassheep.tech/devblog/anatomy-of-a-frame"/>
        <updated>2026-07-19T15:30:00.000Z</updated>
        <summary type="html"><![CDATA[Every real-time strategy game is secretly a metronome. Thirty times a second]]></summary>
        <content type="html"><![CDATA[<p>Every real-time strategy game is secretly a metronome. Thirty times a second
the engine reads your inputs, advances the world one tick, and draws what
happened. This week we finished dissecting that heartbeat across all three
games we track — Tiberian Sun, Red Alert 2, and Yuri's Revenge — and then went
one level deeper, into the bodies of the subsystems the tick calls, all the way
down to the layer where the computer players decide what to build. Along the
way the engine handed us a genuinely strange discovery: one virtual call that
takes its arguments two different ways depending on which game you're playing.</p>
<!-- -->
<p><em>Last verified against the project oracle: 2026-07-19.</em></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-envelope">The envelope<a href="https://rets.dassheep.tech/devblog/anatomy-of-a-frame#the-envelope" class="hash-link" aria-label="Direct link to The envelope" title="Direct link to The envelope" translate="no">​</a></h2>
<p>The outer frame loop — the envelope around the simulation — is now mapped
end-to-end in all three engines, and its spine is identical everywhere: read
input, log sync data, refresh the draw order, run the simulation tick, account
for frame rate, apply queued player commands, advance the frame counter, and
finally bury the dead.</p>
<!-- -->
<p>Two placements in that sentence carry the whole multiplayer story. Queued
commands apply <em>after</em> the tick but <em>before</em> the frame counter advances — so
every player's order lands on exactly the same numbered frame on every machine.
And object destruction is deferred: things that "die" during a tick are only
flagged, then swept at the very end of the frame, <em>after</em> the counter has
already moved on. An object that dies on frame N is still present, in every
list, for all of frame N — every scan traverses it, guarded only by a liveness
check — and is physically freed at end-of-frame at the earliest. Get either of
those wrong in a reimplementation and two machines playing the same game drift
apart within seconds.</p>
<p>That deferred-death queue was itself a small correction. Our object-model notes
had long dismissed it as a limbo bookkeeping list that was never broadcast; the
cross-references say otherwise. It is the engine's real destruction queue —
several teardown paths enqueue into it, and one of the pump's previously
unexplained closing calls is the drain that empties it after the counter ticks.
The drain asks each queued object a virtual "are you ready" question and, only
on consent, removes every occurrence and runs its destructor. We confirmed the
same drain exists in all three binaries — the later games run a few extra type
checks before freeing, the ancestor uses a different flag byte, but the "objects
die on schedule" guarantee holds across the whole lineage. That ordering was the
last unproven pillar of the deterministic world model we're building the
skirmish on, whose keystone is an argument the retail binary supplies for free:
lockstep works across machines with different memory layouts, so gameplay can
never depend on a pointer's <em>value</em> — only on identity and array order. Reproduce
the arrays exactly and you've reproduced the observable world.</p>
<p>One detail in that spine is a lovely period piece. The "refresh the draw order"
step is a <em>single pass</em> of a bubble sort over one display layer per frame.
Sorting everything every frame was too expensive for 1990s hardware, so the
engine amortizes — objects barely move between frames, so one cheap pass nudges
the layer back toward sorted, and because the pass runs before the tick, its
progress is shared game state, not just cosmetics. All three engines run the
identical algorithm.</p>
<p>The divergences in the envelope tell a story about the family. The ancestor
carries a large scenario-transition state machine in its tail — campaign-first
machinery the later games streamlined into a flat four-flag check. And the
newest engine alone has a <strong>phantom random draw</strong>: in networked games, behind a
congestion check, it pulls a number from the lockstep generator and throws it
away. It looks pointless until you remember what lockstep is — every peer must
consume its shared random stream in exactly the same order, so when a branch
that <em>would</em> have drawn doesn't run locally, the engine draws anyway to keep the
count aligned. That's a faithfulness detail the netcode port will have to honor
to the letter.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-letter-inside">The letter inside<a href="https://rets.dassheep.tech/devblog/anatomy-of-a-frame#the-letter-inside" class="hash-link" aria-label="Direct link to The letter inside" title="Direct link to The letter inside" translate="no">​</a></h2>
<p>Inside the envelope is the simulation tick itself: an ordered sweep of over
twenty subsystems — triggers, timers, bombs, lighting, teams, radiation, the
full object walk, factories, houses. The <em>order</em> was pinned some time ago. What
we finished this week was naming every remaining mystery in it, then reversing
every subsystem body behind those names.</p>
<p>Three mysteries had survived in the margins, and each fell to the same
discipline we lean on for anything a misread could quietly corrupt: independent
readers working from the raw bytes, an adversarial pass whose only job is to
refute the first, and a principal who re-dumps every load-bearing fact before
believing it.</p>
<p>A nameless routine the tick calls every frame with a six-millisecond budget
turned out to be the drain half of the dynamic lighting system — cells whose
light changed queue up, and the tick retints a budgeted batch each frame so a
big lighting event can't blow the frame time. The decisive evidence wasn't in
the drain at all; it was in its siblings. Every routine that <em>fills</em> that queue
is a light-source method — a light turns on, marks its cells pending, and the
tick works them off.</p>
<p>An array the tick walks only during real gameplay turned out to be a <em>second</em>,
undocumented animation registry. Certain feedback animations are filed there at
birth, and their destructor checks a per-instance flag to know which of the two
registries to unlink from. Neither community header set knows this one exists;
we're recording it as new.</p>
<p>And the smallest mystery had the best answer. Every object-update call in the
tick goes through the same slot in each object's virtual-function table — except
in Tiberian Sun, where the slot sits exactly one position later. We walked the
compiled type-information structures in all three binaries and dumped the base
class's table from raw bytes: the ancestor carries one extra virtual method that
the later games dropped, and its single insertion shifts every subsequent slot
down by one. Reproduce the newest game perfectly, forget this, and your Tiberian
Sun build calls the wrong function on every object, every frame.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="from-names-to-mechanisms">From names to mechanisms<a href="https://rets.dassheep.tech/devblog/anatomy-of-a-frame#from-names-to-mechanisms" class="hash-link" aria-label="Direct link to From names to mechanisms" title="Direct link to From names to mechanisms" translate="no">​</a></h2>
<p>With every callee named, we reversed the stage bodies end-to-end. Several are
small combat and effect systems with real mechanical personality.</p>
<p><strong>The attached-bomb list</strong> — Ivan's ticking presents, a later-era class the
ancestor engine never had — is one function with three loops and three
<em>different</em> iteration disciplines. The first destroys bombs whose victim no
longer exists, and because the bomb's destructor doesn't remove it from the
list, the loop immediately re-runs its own compaction to sweep the dangling
slot. The invariant that falls out — every surviving bomb has a live target — is
exactly what lets the later loops skip null checks entirely. The visibility rule
is pure Westwood charm: your own bombs you always see; enemy bombs need a
bomb-detecting unit within range, measured as a true 3-D floating-point
distance, re-evaluated on a timer that resets to 45 but — counting the sweep
frame itself — actually fires every 46 frames.</p>
<p><strong>The kamikaze tracker</strong> manages aircraft on a one-way trip, and it taught our
verification pipeline some humility. A first reading claimed its 30-frame timer
counts down in memory; the assembly says the subtraction happens in a register
and is never written back — the timer is a pure "has enough time elapsed"
comparison against a fixed duration. Two behaviors any faithful reimplementation
must keep: every tracked plane has its ammo forced to 1 every 30 frames, so the
crash always delivers its payload; and planes tracked without an explicit target
re-derive their crash point from their <em>current facing</em> every cycle — the aim
drifts as the plane banks — while planes with a stored target keep it forever,
even if the target is long gone. The reverse lane also flagged a garbage write
into the timer struct's padding that reads like an uninitialized variable but is
gameplay-inert — a compiler idiom we'd meet again.</p>
<p><strong>The ion blast</strong> is where the layered reading paid off most, and where the
week's strangest discovery lives. It's a 79-frame effect that sweeps a 7×7 cell
neighborhood each frame looking for infantry and vehicles to zap, walking its
target list <em>backward</em> so an effect deleting itself mid-sweep can never make the
loop skip a neighbor. Its proximity check measures distance in <em>screen</em> pixels —
both positions go through the world-to-camera projection before the compare — so
the zap radius is literally twice as large in the newest game as in its
ancestors, camera-relative by construction. And then the calling convention. The
first reader called the per-frame hit-test a zero-argument accessor; the
adversarial pass refuted that — two extra values are passed; then the principal's
own dump refuted <em>the verifier</em> — in the newest game those two values travel in
CPU registers, while the two older engines push them on the stack. Same call,
same purpose, different convention per binary. We've been burned twice before by
convention misreads that only a live crash exposed. This one we caught on paper,
by stacking three independent readers against each other and believing none of
them until the bytes agreed.</p>
<p>The dynamic lighting drain we'd named earlier turned out to be the most
engineered thing in the tick: a two-phase, wall-clock-budgeted, <em>resumable</em>
pipeline. Changed cells go into a backlog; each frame the drain gets a
six-millisecond allowance and walks the backlog behind a cursor that <em>persists
across frames</em> — if time runs out mid-walk, next frame resumes exactly where it
stopped — polling the clock only every sixteenth cell to keep the timing
overhead itself cheap. Only once the whole backlog is resolved does an
unbudgeted second phase apply everything to the map at once and refresh the
display — once per batch, not once per cell. There's even a vestigial
self-tuning governor at the end: if lighting resolution takes too long, it
consults a hook that was compiled down to "always say no." A quality knob, wired
up and then unplugged before shipping.</p>
<p>The last of these core bodies is a small masterpiece of misdirection. The
Floating Disc's laser isn't a weapon that tracks — it's a wound-up toy. Firing
computes the bearing to the target, rotates it a half turn, and hands the rest
to a nine-beat state machine: two beam arms sweep around the disc in opposite
directions, one step per activation, and geometry does the scheduling — the arms
land on the same facing exactly at step eight. That converged beat draws the
single real beam, applies the single damage event, plays the report sound, and
marks the object for the end-of-frame grave. Every intermediate beam is a purely
visual object with its own lifetime sweep — and every beam allocation is guarded
so an out-of-memory frame silently drops the <em>visual</em> while damage and state
march on. The renderer is allowed to fail; the simulation is not.</p>
<p>Two more stage bodies finally explained the tick's two backward walks and its
oldest piece of trivia. Radiation sites count their lifetime down and, when it
hits zero, delete themselves <em>right there</em>, mid-walk — the destructor compacts
the array by shifting every later element down one slot, so walking backward is
the only safe order. We had that shift-down mechanism only as community folklore
until the destructor's disassembly showed the literal copy loop. The same site
fades its glow as it decays, and that fade carries a genuine retail bug: its
skip-if-unchanged check compares the stored red channel against the incoming red
<em>and</em> the incoming blue, and never looks at green. Faithfully reproduced, quirk
toggle and all. And the production stage answers the trivia question every fan
knows: the build clock has 54 steps — a constant that now sits, verified, in the
comparison instruction of all three games. Each due tick charges the remaining
balance divided by the remaining steps, rounding down, with the final step
sweeping the exact remainder, so a build never over- or under-pays by a cent. If
funds run dry the step rolls back — but the progress-changed flag stays raised
and the timer has already rewound, so a stall costs a full interval and briefly
lies to the sidebar.</p>
<p>The camera stage closed as a verdict rather than a port. Its class has no
runtime type information anywhere, so the lanes chased the singleton's
construction to recover its method tables in all three games — and found the
per-frame body is pure presentation: smooth-scroll easing, viewport clamping,
dirty-flag bookkeeping, and a screen-shake timer driven by the <em>wall clock</em>.
That last fact matters enormously: it is the only nondeterminism in the stage,
and it can never touch game state. The simulation spine now records this stage as
a documented client-side no-op instead of an unexamined hole.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="when-faithful-means-reproducing-the-dice">When faithful means reproducing the dice<a href="https://rets.dassheep.tech/devblog/anatomy-of-a-frame#when-faithful-means-reproducing-the-dice" class="hash-link" aria-label="Direct link to When faithful means reproducing the dice" title="Direct link to When faithful means reproducing the dice" translate="no">​</a></h2>
<p>Several stages look visually trivial and are, underneath, the most
determinism-hostile code in the frame — because in a lockstep game the <em>number</em>
of random draws is as load-bearing as the result. Consume one draw too few and
two machines' random streams diverge, and with them every combat roll for the
rest of the match.</p>
<p>Tiberium regrowth is the clean example. It looks like a timer per resource type:
fire when due, re-arm. The adversarial re-read killed the innocent version. The
number of cells processed per firing is a two-stage computation — first a
deterministic bound from how many cells are queued times the type's growth
percentage, clamped to 5–50, <em>then</em> a random draw modulo that bound, plus one —
and every candidate cell drawn after that consumes a <em>second</em> random number to
build a float-scored priority heap. None of it matters visually; all of it
matters for sync. Even the re-arm interval runs through floating point — an
integer growth rate times a scenario-dependent fraction, truncated — one more
place where a single rounding mode is load-bearing.</p>
<p>Crate placement is the most sync-hostile stage so far: placing one crate may
burn up to a thousand retry attempts, two random draws each, then one final draw
to arm the respawn timer, uniform over a window computed in doubles. The oracle
test for that window failed on first run — we had hand-derived the top of the
range as unreachable, the way the formula reads on paper. The actual bytes of
the scale constant are not exactly the power of two it plays on TV; it's larger
by one part in a billion, just enough that the maximum draw rounds up to the full
upper bound. That failing test is the workflow doing its job: the binary is the
spec, and it disagreed with our arithmetic until we read the constant's real
bytes.</p>
<p>The lightning storm turned out to be a shared ticker for a whole family of
screen-state machines — the storm itself, the nuke-flash fade, and two more
siblings the earlier games don't wire in here. Its best secret hid in a branch
both readers missed and the principal pass caught: the storm's shutdown code is
only <em>reachable</em> when the list of live storm clouds is empty. A storm whose
duration expires stops striking immediately, but keeps its stormy lighting until
the last cloud animation finishes — and when it finally shuts down, control falls
straight through into the countdown for the <em>next</em> queued storm, which can
promote on that very tick. Timing like that is invisible in normal play and
decisive in a replay diff.</p>
<p>The random threads run right up into the computer players, too. One block in the
house update — present only in the newest game — rolls twice on a <em>separate</em>,
non-synced stream and then, on one branch, rolls once on the shared lockstep
stream and discards the result: the same alignment trick as the envelope's
phantom draw, and a port that skips it desyncs every networked match from that
frame on. And the deciders that pick "build what the teams need" versus "build
something random" scale their coin flip by a stored constant sitting a hair
<em>above</em> the exact power of two it approximates — so a single roll value out of
two billion still goes random even at a fully deterministic setting. The same
almost-a-power-of-two footgun, twice in one engine.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="ghosts-in-the-machine">Ghosts in the machine<a href="https://rets.dassheep.tech/devblog/anatomy-of-a-frame#ghosts-in-the-machine" class="hash-link" aria-label="Direct link to Ghosts in the machine" title="Direct link to Ghosts in the machine" translate="no">​</a></h2>
<p>Reversing the same code across three binaries keeps turning up features that
outlived their own wiring, and cross-version reads that refuse a plausible answer.</p>
<p>The archaeology find of the week is the EMP pulse. All three engines run its
per-frame update in the same tick position — and in both later games the code
that would ever <em>create</em> one has exactly zero callers. Proven dead, not folklore:
exhaustive cross-reference on both binaries. In the ancestor the same constructor
has two live call sites gated by warhead flags — Firestorm's EMP was real,
shipped content, and the two later engines carried the entire mechanism forward
disconnected, faithfully executing an empty loop every frame for two decades. Our
port pins the mechanism <em>and</em> the dead-in-two, alive-in-one verdict.</p>
<p>The weather has its own ghost. The ancestor's ion storm is not an earlier draft
of the lightning storm — it's a different machine entirely: a per-tick
probability roll instead of fixed strike cadences, target selection that scans
live units first and falls back to random cells, and none of the cloud-then-bolt
choreography. The lineage rewrote the weather wholesale. And a 120-frame full-map
shroud refresh simply does not exist in the oldest engine — it's a later
addition, one more thing the descendants bolted onto the same tick spine.</p>
<p>Two smaller stages — transient light flashes and laser beams — earned the
cross-version check its keep one more time. The first reader claimed the ancestor
shares its descendants' memory layout for the laser record; the adversarial
re-read of the actual bytes showed the ancestor's late fields sit a further eight
bytes earlier, because it's missing two fields the later games added. Both stages
also share a removal idiom where the loop, already holding an item's position,
politely asks the array to search for the item anyway, then frees memory raw —
and the laser timer reproduces the exact "write compiler padding with stack
garbage" quirk we first met in the kamikaze tracker. Same idiom, second sighting,
all three binaries.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-house-takes-its-turn">The house takes its turn<a href="https://rets.dassheep.tech/devblog/anatomy-of-a-frame#the-house-takes-its-turn" class="hash-link" aria-label="Direct link to The house takes its turn" title="Direct link to The house takes its turn" translate="no">​</a></h2>
<p>The biggest single function in the tick is the per-house update. Each faction —
player, AI, or neutral — gets one call per frame to run its own housekeeping, and
that function turns out to be the engine's town square. Power and radar outages
re-check themselves through timers that deliberately fire one frame <em>early</em>,
stopping themselves and raising a flag the real recompute reads next. Grudges
between houses decay on a hundred-frame cadence and never quite reach zero — the
floor is one, so an AI that has been attacked literally never fully forgives.
Win, lose, and game-over are three flags sharing a single countdown, and before
the engine will admit any of them it spins on a wall-clock budget waiting for
queued announcer speech to finish — the EVA voice always gets the last word. The
win and lose paths then set the main loop's exit flags with mappings that are
exact mirror images of each other, and the lose flag itself is never cleared: a
genuine little retail quirk, faithfully reproduced and test-pinned.</p>
<p>The oddest find sits right after the function's return instruction: a complete,
working copy of its superweapon-sidebar sweep that nothing ever calls. A
whole-binary scan found zero references in the two later games. The oldest game
explains the corpse — there, the update <em>calls</em> that helper as a real subroutine.
The dead copies in the later binaries are the fossil left behind when the
compiler started inlining it: three shipped executables accidentally documenting
their own build history.</p>
<p>The house update dispatches into the AI's actual decision layer — six functions
deciding what building to place next, whether to train a vehicle, infantryman, or
aircraft, when to panic-sell, and which team to raise — and reversing those
justified the adversarial pass immediately. The base-planning function's most
load-bearing branch had been read exactly backwards on the first pass, success
and failure swapped, which would have shipped an AI that cancels plans when
they're viable and builds when they're not. Another reviewer found that the
"grudge" helper doesn't just adjust one hostility score — it silently re-elects
the AI's tracked enemy after <em>every</em> adjustment, and one of its two callers
immediately overwrites the result, making half its work a faithful no-op. The
three per-category production deciders turned out to be one function copy-pasted
per category: an instruction-level diff found exactly five substitutions and not
a byte more. And one detail every first-pass reader missed, caught by re-reading
the raw bytes: when the AI is at its team budget it sets a flag handed straight
into the map-scripted trigger conditions — the AI literally tells the mission
logic "I'm full."</p>
<p>Cross-version reconciliation here delivered a real behavioral difference rather
than shuffled offsets: the middle game evaluates its production dispatch every
single frame, where the newest throttles it to every eighth — same code shape,
eight times the decision cadence. The money check that gates every build, mean-
while, goes through a COM interface embedded inside the house object with a
calling convention the decompiler renders identically to a normal method call —
the exact trap our call-site discipline exists to catch, and the same trap that
made the ion blast's arguments so slippery.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="where-this-leaves-us">Where this leaves us<a href="https://rets.dassheep.tech/devblog/anatomy-of-a-frame#where-this-leaves-us" class="hash-link" aria-label="Direct link to Where this leaves us" title="Direct link to Where this leaves us" translate="no">​</a></h2>
<p>Everything above feeds one goal: a playable skirmish that stays bit-identical
with the original, frame by frame. The frame envelope tells us <em>when</em> things
happen; the tick order tells us <em>what</em> happens when; the stage bodies — every one
now with a reversed, test-pinned implementation or a pinned client-side
classification — tell us <em>how</em>, right down to the decisions the computer players
make. The frame is no longer a list of mysteries in a known order. It's a list of
mechanisms, each pinned by tests whose expected values were hand-derived from the
machine code, so any drift in our implementation fails loudly.</p>
<p>One large helper remains before the AI path is bit-exact end to end: the fallback
that weighs alternate build plans, with its own data-dependent lockstep dice
rolls. And the next move for the frame as a whole is the only witness that can't
be wrong — driving the live original through single frames and diffing its state
against ours at each boundary. We've read the anatomy from the bytes. Now we get
to watch it beat.</p>]]></content>
        <author>
            <name>The reTS project</name>
            <uri>https://github.com/DasSheep/reTS</uri>
        </author>
        <category label="devblog" term="devblog"/>
        <category label="game-loop" term="game-loop"/>
        <category label="verification" term="verification"/>
        <category label="multi-game" term="multi-game"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[Taking the Wheel of the Original Engine]]></title>
        <id>https://rets.dassheep.tech/devblog/taking-the-wheel</id>
        <link href="https://rets.dassheep.tech/devblog/taking-the-wheel"/>
        <updated>2026-07-18T14:45:00.000Z</updated>
        <summary type="html"><![CDATA[Last time, the original engine gave us a second opinion: we ran small pieces of]]></summary>
        <content type="html"><![CDATA[<p>Last time, the original engine gave us a second opinion: we ran small pieces of
its actual code in a sandboxed emulator and compared answers. That was the
engine as a witness. Today it became something more — a car we can drive. Code
of ours now runs <em>inside</em> the live game, steps its simulation forward frame by
frame on command, reads its state between steps, and hands us ground truth no
amount of careful reading can match. It promptly caught two real mistakes that
every static check had blessed.</p>
<!-- -->
<p><em>Last verified against the project oracle: 2026-07-18.</em></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="owning-the-clock">Owning the clock<a href="https://rets.dassheep.tech/devblog/taking-the-wheel#owning-the-clock" class="hash-link" aria-label="Direct link to Owning the clock" title="Direct link to Owning the clock" translate="no">​</a></h2>
<p>The setup sounds like a heist and mostly is one. A small helper library is
injected into a running, unmodified copy of the game on an isolated Linux box —
nothing on disk is patched, and the game itself is not aware of the passenger.
The library opens a tiny local control channel and waits for instructions.</p>
<p>The first hard problem was time. A running game advances its own frame counter
about thirty times a second, which swamps any attempt to "step ten frames and
look around." Freezing everything isn't right either — a frozen game can't
advance at all. The answer is a gate on the engine's own main loop: the loop
runs exactly as retail wrote it, but the gate holds it at a target frame until
asked for more. Ask for fifty frames and the counter moves by exactly fifty,
then freezes again, with the game alive the whole time. Stepping is
deterministic, repeatable, and headless.</p>
<p>The second hard problem was reading state that isn't mid-change. Because the
gate freezes the loop <em>between</em> frames, a read taken while frozen is coherent
by construction. Between steps we capture a snapshot: the frame counter, both
of the engine's random-number streams, and a walk over the live object arrays
folding each unit's position and facing — the same kind of quantized digest
the engine itself uses to detect desyncs in multiplayer.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="two-launches-one-bit-pattern">Two launches, one bit pattern<a href="https://rets.dassheep.tech/devblog/taking-the-wheel#two-launches-one-bit-pattern" class="hash-link" aria-label="Direct link to Two launches, one bit pattern" title="Direct link to Two launches, one bit pattern" translate="no">​</a></h2>
<p>With driving and reading in hand, the flagship experiment became possible.
Launch the game fresh, pin the match seed, let a real skirmish load, then step
to fixed absolute frame numbers and capture at each. Quit everything. Do it
again from scratch.</p>
<p>Two completely independent launches with the same seed produced <strong>byte-identical
capture sequences</strong> across a five-hundred-frame span — every random draw in
perfect agreement. A third run with a different seed diverged immediately,
which matters just as much: an oracle that always says "identical" is
worthless. Same seed in, same simulation out, provable to the bit. That is the
property the whole project stands on, demonstrated end to end on the original
engine itself.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-oracles-first-catch">The oracle's first catch<a href="https://rets.dassheep.tech/devblog/taking-the-wheel#the-oracles-first-catch" class="hash-link" aria-label="Direct link to The oracle's first catch" title="Direct link to The oracle's first catch" translate="no">​</a></h2>
<p>Then we pointed it at our own code, and it earned its keep the same day.</p>
<p>Our reimplementation of the engine's random-number generator was pinned by
hand-derived tests, and those tests were green. But the live engine disagreed:
seeding the real generator in-process and comparing its internal table against
ours revealed that our constructor expanded the seed using a loop counter where
the original chains the <em>previous internal states</em>. The subtle part is why the
tests hadn't caught it — the hand-derived expected values had been worked out
with the same misreading. Implementation and test agreed with each other and
were both wrong. A shared blind spot is invisible to self-consistency; it took
the running engine to break the tie. One fix later, our generator matches the
retail one byte for byte for every seed we've checked.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-crash-that-corrected-the-notebook">The crash that corrected the notebook<a href="https://rets.dassheep.tech/devblog/taking-the-wheel#the-crash-that-corrected-the-notebook" class="hash-link" aria-label="Direct link to The crash that corrected the notebook" title="Direct link to The crash that corrected the notebook" translate="no">​</a></h2>
<p>The second catch arrived as a crash. While extending the state snapshot to walk
the engine's display layers, our code called a small engine routine to ask each
object what type it is — and the game faulted. The disassembly told the story:
of two neighboring virtual calls, one passes its object in a register, the
other — a COM-style interface method — expects its pointer <em>on the stack</em>.
Decompiled output renders both identically, so nothing short of the machine
code (or a live crash) distinguishes them. Our notes had assumed the first
convention for both.</p>
<p>That's now a standing rule in the workflow: for every indirect call a port
replicates, pin the calling convention from the call site's machine code, never
from decompiled pseudocode. It has caught two real bugs in as many sessions.</p>
<p>With the convention corrected, the <em>full</em> snapshot — object positions and
facings included, not just the random streams — passed the same two-launch
test: byte-identical across independent runs at the same seed, divergent on a
different one.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="from-experiment-to-routine">From experiment to routine<a href="https://rets.dassheep.tech/devblog/taking-the-wheel#from-experiment-to-routine" class="hash-link" aria-label="Direct link to From experiment to routine" title="Direct link to From experiment to routine" translate="no">​</a></h2>
<p>The final step was making all this boring. Live confirmation is now a
first-class part of the verification workflow: while reversing a function, we
can call the retail original in-process with chosen arguments and bake its
answer into the ordinary test suite as a tagged literal. The live setup is
needed only at reversing time — the test suite stays fully hermetic and keeps
passing on every machine we check determinism on, including the ARM boxes,
with no game installation anywhere in the loop.</p>
<p>The rule of thumb we adopted for when to bother: <strong>if the port and the study
could share a misread and still agree, get a live confirmation.</strong> Numeric code,
anything random-number-derived, state machines, and indirect-call conventions
all qualify. Both of this week's catches were exactly that class.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="what-the-engine-checks-every-frame">What the engine checks every frame<a href="https://rets.dassheep.tech/devblog/taking-the-wheel#what-the-engine-checks-every-frame" class="hash-link" aria-label="Direct link to What the engine checks every frame" title="Direct link to What the engine checks every frame" translate="no">​</a></h2>
<p>A bonus from the same work: to know what state is worth capturing, we reversed
the engine's own multiplayer sync checksum — the digest each peer computes per
frame to detect desyncs. What the original engine considers "the game state
that must match" turns out to be lean: quantized object positions, unit
facings, object types, one flag per player house, and a draw from the shared
random stream — no health, no credits, no targets. Its ancestor in earlier
Westwood engines checksummed health and money too; this engine's generation
dropped them. The full breakdown is now written up as a reference entry, next
to the damage pipeline.</p>
<p>The oracle has a steering wheel now. The next stretch of work goes back to the
engine itself — the world model, the main loop, and the road toward a playable
skirmish — with the live engine riding along as examiner.</p>]]></content>
        <author>
            <name>The reTS project</name>
            <uri>https://github.com/DasSheep/reTS</uri>
        </author>
        <category label="devblog" term="devblog"/>
        <category label="verification" term="verification"/>
        <category label="oracle" term="oracle"/>
        <category label="determinism" term="determinism"/>
        <category label="rng" term="rng"/>
        <category label="netcode" term="netcode"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[A Second Opinion From the Original Engine]]></title>
        <id>https://rets.dassheep.tech/devblog/a-second-opinion</id>
        <link href="https://rets.dassheep.tech/devblog/a-second-opinion"/>
        <updated>2026-07-16T22:12:30.000Z</updated>
        <summary type="html"><![CDATA[For months, every number in this project has been checked the same way: a person]]></summary>
        <content type="html"><![CDATA[<p>For months, every number in this project has been checked the same way: a person
reads the original engine's instructions, works out by hand what they must
produce, and writes that value into a test. It's rigorous — but it's one
person's arithmetic standing in for the CPU. The weak point was always the
derivation: a subtle misreading can hide in a test for a long time, because the
test and the reading share the same author and the same blind spot. Today the
project got three ways to have that reading checked by something that doesn't
share the author.</p>
<!-- -->
<p><em>Last verified against the project oracle and live site: 2026-07-17.</em></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-machine-as-second-opinion">The machine as second opinion<a href="https://rets.dassheep.tech/devblog/a-second-opinion#the-machine-as-second-opinion" class="hash-link" aria-label="Direct link to The machine as second opinion" title="Direct link to The machine as second opinion" translate="no">​</a></h2>
<p>The first, and the one that changes the methodology, removes the author from the
loop entirely. Take the <em>actual bytes</em> of a function we've reversed, map just
that code into a sandboxed processor emulator, set up the calling convention the
way the engine would, run to the return, and read the result register. The
reimplementation and the original are now handed the same inputs, and their
answers are compared directly. When they disagree, the disagreement is the
finding — no human transcription in between.</p>
<p>The first function put through it was deliberately boring: a pure predicate that
decides whether a given lobby slot is one of the multiplayer starting positions.
Our port models it as a simple range check — is the index within this band? The
original engine does something that <em>looks</em> different: a run of discrete equality
tests, one per start slot, falling through to "no." For every valid input the two
are identical, and the emulated original confirmed it across a batch of cases
that included both edges of the band and values just outside it. That is exactly
the question this tool exists to answer — <em>is our simplification sound?</em> — and now
it can be answered by the machine instead of by a careful reading of it. A second
target that reads fields off an object exercised the harder path, where the
emulator has to be handed a small synthetic object to read from; its inheritance
and pass-through cases agreed too.</p>
<p>This does not replace reversing from the instructions — you still have to
understand a function to port it. What it replaces is the quiet risk that a
plausible-looking reimplementation and a plausible-looking hand-derived
expectation are wrong in the <em>same</em> direction. That risk is not hypothetical.
The very same afternoon, it showed up for real, deep in the campaign AI — and
this time it was a human skeptic, not the emulator, that caught it.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="when-the-blind-spot-actually-bit">When the blind spot actually bit<a href="https://rets.dassheep.tech/devblog/a-second-opinion#when-the-blind-spot-actually-bit" class="hash-link" aria-label="Direct link to When the blind spot actually bit" title="Direct link to When the blind spot actually bit" translate="no">​</a></h2>
<p>The campaign AI work reached the payload of a long thread: a <em>team</em> is the unit
the mission scripting commands — a squad handed a script, and each step of that
script is a <em>mission</em>: go here, guard this, load into that transport, unload,
attack, scatter, patrol, and sixty more. The full behaviour of every one of
Yuri's Revenge's sixty-five mission kinds is now reversed, ported, and pinned by
tests, then diffed against the older engines in the lineage. That work was run
as paired lanes — one agent reverses a mission, an independent one re-reads the
same instructions specifically to <em>refute</em> the first — and those verify lanes
earned their keep with dozens of corrections.</p>
<p>The sharpest correction is a near-perfect illustration of the shared blind spot.
One family of missions is gated on a power-level threshold, and an early reading
declared that whole family dead code, because the threshold constant read as
zero. It isn't zero. The comparison the engine performs consumes eight bytes,
not four — it's a double, not a single-precision float — and the value is one,
not zero. The low four bytes of a double holding 1.0 happen to be all zeroes,
which is exactly what a four-byte reading sees. One operand-size distinction
decided whether an entire branch of behaviour existed; it does, and it's live. A
port and a hand-derived expectation could easily have agreed on the wrong answer
here — which is precisely the failure the emulated-original check is built to
close, and the reason both kinds of second opinion matter. A separate correction
pulled a real lifecycle coupling out of the noise: when a team successfully sends
a member into a structure, it unconditionally arms the flag that governs whether
the team disappears next tick — so a release and a disappearance that look
independent are actually chained. A port written to the obvious reading would
drop that link silently.</p>
<p>Cross-game, the tidy result held: the older engine's version of this dispatcher
has exactly fifty-three mission kinds, and the twelve that the newer engines add
on top are provably <em>absent</em> there — not merely unfound, but sitting behind
padding where their table entries would be. The lineage grew this system by
appending, never renumbering, which is the same pattern the scripting layer as a
whole turned out to follow. Two independent readers converging on the same
fifty-three is, itself, another form of the second opinion this whole day was
about.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="a-third-witness-for-the-playable-half">A third witness, for the playable half<a href="https://rets.dassheep.tech/devblog/a-second-opinion#a-third-witness-for-the-playable-half" class="hash-link" aria-label="Direct link to A third witness, for the playable half" title="Direct link to A third witness, for the playable half" translate="no">​</a></h2>
<p>The last of the day's checks doesn't verify a function — it verifies the
<em>simulation itself</em>, and it arrived as an architecture decision about how the
project's playable side will be built. A browser build is now a first-class
parallel target alongside the native client, both of them thin shells over the
same shared simulation core, eventually joined by a single input-agnostic command
layer, so the same match logic runs behind a desktop window, a browser tab, or a
touchscreen without forking. The in-game interface will come from the reversed
engine, faithfully; only the shell around it is new.</p>
<p>Two facts made the browser path cheap rather than speculative, and the second is
the one that ties it back to the theme. The core that would have to compile to it
already carries no external dependencies, so the build is nearly free. And a
browser build's arithmetic is defined by a <em>specification</em> rather than by whatever
hardware it runs on — which makes it a genuinely independent witness that the
simulation stays deterministic, a fourth check whose numbers come from a written
standard instead of from a particular chip. It confirms the same thing the
emulator and the skeptic confirm, one level up: that our version of the engine
does what the original does, for a reason nobody on the team simply asserted.</p>
<!-- -->
<p>None of the three shares the author's blind spot, which is the whole point. For
months the only check on a hand-derived number was the same hand that derived it.
Now the machine can run the original's own instructions and compare, a second
reader can be pointed at the disassembly with a mandate to prove the first one
wrong, and — as the playable client comes together — a runtime whose arithmetic
answers to a spec can stand in as an outside judge of determinism. Three
independent second opinions, on a project whose entire promise is that its
answers match the original engine's. The loop that catches a shared mistake now
closes at more than one level.</p>]]></content>
        <author>
            <name>The reTS project</name>
            <uri>https://github.com/DasSheep/reTS</uri>
        </author>
        <category label="devblog" term="devblog"/>
        <category label="verification" term="verification"/>
        <category label="oracle" term="oracle"/>
        <category label="campaign" term="campaign"/>
        <category label="ai" term="ai"/>
        <category label="teams" term="teams"/>
        <category label="client" term="client"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[A Callback That Changes the Question, and a Clamp That Wasn't Signed]]></title>
        <id>https://rets.dassheep.tech/devblog/callbacks-and-clamps</id>
        <link href="https://rets.dassheep.tech/devblog/callbacks-and-clamps"/>
        <updated>2026-07-15T21:46:54.000Z</updated>
        <summary type="html"><![CDATA[Reverse engineering has a failure mode that looks nothing like a bug: code]]></summary>
        <content type="html"><![CDATA[<p>Reverse engineering has a failure mode that looks nothing like a bug: code
that reads exactly right and is quietly wrong. A plausible paraphrase of an
instruction and the instruction itself can look identical at a glance, so the
safest-looking line is often the one that never actually matched the binary.
That pattern surfaced three times over today — in a harvester's movement
logic, in a single audio clamp, and in the switch at the heart of the campaign
scripting engine — and each time the fix was the same discipline: trust the
binary over the story you'd expect it to tell.</p>
<!-- -->
<p><em>Last verified against the project oracle and live site: 2026-07-16.</em></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-callback-that-changes-the-question">The callback that changes the question<a href="https://rets.dassheep.tech/devblog/callbacks-and-clamps#the-callback-that-changes-the-question" class="hash-link" aria-label="Direct link to The callback that changes the question" title="Direct link to The callback that changes the question" translate="no">​</a></h2>
<p>The next slice of the harvester mission looked, at first glance, like a
boolean: did the move succeed or not? It isn't. A successful move commits
four things at once: the movement flag goes up, a two-frame animation timer
starts, the animation stage resets, and the mission steps into gather.</p>
<p>The interesting part is what happens on failure, because failure here is
<em>stateful</em>. The engine doesn't just branch on the move result — it re-reads
the harvester's destination and focus <strong>after</strong> the movement callback runs,
which means a model built from their values going into the call would already
be wrong by the time the branch is taken. A destination that's still set
clears the recovery flag and resumes polling. A focus with no destination
becomes the new destination, and the recovery flag is left untouched. Only
the genuine no-target case enters recovery, returns the longer retry delay,
and — for ordinary harvester types — conditionally signals the owning house
that something needs attention.</p>
<p>That action order holds across Tiberian Sun, Firestorm, Red Alert 2, and
Yuri's Revenge, even though each engine keeps the relevant flags at different
struct offsets, calls a differently-named movement helper, and returns the
result in a different register. The port now exposes that order as
a deterministic, inspectable step, while deliberately leaving the live
target search, object mutation, and polling machinery outside of it. That
seam matters beyond just matching the original: it means a future world
implementation can change <em>how</em> a harvester finds its next target without
silently changing what the mission does with the callback's result — the two
concerns stay separated instead of tangled into one function.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-clamp-that-wasnt-signed">The clamp that wasn't signed<a href="https://rets.dassheep.tech/devblog/callbacks-and-clamps#the-clamp-that-wasnt-signed" class="hash-link" aria-label="Direct link to The clamp that wasn't signed" title="Direct link to The clamp that wasn't signed" translate="no">​</a></h2>
<p>Every few days the project runs a conformity sweep: a review pass over
everything landed since the last one, with every flagged item independently
re-verified before anything changes. This round covered eleven separate work
streams from the last two days, and most of it came back clean — economy,
tactical projection, the sidebar, skirmish preferences, and the sync log all
survived untouched.</p>
<p>One genuine bug turned up, and it's a good story about <em>why</em> you reverse from
the actual instructions rather than a plausible paraphrase of them.</p>
<p>The sound engine converts a floating-point volume into an integer value the
audio device understands, then clamps it into range. The port had that clamp
written as an ordinary signed range check: negative values fold to zero,
silence. Reasonable code. It reads exactly like what any volume clamp should
do. The reviewer even flagged it as slightly suspicious, in the way you flag
something that looks a little too clean.</p>
<p>The binary disagreed. The comparison the original engine actually uses is
<strong>unsigned</strong> — not the signed-less-than a range clamp would need, but an
unsigned above-or-equal check. A negative volume, reinterpreted as an
unsigned 32-bit value, doesn't sit near zero — it sits at the very top of the
unsigned range. So retail's clamp doesn't produce silence for a negative
input. It produces <strong>maximum volume</strong>. Both the global and positional
playback paths were re-derived independently from the instruction stream to
confirm it wasn't a one-off artifact — the same unsigned comparison shows up
in both, byte for byte. Both source studies were corrected, and the port now
carries oracle tests that pin the negative-input case and the exact clamp
boundary, not just the values that used to look reasonable. Nobody involved —
not the port, not the reviewer flagging the smell — expected "clamps to max"
as the answer, and that's the whole case: a signed clamp and an unsigned clamp
read almost identically until you read the instruction that separates them.</p>
<p>The rest of the sweep hardened seams rather than changing behavior. The
capability gateway closed out its two remaining unshipped surface items —
bounded summary responses so large results don't blow past sane limits, and
conservative per-capability metadata (game coverage, status, with filtering)
so callers can ask what a capability actually covers before running it.
Asset-format detection stopped promoting ambiguous, magic-less files without
filename corroboration. And a trigger-event regression test that had quietly
become tautological now parses its expected values out of the source study at
test time instead of duplicating them, so drift actually fails the build again
— a small fix that happens to sit right at the doorstep of the day's largest
piece of work.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="sixty-two-ways-for-an-event-to-happen">Sixty-two ways for an event to happen<a href="https://rets.dassheep.tech/devblog/callbacks-and-clamps#sixty-two-ways-for-an-event-to-happen" class="hash-link" aria-label="Direct link to Sixty-two ways for an event to happen" title="Direct link to Sixty-two ways for an event to happen" translate="no">​</a></h2>
<p>That doorstep is the campaign scripting engine. Map triggers are the machinery
every mission is written in: a tag listens for raised events, sweeps its
attached triggers, each trigger keeps a bitmask of which of its conditions
have been met, and a sprung trigger fires its chain of actions. That spine —
the sweep, the latch, the three-way persistence mode that decides whether a
fired trigger dies, waits for its siblings, or lives forever — is now
reversed, ported, and oracle-pinned across Tiberian Sun, Red Alert 2, and
Yuri's Revenge.</p>
<p>Then the two big boundaries fell. The first is the event evaluator itself:
the sixty-two-kind switch that answers "did this specific event actually
occur." It turns out to be two machines wearing one function. A handful of
kinds — timers, scenario variables, credit and light thresholds, a pair of
"does this unit type exist on the map" scans — answer directly. Everything
else flows through a pipeline of vetoes: a gate that matches the raised
event's kind, an object stage, a stage keyed on the house the event is
about, a stage keyed on the house the event <em>names</em>, and then — if nothing
objected — an ambient <strong>true</strong>. Events don't prove they happened; they
survive.</p>
<!-- -->
<p>That inversion explains a piece of mapper folklore: a trigger whose named
house doesn't exist in the scenario simply fires, because the failed lookup
skips the last veto stage entirely.</p>
<p>The decompiler lied twice in that one function, and both lies only came out
under the instruction stream. Four build-event kinds bypass the raised-kind
gate entirely — they re-evaluate on <em>every</em> event sweep, whatever was
raised — but the decompiled C shows only three of the four exclusions,
leaving a case that reads as live code and is actually unreachable. And two
success-flag writes appear in the decompile as stores through two different
parameters when the instructions write the same caller-supplied out-byte
both times. A third find was the engine's own quirk, faithfully preserved
behind a toggle: the "does this tech type exist" check answers <em>false</em> on an
empty battlefield even when the threshold is zero, because the empty-world
short-circuit runs before the threshold is ever compared. The community's
long-standing headers lost one too — the evaluator takes six arguments, not
the five they declare, and the sixth is the attacking unit that exactly one
event kind inspects.</p>
<p>Cross-game, the expected chaos never showed up: event and action numbering
is <em>preserved</em> across the whole lineage. The later engines append new kinds
at the top instead of renumbering — Yuri's Revenge adds nine event kinds and
twenty-eight action kinds over Red Alert 2 — under uniform struct-layout
shifts. The one genuine behavioral fork is ownership propagation: only
Yuri's Revenge records which house triggered an object event and forwards
that house onto the trigger itself, and the field it lands in turns out to
be the very struct member whose insertion caused the offset shift we'd been
noting all along. Yesterday's "these two functions are identical across
games" verdict is corrected accordingly, and the action dispatcher's
skeleton is pinned as well — including the detail that a failed action never
aborts the rest of its chain.</p>
<p>Three unrelated corners of the engine, then, and the same lesson under each:
the harvester's stateful failure, the clamp that goes loud instead of silent,
and the evaluator that fires on a house that isn't there — none of them the
answer the plausible reading would have written. That discipline is exactly
what gates publication, so as those systems close out they join the
<a class="" href="https://rets.dassheep.tech/reference">Engine Reference</a>, where four more completed entries went live
today: how draw layers are filed and sorted before a frame renders, how the
tactical view projects world space to screen space, how the sidebar routes
its commands, and how warhead rules are resolved. Each one only publishes
once its system is fully reversed, ported, and oracle-verified — the same bar
that caught today's volume clamp before it could quietly ship. The
per-action handler bodies, all hundred-and-forty-five of them, are the next
atoms.</p>]]></content>
        <author>
            <name>The reTS project</name>
            <uri>https://github.com/DasSheep/reTS</uri>
        </author>
        <category label="devblog" term="devblog"/>
        <category label="economy" term="economy"/>
        <category label="missions" term="missions"/>
        <category label="audio" term="audio"/>
        <category label="mcp" term="mcp"/>
        <category label="reference" term="reference"/>
        <category label="documentation" term="documentation"/>
        <category label="triggers" term="triggers"/>
        <category label="campaign" term="campaign"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[From the Harvest Route to New Ways In]]></title>
        <id>https://rets.dassheep.tech/devblog/engine-seams</id>
        <link href="https://rets.dassheep.tech/devblog/engine-seams"/>
        <updated>2026-07-14T21:46:54.000Z</updated>
        <summary type="html"><![CDATA[Today connected several kinds of access to the engine. The harvesting work]]></summary>
        <content type="html"><![CDATA[<p>Today connected several kinds of access to the engine. The harvesting work
exposed more of a classic gameplay route as small, explainable contracts. In
parallel, the project changed how creators, AI agents, and readers each reach
those contracts without weakening the faithful core.</p>
<!-- -->
<p><em>Last verified against the project oracle, designs, and live site: 2026-07-16.</em></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="following-resources-to-the-refinery-door">Following resources to the refinery door<a href="https://rets.dassheep.tech/devblog/engine-seams#following-resources-to-the-refinery-door" class="hash-link" aria-label="Direct link to Following resources to the refinery door" title="Direct link to Following resources to the refinery door" translate="no">​</a></h2>
<p>Harvesting is a relay rather than one calculation: inspect a resource cell,
remove an amount, store it on a unit, locate and contact a refinery, unload, and
update the owning house. reTS can now execute and explain the numeric core of
that relay through the refinery-range decision.</p>
<p>The engine generations share concepts but not storage. Red Alert 2 and Yuri's
Revenge keep multiple floating-point resource amounts on a harvester. Tiberian
Sun uses integer storage and distinct unload, silo, and weed paths. The port
keeps these as versioned contracts because the differences affect rounding,
clamping, and when resources become credits.</p>
<p>For the later games, refinery distance also contains a policy choice. Ordinary
harvesters use <code>HarvesterTooFarDistance</code>; types marked as teleporters use
<code>ChronoHarvTooFarDistance</code>. Both accept equality and use the same three-
dimensional distance arithmetic, including the original table-based square-root
approximation and integer conversion. The inspection record explains which rule
was selected and how the final route was chosen.</p>
<p>Tiberian Sun follows a different branch: refinery radio candidates are scanned
before this point, and a separate fixed three-cell check is used later when
rebuilding a path. That divergence remains explicit. Full refinery search,
pathfinding integration, world consumers, and the rest of the harvest mission
are still open.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="a-planned-native-creation-surface">A planned native creation surface<a href="https://rets.dassheep.tech/devblog/engine-seams#a-planned-native-creation-surface" class="hash-link" aria-label="Direct link to A planned native creation surface" title="Direct link to A planned native creation surface" translate="no">​</a></h2>
<p>Lua is planned as an opt-in extension layer beside the original Trigger, Tag,
Event, Action, Team, and Script systems—not as their replacement. Bindings may
only grow over engine behavior that has already been recovered and verified.</p>
<p>The design treats determinism as part of the interface. Simulation-facing
scripts receive controlled engine time and randomness, use typed numeric
boundaries, carry content hashes, and run within per-tick limits. With no Lua
loaded, the faithful vanilla path remains the reference.</p>
<p>This is roadmap and architecture work today, not a shipped Lua runtime. Mission
logic, custom modes, AI, abilities, persistence, UI, and audio hooks remain
planned tiers gated by their engine dependencies.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="a-front-door-that-no-longer-grows-with-the-engine">A front door that no longer grows with the engine<a href="https://rets.dassheep.tech/devblog/engine-seams#a-front-door-that-no-longer-grows-with-the-engine" class="hash-link" aria-label="Direct link to A front door that no longer grows with the engine" title="Direct link to A front door that no longer grows with the engine" translate="no">​</a></h2>
<p>Every previously reversed system had earned its own top-level tool in the MCP
server that AI agents use to query and run the engine's verified logic. That
rule scaled fine when there were a handful of systems. It stops working as
coverage grows: each addition is another schema every connecting client has to
load and pay context for, whether or not that session ever touches the
system, and the AI-facing surface would keep growing in lockstep with the very
engine coverage the project is trying to increase. A snapshot of the server
this week, with the faithful engine still early in its build-out, already
showed several dozen advertised tools compiling into a discovery listing large
enough to crowd a meaningful share of a working context window before an
agent had asked a single question.</p>
<p>The fix does not touch the engine itself. Every verified operation still
exists as the same faithful Rust, producing the same structured breakdown of
how it reached its answer. What changed is how an agent finds and calls them.
The default surface is now a small, fixed gateway: search a compact catalog
for the capability you need, load that one capability's full schema and
documentation only once you have picked it, then call it with validated
arguments. The high-frequency check against the project's binary-derived
oracle suite stays directly callable on its own, since it is used constantly
and costs little to keep advertised. Everything else — economy, presentation,
rules, and every future reversed system — moved into an internal registry
that the gateway searches on demand instead of announcing up front. Roughly
sixty existing operations made that move in one pass, and every one of them
remains reachable through the same three-step search-describe-run path.</p>
<p>The practical effect is a startup listing that stopped tracking engine
completeness. Discovering a capability now costs an agent a search and one
schema load instead of a share of everything the project has ever reversed,
and the project checks that boundary automatically rather than promising to
remember it by hand.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="a-public-explanation-surface">A public explanation surface<a href="https://rets.dassheep.tech/devblog/engine-seams#a-public-explanation-surface" class="hash-link" aria-label="Direct link to A public explanation surface" title="Direct link to A public explanation surface" translate="no">​</a></h2>
<p>The public website and devblog now live in a repository separate from the
project's private working evidence. Public pages are written fresh for their
audience instead of being copied from internal research notes. A blocking
leakage scan checks the output, and factual claims are reviewed against the same
evidence that supports the implementation.</p>
<p>The site, blog, and Engine Reference scaffold are live. The leakage scanner,
structural accuracy checks, and prepublication orchestrator are operational.
Technical meaning still receives a recorded agent claim audit: structural checks
cannot decide whether public prose has drifted from the recovered behavior. The
cross-repository commit and push also remain explicit rather than automatic.</p>
<p>The engine remains the center of all four stories. The harvest route is a
faithful gameplay surface, Lua is a future creation surface over verified
behavior, the gateway is how machines reach that same verified behavior
today, and the knowledge base is the explanation surface beside all of it.</p>]]></content>
        <author>
            <name>The reTS project</name>
            <uri>https://github.com/DasSheep/reTS</uri>
        </author>
        <category label="devblog" term="devblog"/>
        <category label="economy" term="economy"/>
        <category label="harvesting" term="harvesting"/>
        <category label="lua" term="lua"/>
        <category label="mcp" term="mcp"/>
        <category label="documentation" term="documentation"/>
        <category label="tooling" term="tooling"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[Three Foundations in One Day]]></title>
        <id>https://rets.dassheep.tech/devblog/engine-foundations</id>
        <link href="https://rets.dassheep.tech/devblog/engine-foundations"/>
        <updated>2026-07-14T15:50:13.000Z</updated>
        <summary type="html"><![CDATA[July 14 produced three connected foundations: a verified factory heartbeat, an]]></summary>
        <content type="html"><![CDATA[<p>July 14 produced three connected foundations: a verified factory heartbeat, an
executable rules-loading cascade, and a clearer boundary between faithful engine
work and everything that can be built above it.</p>
<!-- -->
<p><em>Last verified against the project oracle, design, and tracker: 2026-07-16.</em></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="a-factory-advances-in-54-steps">A factory advances in 54 steps<a href="https://rets.dassheep.tech/devblog/engine-foundations#a-factory-advances-in-54-steps" class="hash-link" aria-label="Direct link to A factory advances in 54 steps" title="Direct link to A factory advances in 54 steps" translate="no">​</a></h2>
<p>Classic factory production divides a build into exactly 54 progress steps. Cost
is not split into 54 identical payments. Each debit is calculated from the
remaining balance and remaining steps, carrying integer remainders forward so
the final result still equals the advertised cost.</p>
<p>Ordering matters. When a production timer expires, progress advances before the
next debit is calculated. If the house cannot pay, progress rolls back one step,
but the timer has already restarted. The final transition consumes the exact
balance left.</p>
<p>That shared heartbeat sits inside version-specific timing and power behavior.
The later games share the full spend state machine; Tiberian Sun does not write
the same automatic out-of-money hold state. Multiple-factory and low-power
arithmetic also differ, so reTS selects the matching versioned path and exposes
the intermediates rather than presenting one blended formula.</p>
<p>This is the numeric core, not the entire production experience. Sidebar queues,
placement, delivery, and wider object lifecycle work remain open.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="rules-arrive-as-a-cascade">Rules arrive as a cascade<a href="https://rets.dassheep.tech/devblog/engine-foundations#rules-arrive-as-a-cascade" class="hash-link" aria-label="Direct link to Rules arrive as a cascade" title="Direct link to Rules arrive as a cascade" translate="no">​</a></h2>
<p>The engine does not load one definitive rules file. It repeatedly applies layers
to the same objects. A present key replaces the current value; an absent key
leaves the earlier value intact.</p>
<p>Red Alert 2 and Yuri's Revenge apply base rules, language rules, an eligible
session layer, and then the map. Tiberian Sun and Firestorm use a different
middle sequence, with expansion rules and expansion-language data controlled by
separate conditions before the map has the final opportunity to override keys.</p>
<p>This repeated overlay can resemble section inheritance from the outside, but
the verified Yuri's Revenge reader is re-reading sections across files rather
than following parent sections. The current model covers four representative
fields over caller-resolved layers; file discovery, ART and AI families, and the
full type surface remain future work.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="more-ambition-separate-ledgers">More ambition, separate ledgers<a href="https://rets.dassheep.tech/devblog/engine-foundations#more-ambition-separate-ledgers" class="hash-link" aria-label="Direct link to More ambition, separate ledgers" title="Direct link to More ambition, separate ledgers" translate="no">​</a></h2>
<p>The roadmap now names four distinct layers. The faithful engine remains the
foundation and the compatibility oracle. Parameterization may turn a verified
hard limit into configuration while retaining the original value as the
default. Modernization covers opt-in code, assets, presentation, and modding.
Tooling covers editors, converters, workbenches, and inspection surfaces that
consume the engine.</p>
<p>Each layer has its own progress accounting. A useful tool or ambitious design
does not increase the faithful-engine completion figure; only recovered and
verified engine behavior does. That separation lets the project grow without
making its central accuracy claim easier.</p>]]></content>
        <author>
            <name>The reTS project</name>
            <uri>https://github.com/DasSheep/reTS</uri>
        </author>
        <category label="devblog" term="devblog"/>
        <category label="production" term="production"/>
        <category label="rules" term="rules"/>
        <category label="roadmap" term="roadmap"/>
        <category label="versions" term="versions"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[CanBuild Is Not a Boolean]]></title>
        <id>https://rets.dassheep.tech/devblog/canbuild-is-not-a-boolean</id>
        <link href="https://rets.dassheep.tech/devblog/canbuild-is-not-a-boolean"/>
        <updated>2026-07-13T03:27:44.000Z</updated>
        <summary type="html"><![CDATA[“Can this house build this unit?” looks like a yes-or-no question. In Yuri's]]></summary>
        <content type="html"><![CDATA[<p>“Can this house build this unit?” looks like a yes-or-no question. In Yuri's
Revenge, the answer is a three-state decision produced by an order-sensitive
control-flow graph.</p>
<p>Reconstructing that graph took a sequence of small, testable pieces. The final
composition is one of the clearest examples yet of why faithful behavior lives in
the route to an answer, not only in the answer itself.</p>
<!-- -->
<p><em>Last verified against the project oracle: 2026-07-15.</em></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="three-answers-several-routes">Three answers, several routes<a href="https://rets.dassheep.tech/devblog/canbuild-is-not-a-boolean#three-answers-several-routes" class="hash-link" aria-label="Direct link to Three answers, several routes" title="Direct link to Three answers, several routes" translate="no">​</a></h2>
<p>The engine distinguishes:</p>
<ul>
<li class="">buildable;</li>
<li class="">unbuildable; and</li>
<li class="">temporarily unbuildable, such as a currently reached positive build limit.</li>
</ul>
<p>The full decision considers an explicit unbuildable flag, technology levels,
ownership and side restrictions, stolen-technology requirements, the skirmish
superweapon option, prerequisites, and per-type build limits. It also contains
shortcuts for special ownership cases.</p>
<p>Those checks are not a flat list. One ownership shortcut skips later technology
and prerequisite gates but still routes through the build-limit decision. By
contrast, once the decision reaches the prerequisite stage, the
non-human-controlled path returns buildable before the scan and final limit
check; earlier hard gates still apply. Moving either branch to a more “logical”
position changes game behavior.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="testing-the-path-not-just-the-result">Testing the path, not just the result<a href="https://rets.dassheep.tech/devblog/canbuild-is-not-a-boolean#testing-the-path-not-just-the-result" class="hash-link" aria-label="Direct link to Testing the path, not just the result" title="Direct link to Testing the path, not just the result" translate="no">​</a></h2>
<p>Many rejection branches return the same numeric value. A test that checks only
“unbuildable” would still pass if two failures were accidentally reordered.</p>
<p>The reTS breakdown therefore records the stages that actually ran and the exact
terminal reason. The oracle cases deliberately combine multiple possible
failures to prove which one wins. They also pin shortcut-to-limit behavior and
the early non-human return.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="one-version-complete-differences-kept-honest">One version complete, differences kept honest<a href="https://rets.dassheep.tech/devblog/canbuild-is-not-a-boolean#one-version-complete-differences-kept-honest" class="hash-link" aria-label="Direct link to One version complete, differences kept honest" title="Direct link to One version complete, differences kept honest" translate="no">​</a></h2>
<p>This composition is the complete Yuri's Revenge path. Red Alert 2 preserves the
broad shape but omits or changes several pieces. Tiberian Sun has a more
substantially different decision, including its own prerequisite vocabulary and
build-limit behavior.</p>
<p>Those versions will get complete composers of their own. reTS does not model an
older game by running the later path with a handful of fields switched off; that
would make the interface look finished before the behavior really is.</p>]]></content>
        <author>
            <name>The reTS project</name>
            <uri>https://github.com/DasSheep/reTS</uri>
        </author>
        <category label="devblog" term="devblog"/>
        <category label="production" term="production"/>
        <category label="rules" term="rules"/>
        <category label="ai" term="ai"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[Making Time and Chance Deterministic]]></title>
        <id>https://rets.dassheep.tech/devblog/time-and-chance</id>
        <link href="https://rets.dassheep.tech/devblog/time-and-chance"/>
        <updated>2026-07-08T01:37:56.000Z</updated>
        <summary type="html"><![CDATA[An RTS simulation cannot be reproduced exactly if “random” means whatever the]]></summary>
        <content type="html"><![CDATA[<p>An RTS simulation cannot be reproduced exactly if “random” means whatever the
host platform happens to provide, or if timers depend on wall-clock time. The
second foundation milestone gave reTS its own faithful answers to both problems.</p>
<!-- -->
<p><em>Last verified against the project oracle: 2026-07-15.</em></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-same-seed-must-mean-the-same-game">The same seed must mean the same game<a href="https://rets.dassheep.tech/devblog/time-and-chance#the-same-seed-must-mean-the-same-game" class="hash-link" aria-label="Direct link to The same seed must mean the same game" title="Direct link to The same seed must mean the same game" translate="no">​</a></h2>
<p>The original randomizer maintains a 250-word state table. Each draw mutates that
table with wrapping integer operations; ranged draws use an inclusive interval
and a rejection mask rather than a simple remainder. Signed shifts also matter.
Replacing any of those details with a standard Rust generator would produce
perfectly good randomness—and the wrong match.</p>
<p>reTS now reproduces the raw stream, inclusive ranged values, and floating-point
draws from a known seed. The active sequence is the same across Tiberian Sun,
Red Alert 2, and Yuri's Revenge even though the older game's object layout is a
different size.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="a-timer-is-frame-state">A timer is frame state<a href="https://rets.dassheep.tech/devblog/time-and-chance#a-timer-is-frame-state" class="hash-link" aria-label="Direct link to A timer is frame state" title="Direct link to A timer is frame state" translate="no">​</a></h2>
<p>The engine's common timers are compact state machines driven by the current
simulation frame. An inactive sentinel distinguishes a stopped timer from a
running one. Pausing first calculates and stores the remaining duration;
resuming records a new start frame. Expiration clamps the visible remainder at
zero, while the internal subtraction deliberately tolerates the signed frame
counter wrapping around.</p>
<p>Tiberian Sun names the freeze and thaw operations Stop and Start. The later games
call them Pause and Resume. The recovered state transition is otherwise the same.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="a-useful-save-game-surprise">A useful save-game surprise<a href="https://rets.dassheep.tech/devblog/time-and-chance#a-useful-save-game-surprise" class="hash-link" aria-label="Direct link to A useful save-game surprise" title="Direct link to A useful save-game surprise" translate="no">​</a></h2>
<p>The research also found a behavior that future save/load work must preserve: the
scenario random stream is read from saved data and then reconstructed from seed
zero. reTS does not implement that complete save path yet, but the unexpected
reset is now a testable requirement rather than a tempting “bug fix.”</p>
<p>Randomness and time are invisible when they work. That is precisely why they had
to land early: movement choices, combat rolls, AI decisions, replays, and
multiplayer synchronization will all depend on them producing the same answer on
every machine.</p>]]></content>
        <author>
            <name>The reTS project</name>
            <uri>https://github.com/DasSheep/reTS</uri>
        </author>
        <category label="devblog" term="devblog"/>
        <category label="determinism" term="determinism"/>
        <category label="rng" term="rng"/>
        <category label="timers" term="timers"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[Damage Is a Pipeline]]></title>
        <id>https://rets.dassheep.tech/devblog/damage-is-a-pipeline</id>
        <link href="https://rets.dassheep.tech/devblog/damage-is-a-pipeline"/>
        <updated>2026-07-07T08:59:33.000Z</updated>
        <summary type="html"><![CDATA[The first end-to-end piece of reTS was not a menu or a moving unit. It was a]]></summary>
        <content type="html"><![CDATA[<p>The first end-to-end piece of reTS was not a menu or a moving unit. It was a
number: the exact amount of health removed when one object hits another.</p>
<p>That sounds modest. It turned out to be the right foundation for the whole
project, because the original engine does not calculate damage in one neat
formula. It passes the value through a sequence of rules, and the integer result
can change if even two apparently equivalent stages are swapped.</p>
<!-- -->
<p><em>Last verified against the project oracle: 2026-07-15.</em></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="why-order-matters">Why order matters<a href="https://rets.dassheep.tech/devblog/damage-is-a-pipeline#why-order-matters" class="hash-link" aria-label="Direct link to Why order matters" title="Direct link to Why order matters" translate="no">​</a></h2>
<p>The current verified path begins on the firing side, where weapon damage is
adjusted by house, unit, and veterancy effects. The target then applies its armor
modifiers. After that, the warhead handles distance falloff, its armor-versus
table, and the global damage ceiling. Only then is the result applied to health.</p>
<p>Several of those stages truncate a fractional result toward zero. Multiplication
is normally commutative on paper; multiplication with an integer conversion
between stages is not. reTS therefore keeps the stages separate instead of
collapsing them into an elegant but inaccurate expression.</p>
<p>The health application has its own details. Crossing half health and crossing
the configurable red-health threshold are distinct events. Lethal damage is
capped to the health that remained. Those transitions matter to triggers and
presentation, not only to the final hit-point counter.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="healing-takes-a-different-road">Healing takes a different road<a href="https://rets.dassheep.tech/devblog/damage-is-a-pipeline#healing-takes-a-different-road" class="hash-link" aria-label="Direct link to Healing takes a different road" title="Direct link to Healing takes a different road" translate="no">​</a></h2>
<p>Negative damage is treated as healing, but it does not simply run the normal
pipeline backward. The original takes a special short-range path that skips
normal distance falloff and the armor-versus table, then clamps the restored
health to the unit type's maximum.</p>
<p>It is exactly the kind of rule a clean rewrite might “simplify” and quietly get
wrong. In reTS it is explicit, tested, and visible in the result breakdown.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="more-than-a-final-number">More than a final number<a href="https://rets.dassheep.tech/devblog/damage-is-a-pipeline#more-than-a-final-number" class="hash-link" aria-label="Direct link to More than a final number" title="Direct link to More than a final number" translate="no">​</a></h2>
<p>Every damage query can return the intermediate stages that produced its answer.
The same functions are exercised by hand-derived examples, and the inspection
surface is exposed to tools as well as normal code. That pattern—calculate,
explain, and verify—became the template for every system that followed.</p>
<p>This first vertical is currently anchored to Yuri's Revenge behavior. The
cross-version comparison for Tiberian Sun and Red Alert 2 remains separate work;
the post does not assume that similar-looking combat rules are identical.</p>]]></content>
        <author>
            <name>The reTS project</name>
            <uri>https://github.com/DasSheep/reTS</uri>
        </author>
        <category label="devblog" term="devblog"/>
        <category label="combat" term="combat"/>
        <category label="verification" term="verification"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[Introducing reTS]]></title>
        <id>https://rets.dassheep.tech/devblog/welcome</id>
        <link href="https://rets.dassheep.tech/devblog/welcome"/>
        <updated>2026-07-06T02:00:00.000Z</updated>
        <summary type="html"><![CDATA[Welcome to the reTS devblog.]]></summary>
        <content type="html"><![CDATA[<p>Welcome to the <strong>reTS</strong> devblog.</p>
<p><em>Last verified against the project charter: 2026-07-15.</em></p>
<p>reTS is an early project to build a faithful, standalone, moddable
reimplementation of the classic Westwood isometric RTS engine—the one behind the
<strong>Command &amp; Conquer</strong> titles Tiberian Sun, Red Alert 2, and Yuri's Revenge.
Accuracy is the non-negotiable goal; modern, opt-in layers are planned only on
top of verified behavior.</p>
<!-- -->
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="why-this-devblog-exists">Why this devblog exists<a href="https://rets.dassheep.tech/devblog/welcome#why-this-devblog-exists" class="hash-link" aria-label="Direct link to Why this devblog exists" title="Direct link to Why this devblog exists" translate="no">​</a></h2>
<p>Rebuilding a decades-old engine faithfully is a long journey: understand exactly
how the original behaves, rebuild it so it matches, prove the match, and only then
extend it with modern conveniences and a native modding path. This devblog follows
that journey — the milestones, the surprises, and the design decisions behind the
move from a faithful reconstruction to a modern, extensible engine.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="what-to-expect">What to expect<a href="https://rets.dassheep.tech/devblog/welcome#what-to-expect" class="hash-link" aria-label="Direct link to What to expect" title="Direct link to What to expect" translate="no">​</a></h2>
<ul>
<li class=""><strong>How the engine works</strong>, explained for a general audience.</li>
<li class=""><strong>The modernization story</strong> — tooling, configurable limits, modern presentation,
and native scripting/modding — always layered on top of a faithful core.</li>
<li class=""><strong>Milestones</strong> as reTS grows toward being playable end to end.</li>
</ul>
<p>This is an early post on an early project. There's a lot ahead — thanks for
following along.</p>]]></content>
        <author>
            <name>The reTS project</name>
            <uri>https://github.com/DasSheep/reTS</uri>
        </author>
        <category label="devblog" term="devblog"/>
        <category label="announcement" term="announcement"/>
    </entry>
</feed>