From d12459d1e1a4933ce833abe7934e9eadb28df756 Mon Sep 17 00:00:00 2001 From: alex Date: Wed, 9 Sep 2026 10:32:02 -0400 Subject: [PATCH] nav: lane DN, the instrument -- what the classifier hook declares and where each input comes from Names the six regions (four result, two guard) that are the classifier's whole output surface, and then names every predicate the decision consumes next to which side computed it -- because the module takes those predicates as given, so reading them off the live objects is its interface rather than a shortcut. Also records that the node-graph indirect edge path-solver.md could not resolve statically is resolved DYNAMICALLY here, by making the same call from the hook. --- findings/subsystems/nav-classifier-live.md | 57 ++++++++++++++++++++++ 1 file changed, 57 insertions(+) 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).