# `FUN_007066c0` and `FUN_00703730` — the "path solver", read from the instruction stream Lane P2, 2026-09-08. Program `sots` / "Sword of the Stars.exe", ImageBase 0x00400000, all addresses VAs. **Method.** Every function below was disassembled with `objdump -D -b binary -m i386 -M intel` over the raw `.text` image (file offset `VA - 0x400C00`), **each to the next function start** (rule 17), and the padding inspected. Jump tables were resolved byte by byte from `.text`. No claim about control flow here comes from a decompiler. Everything is marked **[V]** instruction-verified by me or **[I]** inferred; there are no delegated reads in this lane. Closes `combat-retreat-pipeline.md` §6's *"The three failure bits `0x008 / 0x010 / 0x400` of `OrderFleetMove`"* and §8's *"the single biggest hole"*. It also closes the type-2 question that `movefleet-position-rounding.md` §6/§7, `strategic-turn-internals.md` §4.3 and lane O's behavioural sweep have each circled from a different side, and it **corrects the name of waypoint type 2**. --- ## 0. Lead: it is not a path *finder* **`FUN_007066c0` performs no graph search over systems.** There is no frontier, no visited set, no relaxation, no priority queue, and no recursion. It is a **per-leg classifier and validator** over a waypoint list its caller already chose: ``` solver(fleet, start, dests[], n) : drop dests[0] if it is the fleet itself or the fleet's current system probe leg (start -> dests[0]) prev = start for i in 0 .. n-1: types[i] = ClassifyLeg(fleet, prev, dests[i], &rangeLeft, &legFlags, &routes[i]) flags |= legFlags ; failIndex = first i with legFlags != 0 rangeLeft = f32(rangeLeft - f32(sqrt(f32(|prev.pos - dests[i].pos|^2)))) if dests[i] is a system where the fleet may refuel: rangeLeft = fullTank prev = dests[i] return true // ALWAYS true past the argument checks ``` The only graph lookup in the whole subtree is `FUN_006e4eb0`, and it is a **single-hop adjacency query**: "is there a node line between system A and system B that this player has discovered?" It answers with the line's path index or −1. It never looks at a third system. **A multi-hop node route in a save is therefore `n` waypoints, one per hop, each classified independently** — the routing decision was made by whoever built `dests[]` (the UI's map click chain, or the AI), never here. [V] The function's **return value is `true` for every call that has a non-null fleet and a non-null start object**; all outcome information travels in the two out-parameters. [V] `ret` (no `ret N`) with eight stack arguments — **cdecl, not thiscall**, despite `ecx` being loaded with the fleet at three call sites. The fleet is passed as argument 1 *and* used as `this` for the range helpers. [V] ### 0.1 Real boundaries (rule 17) | function | Ghidra size | real body | verdict | |---|---|---|---| | `FUN_007066c0` | 584 | `0x7066c0..0x706907`, then 8 × `int3` to `0x706910` | **correct** | | `FUN_00703730` | 1178 | `0x703730..0x703bc9`, then 6 × `int3` to `0x703bd0` | **correct** | | `FUN_008653c0` | 801 | `0x8653c0..0x8656ea`, then 5 × `int3` to `0x8656f0` | **correct** | | `FUN_00707080` | 514 | `0x707080..0x707281`, then 14 × `int3` to `0x707290` | **correct** | Four for four. Recorded because the rule exists — but this lane found the sizes right, and the disassembly to the next start is what establishes that rather than assuming it. ### 0.2 Two functions that read live-in registers `FUN_00703650` and `FUN_00703bd0` each **test a register they never write**: ``` 703650 push ebp; mov ebp,esp 703653 mov eax,[ebp+0xc] ; test eax,eax ; je ... ; mov DWORD PTR [eax],0x0 703660 test ebx,ebx ; <-- ebx is live-in 703668 test esi,esi ; <-- esi is live-in ``` At `FUN_00703730`'s call site 0x703831, `ebx` = the moving fleet and `esi` = the target fleet; at `FUN_00703c90`'s call site 0x703caa, `esi` = the system. Both have exactly one caller. **Treating either as a plain cdecl function and reading only its stack arguments produces nonsense** — I nearly did. This is a new failure mode for the rules file, adjacent to §7.3 of `combat-retreat-pipeline.md`: *check whether a callee reads a callee-saved register before it writes it.* [V] --- ## 1. `FUN_007066c0` — the driver ### 1.1 Signature, from the two call sites and the frame ```c // __cdecl, 8 args, always returns true past the guards. bool PathSolver(StarFleet* fleet, // +0x08 the moving fleet ("this" for the range helpers) MapObject* start, // +0x0c where leg 0 begins (OrderFleetMove passes the fleet) MapObject** dests, // +0x10 the ordered destination list unsigned count, // +0x14 its length (DECREMENTED IN PLACE, see 1.2) int* flagsOut, // +0x18 OR of every leg's flags (optional) int* failIdxOut, // +0x1c index of the FIRST failing leg (optional, -1 = none) int* typesOut, // +0x20 one waypoint type per dest, stride 4 (optional) NodeRoute* routesOut); // +0x24 one NodeRoute per dest, stride 0x10 (optional) ``` `typesOut` and `routesOut` are written **only when non-null**, and each is guarded separately (0x706808 / 0x706814), so a caller may ask for flags alone. Both callers do exactly that or ask for everything: | caller | call | what it wants | |---|---|---| | `FUN_005e6d50` (UI move command) | `(f, f, dests, n, &flags, 0, 0, 0)` | **dry run.** If `flags != 0` it allocates a 0x8f8-byte dialog and hands it `(f, dests, n, flags)` — the move-confirmation prompt. If `flags == 0` it sends the command. [V] | | `FUN_008653c0` `OrderFleetMove` | `(f, f, dests, n, &flags, 0, types._Myfirst, routes._Myfirst)` | the real thing | **The UI tests `flags != 0`; `OrderFleetMove` tests `flags & 0x418`.** That split is what separates the warning bits from the refusal bits, and it is the cleanest evidence for the reading in §3. [V] ### 1.2 The leading-destination drop ``` 706722 ecx = dests[0] 706724 if (fleet == ecx) goto drop 706728 eax = fleet->LocID(+0xa0) 70672e if (!eax || eax->+0x14 != 0) eax = 0 ; inlined StarFleet_GetLocationIfNode 706739 if (eax == ecx) goto drop goto keep drop: dests += 4 ; --count ; 0x70673d ``` So a destination list whose first entry is the fleet itself, or the *system* the fleet is already at, has that entry removed — `dests` and `count` are advanced/decremented **in the solver's own stack copies only** (`count` lives at `[ebp+0x14]`, a by-value argument), so `OrderFleetMove`'s `count` is untouched and it still builds `count` waypoints from its **un-dropped** list. **The `types[]` and `routes[]` the solver writes are therefore shifted by one relative to the waypoints `OrderFleetMove` builds whenever the drop fires**, and `types[count-1]` / `routes[count-1]` are never written at all — they keep the value the vector was constructed with. That is an original defect and it is testable: §7, P4. [V] `FUN_00459f70`, `FUN_0085bf00` and `FUN_00703e90` are all MSVC `vector::resize(n)` — shrink-by-erase on one side, grow-and-value-initialise on the other (`rep stos eax=0` for the `int` vector at 0x459fd8) — **not `reserve`**, so `_Myfirst != _Mylast` and the two out-pointers are non-null whenever `count > 0`, and the untouched tail element is a genuine zero rather than uninitialised memory. Checked precisely because the whole paragraph above collapses if any of the three were a `reserve`. [V] The `+0x14 != 0` guard means the drop only fires for a **system** location, never a point or a fleet. [V] ### 1.3 The three fuel figures ``` 70674b [ebp-0x24] = StarFleet_MinRange(fleet, 0.0f) ; 0x006ff6a0 706757 [ebp-0x38] = FUN_00705d60(fleet, true) ; full tanks 706762 [ebp-0x10] = FUN_00705d60(fleet, false) ; fuel remaining <- the running budget ``` `StarFleet_MinRange` is already `verified` in `addresses.json` with the identical body I read here (min over ships of `ship->Range(+0x20)`, seeded `FLT_MAX` from the `.rdata` word at 0x009e23a8 = `0x7f7fffff`, strictly-greater update, then `f32(best + bias)`); **agreement recorded, no duplicate entry filed.** [V] `FUN_00705d60(fleet, bool useMaxRange)` builds a `vector` of per-ship ranges — `ship->Range(+0x20)` when the flag is false, `Ship_MaxRange` (0x0080c820) when true — alongside a per-design list keyed on `design->+0x12c`, and folds them. **I read only its first ~0x90 bytes and its two call sites**; the fold itself (tanker redistribution) is *not* read. What is established is the argument's polarity and that the result is a float. [V for the polarity, **not read** for the fold] The **pre-flight probe** at 0x706795 passes `&[ebp-0x24]` — the plain `MinRange`, not the effective range — so the first leg is evaluated **twice**, once against the pessimistic budget and once for real. Its `flagsOut` is *assigned* (not OR'd) and its `failIdx` set to 0; its return value and its route are discarded (`routeOut = NULL`). [V] ### 1.4 The per-leg loop, exactly ``` 7067c3 rangeLeft = max(rangeLeft, 0.0f) ; fcomp 0.0 vs rangeLeft, strict 7067df legFlags = 0 7067e6 NodeRoute tmp; ctor 0x006e1b20 -> {vptr=0x00a1cbdc, nrp=-1, nrf=0, nrt=0} 706803 t = ClassifyLeg(fleet, prev, cur, &rangeLeft, &legFlags, &tmp) ; FUN_00703730 706808 if (typesOut) typesOut[i] = t 706814 if (routesOut) { routesOut[i].nrp = tmp.nrp; .nrf = tmp.nrf; .nrt = tmp.nrt } ; vptr NOT copied 70682e if (legFlags) { if (failIdx == -1) failIdx = i; flags |= legFlags } 706844 rangeLeft -= legLength(prev, cur) ; see 1.5 706886 if (cur->+0x14 == 0 && CanRefuelAt(cur, fleet)) rangeLeft = fullTank 7068b2 ++i; dests += 4; routesOut += 0x10; prev = cur 7068ce tmp.vptr = 0x009e22bc ; the INLINED NodeRoute destructor -- see below ``` **Rule 4 trap, and it is a live one here.** `mov DWORD PTR [ebp-0x48],0x9e22bc` on the back edge looks like a second object being installed. It is not: 0x009e22bc is the `Mars::IStreamable` vftable (rtti), and this store is the whole of the inlined `~NodeRoute()` — MSVC's base-class-vptr reset with nothing left to free. The *constructor* runs again at 0x7067e6 at the top of the next iteration, so `nrp` really is reset to −1 every leg. Reading the store as a branch or as a missing re-init would have produced a false "stale node route carried between legs" bug report. [V] The `[ebp-0x4]` writes (0 before the call, −1 on the back edge) are the SEH try-level for that same local, not control flow. [V] ### 1.5 The leg length — and its precision ``` 706844 fld [edi+0x18] ; fsub [esi+0x18] ; fstp DWORD [ebp-0x30] ; dx -> FLOAT32 70684d fld [edi+0x1c] ; fsub [esi+0x1c] ; fstp DWORD [ebp-0x24] ; dy -> FLOAT32 706856 fld [edi+0x20] ; fsub [esi+0x20] ; fstp DWORD [ebp-0x34] ; dz -> FLOAT32 70685f..706876 dx*dx + dy*dy + dz*dz entirely on the x87 stack (53-bit) 706878 fstp DWORD [ebp-0x34] ; sumsq -> FLOAT32 70687b fld [ebp-0x34] ; call 0x924f52 (sqrt) ; fstp DWORD [ebp-0x34] ; len -> FLOAT32 70688a fld [ebp-0x34] ; fstp DWORD [ebp-0x34] ; a no-op reload/store 706890 fld [ebp-0x10] ; fsub [ebp-0x34] ; fstp DWORD [ebp-0x10] ; rangeLeft -> FLOAT32 ``` This is `Mars_Vec3_Length` (lane M §1) **inlined**, with the same two narrowings, plus a third on the subtraction. Lane M's rule holds verbatim: the delta a reimplementation subtracts is `f32(a.c − b.c)`, not the exact difference, and the sum of squares is narrowed **once**, after a 53-bit accumulation. Because a float32 delta has 24 significand bits, each square is exact in double and so is the three-term sum, so a `double` accumulator with a single final narrowing is **bit-identical** here. `fpu_cw = 0x127f`. [V] `prev` is the **previous destination object**, not the fleet's position, from leg 1 onward (`edi = esi` at 0x7068c5) — so the budget is drawn down along the *waypoint chain*, not from where the fleet actually is. On leg 0, `prev` is the `start` argument, which `OrderFleetMove` sets to the fleet. [V] ### 1.6 Refuelling `FUN_00703c90(node, fleet)` — 85 B [V]: ``` owner = MapObject_GetOwner(fleet) ; FUN_0071e280, switch on +0x14: ; 0 -> sys->PID(+0x100), 1 -> fleet->PID(+0x58), else 0 a = FUN_00703bd0(owner) ; esi = node LIVE-IN. For each fleet at the node (vft[2] count, vft[3] index), ; skipping any whose owner differs when owner != 0: ; true if StarFleet_HasFlagShips(f, 2, 0) -- capability mask 2, the tanker bit b = node && owner && node->PID(+0x100) && Relation(node->PID, owner) >= 3 ; FUN_0080e050 return a || b ``` `FUN_0080e050` is `FUN_006d2050(this->PlyrIdx, &this->Team(+0x168), other->PlyrIdx)`. `strategic-turn-internals` §5.2 records that function's result as *"1 ally, 2 NAP, 3 cease-fire"*; if that ordering is right, `>= 3` would mean *"refuel at a cease-fire system but not at an ally's"*, which is almost certainly backwards. **I did not read `FUN_006d2050`.** The `>= 3` test is [V]; the meaning of 3 is **open**, and §5.2's ordering should be re-checked by whoever needs it. Two independent call sites use the same scale with different thresholds (`>= 3` here, `> 0` in `FUN_00817890`), which is itself a hint that the scale is monotone in friendliness and §5.2 has it inverted. [I] Note the refuel test is gated on `cur->+0x14 == 0` — **systems only**, never a point or a fleet. [V] --- ## 2. `FUN_00703730` — `ClassifyLeg`, the actual rule ```c // __thiscall, ret 0x14. Returns the WAYPOINT TYPE for this leg (0 = "no move possible"). int ClassifyLeg(StarFleet* this, MapObject* from, MapObject* to, float* rangeInOut, int* flagsOut, NodeRoute* routeOut); ``` `flagsOut` may be null — the function substitutes a stack dummy at `[ebp-0x18]` so the `or` sites never need a null test, and then **skips the whole flag block** at 0x703846 when the caller passed null. So a caller that wants only the type gets no flag work done at all. [V] ### 2.1 Endpoint classification `MapObject->+0x14` is the kind tag, already established by `MoveFleet`'s arrival switch (`strategic-turn-internals` §4.3): **0 = system, 1 = fleet, 2 = deep-space point**. [V] ``` from: kind 2 -> fromPoint = from kind 0 -> fromSystem = from kind 1 -> loc = from->LocID(+0xa0); loc kind 0 -> fromSystem = loc; loc kind 2 -> fromPoint = loc to: toSystem = (kind==0) ? to : 0 (the neg/sbb/not/and idiom, 0x7037bd) toFleet = (kind==1) ? to : 0 toPoint = (kind==2) ? to : 0 ``` [V] ### 2.2 The default: species decides the waypoint type ``` 703770 driveType = FUN_006ff810(fleet) ``` `FUN_006ff810` (118 B) [V]: ``` if (fleet has no ships) return 0 t = DriveTypeOfSpecies(fleet->PID(+0x58)->Species(+0x5c)) ; FUN_0080c7d0 if (t == 0) return 0 if (fleet has exactly one ship) return t for i in 1 .. nShips-1: if (DriveTypeOfSpecies(fleet->PID->Species) != t) return 0 ; <-- LOOP-INVARIANT, see below return t ``` **Original defect.** The loop body re-reads `fleet->PID->Species` — the *fleet's* owner — on every iteration rather than indexing ship `i`. The compared value is therefore constant and the loop can never fail. The intent was evidently "every ship in this fleet must have the same drive"; as shipped it is dead code. It costs nothing behaviourally (a fleet's ships all belong to one player) but a reimplementation that "fixes" it to read per-ship data would diverge the moment a mixed fleet exists. Recorded as-shipped. [V] `FUN_0080c7d0(species)` (50 B) is a **7-entry jump table** at 0x0080c804, resolved byte by byte [V]: | species | index | jump target | drive type | |---|---|---|---| | Human | 0 | 0x0080c7e2 | **3** | | Hiver | 1 | 0x0080c7fe | **0** | | Tarkas | 2 | 0x0080c7e9 | **1** | | **Liir** | **3** | 0x0080c7f0 | **2** | | _NPC | 4 | 0x0080c7fe | **0** | | Zuul | 5 | 0x0080c7e2 | **3** | | Morrigi | 6 | 0x0080c7f7 | **6** | (species enum from `strategic-turn-internals` §0; index out of 0..6 returns 0.) ### 2.3 The decision, in order ``` A. if (to is a FLEET): targetOnNodeRoute = to->wpts non-empty && IsNodeWaypoint(to->wpts[0].Tp) ok = SolveIntercept(...) ; FUN_00703650, ebx=fleet esi=to live-in if (ok) toSystem = the resolved system B. if (flagsOut): ; the whole block is skipped when null if (to is a fleet && !ok && targetOnNodeRoute) flags |= 0x008 if (AnyShipGroundedByDamage(fleet)) flags |= 0x010 ; FUN_00700240 m = PendingShipActionMask(fleet) ; FUN_006ff990 if (m & 0x100) { m &= ~0x100; flags |= 0x800 } if (m) flags |= 0x001 C. if (to is a POINT): if (!PointVisibleTo(to, owner) && !PointKnownTo(to, owner)) ; +0x8c / +0x90 masks flags |= 0x400 return IsGateTransitWaypoint(driveType) || IsNodeWaypoint(driveType) ? 0 : driveType D. fromHasGate = fromSystem && ServerSystem_HasGate(fromSystem, owner) ; FUN_00744010, GFlags(+0xdc) toHasGate = toSystem && ServerSystem_HasGate(toSystem, owner) canProject = GateProjection(owner, fromSystem, toSystem) ; FUN_00818040 if ( (fromHasGate && (toHasGate || canProject || (toPoint && PointUsableBy(toPoint, owner)))) ; FUN_0080ea30 || (toHasGate && fromPoint) ): capacity = owner->NGts(+0x144) * owner->PrGtTrf(+0x148) ; FUN_0080dc50 cost = (int16)fleet->+0xc0 if (fleet's CURRENT waypoint is already a gate transit) cost = 0 if (owner->GTraf(+0x14c) + cost > capacity) { flags |= 0x004; return 0 } flags &= ~0x010 ; <-- clears the grounded bit return canProject ? 5 : 4 E. if (driveType != 3) return driveType ; every non-node race stops here F. ... the node-route branch, §2.5 ... ``` [V] throughout. Step **E** is the whole story for Hiver (0), Tarkas (1), Liir (2), NPC (0) and Morrigi (6): once the gate block declines, the leg's waypoint type is the species' drive type and **nothing else is checked** — no range, no node line, no route record. Type 0 for Hiver/NPC is not a failure; `MoveFleet`'s switch treats every type it does not name as `step = FPsp2 × dt`. ### 2.4 The gate block, decoded `FUN_00744010(sys, player)` (34 B) is `(sys->GFlags(+0xdc) >> player->PlyrIdx(+0x28)) & 1`. `GFlags` is the one `ServerSystem` presence mask `combat-retreat-pipeline.md` §5 left as *"a second presence source (not read here)"*. **It is the per-player gate mask**, and the whole of §2.3 D is built on it. [V] `FUN_00818040(player, A, B)` (150 B) [V]: ``` if (!A || !B) return false if (!(0.0f < player->CstR(+0x150))) return false if (!HasGate(A, player)) return false if ( HasGate(B, player)) return false ; B must NOT have one return Mars_Vec3_Length(A.pos - B.pos) <= player->CstR ; non-strict; 0x004224b0 ``` **`CstR` now has a reader.** `strategic-turn-internals` §4.3 records it as *"`CstR` unused here"*; it is the **gate-projection radius** — how far past a gate a fleet can be thrown when the far end has no receiving gate. The distance is `Mars_Vec3_Length`, i.e. two float32 narrowings (lane M §1), compared **non-strictly**. [V] So types 4 and 5 are both Hiver gate transits and the distinction is precise: * **type 4** = gate → gate. Both ends carry the player's gate. * **type 5** = gate → **gateless** system within `CstR`. `MoveFleet`'s case 5 is *"roll = rand01() × CstE; if roll > CstT the fleet scatters around the destination"* (B4's correction) — which is exactly the risk of throwing a fleet at a system with nothing to catch it. This **corrects** `strategic-turn-internals` §4.3's medium-confidence guess that type 5 is *"the Zuul node-bore / Morrigi gravity casting"*. It is neither: Zuul and Morrigi can never reach step D unless they somehow hold a gate, and the node bore lives in step F. `CstE`/`CstT`/`CstR` are one coherent trio of **gate-projection** parameters. [V for the classification, [I] for the `CstE`/`CstT` reading, which is B4's] The `flags &= ~0x010` at 0x7039d3 is worth stating on its own: **a gate transit ignores dead drives.** That is the same rule `combat-retreat-pipeline.md` §2.2 found from the other end (the species-1 bypass of `IsGroundedByDamage` on the retreat gate arm), reached independently. [V] ### 2.5 The node-route branch (`driveType == 3`: Human and Zuul only) ``` 7039f6 graph = (fleet->galaxy(+0x10))->vft[1]() ; INDIRECT EDGE -- not resolved 7039fb nrf = 0 ; nrp = -1 ; errBits = 0x002 case fromPoint && toSystem: if (SystemIsFriendly(owner, toSystem)) { nrp = -1; origin = fromPoint; goto CHECK } else { flags |= 0x100; goto FAIL } case toPoint: if (!fromSystem) goto FAIL if (!SystemIsFriendly(owner, fromSystem)) { flags |= 0x200; goto FAIL } if (!PointUsableBy(toPoint, owner)) { flags |= 0x400; goto FAIL } nrp = -1; origin = fromSystem; dest = toPoint; goto CHECK default (system -> system): if (!fromSystem || !toSystem || fromSystem == toSystem) goto FAIL p = FindNodeLine(graph, owner, fromSystem, toSystem) ; FUN_006e4eb0 if (p != -1) { nrp = p; origin = fromSystem; goto CHECK } if (!StarFleet_HasFlagShips(fleet, 0x20000, 0)) { flags |= 0x020; goto FAIL } errBits = 0x040 if (!BoreNodeLine(owner, fromSystem, toSystem, 0, fleet)) ; FUN_006e4de0 { flags |= 0x080; goto FAIL } nrp = -1; origin = fromSystem; goto CHECK CHECK: if (origin && dest && !InRange(fleet, origin, dest, rangeInOut)) { flags |= errBits; goto FAIL } return 3 ; and write the route out FAIL: return 0 ; and write an EMPTY route out ``` [V] * **`0x20000` is the node-bore capability bit** on the fleet's cached capability mask `+0xb8` (`StarFleet_HasFlagShips` 0x00703500, already `verified` from lane Z, delegating to `FUN_00702d70(this, maskA, maskB, out)`). It is the same bit `AddShip` special-cases into `(fleet->galaxy)->+0x114` (`combat-retreat-pipeline.md` §2.4). Zuul node cruisers. [V] * **`FUN_006e4eb0`** (303 B) is the only graph structure in the subtree [V]: ``` if (!player || !A || !B || A->Idx(+0x5c) == B->Idx(+0x5c)) return -1 hi = max(idxA, idxB); lo = min(idxA, idxB) bit = 1 << player->PlyrIdx(+0x28) if (!(graph->+0x24[ (hi-1)*hi/2 + lo ] & bit)) return -1 ; TRIANGULAR adjacency, one dword per pair walk the hash bucket (0x006905a0): for each entry whose {+0xc,+0x10} pair matches {idxA,idxB} in either order and whose +0x2c mask contains the player's bit: score = FUN_006e2130( (graph->+0x4)->+0x8 ) ; <-- LOOP-INVARIANT if (score > best) { best = score; result = entry->+0x8 } return result (initialised to -1) ``` **Original defect #2, and this one is the tie-break.** `score` depends only on `graph`, so it is identical for every candidate. `best` starts at −1, so the **first** matching entry in the bucket wins and every later one is discarded by `score > best` being false on equality. Whatever the ranking was meant to be (line length? a per-line discovery level?), as shipped **`FindNodeLine` is "first match in hash-bucket order"**. A reimplementation must reproduce the bucket order to reproduce the chosen `nrp`, or accept that `nrp` is unmodelled. I did not read `FUN_006e2130` or the hash function 0x006905a0. [V for the invariance, **not read** for the two callees] * **`InRange`** is `FUN_006ffa00` (170 B), and it is the only place a *distance* decides a *failure*. §4 below. ### 2.6 Intercepting a moving fleet `FUN_00703650` (214 B, `ebx` = mover, `esi` = target, both live-in) [V]: ``` if (!mover || !target) return false if (target->wpts empty) return false if (!IsNodeWaypoint(target->wpts[0].Tp)) return false ; target must be on a type-3 leg if (!CanIntercept(mover, target, rangeIn)) return false ; FUN_00703520 myLoc = (mover->LocID && mover->LocID->+0x14 == 0) ? mover->LocID : 0 if (myLoc == StarFleet_ResolveWaypoint(target)) ; I'm at the target's destination *out = AsSystem(NodeTransitOrigin(target)); return true ; -> aim at where it came FROM if (StarFleet_GetLocationIfNode(mover) == NodeTransitOrigin(target)) *out = AsSystem(StarFleet_ResolveWaypoint(target)); return true return true ; *out left 0 ``` `NodeTransitOrigin` is `FUN_006ffab0`: it resolves `fleet->FPlan.pnd(+0xf8)` through the entity hash at `(fleet->galaxy(+0x10)) + 0x80` — the same `IDMap` `combat-retreat-pipeline.md` §5 found at `(S+4)+0x80`. **`pnd` is the network id of the node transit's origin object**, and §6.1 shows where it is written. That is a new name for a field the save format carried unexplained. [V] `AsSystem` is `FUN_0071e340`: `return (x->+0x14 != 0) ? 0 : x`. [V] `FUN_00703520` (292 B) [V] returns **true** when the target is not node-travelling at all (three early outs), and otherwise demands that the mover sit at one end of the target's node line *and* that the whole line be `InRange`. So the geometry is: **you can only cut a node-travelling fleet off at one of the two ends of the line it is on.** Note the third `return true` at 0x70371d with `*out` still 0: an intercept can succeed while resolving no system, in which case `toSystem` stays whatever it was (0 for a fleet target) and the leg falls through to step D/E with no system at either end. --- ## 3. The flag word — and `OrderFleetMove`'s three failure bits Every bit, its site, and its meaning. All [V]. | bit | set at | meaning | fails `OrderFleetMove`? | |---|---|---|---| | **0x001** | 0x703897 | some ship in the fleet is performing a cancellable action | no — **warning** | | 0x002 | 0x703b10 (errBits) | the leg's existing node line is out of fuel range | no | | 0x004 | 0x7039c5 | **gate traffic would exceed `NGts × PrGtTrf`** | no | | **0x008** | 0x703859 | destination is a fleet on a node route and **no intercept could be solved** | **YES** | | **0x010** | 0x70386d | some ship's **drive is destroyed** (`StarShip_IsGroundedByDamage`) — *cleared* at 0x7039d3 on a gate leg | **YES** | | 0x020 | 0x703b8b | no node line between the two systems and the fleet **cannot bore one** | no | | 0x040 | 0x703b63 (errBits) | a line was bored, but the leg is still out of fuel range | no | | 0x080 | 0x703b7d | the **node-bore attempt failed** | no | | 0x100 | 0x703a42 | point → system, and the system is not friendly-owned | no | | 0x200 | 0x703a6a | system → point, and the source system is not friendly-owned | no | | **0x400** | 0x7038c5, 0x703ad3 | the destination **point** is not one this player may move to | **YES** | | **0x800** | 0x70388a | the fleet contains a ship performing action **8** | no — **warning** | `OrderFleetMove` tests `test DWORD PTR [ebp-0x10], 0x418` at 0x865499 — **0x400 | 0x010 | 0x008**, exactly B5's three — and on a hit logs at level 2 with the `.rdata` string at 0x00a31e44: ``` StrategySim: %s (%s) move not permitted at this time. ``` with `%s %s` = the fleet's `FtName(+0x5c)` and the owner's name string (`owner+0x40`), both read through the MSVC `std::string` SSO test (`capacity >= 0x10 → indirect`). [V] **So the three "failure" bits are the three that cannot be repaired by the player pressing OK.** Every other bit is either advisory (0x001, 0x800: "this cancels orders") or a *route-quality* complaint the engine is willing to commit anyway (0x002/0x004/0x020/0x040/0x080/0x100/0x200). The UI's `flags != 0` test is what shows the whole word to the player; the server's `& 0x418` is what refuses. `0x004` **not** being a refusal is the surprising one: a Hiver player can be ordered over gate capacity, the flag is raised, `ClassifyLeg` returns 0 for that leg, and `OrderFleetMove` **still installs the plan** with a type-0 waypoint. That is a testable consequence — §7. ### 3.1 The two per-ship masks `FUN_00700240(fleet)` (80 B): true if **any** ship satisfies `StarShip_IsGroundedByDamage` (0x00815090, already `verified` from lane B5 — agreement recorded). → bit 0x010. [V] `FUN_006ff990(fleet)` (101 B): `OR of (1 << ship->+0x4c)` over ships satisfying `FUN_0081f880(ship)`, where ``` FUN_0081f880(ship) = ship && ship->+0x4c != 0 && ship->+0x4c != 6 && !(ship->+0x4c == 8 && (ship->+0x1c & 8) == 8) ``` `ship->+0x4c` is the ship's **current action**, and the proof is in `OrderFleetMove` itself: at 0x8655ed it open-codes the *identical* three-way predicate and calls `FUN_00849280(S+4, ship, 0)` — the "Ship leaving %s is still doing %s. Cancelling action." cancel that `strategic-turn-internals` §4.3 already names — on every ship that passes it. So bit 0x001 means "this order will cancel N ship actions" and bit 0x800 singles out action **8**. Both are prompts, not refusals. [V] *(A shift by `ship->+0x4c` means the action enum must stay under 32; nothing bounds-checks it.)* --- ## 4. Precision profile Ordered by how much a reimplementation would suffer from getting it wrong. **1. `InRange` — `FUN_006ffa00`, the only float that decides a failure.** [V] ``` 6ffa0f d.x = f32(B.pos.x - A.pos.x) ... d.y, d.z ; each stored to a float32 slot 6ffa2d..6ffa44 dx*dx + dy*dy + dz*dz on the x87 stack (53-bit) 6ffa46 fstp DWORD [ebp-0x4] ; sumsq -> FLOAT32 6ffa4d r = rangePtr ? *rangePtr : StarFleet_MinRange(fleet, 0.0f) 6ffa63 cap = FUN_006ff710(fleet) ; min over ships of Ship_MaxRange 6ffa6b..6ffa83 r = (r > cap) ? cap : r ; both read back from FLOAT32 slots 6ffa86 fld [ebp+0x8] ; fmul st(0),st ; r*r -- LEFT IN THE REGISTER 6ffa8b fld [ebp-0x4] ; fcompp ; compares f32(sumsq) with the 53-bit r*r 6ffa95 jp -> false ; true iff sumsq <= r*r ``` **`r*r` is never stored.** The comparison is `f32(sumsq) <= (double)r × (double)r`. A reimplementation that writes `sumsq <= f32(r*r)` — the natural mirror of every other narrowing in this engine — will disagree exactly at the boundary, which is precisely where a fuel check lives. This is the same class of finding as lane M's rounded reciprocal, and it is the one number in this subsystem that flips a decision. Also: the sense is **`<=`**, non-strict, established from `test ah,0x41` + `jp` (equal ⇒ C3 alone ⇒ odd parity ⇒ `jp` not taken ⇒ true). **2. `FUN_006ff710`** (155 B) — the tank cap. Min over ships of `Ship_MaxRange(ship)` (0x0080c820), seeded `FLT_MAX`, **with an early exit the moment the running min is `<= 0`** (`fldz; fld best; fcom st(1); test ah,0x41; jnp exit`) — so it is *not* a pure min when a zero-range ship appears before a negative one. Returns `0.0f`, not `FLT_MAX`, for a fleet with no ships. [V] **3. The leg length** — §1.5. Three f32 deltas, one narrowing on the sum, one on the sqrt, one on the subtraction. Bit-identical to a double accumulator with a single final narrowing. **4. `Mars_Vec3_Length` in the gate-projection test** — lane M §1's two narrowings, compared `<=` against `CstR` read from a float32 field. **5. `rangeLeft = max(rangeLeft, 0.0f)`** at the top of every leg (0x7067c3), via `fcomp` of the constant 0.0 against the slot: `!(0.0 <= rangeLeft) ⇒ 0`. NaN would clamp to 0. [V] **6. `MinRange`'s seed and update** — `FLT_MAX` (0x009e23a8 = `0x7f7fffff`), strict `best > range` update, so on an exact tie the **earlier** ship wins and an empty fleet returns `FLT_MAX + bias`. Same shape as the retreat destination search's tie-break (`combat-retreat-pipeline.md` §2.1). [V] `fpu_cw = 0x127f` throughout — 53-bit precision, round-to-nearest — as lane F measured. Nothing in this subtree changes it. --- ## 5. Why waypoint type 2 is never produced — and what it actually is **Answer: type 2 is the Liir drive.** `FUN_0080c7d0` maps species 3 → 2 and nothing else does. Lane O's 20+ observations were taken on *"both node-drive races"* — Human (0) and Zuul (5) — and `FUN_0080c7d0` maps both of those to **3**. Every one of those observations was forced by the table, and no amount of single- vs multi-hop, natural vs rip-bored, planned vs in-transit variation could have produced a 2. [V] The reachability argument, in full: * The classifier can only return `0`, `driveType`, `3`, `4` or `5` (five `ret` sites, §2.3). [V] * `driveType` is a **pure function of the owning player's species** — no ship data, no terrain, no tech (§2.2; the one loop that reads ship data is the invariant no-op). [V] * The node-route branch (§2.5) is entered **only** when `driveType == 3` and returns only `3` or `0`. [V] * The gate branch returns only `4` or `5`, and its guard is `GFlags` — the gate mask. [V] So a **Human or Zuul fleet can never carry a type-2 waypoint**, and a **Liir fleet's every straight leg is type 2**. There is no bug, no unreachable path, and nothing to fix. ### 5.1 Correction: "node line" is the wrong name for type 2 `strategic-turn-internals` §4.1/§4.3 names 0x00705510 `NodeLine::Step` and 0x00702e20 `FindNodeLines`, and `movement.h` in the engine calls kind 2 `NodeLine`. On this evidence that naming is **wrong**, and it is what made B4's *"the type-2 node-line step is wrong by construction"* and lane O's *"type 2 never appears"* look like the same puzzle when they are not: * type 2 belongs to **Liir**, the one race with no node drive and no gates; * its speed profile is built from `STUTTER_SYSTEM_INFLUENCE_RADIUS`, `STUTTER_MIN_SPEED`, `STUTTER_MAX_SPEED` and is **slowest at a system and fastest in deep space** (`v = speed × ((MAX−MIN)×(dist/RADIUS) + MIN)`, `formula-gaps.md`); * the image carries `SHIP_STUTTERWARP_DEEPSPACE_MANEUVER_FACTOR` as a ship key; * `IsNodeWaypoint` deliberately excludes 2 and accepts only 3 — which is now simply consistent rather than anomalous. Type 2 is the **Liir stutterwarp**. `FUN_00702e20` finds *systems whose influence spheres the leg crosses* (`formula-gaps.md` already reads it as a ray/sphere test against systems), not node lines. Renaming is proposed in `addresses.d/lane-p2.json`: `Stutter_Step` / `FindStutterInfluenceSystems`, with the old names kept as aliases in the comments so the earlier findings remain searchable. [V for the species mapping and the predicate tables; **[I]** for the *name* "stutterwarp", which rests on the constant names and the shape of the speed curve, not on a string that says so.] ### 5.2 What this predicts for lane M and B4 Lane M could not build a type-2 workload and correctly said so. The reason is now concrete: **`ref-turn2` has no Liir player.** Exercising type 2 does not require a node line at all — it requires a **Liir fleet moving anywhere**. That is a far cheaper save to author than the one lane M was contemplating, and B4's untested `NodeLineStep`/`BuildStutterSegments` become reachable the moment such a save exists. --- ## 6. What reaches saved state `OrderFleetMove` (0x008653c0) is the writer. Read in full to the next function start. [V] ```c bool StrategyServer::OrderFleetMove(StarFleet* f, MapObject** dests, unsigned count) { if (!f) return false; if (PosDiffersFromPointLocation(f)) { // FUN_0080ec50: exact fucompp on all three components, this->vft[2](); // and only when LocID->+0x14 == 2 (a POINT) if (PosDiffersFromPointLocation(f)) f->Pos = f->LocID->Pos; } vector types(count); // 0x00459f70 vector routes(count); // 0x0085bf00 int flags = 0; PathSolver(f, f, dests, count, &flags, nullptr, types.data(), routes.data()); if (flags & 0x418) { Log(2, "StrategySim: %s (%s) move not permitted at this time.\n", ...); return false; } vector wpts(count); // 0x00703e90; stride 0x1c for (i = 0; i < count; ++i) { wpts[i].Wpt(+0x4) = dests[i] ? dests[i]->id(+0x4) : 0; Waypoint_Set(&wpts[i], types[i], &routes[i]); // FUN_007006e0 } StarFleet::SetFlightPlan(f, wpts.data(), wpts.size(), f->LocID ? f->LocID->id(+0x4) : 0); // 0x00707080 for (ship in f->NShips) if (ship is acting) CancelShipAction(this, ship, 0); // 0x00849280 return true; } ``` `FUN_007006e0` is `Waypoint::Set(int Tp, const NodeRoute* r)`: `+0x8 = Tp`, `+0x10 = r->nrp`, `+0x14 = r->nrf`, `+0x18 = r->nrt` — which pins `Waypoint = {vptr, Wpt@4, Tp@8, NodeRoute nrt@0xc}` at **0x1c** bytes (confirmed independently by the `0x92492493` divide-by-28 at 0x865594 and the `add edi,0x1c` stride). Exactly `struct-recovery.md` §3.1. [V] ### 6.1 `SetFlightPlan` — `FUN_00707080`, every write | write | field | value | |---|---|---| | `owner->+0x14c` −= `f->+0xc0` | **`ServerPlayer.GTraf`** | debited **before** the change if the *old* first waypoint was a gate transit | | `f->+0xc8..0xd7` | **`FlightPlan.wpts`** | assigned from a zeroed temp (`FUN_00703cf0`), then the new list inserted (`FUN_00706c90`) | | `f->+0xd8` | **`FPsp2`** | 0.0f from the temp, then recomputed by `FUN_00705c70` | | `f->+0xdc` | **`FPeta2`** | 0 | | `f->+0xe0..0xeb` | **`FPogn2`** | 0,0,0 then **`f->Pos`** — the position the order was given from | | `f->+0xec..0xf7` | **`FPdpos`** | 0,0,0 then the **first waypoint target's `Pos`**, resolved through the IDMap at `(f->galaxy)+0x80`; left zero if it does not resolve | | `f->+0xf8` | **`pnd`** | 0 then the **origin location's network id** (`f->LocID->+0x4`, or 0) | | `f->+0xfc` | **`FtTrans`** | **`wpts[0].Tp`** — a second, saved copy of the first leg's waypoint type | | `f->+0x100..0x10b` | **`FtOrig`** | `f->Pos` | | `owner->+0x14c` += `f->+0xc0` | **`GTraf`** | re-credited if the *new* first waypoint is a gate transit | Field names from `objects/layouts.md`; every offset above is [V] from the instruction stream and every one of them is a **saved** field the autosave oracle sees. `FtTrans = wpts[0].Tp` is new — the save carries the first waypoint's type twice, once inside `FPlan.wpts` and once as a top-level `StarFleet` int. [V] Also reaching saved state on this path, but outside `SetFlightPlan`: * **`StarFleet.Pos`** (+0x18) — the snap, but *only* when the fleet's location is a deep-space point. `combat-retreat-pipeline.md` §2.5 records the exact-IEEE comparison correctly and misses the `+0x14 == 2` guard; corrected here. [V] * **every acting ship's state**, via `FUN_00849280`. * **`ServerPlayer.GTraf`**, above. Against B5's seven-container template: this path writes **four** of them (`FlightPlan`/`wpts`, `GTraf`, `StarFleet.Pos`, and the new `FtTrans`), and adds the `nrt{nrp,nrf,nrt}` records, whose provenance is now known: * **`nrp`** = the node line's path index from `FindNodeLine` (`entry->+0x8`), or **−1**; * **`nrf`** = the leg's **origin** object's network id (`origin->+0x4`), or 0; * **`nrt`** = the leg's **destination** object's network id, or 0; * and **all three are written only when the leg's type is 3.** For every other type the saved record is `{-1, 0, 0}` — `FUN_00703730` resets it at 0x703a85 and only then fills it in (0x703aa8..0x703bb1). [V] That last point is a save-visible invariant: **`Tp != 3 ⟹ nrt == {-1,0,0}`**, on every waypoint of every fleet. It is checked in §7.1 — 58 waypoints, 0 failures. --- ## 7. A falsifiable prediction (rule 2) Written before any run. > **P1.** In every save in `verify/results/saves/`, and in every autosave any later lane takes, a waypoint > whose `Tp` is not 3 has `nrt = {nrp: -1, nrf: 0, nrt: 0}`, and a waypoint whose `Tp` is 3 has > `nrf` and `nrt` equal to network ids that resolve, with `nrp` either −1 (a freshly bored line, or a > point endpoint) or a non-negative path index. > > **P2.** `StarFleet.FtTrans` equals `FPlan.wpts[0].Tp` for every fleet with a non-empty flight plan, and is > unchanged from its previous value for every fleet with an empty one. > > **P3.** No fleet owned by a Human or Zuul player ever carries `Tp == 2`; no fleet owned by a Liir player > ever carries `Tp == 3`; `Tp` 4 or 5 appears only for a player whose `NGts(+0x144)` is non-zero. > > **P4 (the cheap VM test, and the one worth doing).** Give a fleet a **multi-hop** move order whose **first > click is the system the fleet is already parked at** — click your own system, then one or two more, then > confirm. The resulting `FPlan.wpts` will have `n` entries, and the **types will be shifted by one**: > `wpts[0].Tp` will be the type computed for `wpts[1]`'s destination, and **`wpts[n-1].Tp` will be 0** > whatever the race is. For a Human or Zuul fleet the tell is unmistakable — the last waypoint of a node > route saves as `Tp 0` with `nrt {-1,0,0}` instead of `Tp 3` with a live `nrp/nrf/nrt`. Set `n = 2` for the > smallest case: `wpts[0].Tp` = the type of the leg to the second click, `wpts[1].Tp` = 0. P1 and P2 are checkable **right now, offline**, against the 11 existing saves with `save_reader.py` — no VM, no build. P3 needs a Liir save, which is the workload §5.2 argues for anyway. P4 needs the VM, one save with a movable fleet, and about a minute; it is a **predicted bug in the original**, so a clean result falsifies §1.2's reading of the drop rather than merely failing to confirm it. P4's most likely way to be wrong: the UI may refuse to place a waypoint on the fleet's own system, in which case the drop is only reachable from the AI's `OrderFleetMove` call sites (`FUN_007a4ff0`, `FUN_00865780`) and the test must be run against an AI turn instead. The retreat pipeline cannot reach it — it passes `count = 1` with a destination the phase-1 search explicitly excludes the battle system from, and the fleet is at the battle system. ### 7.1 P1, P2 and a third check — **run, and green** P1 and P2 were written above from the disassembly alone, then run against all 11 curated saves with `verify/save-reader/save_reader.py`. A third check went in at the same time, because reading §2.5 makes it obvious and it is free: > **P5.** For a chain of type-3 waypoints, `nrf[0] == pnd`, `nrt[i] == Wpt[i]`, and `nrf[i+1] == nrt[i]` — > because `ClassifyLeg` is handed `(prev, cur)` and writes `origin->id` / `dest->id`, and §1.4 sets > `prev = cur` on the back edge. | | plans | waypoints | pass | fail | |---|---|---|---|---| | **P1** `Tp != 3 ⟹ nrt == {-1,0,0}`; `Tp == 3 ⟹ nrt == Wpt && nrf != 0` | 46 | **58** | 58 | **0** | | **P2** `FtTrans == wpts[0].Tp` | **46** | — | 46 | **0** | | **P5** `nrf[0] == pnd` and `nrf[i+1] == nrt[i]` | **46** | — | 46 | **0** | `Tp` histogram over all 58: **{3: 57, 1: 1}**. Zero type 2, zero type 0, zero 4, zero 5. What that buys, and what it does not (rule 15). The coverage is thin in exactly the way lane O's was: nine of the eleven saves are the same Zuul game at successive turns, so the 57 type-3 waypoints are far from 57 independent observations. **But three things here could not have been guessed:** 1. **`nrt == Wpt` on all 57.** Two fields the save format carries separately, that no earlier lane connected, and the reason they are equal is `ClassifyLeg` writing `dest->+0x4` into `nrt` while `OrderFleetMove` writes the same `dests[i]->+0x4` into `Wpt`. Predicted from the instruction stream, confirmed. 2. **The `nrf` chain closes through `pnd`** across the three-hop plans in `zuul-turn23-fleet23.sav` (`80→272`, `272→368`, `368→288`, with `pnd = 80`). That is §1.4's `prev = cur` back edge, visible in a save. 3. **`nrp` splits exactly where §2.5 says it should.** `human-turn3-noderoute.sav` — Human, cannot bore — has `nrp` = 37, 16, 9: all **non-negative**, all from `FindNodeLine`. The Zuul saves carry a **mix**: 52/53/54/56 where a line already existed, and **−1** where one did not. −1 is what 0x703adb writes after a successful `BoreNodeLine`, and Zuul are the rip-borers. Nobody looked for this; the split falls out of the branch structure and it is there. **The one non-Zuul, non-Human data point is the best of all.** `turn3-state.sav`'s single flight plan is **`Tp = 1`** with `nrt = {-1, 0, 0}`. That save's players are species **{0 Human ×2, 2 Tarkas ×2, 4 NPC ×4}**, and `FUN_0080c7d0` maps Human→3, Tarkas→**1**, NPC→0. **Type 1 can only have come from a Tarka fleet**, and the empty route record is P1 on the only non-node waypoint the campaign has ever recorded. That is a live confirmation of a *second* row of the drive-type table, from a save that predates this lane, on a race nobody was looking at. (The save does not carry a player *handle*, only `PlyrIdx`, so the fleet→player link is by elimination over the species present, not by direct lookup.) **Still at zero observations: types 0, 2, 4, 5** — Hiver/NPC, Liir, and both gate transits. P3 and P4 stand unrun. **How each could be wrong, and the symptom:** 1. `nrt` is written by more than one producer. `MoveFleet`'s multi-waypoint continuation calls `FUN_00703730` twice (0x7da5c6, 0x7da5da) and I did **not** read those call sites. If either writes a route into an existing waypoint, P1 breaks with a `Tp != 3` waypoint carrying a live `nrf`. That would not falsify §2's reading of the classifier — it would mean the classifier has a second consumer that re-types a waypoint after the fact. 2. `FtTrans` may have another writer. I found exactly one (`SetFlightPlan`), by reading, not by an image-wide displacement scan for `+0xfc`. Symptom: P2 fails on a fleet whose plan was not installed by `OrderFleetMove`. 3. P3's `Tp == 2` half rests on `FUN_0080c7d0` being the **only** producer of a drive type. It is the only caller-visible one in this subtree, but `FUN_0080c7d0` has other callers I did not enumerate. Symptom: a type-2 waypoint on a non-Liir fleet — which would be the more interesting result and should be chased, not explained away. 4. A Hiver player with zero gates: P3's last clause predicts no type 4/5. If one appears, `GFlags` is set by something other than gate construction and §2.4's identification of `+0xdc` is wrong. --- ## 8. Corrections to earlier findings **8.1 To `strategic-turn-internals.md` §4.3 — waypoint type 5.** *"the identity of waypoint type 5 (a probabilistic jump using the player's `CstE/CstT` — consistent with the Zuul node-bore / Morrigi gravity casting; `CstR` unused here)"*. Type 5 is a **Hiver gate throw to a system with no receiving gate**, chosen at 0x7039e4 by `FUN_00818040`, whose radius **is `CstR`**. `CstR` is not unused; it is the gate-projection radius. Zuul and Morrigi cannot reach the site. **8.2 To `strategic-turn-internals.md` §4.1/§4.3 and the engine's `WaypointKind::NodeLine` — type 2.** Type 2 is the **Liir** drive, not a node line. §5.1. **8.3 To `combat-retreat-pipeline.md` §2.5's `OrderFleetMove` line.** *"It snaps the fleet's position onto its current system when they differ"* — it snaps onto its current **point** (`FUN_006fe320` requires `LocID->+0x14 == 2`); a fleet sitting at a system is never snapped. The exact-`fucompp` observation stands. **8.4 To `combat-retreat-pipeline.md` §5's `ServerSystem` mask table.** `GFlags(+0xdc)`, listed as *"a second presence source (not read here)"*, is the **per-player gate mask**, read by `FUN_00744010`. **8.5 Agreements recorded, entries dropped (rule 14).** `StarFleet_MinRange` 0x006ff6a0, `StarFleet_ResolveWaypoint` 0x00701390, `IsGateTransitWaypoint` 0x0056e6e0, `IsNodeWaypoint` 0x0056e720, `StarShip_IsGroundedByDamage` 0x00815090, `StarFleet_GetLocationIfNode` 0x006fe300, `StarFleet_HasFlagShips` 0x00703500 and `StrategyServer_OrderFleetMove` 0x008653c0 all already exist with prototypes matching what I read. **Eight independent re-derivations agreeing; no duplicate rows filed.** `StrategyServer_OrderFleetMove`'s existing prototype says the meaning of the three bits *"is not known"* — the new `lane-p2.json` does not re-file the address; the meaning goes in this document and in the `PathSolver` entry that names it. --- ## 9. What this lane did **not** read * **`FUN_00705d60`'s fold** — past its first ~0x90 bytes. The polarity of its bool and the type of its result are established; the tanker redistribution that turns per-ship ranges into a fleet range is not. It is the input to every range decision in §4, so this is the largest remaining hole in the *numbers*. * **`FUN_006e4de0`** — the node **bore**. Read only as a call shape (`(owner, from, to, 0, fleet)`, cdecl, 5 args) and by its two outcomes. It creates a node line and therefore almost certainly writes saved state that this document does not list. * **`FUN_006e2130`** and the bucket walk `FUN_006905a0` in `FindNodeLine`. The invariance of the ranking term is [V]; what it computes is not read. * **`FUN_006d2050`** — the relation scale. §1.6. * **`FUN_0080c820`** (`Ship_MaxRange`), **`FUN_00705c70`** (the speed recompute that sets `FPsp2`), **`FUN_00703cf0`**, **`FUN_00706c90`**, **`FUN_00849280`** past B5's first ~60 bytes. * **`MoveFleet`'s two `FUN_00703730` call sites** (0x7da5c6, 0x7da5da) — the multi-waypoint continuation. Prediction P1's first falsification route. * **Indirect edges.** Three on the main line, none resolved: `(fleet->galaxy(+0x10))->vft[1]()` at 0x703a14 (the node graph getter — the single most important one, since the whole of §2.5 hangs off its result), `(S+4)->vft[2]()` at 0x86540e in `OrderFleetMove`, and the `vft[2]`/`vft[3]` pair inside `FUN_00703bd0` that enumerates a node's fleets. `tools/vtable_map.py` and `ghidra/vtable-owners.json` were consulted for callers of `FUN_007066c0` (two direct, both named in §1.1; the E8/E9 sweep found no tail-call thunk into it), but the three sites above are **outbound** edges and lane V2's inversion does not reach them. * **The class of the kind-2 "point" object.** It has `Pos` at +0x18, a kind tag at +0x14 and two per-player bitmasks at +0x8c / +0x90. It is not `Game::StarMapNode` (too small). Unresolved. **The strongest and weakest sentence in this document are the same one:** the classifier's rules are read instruction by instruction and are internally consistent with `FUN_0080c7d0`, the two waypoint jump tables, `OrderFleetMove`'s `0x418` mask, the UI's `!= 0` mask and five verified object layouts — and **not one leg of it has ever been observed executing under an instrument.** --- ## 10. The reimplementation `sots-engine` branch **`wip/pathing`**, module `src/game/nav/` (a new directory; lane N owns `src/game/sim/`, A2 `src/app`, D2 `src/game/data`). Clean-room: no addresses, no raw identifiers, no game data. Modelled as pure functions returning a plan, not a mutation — the same split `game/combat` uses, because installing a flight plan touches saved state the module does not own: `DriveTypeOfSpecies`, `IsGateTransitWaypoint`, `IsNodeWaypoint`, `FleetDriveType`, `GateProjectionReaches`, `GateTransitAllowed`, `LegInRange` (with the unstored `r*r`), `LegLength` (the five narrowings), `ClassifyLeg`, `SolvePath`, and the flag constants with `kOrderRefusalMask`. Gates run as separate commands; results in the lane report.