Compare commits
2 commits
feaa70e15e
...
3bfde5a70d
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3bfde5a70d | ||
|
|
d12459d1e1 |
2 changed files with 66 additions and 0 deletions
|
|
@ -148,6 +148,15 @@ it fails if a corpus test skips.
|
|||
|
||||
### Track 1 — displacement: `compared → replaced`, then `modelled → compared`
|
||||
|
||||
**Pick the replace unit by its WRITE SET, not by its name.** Lane CR's failure is the rule's
|
||||
source: our code replaced `ProcessResearch` and produced **all 13** tech-tree leaves the turn moves
|
||||
— the model was right — and the oracle still missed by 16, every one written by
|
||||
`OnTechResearched`, a neighbouring function. A replace boundary that splits a write set cannot
|
||||
pass an oracle no matter how correct the model is. So before attempting one, enumerate the leaves
|
||||
the turn moves in that area and ask which functions write them; if the answer is more than one,
|
||||
the unit is the union or the attempt is wasted. The compare mode's guard spans already name the
|
||||
write set — CR's five spans predicted exactly the five fields the oracle later billed.
|
||||
|
||||
Only `replaced` means the original's code did not run. The bar, written once: an un-instrumented
|
||||
oracle first (`certified-pairs.md` format), then the same procedure with the hook in `replace`
|
||||
mode and the **same hashes**, with a count from the hook proving the path executed (rule 1).
|
||||
|
|
|
|||
|
|
@ -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).
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue