diff --git a/findings/subsystems/nav-classifier-live.md b/findings/subsystems/nav-classifier-live.md index 9dd24f8..f8e5ca1 100644 --- a/findings/subsystems/nav-classifier-live.md +++ b/findings/subsystems/nav-classifier-live.md @@ -130,3 +130,60 @@ from that readout rather than hoped for. * **Three input gaps, counted per call rather than argued away:** the intercept solve for a destination fleet already on a node leg (`intercept_gap`), the node bore (`bore_arm`), and the unread friendliness test `FUN_00817890` on the point↔system arms (`friendly_recon`). + +--- + +## 2. The instrument + +Engine branch `wip/dn`, commit `17d635b`, two new hook descriptors in +`src/shim/hooks/path_classify.{h,cpp}` over a pure, host-buildable adapter in +`src/shim/hooks/path_inputs.{h,cpp}`. + +### 2.1 What is declared + +| region | kind | what | +|---|---|---| +| **return value** | result | the waypoint kind, diffed by the template | +| `range` | result | `*rangeInOut` — an IN parameter despite its name (`PathSolver` does the draw-down itself), declared so that the *original* not writing it is checked rather than assumed | +| `flags` | result | `*flagsOut` | +| `route` | result | `routeOut+4..+0xf` — `nrp`, `nrf`, `nrt`, the only three words the original writes | +| `route_obj` | **guard** | the whole 0x10-byte `NodeRoute`, so a vptr write would show | +| `fleet` | **guard** | the whole 0x120-byte `StarFleet`. The classifier is supposed to be pure with respect to the moving fleet; this is the region that would say otherwise | + +That is the function's entire output surface. It returns a kind, ORs a flag word and fills a +route; there is nothing else to declare. + +### 2.2 Where the inputs come from, one by one + +The module takes its predicates as **given** — `FleetState` holds `anyShipGrounded`, +`canBoreNodeLines`, `tankCapacity` and the rest as plain booleans and scalars — so reading them +off the live objects is not a shortcut, it is the module's own interface. Named here so a +reader can see which side computed what: + +| input | source | ours or theirs | +|---|---|---| +| species, ship count, gate cost, gate traffic used/capacity, `CstR` | field reads | facts | +| endpoint kind / id / position / system index | field reads | facts | +| "we have a gate here" | `(GFlags >> PlyrIdx) & 1`, computed in the hook | ours | +| point visible / known, and the species-4 bypass | the two masks at `+0x8c`/`+0x90`, in the hook | ours | +| "the destination fleet is on a node leg" | its front waypoint's type, in the hook | ours | +| tank capacity | `StarFleet_MinTankCapacity` | **theirs** (a pure reader) | +| any ship grounded | `StarFleet_AnyShipGroundedByDamage` | **theirs** | +| pending action mask | `StarFleet_PendingShipActionMask` | **theirs**; the 0x100 split into `0x800` vs `0x001` is ours | +| can bore | `StarFleet_HasFlagShips(fleet, 0x20000, 0)` | **theirs** | +| the single-hop node lookup | `NodeGraph_FindNodeLine`, **called before the original runs** | **theirs** — so `nrp` on an existing line is an input, not a result | +| the drive type | recorded from `StarFleet_GetDriveType` but **not fed to `ours`** | ours computes it from species; theirs is the check | + +The node graph itself comes through `(fleet->galaxy(+0x10))->vft[1]()` — the indirect edge +`path-solver.md` §9 lists as *"the single most important one"* of three it could not resolve +statically. This lane resolves it **dynamically** instead, by making the same call from the +hook and validating the object before using it. That is recorded as +`Galaxy_vft_off_GetNodeGraph` in `ghidra/addresses.d/dn.json`. + +### 2.3 Why `BoreLine` can never fire from our side + +`ClassifyLeg`'s node arm *writes* when it bores: `FUN_006e4de0` creates a node line in the live +graph. Our `NodeGraph::BoreLine` therefore returns false unconditionally and sets a flag, and +the descriptor reports the call as reaching an uncompared arm. Two consequences worth stating: +a compare in which the bore arm fires is **not** evidence either way, and a `replace` mode on +this hook is refused outright (it would also have to solve an intercept it cannot).