re: rebuild game_data on the IDXD record table — 966 misses and 596 flattened reads
Measured first, over GP_MAIN_GAME_E.pak, comparing every named-field read the
six struct loaders performed against the record table: 4435 reads, 2872 agreed,
**966 returned None for a field that has a value**, **596 flattened a field that
several records carry**, 1 was wrong (a weapon whose TargetType is the empty
string read back as the neighbouring token "Skip"). The prior report of
4453/2887/974/591/1 is the same picture; the small differences are definitional
(I count a read as flattened only when the records disagree).
Every read now goes through IdxdObject::record, and the types say where a value
comes from:
* Weapon = the `Weapon` record (launcher) + the `Shell` record (projectile).
Both carry an ID and a Name and — with `ShellWake` — an `Interval`, which the
flat reader merged; they are separate fields now. Power/Velocity/ranges/
LifeTime are Shell fields, which is why 427 weapon reads used to miss.
* CraftUnit/Vessel = `Generic` (hull) + `Maneuver` (flight model) +
`StructureCount` (counts) + `Shield`, plus a new `hardpoints: Vec<Hardpoint>`
— one entry per Turret_/Bridge_/Thruster_/Hatch_/ShieldGenerator_ record, each
with its own HP. A flat HP could only ever be one of them.
* PlayerConfig = `Player`, plus `phases: Vec<PlayerPhase>` (SpaceSize/SupplyRange
are per Phase_N) and `score: ByDifficulty<ScoreRules>` (MainMissionBonus is per
Score_<difficulty>; the flat answer was the Easy one).
* Character faces come from the `Faces` record's field names (identical output to
the old token scrape, 0 of 68 objects differ — now by construction).
* Stage = `StageResource` + `phases: Vec<StagePhase>`, and the packages it names.
* The `fields: BTreeMap` on every struct became `records: RecordSet`, which keeps
the record boundary; `RecordSet::everywhere(field)` answers "which record".
The token-scraping loaders move too, and this is where the old reader was worst:
* Arsenal: options are the positional fields of the STANDARD_<slot> records. The
scrape returned 16 nose options of which 8 were field keys and pilot names, and
47 for arm3 of which 38 were junk, while missing Mine_B2A and No_Equipment.
Now 8/12/9/9, all real weapons.
* Squadron: one record per squadron, members are Count*4 positional slots
(unit, message set, n, pilot) — 1160 squadrons with ids and 2295 member tuples,
against 28 idless squadrons and 47 members before. Agrees exactly with the
independent Python decode in docs/re/structures/unit-group-table.md.
* DemoMessage: 11775 lines against 10263, every one with a speaker, a portrait,
a delivery mode and a voice token, from fixed positional slots.
* PilotRoster: assignments are the records the `UNITS` record names, so each one
now carries its unit id, its loadout and the player marker.
* UnitRoster: the roster is the field *names* of the single `EnumUnit` record.
* load_weapons selects on the records (Weapon + Shell) rather than on token[0],
whose first byte is often a stray pool byte ("#Weapon", "%Weapon"). Same 131
objects, no heuristic. GP_HANGAR_ARSENAL.pak holds none of them — the module
doc's claim that player weapons live there was wrong.
schema:: constants keep their names and values but are documented for what they
are: record 0's name hash (PLAYER = Difficulty_Easy, UNIT = Maneuver, VESSEL =
Bridge_000, MESSAGE = Message_000), not a schema id.
Two things the migration exposes and does not fix, flagged in the docs instead:
load_units' bucket is 43 Type=Craft + 46 Type=Vessel objects (new `unit_type`
field lets a caller separate them), and StructureCount.TurretCount is not the
number of Turret_* records (the player's craft says 4 and has 63).
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PMRJjbxLqZtsb5Vb7KunPE
This commit is contained in:
@@ -7,10 +7,10 @@ fn main(){
|
||||
let mut seen=std::collections::BTreeSet::new(); let mut shown=0;
|
||||
println!("{} pilot-roster configs; distinct line-ups:", rosters.len());
|
||||
for r in &rosters{
|
||||
let key:String=r.pilots.iter().map(|(c,p)|format!("{c}:{p}")).collect::<Vec<_>>().join(",");
|
||||
let key:String=r.pilots().iter().map(|(c,p)|format!("{c}:{p}")).collect::<Vec<_>>().join(",");
|
||||
if seen.insert(key) && shown<8 {
|
||||
shown+=1;
|
||||
let flt:Vec<String>=r.pilots.iter().map(|(c,p)|format!("{c}={p}")).collect();
|
||||
let flt:Vec<String>=r.pilots().iter().map(|(c,p)|format!("{c}={p}")).collect();
|
||||
println!(" {}", flt.join(" "));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ fn main(){
|
||||
let sq=game_data::load_squadrons(&pak);
|
||||
println!("{} squadron definitions", sq.len());
|
||||
for s in sq.iter().filter(|s|s.side.as_deref()==Some("TCAF")).take(6){
|
||||
let mem:Vec<String>=s.members.iter().map(|m|m.trim_start_matches("UN_").chars().take(18).collect()).collect();
|
||||
println!(" {:8} {:22} {:24} x{} {:?}", s.id.clone().unwrap_or("?".into()), s.formation_id.clone().unwrap_or_default(), s.ai_id.clone().unwrap_or_default(), s.count.unwrap_or(0), mem);
|
||||
let mem:Vec<String>=s.members.iter().map(|m|format!("{}/{}",m.unit.trim_start_matches("UN_").chars().take(18).collect::<String>(),m.pilot.clone().unwrap_or("·".into()))).collect();
|
||||
println!(" {:8} {:22} {:24} x{} {:?}", s.id.clone(), s.formation_id.clone().unwrap_or_default(), s.ai_id.clone().unwrap_or_default(), s.count.unwrap_or(0), mem);
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -4021,9 +4021,9 @@ fn build_game_snapshot(source: &SourceKind) -> Option<GameSnapshot> {
|
||||
continue;
|
||||
}
|
||||
let objectives: Vec<String> =
|
||||
(1..=s.phases).flat_map(|p| text.objectives(&s.id, p)).map(str::to_string).collect();
|
||||
(1..=s.phase_count()).flat_map(|p| text.objectives(&s.id, p)).map(str::to_string).collect();
|
||||
let lose: Vec<String> =
|
||||
(1..=s.phases).flat_map(|p| text.lose_conditions(&s.id, p)).map(str::to_string).collect();
|
||||
(1..=s.phase_count()).flat_map(|p| text.lose_conditions(&s.id, p)).map(str::to_string).collect();
|
||||
let enemies: Vec<String> = rosters
|
||||
.iter()
|
||||
.find(|r| r.stage.as_deref() == Some(s.id.as_str()))
|
||||
@@ -4039,8 +4039,8 @@ fn build_game_snapshot(source: &SourceKind) -> Option<GameSnapshot> {
|
||||
.unwrap_or_default();
|
||||
missions.push(MissionRow {
|
||||
id: s.id.clone(),
|
||||
location: s.location.unwrap_or_default().replace('_', " "),
|
||||
phases: s.phases,
|
||||
location: s.location.clone().unwrap_or_default().replace('_', " "),
|
||||
phases: s.phase_count(),
|
||||
objectives,
|
||||
lose,
|
||||
enemies,
|
||||
@@ -4055,9 +4055,9 @@ fn build_game_snapshot(source: &SourceKind) -> Option<GameSnapshot> {
|
||||
let mut seen = std::collections::BTreeSet::new();
|
||||
let mut flights = Vec::new();
|
||||
for r in gd::load_pilot_rosters(&h) {
|
||||
let key: String = r.pilots.iter().map(|(c, p)| format!("{c}:{p}")).collect::<Vec<_>>().join(",");
|
||||
let key: String = r.pilots().iter().map(|(c, p)| format!("{c}:{p}")).collect::<Vec<_>>().join(",");
|
||||
if seen.insert(key) {
|
||||
flights.push(r.pilots);
|
||||
flights.push(r.pilots());
|
||||
}
|
||||
}
|
||||
(arsenal, flights)
|
||||
|
||||
Reference in New Issue
Block a user