Commit Graph

5 Commits

Author SHA1 Message Date
bf6825e278 re(units): target missions by roster, harvest S09 — 36 units / 3 439 defaulted values
examples/roster_target.rs ranks stages by how many roster units are still
unharvested. The EnumUnit_S<NN> tables are found by hashing candidate TOC paths
(hash::TOC_NAME_SCHEMES) — UnitRoster::stage can only infer a tag when the roster
carries a UN_S<NN>_ prop, which most do not.

It picked S09 (10 missing). Flying it: 26 -> 36 units, 3 345 -> 4 785 rows,
2 351 -> 3 439 defaulted-on-disc values. New: e102_Battleship, e104_Carrier,
e107_AAFrigate, e011_Attacker_B, e008_TurretPlus, be001_TerrafoamingUnit,
e001_Elan_GR{,_Violeta}, f102_LightCarrier_Inv, f106_Destroyer_Inv.

Also settled: the definition objects are mission-independent. Eleven units appear in
more than one snapshot and four are not byte-identical, but compared through the
layout ZERO mapped fields differ — the 12 differing slots are all unmapped (offsets
4/8/16/20 and 0x250/0x268/0x300-0x308/0x330-0x338: object header and sub-object
pointers). So a harvested value is the definition, not a per-mission tweak, and the
earlier UN_f201_TCAF_Tanker flag resolves the same way. Cross-checks over three
snapshots: 1 052 agree, 0 disagree.

Third angle field found the same way (Through_AngleMaximum = 60 degrees in radians),
so the degrees<->radians rule covers any name containing "Angle".

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NptfmpjdpNCKEez6d2xvA9
2026-08-13 13:48:12 +00:00
14d5ed54d1 re(units): read definitions with the code-derived layout — defaulted values 1 059 -> 2 351
unit_runtime.py can only place fields the disc values (it scores triples against
disc records): 58 of 153 from one snapshot. data/unit_definition_layout.txt came
from the title's loader instead, so it places all 159 — including the fields no
disc record sets, which is the Route-B target.

tools/re-capture/unit_dump_layout.py reads every field of every live definition
object with that layout and keeps the discipline: a field the disc DOES value is a
check, not a new value. Over two snapshots (19 objects): 700 cross-checks agree,
0 disagree.

  fields placed per object            58 of 153 -> 159
  rows over those 19 units                  609 -> 2 736
  defaulted-on-disc values (whole file)   1 059 -> 2 351

Two traps recorded: the layout table's offsets are DECIMAL while the solver CSV
prints hex (parsing as hex fails the cross-check on everything — which is how it
announced itself), and angle fields can carry a prefix (AB_AA_PitchPlus is still an
angle, so the AV_/AA_ test must match anywhere in the name).

Flagged: UN_f201_TCAF_Tanker's object is not byte-identical between the two
missions — per-mission override or a runtime-mutated field; needs a third snapshot
to separate.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NptfmpjdpNCKEez6d2xvA9
2026-08-13 13:30:49 +00:00
d61baddaf0 re(units): stage select lifts the per-stage coverage limit — 21 -> 26 units
Unit definitions are instantiated per stage, so the parked "coverage grows by
visiting missions" limit was blocked on progress. With the save's stage field
solved, any story stage can be flown from a hand-edited save and snapshotted.

First harvest (a Night Ravens mission, 027 objectives, different roster):
  units with runtime values   21 -> 26
  csv rows                 1 827 -> 2 143
  defaulted-on-disc values   963 -> 1 059

New: UN_e004_ADAN_ElanPlus_N, UN_e006_ADAN_Vindicator_Margras,
UN_f002_TCAF_DeltaSaber_W, UN_f002_TCAF_DeltaSaber_W_Player (a second player craft)
and UN_mn040_Asteroid_Big (47 defaulted values, Size_Z = 2000).

Two traps recorded with the fix: launch_mission.sh presses A on the PRESELECTED
slot, which is the last-used one (03 here), so a probe in slot 01 is ignored — the
first attempt flew stage 02 again and the solver correctly found 0 new units; patch
every slot or confirm the highlighted slot by screenshot. And back up before
patching: the mid-run backup already held the probe, so slot 01 was restored from
the earlier pristine copy (md5 142b4f43…, verified byte-identical).

Token file for the solver: idxd_tokens <GP_MAIN_GAME_E.pak> Generic — 110 UN_*
records, the disc's full unit count.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NptfmpjdpNCKEez6d2xvA9
2026-08-13 13:16:04 +00:00
1ee2d5e406 re: widen unit coverage to all six tutorials, and prove the fields are run-invariant
Captures the five remaining tutorials (grab_tutorial.sh, one cold boot each)
and re-solves over seven snapshots from seven separate emulator runs. Coverage
18 -> 21 units, confirmed fields 22 -> 27 (Size_Y, MassScore, MinimumVelocity,
AV_PitchPlus_Min, AV_PitchMinus_Min join the zero-contradiction set).

The multi-run union exposed something the single-run check could not: the same
unit's object is NOT byte-identical between runs. unit_runtime.py --crosscheck
pins down why -- exactly 15 words differ, 13 of them holding guest heap
pointers, and none of them is a solved or interpolated field offset. So every
value reported is run-invariant, which is a stronger statement than the
within-run identity check that came before it.

The two non-pointer stragglers are a real caveat, now documented rather than
smoothed over: +0x2c8 reads 8000.0 for UN_f001_TCAF_DeltaSaber_T_Ttrl in two
tutorials and 10000.0 in a third, with a 0/1 flag at +0x2d0. A couple of words
in the object are set per stage, so it is mostly but not entirely the parsed
table. Unidentified, marked NEEDS-HUMAN.

Coverage honesty: all six tutorials together add only 3 units the missions do
not already have -- they reuse one training box, one drone and the player
craft. Further coverage needs story progress, not more tutorials.

Two navigation facts encoded in grab_tutorial.sh, both learned by breaking
them: the main menu is not input-ready for ~10 s after the title tap and early
d-pad presses are dropped (which sends the A to NEW GAME); and NEW GAME is not
a shortcut to Stage 01 -- it gates on DIFFICULTY then plays the prologue. A
NEW GAME excursion to the READY ROOM leaves game01/savedata byte-identical,
verified by diff against a backup.
2026-07-29 18:22:47 +00:00
2fa4c33d1a re: solve the runtime Unit (craft/vessel) struct from live guest memory
Craft stats were the last parked Route-B target: the Hangar exposes only a
weight class, and in menus the flight-model object is not instantiated at all.
It is instantiated in-mission, so this reads it there.

The definition class is discovered rather than assumed (unit_discover.py):
scanning for pointers to the disc ID strings and tallying the word behind each
pointer site picks out vtable 0x820af844 -- one object per unit ID, name-record
pointer at +0x04, string at +0x10, exactly the Weapon shape. The neighbouring
class 0x820af030 is the spawned entity, not the definition; the two are told
apart by one-object-per-ID and by 14/14 objects being byte-identical across two
snapshots 12 minutes and one firefight apart.

22 fields bind with zero contradictions on >=3 distinct values. Beyond that,
the Maneuver sub-record turns out to be laid out in schema declaration order,
4 bytes per field, base 0x9c with a two-slot gap after AA_Roll_Min -- 29
independently solved anchors fit two exact bases with no conflicts. That rule
pins five fields NO unit table ever values (PitchDragFactor, RollDragFactor,
DragFactorThreshold, ArterBurner_Acc, DecPitchFactor); PitchDrag == RollDrag ==
the disc's YawDrag on 18/18 units corroborates the interpolation.

Angles are float32 radians at runtime and degrees on disc.

Coverage is 18 of 110 units: unlike weapons, unit definitions are instantiated
per stage, so it grows by visiting missions. The AI-behaviour tail of Maneuver
is explicitly NOT resolved and marked tentative in the CSV.

Captured from Xenia Canary (BASIC CONTROLS tutorial + Stage 02 from save 01).
2026-07-29 17:27:53 +00:00