The container's derived fields turned out to be reproducible -- length+10 at
+0x30, payload length at +0x8c, adler32(payload) at +0x8e, everything else
copied -- and savegame_edit.py re-wraps a real save BYTE-IDENTICALLY, which is
the check that those three are the only ones. A hand-written save then loaded.
That replaced a blocked experiment (the tail question needed a mission payout,
and none of the currently developable items even sit in the disputed range) with
a direct one: write the blob, read the Arsenal.
- controls: 4 at index 9 -> STILETTO BG1 Developed, 21 -> FALCON 9AM
Developed. A hand-written 4 reaches the screen.
- tail: 4 at 33 and 45 left their rows dashed (both on screen, not below the
fold), and 38 left TOMAHAWK ALPHA RAIL GUN at "0 P" -- not owned. So the
tail is not the weapon.tbl order continued.
- clearing the real save's {22,26,39,46,47} cost the Tomahawk its Developed
status, which puts its flag in that set (39 positionally) -- but a uniform
+1 fails for SPECIAL, so no shift is asserted. Indices >=32 stay marked.
Two behaviours fell out. The title RE-DERIVES developable state on load and
announces it ("You can now develop Broad Sword ..."), so only the 4s are stored
state and a written 2 is pointless. And a no-cost item is bought for 0 P rather
than granted -- TOMAHAWK at "0 P" is what unowned looks like -- which is the
actual reason items read Developed in a save where nothing was spent.
Slot 03 was restored from its archived original (md5 verified); slots 01/02 were
never touched.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
10 KiB
The Arsenal develop economy, and what the save's blob indexes
2026-08-11. The save file's 54-byte blob was shown to be per-item development
state by a runtime differential —
one weapon bought, two entries moved, 2 → 4 for the item and 0 → 2 for the
successor. That named the values. This note names the index space, and in
passing recovers the whole develop economy from the disc.
Where the Arsenal gets its data
dat/GP_HANGAR_ARSENAL.pak carries one screen-config record per language whose
fields point at the rest:
PATH = dat\GP_HANGAR_ARSENAL.pak+eng\
WEAPON = weapon.tbl STRINGS = strings.tbl
WEAPON_CATEGORIES = 8 Detail_Window_Known / Detail_Window_Unknown
ConditionToDevelop = 757,228 WeaponDesc = 734,502 Range = 891,73 …
WEAPON_CATEGORIES = 8 is the eight tabs the screen cycles with LB/RB (GUN,
BEAM, LASER, MULTIPURPOSE MISSILE, ANTI-SHIP MISSILE, BOMB/ROCKET, CANNON,
SPECIAL). The …= x,y fields are screen positions for that screen's labels —
ConditionToDevelop is where the conditions text is drawn, not a condition.
eng\weapon.tbl — the item table (IDXD, schema 0x03c244b5, 66 records)
Its string pool opens with a stage list, then the literal key WEAPONS
followed by the item ids in table order:
No_Equipment, Machiene_Cannon_MG1, Machiene_Cannon_MG2H, Machiene_Cannon_MG3,
Machiene_Cannon_MG5H, Broad_Sword_SG1, Swing_Sword_SG2H, Twin_Sword_SG3II,
Frail_GP37, Stiletto_BG1, Dagger_BG2, Rapier_BG4H, Pilum_BP, … , Wep_83,
Wep_84, Wep_85, NullWeapon_Arm1, NullWeapon_Arm2, NullWeapon_Arm3
The ids are grouped by the eight categories in the same order the screen shows
them, which is why No_Equipment … Wep_83 is exactly 54 entries — the blob's
length.
Each item then has a record: Size, Weight, Range, Power, Speed, Stage, MissionObjective, Dependency, Points. For the first record all of it reads
straight out, and it matches the running game exactly:
| field | disc | in-game DATA SHEET for Light Machine Gun MG I |
|---|---|---|
| Range | D |
Range Class D |
| Power | E |
Damage Class E |
| Speed | - |
Speed Class – |
| Weight | 0.3 |
Weight Class Light |
| Points | 4000 |
4000 P on the develop row |
Caveat, and it is a real one: IDXD string pools store each distinct string
once, so from the second record onward every value that repeats an earlier
string is absent from the token stream. Record 2 (MG2H) shows only its unique
values — 0.4 and 3000 — and record 6 shows no number at all because its cost
had already been seen. So weapon.tbl's per-item fields cannot be read from the
pool alone; they need the binary node/index region, which is still undecoded
(see the module docs in idxd.rs). The prose table below is the practical
substitute.
eng\strings.tbl — the economy in prose (IXUD, UTF-16BE)
The same pak's string table holds, per item: the internal id, the display name, the description, and a "Conditions to obtain …" block that spells out the gate and the price:
Conditions to obtain Light Swivel Machine Gun MG2H — Completed development of the Light Machine Gun MG1 — Must spend 3000 points to develop
All 60 of them are extracted to
captures/arsenal-develop-conditions.csv
with the stage gate, the cost and the prerequisite split out. The shape of the
economy:
- gates are stage completion (
Complete Stage 1 … 15), a predecessor item (Completed development of the …), or an ace kill (Shoot down the ace pilot in Stage 11/13/14); - costs run 3 000 → 350 000 points, and twenty items have no cost at all —
they are granted the moment their gate opens (
Tomahawk Alpha Rail Gun — Complete Stage 1), which is why they read asDevelopedin a list the player never spent points in; - two items are anomalies worth flagging:
Adhesive Mine B2Asays "Must spend points to develop" with no number, and ten late entries (Ballista GSH,Designator LH,Smoke Marker Launcher,Bumble UV,Ballista 24 Rocket Launcher,Thrush 220AM,Divider L3GP,Mace GP25H,Thor Gun System,Spitfire BX) have a conditions header and no conditions — ❔ cut content or non-player weapons.
What the blob indexes: the first 32 entries, confirmed
Predicting the save state from the conditions and comparing against the actual
blob is a strong test, because it is made before looking at the blob. At the
captured state (Stage 1 complete, Stage 02 at standby, 4101 P) the conditions say
exactly six items are developable — Light Machine Gun MG1, Broad Sword SG1,
Dagger BG2, Pilum BP, Hound SMH, Dart 23 Rocket — and the blob's six 2s sit
at WEAPONS-order indices 1, 5, 10, 12, 27, 31, which are those six items, in
order. Nothing had to be fitted.
| blob | value | item (WEAPONS order) | confirmed by |
|---|---|---|---|
| 0 | 4 | No_Equipment |
always available |
| 1 | 2→4 | Machiene_Cannon_MG1 |
the differential: bought for 4000 P |
| 2 | 0→2 | Machiene_Cannon_MG2H |
the differential: announced as newly developable |
| 5 | 2 | Broad_Sword_SG1 |
BROAD SWORD SG1 5000P, below the fold in the GUN list |
| 9 | 4 | Stiletto_BG1 |
Developed; "Initially Mounted"; mounted as NOSE WEAPON |
| 10 | 2 | Dagger_BG2 |
DAGGER BG2 5000P |
| 12 | 2 | Pilum_BP |
PILUM BP 6000P |
| 21 | 4 | Falcon_9AM |
Developed; "Initially Mounted"; mounted as MAIN WEAPON 1 |
| 22 | 4 | Buzzard_10AM |
Developed; free at Stage 1 |
| 26 | 4 | Terrier_SMH |
Developed; free at Stage 1 |
| 27 | 2 | Hound_SMH |
HOUND SMH 10000P |
| 31 | 2 | Dirt_23_Rocket |
DART 23 ROCKET 3500P ("Dart" is the localised "Dirt") |
Twelve concordances, no contradiction, over indices 0–31.
What the blob does not index: the tail is unexplained
Past index 31 the same alignment fails, and it fails against the screen, not against a theory:
Tomahawk_a_Rail_Gunis WEAPONS index 38 and the CANNON list shows itDeveloped(it is free once Stage 1 is complete) — but blob[38] = 0.- The blob's remaining
4s are at 33, 39, 45, 46, 47. In WEAPONS order those areEMP_Mine_B8E,Sling_75KG,Shield_Doubler,Booster,FireControlSystem— and every one of them is shown as not developed: BOMB/ROCKET lists onlyDART 23 ROCKET 3500P, CANNON only the Tomahawk, and the SPECIAL tab is entirely empty (all rows dashed). Their conditions agree — Shield Doubler needs Stage 12, Booster an ace kill in Stage 11, FCS Stage 6.
So five 4s in the tail correspond to nothing the Arsenal reports as owned, and
one genuinely-owned item reads 0. A single +1 shift does not repair it
either: blob[39] = 4 would put the Tomahawk right, but then blob[33] = 4
lands on Arrow_27_Rocket (Stage 2 + 5000 P, unreachable here) and 45/46/47
still land on locked SPECIAL items.
Tested actively, with a hand-written save
Waiting for a mission payout was not necessary. The save container's derived
fields turned out to be reproducible (see
savegame_edit.py — the whole
container re-wraps byte-identically), so the blob could be written and the
result read off the screen. A probe save was built in our own throwaway slot 03
with the blob zeroed except 0, 9, 21, 33, 38, 45 = 4 and 39, 46 = 2, and it
loaded.
What it showed:
- The controls rendered.
9→ BEAM showsSTILETTO BG1 Developed;21→ MULTIPURPOSE MISSILE showsFALCON 9AM Developed. So a hand-written4does reach the screen, and indices 9 and 21 are those items — now by experiment, not by correlation. - The tail probes rendered nothing.
33should have made a BOMB/ROCKET rowDeveloped(row 3 of that list, on screen, not below the fold) — it stayed dashed.45should have made a SPECIAL rowDeveloped— the SPECIAL tab stayed entirely empty. And38should have been the Tomahawk — instead the CANNON list showedTOMAHAWK ALPHA RAIL GUN — 0 P, i.e. not owned. - Clearing the real save's tail
4s cost the Tomahawk its status. In the unedited save the Tomahawk readsDevelopedwhileblob[38] = 0; the probe cleared{22, 26, 39, 46, 47}and the Tomahawk stopped being owned — so its flag is one of the cleared entries, with 39 the positional candidate. A uniform+1shift past index 32 would explain that, but it fails elsewhere: under it the real save's45/46/47are Chaff Flare Dispencer / Shield Doubler / Booster, and the SPECIAL tab is empty in that same save.
So the tail is not a shifted copy of the list either. Status: ❔ — the value alphabet and indices 0–31 are settled; indices ≥32 are not, and must not go into the reimplementation. The next probe is cheap now that saves can be written: set one tail index at a time and read which row changes.
Two behaviours the probe exposed
2is derived, not stored. On load the title recomputes which items are developable from its own conditions and announces the difference — zeroing Light Machine Gun MG1 and Broad Sword SG1 produced "You can now develop Broad Sword …" and both came back priced in the list. Only the owned (4) entries are authoritative state; writing2is pointless, and writing0over an item whose conditions are met is undone at load.- The free items are bought for zero, not granted.
TOMAHAWK ALPHA RAIL GUN — 0 Pis what an unowned no-cost item looks like. That is the real reason a save where the player never spent points still shows items asDeveloped(this note previously said "granted" — the mechanism is a 0 P purchase).
Evidence
captures/arsenal-develop-conditions.csv— all 60 conditions blocks.captures/arsenal-categories-all.png— the eight category tabs.captures/arsenal-gun-below-fold.png— Broad Sword SG1, the entry that is not visible without scrolling.captures/arsenal-probe-save-result.png— the hand-written save's result: controls at 9/21 renderDeveloped, the tail probes render nothing, and the Tomahawk shows0 P.captures/arsenal-gun-list-predevelop.png·captures/arsenal-mg1-developed.png— the differential itself.