Compare commits
123 Commits
auto/re-sh
...
auto/re-ca
| Author | SHA1 | Date | |
|---|---|---|---|
| eac92c3e44 | |||
| 8b10c0451f | |||
| ae5f322c03 | |||
| 0cb81b6200 | |||
| f6974ff3f0 | |||
| 6ebbbeff65 | |||
| 4dbd4f6cef | |||
| fb1405b13f | |||
| 86fcfcc8b0 | |||
| 1efc3f567d | |||
| 6d0bdf0179 | |||
| 0221217968 | |||
| bf6825e278 | |||
| 14d5ed54d1 | |||
| d61baddaf0 | |||
| d1b50dc1e6 | |||
| 3b35c48fce | |||
| 4cdbbc2d48 | |||
| 27577538cb | |||
| 92d36ec10f | |||
| 9fd5dcd37d | |||
| 0cf9220d24 | |||
| 0776beb6f4 | |||
| 33dc12c948 | |||
| 41b59faf5f | |||
| 6d92e6c114 | |||
| 1b618f500f | |||
| 7cd9fb8e0b | |||
| 19b338d9d8 | |||
| d674cddad4 | |||
| 0434afa6a6 | |||
| 2ed475868b | |||
| be42e998bc | |||
| 71adafe7f7 | |||
| a3868fab5b | |||
| bab2f9e6ca | |||
| f4f3bcb20a | |||
| 892f1e3979 | |||
| 3322202732 | |||
| a836f8e9ea | |||
| 4d8c1b9457 | |||
| 33a7bf836b | |||
| c79c0f6ccf | |||
| e8434541f2 | |||
| 1d8b783d26 | |||
| cdaeac8e1b | |||
| 0625976ed7 | |||
| 095816834a | |||
| 27fd9f186f | |||
| 85ad540a7e | |||
| f340815db6 | |||
| 9d33345560 | |||
| 2f0ce19f68 | |||
| f835705b48 | |||
| ee2b98341d | |||
| 6634d79c2e | |||
| c9c98cf942 | |||
| db8104ab99 | |||
| bb0c94c0ce | |||
| fd34d350f2 | |||
| 55e1fe3783 | |||
| 7f1ac41563 | |||
| d83bdd3b68 | |||
| 1815490d01 | |||
| b58f42e46d | |||
| df7a10749a | |||
| 845b63d7dd | |||
| dde52c02e7 | |||
| 51723af59a | |||
| 76a433f75c | |||
| 26f1bdf6fd | |||
| 3245aca8cb | |||
| aa82d293a9 | |||
| 6d96446735 | |||
| f655c90b0c | |||
| aedad811bc | |||
| 64d372c7e8 | |||
| 0f9c95c52e | |||
| 343a26ecc0 | |||
| 27a0701e0d | |||
| f18d5919f7 | |||
| d660705c47 | |||
| 7c514e3a57 | |||
| b5a4764567 | |||
| f34280476d | |||
| 140856b3f9 | |||
| 4097e8503f | |||
| c9916bcb41 | |||
| c5a5065389 | |||
| 5b8b5e0ab1 | |||
| d9c69c566c | |||
| d9ffc223bc | |||
| ac10d3bd19 | |||
| 9228255bfa | |||
| 35e2d1067f | |||
| de357ffdc9 | |||
| f65387143e | |||
| dd8770826b | |||
| 8814df843b | |||
| 8b7c9da067 | |||
| a7905d0f6b | |||
| 671891f9bf | |||
| 38f58ea559 | |||
| bb9e75fe6a | |||
| df45a1986b | |||
| 98b3cf56ac | |||
| bc42c0970e | |||
| 2da9b8a9b8 | |||
| eac3435cc7 | |||
| 6e0fbe52a6 | |||
| 841156a95d | |||
| dc97dc215d | |||
| f5f95be0a1 | |||
| f88f4f6847 | |||
| 6ebf2ba00a | |||
| f0ceda1c6f | |||
| 5cc995e864 | |||
| 882dbc2ae2 | |||
| 398e8ae0af | |||
| 5c3e3dfe47 | |||
| f4d59c5783 | |||
| 530555de9f | |||
| 69b4a2e569 |
@@ -12,7 +12,12 @@
|
||||
|
||||
ship e106 ref=e106_bdy_04
|
||||
e106_bdy_01 1 0 0 0 1 0 0 0 1 -263.9948 -150.43242 1164.8667
|
||||
e106_bdy_02 -1 0 0 0 1 0 0 0 1 264.0088 -150.41339 1164.8651
|
||||
# 2026-08-12: this rotation was diag(-1,1,1) - the X-flip `correlate` bakes in
|
||||
# when a draw's buffer is the mirror of the decoded one. With distinct anchor
|
||||
# assignment the twins now decode to their OWN buffers (the container carries
|
||||
# both halves), so the mirror lives in the data and the placement is plain
|
||||
# identity. Re-emitting from the capture agrees: `1 0 0 0 1 0 0 0 1 264.04343`.
|
||||
e106_bdy_02 1 0 0 0 1 0 0 0 1 264.0088 -150.41339 1164.8651
|
||||
e106_bdy_03 1 0 0 0 1 0 0 0 1 0.0029247368 -34.517372 1075.878
|
||||
e106_bdy_04 1 0 0 0 1 0 0 0 1 0 0 0
|
||||
e106_brg_01 1 0 0 0 1 0 0 0 1 -0.005471501 153.31795 -164.03748
|
||||
|
||||
177
crates/sylpheed-formats/data/unit_definition_layout.txt
Normal file
@@ -0,0 +1,177 @@
|
||||
# Unit / vessel definition object layout — offset, field, kind.
|
||||
#
|
||||
# Read out of the title's own loader, not inferred: `sub_82341A20` builds every
|
||||
# key as `addi r4, r30, -N` with `r30 = 0x82088f94`, so the field NAME for each
|
||||
# store is a string in the executable image; pairing each key with the first
|
||||
# store after its accessor call gives the offset. The accessor for floats is
|
||||
# `sub_822FC5A8`, which returns 0.0 when the pool has no value for the key.
|
||||
#
|
||||
# Verified against live objects dumped from a running Stage 02: 406 field values
|
||||
# agree with the disc records and 0 disagree, over 11 objects spanning both the
|
||||
# UNIT and VESSEL schemas (see docs/re/live-unit-definitions.md).
|
||||
#
|
||||
# Two conventions matter for a reimplementation:
|
||||
# * angle fields are DEGREES on disc and RADIANS in this object;
|
||||
# * a field the disc record omits is left at the accessor's miss value (0.0 for
|
||||
# floats) unless something later derives it — `Size_Y` takes `Size_X`.
|
||||
#
|
||||
# offset kind field
|
||||
24 str Model
|
||||
28 str CollisionModel
|
||||
48 f32 Size_X
|
||||
52 f32 Size_Y
|
||||
56 f32 Size_Z
|
||||
64 f32 Color_R
|
||||
68 f32 Color_G
|
||||
72 f32 Color_B
|
||||
80 f32 Size_Radius
|
||||
84 f32 HP
|
||||
88 f32 HQRatio
|
||||
92 f32 ShieldRatio
|
||||
96 f32 ThrusterRatio
|
||||
100 word IsDestructible
|
||||
104 str NamePlate
|
||||
112 word MountedShieldGenerator
|
||||
116 f32 ResistanceToOptics
|
||||
120 f32 ResistanceToShell
|
||||
124 f32 ResistanceToExplosion
|
||||
128 f32 ResistanceToPlayer
|
||||
132 f32 ResistanceParalyze
|
||||
136 str HUDMarkerID
|
||||
156 f32 MinimumVelocity
|
||||
160 f32 MaximumVelocity
|
||||
164 f32 CruisingVelocity
|
||||
168 f32 Acceleration
|
||||
172 f32 Deceleration
|
||||
176 f32 AV_PitchPlus_Max
|
||||
180 f32 AV_PitchPlus_Min
|
||||
184 f32 AA_PitchPlus_Max
|
||||
188 f32 AA_PitchPlus_Min
|
||||
192 f32 AV_PitchMinus_Max
|
||||
196 f32 AV_PitchMinus_Min
|
||||
200 f32 AA_PitchMinus_Max
|
||||
204 f32 AA_PitchMinus_Min
|
||||
208 f32 AV_Yaw_Max
|
||||
212 f32 AV_Yaw_Min
|
||||
216 f32 AA_Yaw_Max
|
||||
220 f32 AA_Yaw_Min
|
||||
224 f32 AV_Roll_Max
|
||||
228 f32 AV_Roll_Min
|
||||
232 f32 AA_Roll_Max
|
||||
236 f32 AA_Roll_Min
|
||||
248 f32 SideThrustVelocity_Max
|
||||
252 f32 SideThrustAcceleration
|
||||
256 f32 MaximumBank_Normal
|
||||
260 f32 YawDragFactor
|
||||
264 f32 PitchDragFactor
|
||||
268 f32 RollDragFactor
|
||||
272 f32 DragFactorThreshold
|
||||
276 f32 ArterBurner_Vc
|
||||
280 f32 ReverseThrust_Vc
|
||||
284 f32 ArterBurner_Acc
|
||||
288 f32 ReverseThrust_Acc
|
||||
292 f32 AccPitchFactor
|
||||
296 f32 DecPitchFactor
|
||||
300 f32 AV_AxisMode_Max
|
||||
304 f32 AV_AxisMode_Min
|
||||
308 f32 AA_AxisMode_Max
|
||||
312 f32 AA_AxisMode_Min
|
||||
316 f32 PowerCutConsumeShield
|
||||
320 f32 PowerCutDeceleration
|
||||
324 f32 AB_ConsumeShield_Begin
|
||||
328 f32 AB_ConsumeShield
|
||||
332 f32 AB_AV_PitchPlus
|
||||
336 f32 AB_AA_PitchPlus
|
||||
340 f32 AB_AV_PitchMinus
|
||||
344 f32 AB_AA_PitchMinus
|
||||
348 f32 AB_AV_Yaw
|
||||
352 f32 AB_AA_Yaw
|
||||
356 f32 AB_AV_Roll
|
||||
360 f32 AB_AA_Roll
|
||||
364 word SideRoll
|
||||
368 f32 SideRoll_Time
|
||||
372 f32 SideRoll_Length
|
||||
376 word BarrelRoll
|
||||
380 f32 BarrelRoll_CountMinimum
|
||||
384 f32 BarrelRoll_CountMaximum
|
||||
388 f32 BarrelRoll_Time
|
||||
392 f32 BarrelRoll_Radius
|
||||
396 word TurnAttack
|
||||
400 f32 TurnAttack_CutoffRatio
|
||||
404 f32 TurnAttack_DoubleRatio
|
||||
408 f32 CutoffTimeMin
|
||||
412 f32 CutoffTimeMax
|
||||
416 f32 TurnAttack_DoubleTimeMin
|
||||
420 f32 TurnAttack_DoubleTimeMax
|
||||
424 word TurnAway
|
||||
428 f32 Turn_AngularVelocity
|
||||
432 f32 TurnAway_Time_Minimum
|
||||
436 f32 TurnAway_Time_Maximum
|
||||
440 word BoostAway
|
||||
444 f32 BoostAway_Time_Minimum
|
||||
448 f32 BoostAway_Time_Maximum
|
||||
452 word HoldPosition
|
||||
456 f32 HoldPosition_LengthMin
|
||||
460 f32 HoldPosition_LengthMax
|
||||
464 f32 HoldPosition_MinimumTime
|
||||
468 f32 HoldPosition_MaximumTime
|
||||
472 f32 HoldPosition_SideRatio
|
||||
476 f32 HoldPosition_BackRatio
|
||||
480 f32 HoldPosition_CutoffRatio
|
||||
484 f32 HoldPosition_CancelTime
|
||||
488 word Slalom
|
||||
492 f32 Slalom_CutoffRatio
|
||||
496 f32 Slalom_TurnCount_Min
|
||||
500 f32 Slalom_TurnCount_Max
|
||||
504 word Through
|
||||
508 f32 Through_CutoffRatio
|
||||
512 f32 Through_AngleMinimum
|
||||
516 f32 Through_AngleMaximum
|
||||
520 f32 Through_Time1Max
|
||||
524 f32 Through_Time1Min
|
||||
528 f32 Through_Time2Max
|
||||
532 f32 Through_Time2Min
|
||||
536 f32 Through_LengthMinimum
|
||||
540 f32 Through_LengthMaximum
|
||||
544 word SolidCutoff
|
||||
548 f32 SolidCutoff_Ratio
|
||||
552 f32 SolidCutoff_LengthMin
|
||||
556 f32 SolidCutoff_LengthMax
|
||||
560 f32 HomingResistAdjustment
|
||||
564 f32 UsingChaffRatio
|
||||
568 f32 MaxValue
|
||||
572 f32 ChargeDelay
|
||||
576 f32 ChargeDelay_Break
|
||||
580 f32 ChargeSpeed
|
||||
584 f32 Delay
|
||||
588 f32 DelayAdjustment
|
||||
596 str LowerHPFxModel
|
||||
604 str ExplosionFxModel
|
||||
612 str DestroyMotionName
|
||||
620 str ExplosionMotionName
|
||||
624 f32 DestroyMotionTime
|
||||
628 f32 DryMass
|
||||
632 f32 GrossMass
|
||||
636 word Thruster
|
||||
640 word SideThruster
|
||||
644 word ExplosionSE
|
||||
648 word JumpIn
|
||||
652 word JumpOut
|
||||
656 word ShipEnvironmentSE
|
||||
660 word LowerHPSE
|
||||
664 f32 LowerHPThresholdRatio
|
||||
668 f32 SELength
|
||||
672 f32 RadarRange
|
||||
676 f32 FCSRange
|
||||
680 f32 FiringRange
|
||||
684 word MountedFCS
|
||||
692 f32 AttackVesselPoint
|
||||
696 f32 AttackCraftPoint
|
||||
700 f32 DefencePoint
|
||||
736 str ShieldRecoverEffectName
|
||||
740 str ShieldHitEffectName
|
||||
744 str JumpIn
|
||||
748 str JumpOut
|
||||
752 str FadeIn
|
||||
756 str FadeOut
|
||||
760 str Effect_Paralyze
|
||||
22
crates/sylpheed-formats/examples/bench_ships.rs
Normal file
@@ -0,0 +1,22 @@
|
||||
//! Time repeated per-ship decodes of one container, as the viewer does.
|
||||
use std::collections::HashSet;
|
||||
use std::time::Instant;
|
||||
use sylpheed_formats::mesh::Xbg7Model;
|
||||
use sylpheed_formats::ship::{is_base_part, ship_id_of};
|
||||
fn main() {
|
||||
let a: Vec<String> = std::env::args().collect();
|
||||
let bytes = std::fs::read(&a[1]).unwrap();
|
||||
let names = sylpheed_formats::mesh::xbg7_resource_names(&bytes);
|
||||
let ids: Vec<String> = {
|
||||
let mut v: Vec<String> = names.iter().filter(|n| is_base_part(n))
|
||||
.filter_map(|n| ship_id_of(n).map(|s| s.to_string())).collect();
|
||||
v.sort(); v.dedup(); v.truncate(5); v
|
||||
};
|
||||
for id in &ids {
|
||||
let want: HashSet<String> = names.iter()
|
||||
.filter(|n| ship_id_of(n) == Some(id.as_str())).cloned().collect();
|
||||
let t = Instant::now();
|
||||
let got = Xbg7Model::models_named(&bytes, &want, &|| false);
|
||||
println!("{id}: {} models in {:?}", got.len(), t.elapsed());
|
||||
}
|
||||
}
|
||||
123
crates/sylpheed-formats/examples/better_home.rs
Normal file
@@ -0,0 +1,123 @@
|
||||
//! Does a resource have a CLEANER home in its container than the one we picked?
|
||||
//!
|
||||
//! After the pad-scoring fix, only two resources on the disc still decode to an
|
||||
//! index run with degenerate triangles — `e201_bdy_03_m` (2 containers) and
|
||||
//! `_rou_f402_dead` (9). Degeneracy says the run does not fit the pool, so either
|
||||
//! the vertex block is wrong or the candidate list never offered the right one.
|
||||
//! This walks every candidate vertex-run start for the resource's declaration and
|
||||
//! scores each `(start, pad)` the way the anchor now does — degenerate triangles
|
||||
//! first, then winding, plus coverage — so the answer is one of:
|
||||
//! * a strictly cleaner candidate exists (the selection is at fault),
|
||||
//! * several are equally clean (genuinely ambiguous), or
|
||||
//! * nothing is clean (the block is not in the candidate list at all).
|
||||
//!
|
||||
//! Usage: better_home <container.xpr> <resource-name>
|
||||
use sylpheed_formats::mesh::{debug_resource_params, debug_vertex_run_starts, Xbg7Model};
|
||||
|
||||
fn be16(b: &[u8], at: usize) -> u32 {
|
||||
((b[at] as u32) << 8) | b[at + 1] as u32
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let a: Vec<String> = std::env::args().collect();
|
||||
let bytes = std::fs::read(&a[1]).expect("container");
|
||||
let name = &a[2];
|
||||
|
||||
let Some((markers, stride)) = debug_resource_params(&bytes, name) else {
|
||||
eprintln!("no such XBG7 resource: {name}");
|
||||
std::process::exit(1);
|
||||
};
|
||||
let (vc, ic) = markers[0];
|
||||
println!("{name}: {} marker(s), first = {vc} verts / {ic} indices, stride {stride}", markers.len());
|
||||
|
||||
// Where did the decoder put it?
|
||||
let ours = Xbg7Model::stage_models(&bytes)
|
||||
.into_iter()
|
||||
.find(|m| m.name == *name)
|
||||
.and_then(|m| m.meshes.first().and_then(|s| s.vbuf_offset));
|
||||
println!("our anchor: {ours:?}");
|
||||
|
||||
let starts = debug_vertex_run_starts(&bytes, stride);
|
||||
println!("{} candidate vertex-run starts for stride {stride}", starts.len());
|
||||
|
||||
// Score every (start, pad): degenerate triangles, winding against the stored
|
||||
// normals, and whether the run covers the pool exactly.
|
||||
let mut rows: Vec<(usize, f32, usize, usize, usize, bool)> = Vec::new(); // degen, wind, start, pad, max_idx, covered
|
||||
for &vb in &starts {
|
||||
for pad in 0..=3usize {
|
||||
if vb < ic * 2 + pad {
|
||||
continue;
|
||||
}
|
||||
let ib = vb - ic * 2 - pad;
|
||||
if ib + ic * 2 > bytes.len() || vb + vc * stride > bytes.len() {
|
||||
continue;
|
||||
}
|
||||
let idx: Vec<u32> = (0..ic).map(|k| be16(&bytes, ib + k * 2)).collect();
|
||||
let max_idx = *idx.iter().max().unwrap_or(&0) as usize;
|
||||
if max_idx >= vc {
|
||||
continue; // out of range — not a candidate at all
|
||||
}
|
||||
let pos: Vec<[f32; 3]> = (0..vc)
|
||||
.map(|v| {
|
||||
let at = vb + v * stride;
|
||||
[
|
||||
f32::from_be_bytes(bytes[at..at + 4].try_into().unwrap()),
|
||||
f32::from_be_bytes(bytes[at + 4..at + 8].try_into().unwrap()),
|
||||
f32::from_be_bytes(bytes[at + 8..at + 12].try_into().unwrap()),
|
||||
]
|
||||
})
|
||||
.collect();
|
||||
if pos.iter().any(|p| p.iter().any(|c| !c.is_finite() || c.abs() > 1e6)) {
|
||||
continue;
|
||||
}
|
||||
let mut degen = 0usize;
|
||||
let (mut agree, mut counted) = (0usize, 0usize);
|
||||
for t in idx.chunks_exact(3) {
|
||||
let (x, y, z) = (t[0] as usize, t[1] as usize, t[2] as usize);
|
||||
if x == y || y == z || x == z {
|
||||
degen += 1;
|
||||
continue;
|
||||
}
|
||||
// Winding needs normals; use the geometric centroid normal as a
|
||||
// stand-in so this stays declaration-agnostic: a consistent mesh
|
||||
// has all faces pointing away from the centroid on a convex-ish
|
||||
// hull. Weak, so degeneracy leads the sort.
|
||||
let e1 = [pos[y][0] - pos[x][0], pos[y][1] - pos[x][1], pos[y][2] - pos[x][2]];
|
||||
let e2 = [pos[z][0] - pos[x][0], pos[z][1] - pos[x][1], pos[z][2] - pos[x][2]];
|
||||
let f = [
|
||||
e1[1] * e2[2] - e1[2] * e2[1],
|
||||
e1[2] * e2[0] - e1[0] * e2[2],
|
||||
e1[0] * e2[1] - e1[1] * e2[0],
|
||||
];
|
||||
let cx: [f32; 3] = {
|
||||
let mut c = [0.0f32; 3];
|
||||
for p in &pos {
|
||||
for k in 0..3 {
|
||||
c[k] += p[k] / pos.len() as f32;
|
||||
}
|
||||
}
|
||||
c
|
||||
};
|
||||
let out = [pos[x][0] - cx[0], pos[x][1] - cx[1], pos[x][2] - cx[2]];
|
||||
counted += 1;
|
||||
if f[0] * out[0] + f[1] * out[1] + f[2] * out[2] > 0.0 {
|
||||
agree += 1;
|
||||
}
|
||||
}
|
||||
let w = if counted == 0 { 0.0 } else { agree as f32 / counted as f32 };
|
||||
rows.push((degen, w.max(1.0 - w), vb, pad, max_idx, max_idx + 1 == vc));
|
||||
}
|
||||
}
|
||||
rows.sort_by(|a, b| a.0.cmp(&b.0).then(b.1.total_cmp(&a.1)));
|
||||
println!("\n{} in-range candidates; best 12 by (degenerate, winding):", rows.len());
|
||||
for (d, w, vb, pad, mx, cov) in rows.iter().take(12) {
|
||||
let mark = if Some(*vb) == ours { " <-- ours" } else { "" };
|
||||
println!(
|
||||
" vb 0x{vb:07X} pad {pad} degen {d:>4} wind {w:.3} max_idx {mx}/{} {}{mark}",
|
||||
vc - 1,
|
||||
if *cov { "covers" } else { "SHORT" }
|
||||
);
|
||||
}
|
||||
let clean = rows.iter().filter(|r| r.0 == 0 && r.5).count();
|
||||
println!("\n{clean} candidates are degenerate-free AND cover the pool exactly");
|
||||
}
|
||||
53
crates/sylpheed-formats/examples/bounds_in_descriptor.rs
Normal file
@@ -0,0 +1,53 @@
|
||||
//! Does a resource's DESCRIPTOR carry its bounding box?
|
||||
//!
|
||||
//! The last cross-container disagreements are 24-vertex bound boxes swapping
|
||||
//! identities; no anchoring rule can pin them (see docs). If the descriptor
|
||||
//! states the box, that is the missing information. This decodes the resource,
|
||||
//! takes the box its geometry actually spans, and searches the descriptor for
|
||||
//! those float values.
|
||||
//!
|
||||
//! Usage: bounds_in_descriptor <container.xpr> <resource>...
|
||||
use sylpheed_formats::mesh::{xbg7_descriptor_range, Xbg7Model};
|
||||
use std::collections::HashSet;
|
||||
|
||||
fn main() {
|
||||
let a: Vec<String> = std::env::args().collect();
|
||||
let bytes = std::fs::read(&a[1]).expect("container");
|
||||
let want: HashSet<String> = a[2..].iter().cloned().collect();
|
||||
for m in Xbg7Model::models_named(&bytes, &want, &|| false) {
|
||||
let (mut lo, mut hi) = ([f32::MAX; 3], [f32::MIN; 3]);
|
||||
for s in &m.meshes {
|
||||
for q in &s.positions {
|
||||
for k in 0..3 {
|
||||
lo[k] = lo[k].min(q[k]);
|
||||
hi[k] = hi[k].max(q[k]);
|
||||
}
|
||||
}
|
||||
}
|
||||
let Some((d0, d1)) = xbg7_descriptor_range(&bytes, &m.name) else { continue };
|
||||
println!(
|
||||
"{} descriptor 0x{d0:x}..0x{d1:x} ({} bytes), box lo{:?} hi{:?}",
|
||||
m.name,
|
||||
d1 - d0,
|
||||
lo.map(|v| v.round()),
|
||||
hi.map(|v| v.round())
|
||||
);
|
||||
// Where in the descriptor does each bound value appear (±0.01)?
|
||||
let targets: Vec<(&str, f32)> = vec![
|
||||
("lo.x", lo[0]), ("lo.y", lo[1]), ("lo.z", lo[2]),
|
||||
("hi.x", hi[0]), ("hi.y", hi[1]), ("hi.z", hi[2]),
|
||||
];
|
||||
for (label, v) in targets {
|
||||
let mut at: Vec<usize> = Vec::new();
|
||||
let mut o = d0;
|
||||
while o + 4 <= d1 {
|
||||
let f = f32::from_be_bytes(bytes[o..o + 4].try_into().unwrap());
|
||||
if (f - v).abs() <= 0.01 * (1.0 + v.abs()) {
|
||||
at.push(o - d0);
|
||||
}
|
||||
o += 4;
|
||||
}
|
||||
println!(" {label:5} {v:10.3} at descriptor offsets {:x?}", &at[..at.len().min(6)]);
|
||||
}
|
||||
}
|
||||
}
|
||||
222
crates/sylpheed-formats/examples/capture_ib_truth.rs
Normal file
@@ -0,0 +1,222 @@
|
||||
//! Where does a drawn block's INDEX buffer really live?
|
||||
//!
|
||||
//! Our XBG7 anchor scan only ever *assumes* the layout `[index buffer][vertex
|
||||
//! buffer]` with a pad of at most 3 bytes between them (`anchor_pool_mesh`:
|
||||
//! `ib = vb - idx_count*2 - pad`). Nothing on disc states it, and it is the gate
|
||||
//! that rejected the capture-proven `e106_eng_02_l` block
|
||||
//! (docs/re/captures/…): the block's index data was not where the decoder
|
||||
//! looked. The F10 ship capture was extended on 2026-08-13 to log each draw's
|
||||
//! index-buffer base, count and min/max index value, so the assumption is now
|
||||
//! directly checkable:
|
||||
//!
|
||||
//! * `vbase - ibase` is the real gap in guest memory, and a stage container is
|
||||
//! uploaded contiguously (see `shared_vbase_check`), so the same difference
|
||||
//! holds in the file;
|
||||
//! * `max index vs vcount` says whether a draw covers its whole vertex pool —
|
||||
//! the "buffer not covered" miss class is a *sub-range draw* if it does not.
|
||||
//!
|
||||
//! Usage:
|
||||
//! cargo run --release --example capture_ib_truth -- <Stage_SNN.xpr> <capture.log>...
|
||||
use sylpheed_formats::mesh::{debug_resource_params, xbg7_resource_names, Xbg7Model};
|
||||
use sylpheed_formats::ship_capture::{parse_capture, CapturedDraw};
|
||||
use std::collections::HashMap;
|
||||
|
||||
fn q(v: f32) -> i64 {
|
||||
(v as f64 * 1e4).round() as i64
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let a: Vec<String> = std::env::args().collect();
|
||||
if a.len() < 3 {
|
||||
eprintln!("usage: capture_ib_truth <container.xpr> <capture.log>...");
|
||||
std::process::exit(2);
|
||||
}
|
||||
let bytes = std::fs::read(&a[1]).expect("container");
|
||||
|
||||
// One entry per (log, vbase): the capture already de-dups per placement, and
|
||||
// a buffer drawn at several transforms has the same index buffer each time.
|
||||
let mut draws: Vec<CapturedDraw> = Vec::new();
|
||||
let mut seen = std::collections::HashSet::new();
|
||||
for log in &a[2..] {
|
||||
let text = std::fs::read_to_string(log).expect("log");
|
||||
for d in parse_capture(&text) {
|
||||
// One entry per (log, vbase, index range): the engine issues SEVERAL
|
||||
// draws over one vertex buffer, each with its own index sub-range, and
|
||||
// it is their UNION that describes the block. (Captures taken before
|
||||
// 2026-08-13 de-dup by (vbase, transform) and so hold only the first
|
||||
// batch — such a log reads as a mysteriously short draw.)
|
||||
let k = d.ib.map(|i| (i.ibase, i.icount)).unwrap_or((0, 0));
|
||||
if d.ib.is_some() && d.pos.len() >= 4 && seen.insert((log.clone(), d.vbase, k)) {
|
||||
draws.push(d);
|
||||
}
|
||||
}
|
||||
}
|
||||
eprintln!("{} drawn buffers with an index buffer", draws.len());
|
||||
|
||||
// ── Place the drawn buffers in the file: POSITION is f32×3 big-endian at
|
||||
// vertex offset 0, so the dumped positions are a literal byte pattern.
|
||||
let be = |at: usize| f32::from_be_bytes(bytes[at..at + 4].try_into().unwrap());
|
||||
let mut index: HashMap<(i64, i64, i64), Vec<u32>> = HashMap::new();
|
||||
let mut o = 0usize;
|
||||
while o + 12 <= bytes.len() {
|
||||
let (x, y, z) = (be(o), be(o + 4), be(o + 8));
|
||||
if x.is_finite() && y.is_finite() && z.is_finite() && x.abs() < 1e6 && y.abs() < 1e6 && z.abs() < 1e6 {
|
||||
index.entry((q(x), q(y), q(z))).or_default().push(o as u32);
|
||||
}
|
||||
o += 4;
|
||||
}
|
||||
let mut deltas: HashMap<i64, usize> = HashMap::new();
|
||||
let mut hits: Vec<(i64, usize, &CapturedDraw)> = Vec::new(); // (delta, file offset, draw)
|
||||
for d in &draws {
|
||||
let k = (q(d.pos[0][0]), q(d.pos[0][1]), q(d.pos[0][2]));
|
||||
for dx in -1..=1i64 {
|
||||
for dy in -1..=1i64 {
|
||||
for dz in -1..=1i64 {
|
||||
let Some(cands) = index.get(&(k.0 + dx, k.1 + dy, k.2 + dz)) else { continue };
|
||||
for &off in cands {
|
||||
for stride in (12..=64).step_by(4) {
|
||||
let ok = (1..4).all(|j| {
|
||||
let at = off as usize + j * stride;
|
||||
at + 12 <= bytes.len()
|
||||
&& (0..3).all(|c| (be(at + c * 4) - d.pos[j][c]).abs() <= 1e-4)
|
||||
});
|
||||
if ok {
|
||||
let delta = d.vbase as i64 - off as i64;
|
||||
*deltas.entry(delta).or_default() += 1;
|
||||
hits.push((delta, off as usize, d));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
let Some((&base_delta, &n)) = deltas.iter().max_by_key(|(_, n)| **n) else {
|
||||
eprintln!("no draw could be placed in this container");
|
||||
std::process::exit(1);
|
||||
};
|
||||
println!("container load constant: vbase - file_offset = 0x{base_delta:X} ({n} buffers agree)");
|
||||
|
||||
// ── Our decoder's view of the same container.
|
||||
let models = Xbg7Model::stage_models(&bytes);
|
||||
let mut by_off: HashMap<usize, Vec<(String, usize, usize)>> = HashMap::new();
|
||||
for m in &models {
|
||||
for sm in &m.meshes {
|
||||
if let Some(off) = sm.vbuf_offset {
|
||||
by_off
|
||||
.entry(off)
|
||||
.or_default()
|
||||
.push((m.name.clone(), sm.positions.len(), sm.indices.len()));
|
||||
}
|
||||
}
|
||||
}
|
||||
println!("decoded {} resources, {} distinct vertex offsets\n", models.len(), by_off.len());
|
||||
|
||||
// Declared-but-not-decoded resources, indexed by their first marker's
|
||||
// (vertex, index) counts. A drawn buffer our decoder cannot name is the one
|
||||
// thing a capture can give the residual misses: ground truth for where the
|
||||
// block actually is. Matching on counts is enough to propose an identity —
|
||||
// then `debug_try_anchor` at that offset says which gate rejects it.
|
||||
let decoded_names: std::collections::HashSet<String> =
|
||||
models.iter().map(|m| m.name.clone()).collect();
|
||||
let mut undecoded_by_counts: HashMap<(usize, usize), Vec<String>> = HashMap::new();
|
||||
for n in xbg7_resource_names(&bytes) {
|
||||
if decoded_names.contains(&n) {
|
||||
continue;
|
||||
}
|
||||
if let Some((markers, _)) = debug_resource_params(&bytes, &n) {
|
||||
if let Some(&(v, i)) = markers.first() {
|
||||
undecoded_by_counts.entry((v, i)).or_default().push(n);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ── The report: one row per drawn BUFFER, aggregating its index batches.
|
||||
let mut per_buf: HashMap<u32, (usize, Vec<sylpheed_formats::ship_capture::CapturedIndexBuffer>, u32)> =
|
||||
HashMap::new();
|
||||
for (delta, voff, d) in &hits {
|
||||
if *delta != base_delta {
|
||||
continue;
|
||||
}
|
||||
let e = per_buf.entry(d.vbase).or_insert((*voff, Vec::new(), d.vcount));
|
||||
let ib = d.ib.unwrap();
|
||||
if !e.1.contains(&ib) {
|
||||
e.1.push(ib);
|
||||
}
|
||||
}
|
||||
|
||||
let (mut pad0, mut pad_small, mut pad_off, mut unnamed) = (0usize, 0usize, 0usize, 0usize);
|
||||
let (mut cover_exact, mut cover_short, mut idx_equal, mut idx_partial) = (0usize, 0usize, 0usize, 0usize);
|
||||
let mut rows: Vec<(usize, String)> = Vec::new();
|
||||
for (_, (voff, ibs, vcount)) in per_buf.iter() {
|
||||
let batches = ibs.len();
|
||||
let total: u32 = ibs.iter().map(|i| i.icount).sum();
|
||||
let lo = ibs.iter().map(|i| i.ibase).min().unwrap() as i64 - base_delta;
|
||||
let hi = ibs.iter().map(|i| i.ibase + i.icount * 2).max().unwrap() as i64 - base_delta;
|
||||
let umax = ibs.iter().map(|i| i.imax).max().unwrap();
|
||||
let gap = *voff as i64 - hi; // bytes from the end of the index data to the vertex buffer
|
||||
let names = by_off.get(voff);
|
||||
let dec_idx = names
|
||||
.and_then(|v| v.iter().find(|(_, p, _)| *p as u32 == *vcount).map(|(_, _, i)| *i as u32));
|
||||
// The decoder's assumption, scored: it expects the whole index buffer at
|
||||
// `vb - 2*idx_count - pad`, pad ≤ 3.
|
||||
let dec_pad = dec_idx.map(|i| *voff as i64 - (i as i64) * 2 - lo);
|
||||
match dec_pad {
|
||||
Some(0) => pad0 += 1,
|
||||
Some(p) if (1..=3).contains(&p) => pad_small += 1,
|
||||
Some(_) => pad_off += 1,
|
||||
None => unnamed += 1,
|
||||
}
|
||||
if umax + 1 == *vcount {
|
||||
cover_exact += 1;
|
||||
} else {
|
||||
cover_short += 1;
|
||||
}
|
||||
match dec_idx {
|
||||
Some(i) if i == total => idx_equal += 1,
|
||||
Some(_) => idx_partial += 1,
|
||||
None => {}
|
||||
}
|
||||
rows.push((
|
||||
*voff,
|
||||
format!(
|
||||
"vb 0x{:07X} v={:<6} batches {:<3} idx {:<6} span {:<7} gap {:<8} decpad {:<7} cover {:<10} {}",
|
||||
voff,
|
||||
vcount,
|
||||
batches,
|
||||
total,
|
||||
hi - lo,
|
||||
gap,
|
||||
dec_pad.map(|p| p.to_string()).unwrap_or_else(|| "?".into()),
|
||||
if umax + 1 == *vcount { "exact".to_string() } else { format!("{}/{}", umax, vcount - 1) },
|
||||
names
|
||||
.map(|v| v
|
||||
.iter()
|
||||
.map(|(n, p, i)| format!("{n}(v{p},i{i})"))
|
||||
.collect::<Vec<_>>()
|
||||
.join(" "))
|
||||
.unwrap_or_else(|| {
|
||||
// Nothing of ours sits here — is it a resource that never
|
||||
// decodes? Propose it by (vertex, index) counts.
|
||||
undecoded_by_counts
|
||||
.get(&(*vcount as usize, total as usize))
|
||||
.map(|v| format!("MISSED? {}", v.join(" ")))
|
||||
.unwrap_or_else(|| "-".into())
|
||||
})
|
||||
),
|
||||
));
|
||||
}
|
||||
rows.sort();
|
||||
for (_, r) in &rows {
|
||||
println!("{r}");
|
||||
}
|
||||
println!("\nplaced {} drawn buffers in this container", rows.len());
|
||||
println!(
|
||||
"DECODER layout assumption — whole index buffer at vb - 2*idx_count - pad: pad 0 {pad0} · pad 1..3 {pad_small} · elsewhere {pad_off} · not decoded here {unnamed}"
|
||||
);
|
||||
println!(
|
||||
"index extent: our idx_count == sum of captured batches for {idx_equal} buffers, differs for {idx_partial}"
|
||||
);
|
||||
println!("vertex-pool coverage by the union of batches: exact {cover_exact} · short {cover_short}");
|
||||
}
|
||||
175
crates/sylpheed-formats/examples/capture_index_bytes.rs
Normal file
@@ -0,0 +1,175 @@
|
||||
//! Do our decoded triangle indices equal the ones the GPU actually read?
|
||||
//!
|
||||
//! `capture_ib_truth` established *where* a block's index buffer lives and that
|
||||
//! its length matches ours. This asks the stronger question: are the index VALUES
|
||||
//! the same, in the same order? The capture prints the first 24 indices of every
|
||||
//! draw batch verbatim (`idx: …`), and a batch's `ibase` locates it inside the
|
||||
//! block's index buffer — so for each drawn buffer we can line the captured run
|
||||
//! up against `GameMesh::indices` at the right offset and compare element by
|
||||
//! element. That tests the whole index path at once: the anchor, the u16
|
||||
//! big-endian read, the triangle-list interpretation (a strip would disagree
|
||||
//! immediately), and the sub-mesh carve.
|
||||
//!
|
||||
//! Usage:
|
||||
//! cargo run --release --example capture_index_bytes -- <Stage_SNN.xpr> <capture.log>...
|
||||
use sylpheed_formats::mesh::Xbg7Model;
|
||||
use sylpheed_formats::ship_capture::{parse_capture, CapturedDraw};
|
||||
use std::collections::HashMap;
|
||||
|
||||
fn q(v: f32) -> i64 {
|
||||
(v as f64 * 1e4).round() as i64
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let a: Vec<String> = std::env::args().collect();
|
||||
if a.len() < 3 {
|
||||
eprintln!("usage: capture_index_bytes <container.xpr> <capture.log>...");
|
||||
std::process::exit(2);
|
||||
}
|
||||
let bytes = std::fs::read(&a[1]).expect("container");
|
||||
|
||||
let mut draws: Vec<CapturedDraw> = Vec::new();
|
||||
let mut seen = std::collections::HashSet::new();
|
||||
for log in &a[2..] {
|
||||
let text = std::fs::read_to_string(log).expect("log");
|
||||
for d in parse_capture(&text) {
|
||||
let k = d.ib.map(|i| (i.ibase, i.icount)).unwrap_or((0, 0));
|
||||
if d.ib.map_or(false, |i| i.head_len > 0) && d.pos.len() >= 4 && seen.insert((log.clone(), d.vbase, k)) {
|
||||
draws.push(d);
|
||||
}
|
||||
}
|
||||
}
|
||||
eprintln!("{} draw batches with an index head", draws.len());
|
||||
|
||||
// Place each drawn buffer in the file by its dumped positions (see
|
||||
// capture_ib_truth for the method) and keep the modal load constant.
|
||||
let be = |at: usize| f32::from_be_bytes(bytes[at..at + 4].try_into().unwrap());
|
||||
let mut index: HashMap<(i64, i64, i64), Vec<u32>> = HashMap::new();
|
||||
let mut o = 0usize;
|
||||
while o + 12 <= bytes.len() {
|
||||
let (x, y, z) = (be(o), be(o + 4), be(o + 8));
|
||||
if x.is_finite() && y.is_finite() && z.is_finite() && x.abs() < 1e6 && y.abs() < 1e6 && z.abs() < 1e6 {
|
||||
index.entry((q(x), q(y), q(z))).or_default().push(o as u32);
|
||||
}
|
||||
o += 4;
|
||||
}
|
||||
let mut deltas: HashMap<i64, usize> = HashMap::new();
|
||||
let mut hits: Vec<(i64, usize, &CapturedDraw)> = Vec::new();
|
||||
for d in &draws {
|
||||
let k = (q(d.pos[0][0]), q(d.pos[0][1]), q(d.pos[0][2]));
|
||||
for dx in -1..=1i64 {
|
||||
for dy in -1..=1i64 {
|
||||
for dz in -1..=1i64 {
|
||||
let Some(cands) = index.get(&(k.0 + dx, k.1 + dy, k.2 + dz)) else { continue };
|
||||
for &off in cands {
|
||||
for stride in (12..=64).step_by(4) {
|
||||
let ok = (1..4).all(|j| {
|
||||
let at = off as usize + j * stride;
|
||||
at + 12 <= bytes.len()
|
||||
&& (0..3).all(|c| (be(at + c * 4) - d.pos[j][c]).abs() <= 1e-4)
|
||||
});
|
||||
if ok {
|
||||
*deltas.entry(d.vbase as i64 - off as i64).or_default() += 1;
|
||||
hits.push((d.vbase as i64 - off as i64, off as usize, d));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
let Some((&base_delta, _)) = deltas.iter().max_by_key(|(_, n)| **n) else {
|
||||
eprintln!("no draw could be placed in this container");
|
||||
std::process::exit(1);
|
||||
};
|
||||
println!("load constant 0x{base_delta:X}");
|
||||
|
||||
// Our decode, indexed by vertex offset. A model may hold several sub-meshes;
|
||||
// compare against the one whose vertex count matches the draw.
|
||||
let models = Xbg7Model::stage_models(&bytes);
|
||||
let mut by_off: HashMap<usize, Vec<(String, usize, Vec<u32>)>> = HashMap::new();
|
||||
for m in &models {
|
||||
for sm in &m.meshes {
|
||||
if let Some(off) = sm.vbuf_offset {
|
||||
by_off
|
||||
.entry(off)
|
||||
.or_default()
|
||||
.push((m.name.clone(), sm.positions.len(), sm.indices.clone()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Where does each buffer's index data START? Take it from the capture, not
|
||||
// from an assumed pad: `capture_ib_truth` established that a buffer's batches
|
||||
// tile its index buffer exactly (sum of counts == our idx_count, span ==
|
||||
// 2*count), so the lowest `ibase` over a buffer's batches IS the block's
|
||||
// index start. Assuming `vb - 2*len` instead is wrong for the buffers that
|
||||
// sit at pad 2 and shifts the whole comparison by one element.
|
||||
let mut ib_start: HashMap<u32, i64> = HashMap::new();
|
||||
for (delta, _, d) in &hits {
|
||||
if *delta != base_delta {
|
||||
continue;
|
||||
}
|
||||
let ibase = d.ib.unwrap().ibase as i64;
|
||||
ib_start.entry(d.vbase).and_modify(|e| *e = (*e).min(ibase)).or_insert(ibase);
|
||||
}
|
||||
|
||||
let (mut agree, mut disagree, mut unmatched, mut nooverlap) = (0usize, 0usize, 0usize, 0usize);
|
||||
let mut bad: Vec<String> = Vec::new();
|
||||
let mut checked_elems = 0usize;
|
||||
let mut done: std::collections::HashSet<(u32, u32)> = std::collections::HashSet::new();
|
||||
for (delta, voff, d) in &hits {
|
||||
if *delta != base_delta {
|
||||
continue;
|
||||
}
|
||||
let ib = d.ib.unwrap();
|
||||
if !done.insert((d.vbase, ib.ibase)) {
|
||||
continue;
|
||||
}
|
||||
let Some(cands) = by_off.get(voff) else {
|
||||
unmatched += 1;
|
||||
continue;
|
||||
};
|
||||
let Some((name, _, ours)) = cands.iter().find(|(_, p, _)| *p as u32 == d.vcount) else {
|
||||
unmatched += 1;
|
||||
continue;
|
||||
};
|
||||
// Element 0 of our index list is the block's index start, as observed.
|
||||
let ours_start = ib_start[&d.vbase] - base_delta;
|
||||
let batch_off = ib.ibase as i64 - base_delta - ours_start;
|
||||
if batch_off < 0 || batch_off % 2 != 0 {
|
||||
nooverlap += 1;
|
||||
continue;
|
||||
}
|
||||
let first = (batch_off / 2) as usize;
|
||||
let n = (ib.head_len as usize).min(ours.len().saturating_sub(first));
|
||||
if n == 0 {
|
||||
nooverlap += 1;
|
||||
continue;
|
||||
}
|
||||
let mism = (0..n).find(|&k| ours[first + k] != ib.head[k]);
|
||||
checked_elems += n;
|
||||
match mism {
|
||||
None => agree += 1,
|
||||
Some(k) => {
|
||||
disagree += 1;
|
||||
if bad.len() < 8 {
|
||||
bad.push(format!(
|
||||
"{name} vb 0x{:07X} batch@{first}: ours {:?} != captured {:?} (first differs at {k})",
|
||||
voff,
|
||||
&ours[first..first + n.min(12)],
|
||||
&ib.head[..n.min(12)]
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
println!(
|
||||
"index runs compared: {agree} identical · {disagree} differing · {unmatched} no decoded resource · {nooverlap} batch outside our buffer"
|
||||
);
|
||||
println!("{checked_elems} index elements checked against the GPU");
|
||||
for b in &bad {
|
||||
println!(" MISMATCH {b}");
|
||||
}
|
||||
}
|
||||
112
crates/sylpheed-formats/examples/capture_truth_scan.rs
Normal file
@@ -0,0 +1,112 @@
|
||||
//! Which container did each captured draw come from, and where in it?
|
||||
//!
|
||||
//! Extends the single-container `--map` check in `shared_vbase_check` to a whole
|
||||
//! `resource3d` directory. For each container it indexes every 4-byte-aligned
|
||||
//! position triple, looks up each draw's first dumped position, confirms the run
|
||||
//! at a fixed stride, and reports the modal `vbase − offset`. A container the
|
||||
//! engine loaded shows one dominant constant; an unrelated one shows noise.
|
||||
//!
|
||||
//! The output is capture-named ground truth for anchors far beyond the one ship
|
||||
//! `Stage_S01` gave us.
|
||||
//!
|
||||
//! Usage: capture_truth_scan <resource3d_dir> <capture.log>...
|
||||
use sylpheed_formats::ship_capture::{parse_capture, parse_drawlog, CapturedDraw};
|
||||
use std::collections::HashMap;
|
||||
|
||||
fn q(v: f32) -> i64 {
|
||||
(v as f64 * 1e4).round() as i64
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let a: Vec<String> = std::env::args().collect();
|
||||
let dir = &a[1];
|
||||
let mut draws: Vec<CapturedDraw> = Vec::new();
|
||||
let mut seen = std::collections::HashSet::new();
|
||||
for log in &a[2..] {
|
||||
let text = std::fs::read_to_string(log).expect("log");
|
||||
let mut d = parse_capture(&text);
|
||||
if d.is_empty() {
|
||||
d = parse_drawlog(&text);
|
||||
}
|
||||
for x in d {
|
||||
// One entry per buffer; the logs are already deduped per transform.
|
||||
if x.pos.len() >= 8 && x.vcount >= 20 && seen.insert((log.clone(), x.vbase)) {
|
||||
draws.push(x);
|
||||
}
|
||||
}
|
||||
}
|
||||
eprintln!("{} distinct (log, vbase) draws to place", draws.len());
|
||||
|
||||
let mut files: Vec<_> = std::fs::read_dir(dir)
|
||||
.unwrap()
|
||||
.flatten()
|
||||
.map(|e| e.path())
|
||||
.filter(|p| p.extension().and_then(|s| s.to_str()) == Some("xpr"))
|
||||
.collect();
|
||||
files.sort();
|
||||
|
||||
let mut placed = 0usize;
|
||||
for f in &files {
|
||||
let Ok(bytes) = std::fs::read(f) else { continue };
|
||||
// Index quantised position triples. Junk floats (NaN/huge) are skipped,
|
||||
// which prunes most of a texture-heavy container.
|
||||
let be = |at: usize| f32::from_be_bytes(bytes[at..at + 4].try_into().unwrap());
|
||||
let mut index: HashMap<(i64, i64, i64), Vec<u32>> = HashMap::new();
|
||||
let mut o = 0usize;
|
||||
while o + 12 <= bytes.len() {
|
||||
let (x, y, z) = (be(o), be(o + 4), be(o + 8));
|
||||
if x.is_finite() && y.is_finite() && z.is_finite() && x.abs() < 1e6 && y.abs() < 1e6 && z.abs() < 1e6
|
||||
{
|
||||
index.entry((q(x), q(y), q(z))).or_default().push(o as u32);
|
||||
}
|
||||
o += 4;
|
||||
}
|
||||
let mut deltas: HashMap<i64, Vec<(u32, u32)>> = HashMap::new();
|
||||
for d in &draws {
|
||||
let k = (q(d.pos[0][0]), q(d.pos[0][1]), q(d.pos[0][2]));
|
||||
// ±1 in each axis: the log rounds, our file value may round the
|
||||
// other way at a tie.
|
||||
for dx in -1..=1i64 {
|
||||
for dy in -1..=1i64 {
|
||||
for dz in -1..=1i64 {
|
||||
let Some(cands) = index.get(&(k.0 + dx, k.1 + dy, k.2 + dz)) else {
|
||||
continue;
|
||||
};
|
||||
for &off in cands {
|
||||
for stride in (12..=64).step_by(4) {
|
||||
let ok = (1..4).all(|j| {
|
||||
let at = off as usize + j * stride;
|
||||
at + 12 <= bytes.len()
|
||||
&& (0..3).all(|c| {
|
||||
(be(at + c * 4) - d.pos[j][c]).abs() <= 1e-4
|
||||
})
|
||||
});
|
||||
if ok {
|
||||
deltas
|
||||
.entry(d.vbase as i64 - off as i64)
|
||||
.or_default()
|
||||
.push((d.vbase, d.vcount));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
let mut top: Vec<_> = deltas.into_iter().collect();
|
||||
top.sort_by_key(|(_, v)| std::cmp::Reverse(v.len()));
|
||||
if let Some((delta, hits)) = top.first() {
|
||||
if hits.len() >= 3 {
|
||||
placed += hits.len();
|
||||
println!(
|
||||
"{:<22} base=0x{:<10X} buffers={}",
|
||||
f.file_name().unwrap().to_string_lossy(),
|
||||
delta,
|
||||
hits.len()
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
eprintln!("{placed} draws placed in a container");
|
||||
}
|
||||
34
crates/sylpheed-formats/examples/composite_geometry.rs
Normal file
@@ -0,0 +1,34 @@
|
||||
//! How much of the "decoded geometry" belongs to SCENE COMPOSITES rather than to
|
||||
//! real meshes? A composite (`rou_*`, `e_rou_*`) carries node transforms; the
|
||||
//! anchor scan nevertheless finds a block for it, and that pseudo-geometry lands
|
||||
//! in coverage and consistency counts.
|
||||
//! Usage: composite_geometry <resource3d_dir>
|
||||
use sylpheed_formats::mesh::{scene_world_nodes, Xbg7Model};
|
||||
fn main() {
|
||||
let dir = std::env::args().nth(1).expect("resource3d dir");
|
||||
let mut files: Vec<_> = std::fs::read_dir(&dir)
|
||||
.unwrap()
|
||||
.flatten()
|
||||
.map(|e| e.path())
|
||||
.filter(|p| p.extension().and_then(|s| s.to_str()) == Some("xpr"))
|
||||
.collect();
|
||||
files.sort();
|
||||
let (mut total, mut composite, mut composite_named) = (0usize, 0usize, 0usize);
|
||||
for f in &files {
|
||||
let Ok(bytes) = std::fs::read(f) else { continue };
|
||||
for m in Xbg7Model::anchor_models_cancellable(&bytes, 0.0, &|| false) {
|
||||
total += 1;
|
||||
let has_nodes = !scene_world_nodes(&bytes, &m.name).is_empty();
|
||||
let named = m.name.starts_with("rou_") || m.name.starts_with("e_rou_");
|
||||
if has_nodes {
|
||||
composite += 1;
|
||||
}
|
||||
if named {
|
||||
composite_named += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
println!(
|
||||
"{total} decoded; {composite} have scene nodes (a composite), {composite_named} are named rou_/e_rou_"
|
||||
);
|
||||
}
|
||||
87
crates/sylpheed-formats/examples/consensus_check.rs
Normal file
@@ -0,0 +1,87 @@
|
||||
//! Minority report: which container decodes a shared resource differently from
|
||||
//! all the others?
|
||||
//!
|
||||
//! Cross-container consistency has been measured as "do the spans agree", which
|
||||
//! only says a resource is inconsistent — not which copy is wrong. With three or
|
||||
//! more copies the majority is the reference, and the minority names the
|
||||
//! container AND the resource to look at. That is what caught the `n054`/`n056`
|
||||
//! shift chain after the exact-coverage fix.
|
||||
//!
|
||||
//! Usage: consensus_check <resource3d_dir> [--list]
|
||||
use sylpheed_formats::mesh::Xbg7Model;
|
||||
use std::collections::{BTreeMap, HashMap};
|
||||
|
||||
fn main() {
|
||||
let dir = std::env::args().nth(1).expect("resource3d dir");
|
||||
let list = std::env::args().any(|a| a == "--list");
|
||||
let mut files: Vec<_> = std::fs::read_dir(&dir)
|
||||
.unwrap()
|
||||
.flatten()
|
||||
.map(|e| e.path())
|
||||
.filter(|p| p.extension().and_then(|s| s.to_str()) == Some("xpr"))
|
||||
.collect();
|
||||
files.sort();
|
||||
|
||||
// name -> [(container, verts, tris, span)]
|
||||
let mut seen: BTreeMap<String, Vec<(String, usize, usize, [i64; 3])>> = BTreeMap::new();
|
||||
for f in &files {
|
||||
let Ok(bytes) = std::fs::read(f) else { continue };
|
||||
let where_ = f.file_name().unwrap().to_string_lossy().to_string();
|
||||
for m in Xbg7Model::anchor_models_cancellable(&bytes, 0.0, &|| false) {
|
||||
let (mut lo, mut hi) = ([f32::MAX; 3], [f32::MIN; 3]);
|
||||
for s in &m.meshes {
|
||||
for q in &s.positions {
|
||||
for k in 0..3 {
|
||||
lo[k] = lo[k].min(q[k]);
|
||||
hi[k] = hi[k].max(q[k]);
|
||||
}
|
||||
}
|
||||
}
|
||||
if lo[0] == f32::MAX {
|
||||
continue;
|
||||
}
|
||||
let v: usize = m.meshes.iter().map(|s| s.positions.len()).sum();
|
||||
let t: usize = m.meshes.iter().map(|s| s.indices.len() / 3).sum();
|
||||
let span = [
|
||||
(hi[0] - lo[0]).round() as i64,
|
||||
(hi[1] - lo[1]).round() as i64,
|
||||
(hi[2] - lo[2]).round() as i64,
|
||||
];
|
||||
seen.entry(m.name.clone()).or_default().push((where_.clone(), v, t, span));
|
||||
}
|
||||
}
|
||||
|
||||
let (mut resources, mut minority) = (0usize, 0usize);
|
||||
let mut rows: Vec<String> = Vec::new();
|
||||
for (name, list_) in &seen {
|
||||
// Only compare decodes that agree on how much geometry they found, and
|
||||
// only where a majority can exist.
|
||||
if list_.len() < 3 || !list_.iter().all(|e| e.1 == list_[0].1 && e.2 == list_[0].2) {
|
||||
continue;
|
||||
}
|
||||
let mut votes: HashMap<[i64; 3], usize> = HashMap::new();
|
||||
for e in list_ {
|
||||
*votes.entry(e.3).or_default() += 1;
|
||||
}
|
||||
let (best, n) = votes.iter().max_by_key(|(_, n)| **n).unwrap();
|
||||
if *n * 2 <= list_.len() {
|
||||
continue; // no majority — cannot call anyone the odd one out
|
||||
}
|
||||
resources += 1;
|
||||
for e in list_.iter().filter(|e| e.3 != *best) {
|
||||
minority += 1;
|
||||
rows.push(format!(
|
||||
"{:<22} {name:<26} v={:<6} {:?} vs the other {n} containers' {:?}",
|
||||
e.0, e.1, e.3, best
|
||||
));
|
||||
}
|
||||
}
|
||||
if list {
|
||||
for r in &rows {
|
||||
println!("{r}");
|
||||
}
|
||||
}
|
||||
println!(
|
||||
"{minority} minority decodes across {resources} resources that have a majority"
|
||||
);
|
||||
}
|
||||
@@ -16,7 +16,7 @@
|
||||
//! Usage:
|
||||
//! SYLPHEED_ISO=... cargo run --release --example correlate_capture -- \
|
||||
//! <capture.log> <Stage_SNN> <ship_id> [ref_part_substr] [--emit]
|
||||
//! e.g. SYLPHEED_ISO="/home/fabi/RE Project Sylpheed/Project Sylpheed - Arc of
|
||||
//! e.g. SYLPHEED_ISO="/home/fabi/RE - Project Sylpheed/Project Sylpheed - Arc of
|
||||
//! Deception (USA, Europe) (En,Ja).iso" \
|
||||
//! cargo run --release --example correlate_capture -- \
|
||||
//! xenia_ship_capture.log Stage_S01 e106 bdy_04 --emit
|
||||
|
||||
48
crates/sylpheed-formats/examples/coverage_audit.rs
Normal file
@@ -0,0 +1,48 @@
|
||||
//! Do real index buffers address their whole vertex pool?
|
||||
//!
|
||||
//! `validate_block` rejects a block whose indices reach fewer than `vtx_count−4`
|
||||
//! vertices ("buffer not covered"). That gate is the furthest-reached rejection
|
||||
//! for a handful of resources that never decode — so the question is whether it
|
||||
//! is well founded. This measures the slack on every block that DOES decode: if
|
||||
//! real geometry always covers its pool, under-coverage is good evidence of a
|
||||
//! wrong candidate and the gate stands.
|
||||
use sylpheed_formats::mesh::Xbg7Model;
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
fn main() {
|
||||
let dir = std::env::args().nth(1).expect("resource3d dir");
|
||||
let mut files: Vec<_> = std::fs::read_dir(&dir)
|
||||
.unwrap()
|
||||
.flatten()
|
||||
.map(|e| e.path())
|
||||
.filter(|p| p.extension().and_then(|s| s.to_str()) == Some("xpr"))
|
||||
.collect();
|
||||
files.sort();
|
||||
|
||||
let mut hist: BTreeMap<i64, usize> = BTreeMap::new();
|
||||
let mut worst: Vec<(i64, String)> = Vec::new();
|
||||
for f in &files {
|
||||
let Ok(bytes) = std::fs::read(f) else { continue };
|
||||
for m in Xbg7Model::anchor_models_cancellable(&bytes, 0.0, &|| false) {
|
||||
for sub in &m.meshes {
|
||||
if sub.positions.is_empty() || sub.indices.is_empty() {
|
||||
continue;
|
||||
}
|
||||
let max_idx = *sub.indices.iter().max().unwrap() as i64;
|
||||
let slack = sub.positions.len() as i64 - 1 - max_idx;
|
||||
*hist.entry(slack.min(20)).or_default() += 1;
|
||||
if slack > 4 {
|
||||
worst.push((slack, format!("{} in {}", m.name, f.file_name().unwrap().to_string_lossy())));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
println!("unreferenced tail vertices (vtx_count − 1 − max index), over decoded sub-meshes:");
|
||||
for (slack, n) in &hist {
|
||||
println!(" {:>3}{} : {n}", slack, if *slack == 20 { "+" } else { " " });
|
||||
}
|
||||
worst.sort_by_key(|(s, _)| std::cmp::Reverse(*s));
|
||||
for (s, w) in worst.iter().take(5) {
|
||||
println!(" largest slack {s}: {w}");
|
||||
}
|
||||
}
|
||||
71
crates/sylpheed-formats/examples/desc_dump.rs
Normal file
@@ -0,0 +1,71 @@
|
||||
//! Dump an XBG7 resource's descriptor around each index marker, to see whether a
|
||||
//! grouped pool carries a declaration PER sub-mesh (the 2026-08-13 mixed-stride
|
||||
//! finding says it must: n201's four sub-meshes use strides 24/24/24/28 and four
|
||||
//! different vertex shaders).
|
||||
//! Usage: desc_dump <container.xpr> <resource>
|
||||
|
||||
fn be32(b: &[u8], o: usize) -> u32 {
|
||||
u32::from_be_bytes(b[o..o + 4].try_into().unwrap())
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let a: Vec<String> = std::env::args().collect();
|
||||
let bytes = std::fs::read(&a[1]).unwrap();
|
||||
let want = &a[2];
|
||||
// Walk the XPR2 directory by hand (the crate's reader is private).
|
||||
// 16-byte directory entries at 0x10: type tag, data offset, descriptor size,
|
||||
// name offset — all relative to 0x10 (see texture::Xpr2ResourceEntry).
|
||||
let num = be32(&bytes, 12) as usize;
|
||||
for i in 0..num {
|
||||
let e = &bytes[0x10 + i * 16..0x10 + i * 16 + 16];
|
||||
let tag = &e[0..4];
|
||||
let data_off = be32(e, 4) as usize + 0x10;
|
||||
let desc_size = be32(e, 8) as usize;
|
||||
let name_off = be32(e, 12) as usize + 0x10;
|
||||
if tag != b"XBG7" {
|
||||
continue;
|
||||
}
|
||||
let name = {
|
||||
let s = name_off.min(bytes.len());
|
||||
let end = bytes[s..].iter().position(|&c| c == 0).unwrap_or(0) + s;
|
||||
String::from_utf8_lossy(&bytes[s..end]).to_string()
|
||||
};
|
||||
if name != *want {
|
||||
continue;
|
||||
}
|
||||
let desc = &bytes[data_off..(data_off + desc_size).min(bytes.len())];
|
||||
println!("{name}: descriptor at 0x{data_off:X}, {} bytes", desc.len());
|
||||
// markers: a == c*2, c%3==0, vertex count 32 bytes earlier
|
||||
let mut rel = 0usize;
|
||||
while rel + 8 <= desc.len() {
|
||||
let (x, c) = (be32(desc, rel), be32(desc, rel + 4));
|
||||
if c >= 3 && c % 3 == 0 && c < 400_000 && x == c * 2 && rel >= 32 {
|
||||
let vc = be32(desc, rel - 32);
|
||||
if (3..=200_000).contains(&vc) {
|
||||
println!("\n marker @0x{rel:X}: {vc} verts / {c} indices — declaration triples after it:");
|
||||
let mut r = rel + 8;
|
||||
let mut stride = 0u32;
|
||||
for _ in 0..12 {
|
||||
if r + 12 > desc.len() {
|
||||
break;
|
||||
}
|
||||
let (o, code, usage) = (be32(desc, r), be32(desc, r + 4), be32(desc, r + 8) >> 16);
|
||||
if o == 0x00FF_0000 || code == 0xFFFF_FFFF || o > 0x1000 {
|
||||
println!(" end marker @0x{r:X}: off=0x{o:X} code=0x{code:X}");
|
||||
break;
|
||||
}
|
||||
println!(" off {o:>3} code 0x{:06X} usage {usage}", code & 0xFF_FFFF);
|
||||
stride = stride.max(o + 4);
|
||||
r += 12;
|
||||
}
|
||||
println!(" (min stride from element offsets: {stride})");
|
||||
rel += 8;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
rel += 4;
|
||||
}
|
||||
return;
|
||||
}
|
||||
eprintln!("resource not found");
|
||||
}
|
||||
80
crates/sylpheed-formats/examples/edge_cap_sweep.rs
Normal file
@@ -0,0 +1,80 @@
|
||||
//! Calibrate the connectivity cap against the whole disc.
|
||||
//!
|
||||
//! `XBG7_EDGE_CAP` sets the cap; this reports, for one setting, how much
|
||||
//! geometry decodes and how self-consistent it is across containers — the two
|
||||
//! numbers any change to the cap has to trade off. Run it once per cap value.
|
||||
use sylpheed_formats::mesh::Xbg7Model;
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
fn main() {
|
||||
let dir = std::env::args().nth(1).expect("resource3d dir");
|
||||
let mut files: Vec<_> = std::fs::read_dir(&dir)
|
||||
.unwrap()
|
||||
.flatten()
|
||||
.map(|e| e.path())
|
||||
.filter(|p| p.extension().and_then(|s| s.to_str()) == Some("xpr"))
|
||||
.collect();
|
||||
files.sort();
|
||||
|
||||
// name -> (verts, tris) -> spans seen, exactly as mesh_consistency_disc.rs.
|
||||
let mut seen: BTreeMap<String, Vec<([i64; 3], usize, usize)>> = BTreeMap::new();
|
||||
let (mut models, mut verts) = (0usize, 0usize);
|
||||
for f in &files {
|
||||
let Ok(bytes) = std::fs::read(f) else { continue };
|
||||
for m in Xbg7Model::anchor_models_cancellable(&bytes, 0.0, &|| false) {
|
||||
let (mut lo, mut hi) = ([f32::MAX; 3], [f32::MIN; 3]);
|
||||
for s in &m.meshes {
|
||||
for q in &s.positions {
|
||||
for k in 0..3 {
|
||||
lo[k] = lo[k].min(q[k]);
|
||||
hi[k] = hi[k].max(q[k]);
|
||||
}
|
||||
}
|
||||
}
|
||||
if lo[0] == f32::MAX {
|
||||
continue;
|
||||
}
|
||||
let v: usize = m.meshes.iter().map(|s| s.positions.len()).sum();
|
||||
let t: usize = m.meshes.iter().map(|s| s.indices.len() / 3).sum();
|
||||
models += 1;
|
||||
verts += v;
|
||||
if std::env::var("DUMP").is_ok() {
|
||||
// Per-resource signature, so two cap settings can be diffed:
|
||||
// a cap change that silently MOVES an existing anchor is the
|
||||
// risk a coverage count cannot see.
|
||||
println!(
|
||||
"{}|{}|{v}|{t}|{}|{}|{}|{}",
|
||||
f.file_name().unwrap().to_string_lossy(),
|
||||
m.name,
|
||||
m.meshes[0].vbuf_offset.unwrap_or(0),
|
||||
(hi[0] - lo[0]).round() as i64,
|
||||
(hi[1] - lo[1]).round() as i64,
|
||||
(hi[2] - lo[2]).round() as i64
|
||||
);
|
||||
}
|
||||
seen.entry(m.name.clone()).or_default().push((
|
||||
[
|
||||
(hi[0] - lo[0]).round() as i64,
|
||||
(hi[1] - lo[1]).round() as i64,
|
||||
(hi[2] - lo[2]).round() as i64,
|
||||
],
|
||||
v,
|
||||
t,
|
||||
));
|
||||
}
|
||||
}
|
||||
let (mut shared, mut inconsistent) = (0usize, 0usize);
|
||||
for (_, list) in &seen {
|
||||
if list.len() < 2 || !list.iter().all(|e| e.1 == list[0].1 && e.2 == list[0].2) {
|
||||
continue;
|
||||
}
|
||||
shared += 1;
|
||||
if list.iter().any(|e| e.0 != list[0].0) {
|
||||
inconsistent += 1;
|
||||
}
|
||||
}
|
||||
println!(
|
||||
"cap={} models={models} verts={verts} shared={shared} inconsistent={inconsistent}",
|
||||
std::env::var("XBG7_EDGE_CAP").unwrap_or_else(|_| "library default".into())
|
||||
);
|
||||
}
|
||||
106
crates/sylpheed-formats/examples/envelope_screen.rs
Normal file
@@ -0,0 +1,106 @@
|
||||
//! Does every part of a ship sit inside the envelope its siblings describe?
|
||||
//!
|
||||
//! `slab_screen` compared *scale* and could not see the `e106` slab. What the eye
|
||||
//! used when the render exposed it was **relationship**: a blocky mass sitting
|
||||
//! apart from the hull. This measures that — assemble a ship, and for each part
|
||||
//! ask how far its world box protrudes beyond the box of all the OTHER parts,
|
||||
//! relative to the ship's own size. A mis-anchored block sticks out; a real part,
|
||||
//! however big, is part of the silhouette.
|
||||
//!
|
||||
//! Usage: envelope_screen <resource3d_dir> [protrusion_fraction]
|
||||
use sylpheed_formats::mesh::Xbg7Model;
|
||||
use sylpheed_formats::ship::{assemble_ship, ship_id_of};
|
||||
use std::collections::{BTreeSet, HashSet};
|
||||
|
||||
fn main() {
|
||||
let dir = std::env::args().nth(1).expect("resource3d dir");
|
||||
let limit: f32 = std::env::args().nth(2).and_then(|s| s.parse().ok()).unwrap_or(0.35);
|
||||
let mut files: Vec<_> = std::fs::read_dir(&dir)
|
||||
.unwrap()
|
||||
.flatten()
|
||||
.map(|e| e.path())
|
||||
.filter(|p| p.extension().and_then(|s| s.to_str()) == Some("xpr"))
|
||||
.collect();
|
||||
files.sort();
|
||||
|
||||
let mut flagged = 0usize;
|
||||
for f in &files {
|
||||
let Ok(bytes) = std::fs::read(f) else { continue };
|
||||
let ids: BTreeSet<String> = Xbg7Model::anchor_models_cancellable(&bytes, 0.0, &|| false)
|
||||
.iter()
|
||||
.filter_map(|m| ship_id_of(&m.name).map(|s| s.to_string()))
|
||||
.collect();
|
||||
for id in &ids {
|
||||
let placed = assemble_ship(&bytes, id, true);
|
||||
if placed.len() < 3 {
|
||||
continue;
|
||||
}
|
||||
let want: HashSet<String> = placed.iter().map(|p| p.resource.clone()).collect();
|
||||
let models = Xbg7Model::models_named(&bytes, &want, &|| false);
|
||||
// World box per placement.
|
||||
let mut boxes: Vec<(String, [f32; 3], [f32; 3])> = Vec::new();
|
||||
for p in &placed {
|
||||
let Some(m) = models.iter().find(|m| m.name == p.resource) else { continue };
|
||||
let (mut lo, mut hi) = ([f32::MAX; 3], [f32::MIN; 3]);
|
||||
for s in &m.meshes {
|
||||
for q in &s.positions {
|
||||
let w = p.apply(*q);
|
||||
for k in 0..3 {
|
||||
lo[k] = lo[k].min(w[k]);
|
||||
hi[k] = hi[k].max(w[k]);
|
||||
}
|
||||
}
|
||||
}
|
||||
if lo[0] != f32::MAX {
|
||||
boxes.push((p.resource.clone(), lo, hi));
|
||||
}
|
||||
}
|
||||
if boxes.len() < 3 {
|
||||
continue;
|
||||
}
|
||||
for i in 0..boxes.len() {
|
||||
// Envelope of every OTHER part.
|
||||
let (mut lo, mut hi) = ([f32::MAX; 3], [f32::MIN; 3]);
|
||||
for (j, b) in boxes.iter().enumerate() {
|
||||
if i == j {
|
||||
continue;
|
||||
}
|
||||
for k in 0..3 {
|
||||
lo[k] = lo[k].min(b.1[k]);
|
||||
hi[k] = hi[k].max(b.2[k]);
|
||||
}
|
||||
}
|
||||
// Per-AXIS: a bow legitimately extends the ship along its long
|
||||
// axis, so protrusion only means something measured against the
|
||||
// envelope's size IN THAT AXIS. The e106 slab stuck ~1 500 out in
|
||||
// Y where its siblings spanned ~800.
|
||||
let mut worst = 0.0f32;
|
||||
let mut worst_out = 0.0f32;
|
||||
for k in 0..3 {
|
||||
let size_k = hi[k] - lo[k];
|
||||
if size_k <= 1.0 {
|
||||
continue;
|
||||
}
|
||||
let out_k = (lo[k] - boxes[i].1[k]).max(boxes[i].2[k] - hi[k]).max(0.0);
|
||||
if out_k / size_k > worst {
|
||||
worst = out_k / size_k;
|
||||
worst_out = out_k;
|
||||
}
|
||||
}
|
||||
let (out, size) = (worst_out, 1.0f32);
|
||||
let _ = size;
|
||||
if worst > limit {
|
||||
flagged += 1;
|
||||
println!(
|
||||
"{:<20} {:<22} protrudes {:>7.0} beyond its siblings ({:.0}% of the ship)",
|
||||
f.file_name().unwrap().to_string_lossy(),
|
||||
boxes[i].0,
|
||||
out,
|
||||
100.0 * worst
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
println!("{flagged} parts protrude more than {:.0}% of their ship's size", 100.0 * limit);
|
||||
}
|
||||
35
crates/sylpheed-formats/examples/filter_consistency.rs
Normal file
@@ -0,0 +1,35 @@
|
||||
//! Does a FILTERED decode agree with the full one?
|
||||
//!
|
||||
//! Distinct anchor assignment resolves collisions against the set of resources
|
||||
//! being decoded — so `models_named` (a filtered subset, used by the ship
|
||||
//! assembler and the viewer) can reach a different answer from a whole-container
|
||||
//! decode. This measures that directly.
|
||||
//! Usage: filter_consistency <container.xpr> [resource...]
|
||||
use sylpheed_formats::mesh::Xbg7Model;
|
||||
use std::collections::HashSet;
|
||||
fn main() {
|
||||
let a: Vec<String> = std::env::args().collect();
|
||||
let bytes = std::fs::read(&a[1]).expect("container");
|
||||
let full = Xbg7Model::anchor_models_cancellable(&bytes, 0.0, &|| false);
|
||||
let names: Vec<String> = if a.len() > 2 {
|
||||
a[2..].to_vec()
|
||||
} else {
|
||||
full.iter().map(|m| m.name.clone()).collect()
|
||||
};
|
||||
let mut differ = 0usize;
|
||||
for n in &names {
|
||||
let want: HashSet<String> = std::iter::once(n.clone()).collect();
|
||||
let one = Xbg7Model::models_named(&bytes, &want, &|| false);
|
||||
let (Some(f), Some(s)) = (full.iter().find(|m| &m.name == n), one.first()) else {
|
||||
continue;
|
||||
};
|
||||
let off = |m: &Xbg7Model| m.meshes.first().and_then(|s| s.vbuf_offset).unwrap_or(0);
|
||||
if off(f) != off(s) {
|
||||
differ += 1;
|
||||
if differ <= 10 {
|
||||
println!("{n}: full decode at 0x{:x}, filtered at 0x{:x}", off(f), off(s));
|
||||
}
|
||||
}
|
||||
}
|
||||
println!("{differ} of {} resources decode differently when filtered", names.len());
|
||||
}
|
||||
27
crates/sylpheed-formats/examples/find_ib.rs
Normal file
@@ -0,0 +1,27 @@
|
||||
//! Where could a resource's index buffer be, given the vertex buffer a runtime
|
||||
//! capture proves the engine drew from? Tests the anchor scan's adjacency
|
||||
//! assumption against ground truth.
|
||||
use sylpheed_formats::mesh::{debug_find_index_buffer, debug_resource_params};
|
||||
fn main() {
|
||||
let a: Vec<String> = std::env::args().collect();
|
||||
let bytes = std::fs::read(&a[1]).unwrap();
|
||||
for pair in a[2..].iter() {
|
||||
let (name, off) = pair.split_once('@').unwrap();
|
||||
let vb = usize::from_str_radix(off.trim_start_matches("0x"), 16).unwrap();
|
||||
let params = debug_resource_params(&bytes, name);
|
||||
let markers = params.as_ref().map(|(m, _)| m.clone()).unwrap_or_default();
|
||||
println!("{name} @ 0x{vb:x} markers={markers:?}");
|
||||
let hits = debug_find_index_buffer(&bytes, name, vb);
|
||||
if hits.is_empty() {
|
||||
println!(" no index buffer anywhere in the container validates this block");
|
||||
}
|
||||
let mut hits = hits;
|
||||
hits.sort_by_key(|(_, d)| d.abs());
|
||||
for (ib, d) in hits.iter().take(8) {
|
||||
println!(" ib 0x{ib:x} vb-ib = {d} bytes");
|
||||
}
|
||||
if hits.len() > 8 {
|
||||
println!(" … {} total", hits.len());
|
||||
}
|
||||
}
|
||||
}
|
||||
44
crates/sylpheed-formats/examples/find_mirror.rs
Normal file
@@ -0,0 +1,44 @@
|
||||
//! Does the container hold an X-mirrored copy of a resource's decoded buffer?
|
||||
//!
|
||||
//! The twin invariant (see `twin_mirror_audit`) flags `…_01`/`…_02` pairs that
|
||||
//! decode to unrelated geometry. If the mirror of one twin's buffer exists
|
||||
//! somewhere else in the container, that offset is where the other twin belongs
|
||||
//! and the pair is a mis-anchor; if it does not exist, the pair is simply not a
|
||||
//! mirrored pair.
|
||||
//!
|
||||
//! Usage: find_mirror <container.xpr> <resource>...
|
||||
use sylpheed_formats::mesh::Xbg7Model;
|
||||
use std::collections::HashSet;
|
||||
|
||||
fn main() {
|
||||
let a: Vec<String> = std::env::args().collect();
|
||||
let bytes = std::fs::read(&a[1]).expect("container");
|
||||
let want: HashSet<String> = a[2..].iter().cloned().collect();
|
||||
let models = Xbg7Model::models_named(&bytes, &want, &|| false);
|
||||
let be = |at: usize| f32::from_be_bytes(bytes[at..at + 4].try_into().unwrap());
|
||||
|
||||
for m in &models {
|
||||
let pos: Vec<[f32; 3]> = m.meshes.iter().flat_map(|s| s.positions.clone()).take(8).collect();
|
||||
if pos.len() < 8 {
|
||||
continue;
|
||||
}
|
||||
let anchored = m.meshes[0].vbuf_offset.unwrap_or(0);
|
||||
let (mut direct, mut mirror) = (Vec::new(), Vec::new());
|
||||
for o in (0..bytes.len().saturating_sub(12 + 8 * 24)).step_by(4) {
|
||||
for (flip, out) in [(1.0f32, &mut direct), (-1.0f32, &mut mirror)] {
|
||||
if (0..8).all(|k| {
|
||||
let at = o + k * 24;
|
||||
(be(at) - flip * pos[k][0]).abs() <= 1e-4
|
||||
&& (be(at + 4) - pos[k][1]).abs() <= 1e-4
|
||||
&& (be(at + 8) - pos[k][2]).abs() <= 1e-4
|
||||
}) {
|
||||
out.push(o);
|
||||
}
|
||||
}
|
||||
}
|
||||
println!(
|
||||
"{:<20} anchored 0x{anchored:x} direct copies {:x?} mirrored copies {:x?}",
|
||||
m.name, direct, mirror
|
||||
);
|
||||
}
|
||||
}
|
||||
60
crates/sylpheed-formats/examples/gate_histogram.rs
Normal file
@@ -0,0 +1,60 @@
|
||||
//! Which gate stops the resources that never decode?
|
||||
//! Usage: gate_histogram <resource3d_dir> [max_resources]
|
||||
use sylpheed_formats::mesh::{debug_best_rejection, xbg7_resource_names, Xbg7Model};
|
||||
use std::collections::{BTreeMap, HashSet};
|
||||
|
||||
fn main() {
|
||||
let dir = std::env::args().nth(1).expect("resource3d dir");
|
||||
let cap: usize = std::env::args().nth(2).and_then(|s| s.parse().ok()).unwrap_or(usize::MAX);
|
||||
let mut files: Vec<_> = std::fs::read_dir(&dir)
|
||||
.unwrap()
|
||||
.flatten()
|
||||
.map(|e| e.path())
|
||||
.filter(|p| p.extension().and_then(|s| s.to_str()) == Some("xpr"))
|
||||
.collect();
|
||||
files.sort();
|
||||
|
||||
let mut hist: BTreeMap<usize, (usize, String)> = BTreeMap::new();
|
||||
let mut done = 0usize;
|
||||
for f in &files {
|
||||
if done >= cap {
|
||||
break;
|
||||
}
|
||||
let Ok(bytes) = std::fs::read(f) else { continue };
|
||||
let names = xbg7_resource_names(&bytes);
|
||||
if names.is_empty() {
|
||||
continue;
|
||||
}
|
||||
let got: HashSet<String> = Xbg7Model::anchor_models_cancellable(&bytes, 0.0, &|| false)
|
||||
.into_iter()
|
||||
.map(|m| m.name)
|
||||
.collect();
|
||||
for n in names.iter().filter(|n| !got.contains(*n)) {
|
||||
if done >= cap {
|
||||
break;
|
||||
}
|
||||
done += 1;
|
||||
if let Some((rank, why)) = debug_best_rejection(&bytes, n) {
|
||||
let e = hist.entry(rank).or_insert((0, String::new()));
|
||||
e.0 += 1;
|
||||
if e.1.is_empty() {
|
||||
e.1 = format!("{n}: {why}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
println!("furthest gate reached, over {done} resources that never decode:");
|
||||
let label = |r: usize| match r {
|
||||
0 => "no gate reached",
|
||||
1 => "index out of range",
|
||||
2 => "buffer not covered by indices",
|
||||
3 => "degenerate / implausible positions",
|
||||
4 => "connectivity (mean edge / diagonal)",
|
||||
5 => "winding consistency",
|
||||
9 => "grouped pool (different path)",
|
||||
_ => "?",
|
||||
};
|
||||
for (r, (n, ex)) in &hist {
|
||||
println!(" {:<38} {n:>5} e.g. {ex}", label(*r));
|
||||
}
|
||||
}
|
||||
2
crates/sylpheed-formats/examples/grouped_report.rs
Normal file
@@ -0,0 +1,2 @@
|
||||
fn main(){let a:Vec<String>=std::env::args().collect();let b=std::fs::read(&a[1]).unwrap();
|
||||
for l in sylpheed_formats::mesh::debug_grouped_report(&b,&a[2],a[3].parse().unwrap()){println!("{l}")}}
|
||||
32
crates/sylpheed-formats/examples/index_hash_dump.rs
Normal file
@@ -0,0 +1,32 @@
|
||||
//! Dump a fingerprint of every decoded index run, so two decoder settings can be
|
||||
//! diffed exactly. Usage: index_hash_dump <resource3d_dir>
|
||||
use sylpheed_formats::mesh::Xbg7Model;
|
||||
|
||||
fn main() {
|
||||
let dir = std::env::args().nth(1).expect("resource3d dir");
|
||||
let mut files: Vec<_> = std::fs::read_dir(&dir)
|
||||
.unwrap()
|
||||
.flatten()
|
||||
.map(|e| e.path())
|
||||
.filter(|p| p.extension().and_then(|s| s.to_str()) == Some("xpr"))
|
||||
.collect();
|
||||
files.sort();
|
||||
for f in &files {
|
||||
let Ok(bytes) = std::fs::read(f) else { continue };
|
||||
let w = f.file_name().unwrap().to_string_lossy().to_string();
|
||||
for m in Xbg7Model::stage_models(&bytes) {
|
||||
for (k, sm) in m.meshes.iter().enumerate() {
|
||||
let mut h = 1469598103934665603u64;
|
||||
for i in &sm.indices {
|
||||
h = (h ^ *i as u64).wrapping_mul(1099511628211);
|
||||
}
|
||||
println!(
|
||||
"{w} {} {k} vb={:?} n={} h={h:016x}",
|
||||
m.name,
|
||||
sm.vbuf_offset,
|
||||
sm.indices.len()
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
134
crates/sylpheed-formats/examples/index_pad_check.rs
Normal file
@@ -0,0 +1,134 @@
|
||||
//! Is our index list one element late? A capture-free check of the `pad` choice.
|
||||
//!
|
||||
//! `anchor_pool_mesh` tries `ib = vb − idx_count*2 − pad` for `pad` in `0..=3`,
|
||||
//! **pad 0 first and with the looser winding gate** (`XBG7_PAD0_CONSISTENCY`,
|
||||
//! 0.70) than the pad ≥ 1 path (0.85). The 2026-08-13 index-byte comparison
|
||||
//! against the runtime showed 17 draw batches whose captured indices equal our
|
||||
//! decoded list shifted by exactly one element — all of them on buffers whose
|
||||
//! real index data sits at pad 2. A one-element shift re-wires every triangle,
|
||||
//! and it is invisible to every count-based metric (the count, the coverage and
|
||||
//! the positions are all still right).
|
||||
//!
|
||||
//! This reproduces that finding from the container alone: for each resource it
|
||||
//! reads the index run at pad 0 and at pad 2 and scores both on two properties a
|
||||
//! correct triangle list has — **no degenerate triangles** (two equal indices)
|
||||
//! and consistent winding against the stored normals.
|
||||
//!
|
||||
//! Usage: index_pad_check <container.xpr> [name-substring]
|
||||
use sylpheed_formats::mesh::Xbg7Model;
|
||||
|
||||
fn be16(b: &[u8], at: usize) -> u32 {
|
||||
((b[at] as u32) << 8) | b[at + 1] as u32
|
||||
}
|
||||
|
||||
/// Degenerate triangles (a repeated index) and the fraction of triangles whose
|
||||
/// face normal agrees with their vertices' stored normals.
|
||||
fn score(idx: &[u32], pos: &[[f32; 3]], nrm: &[[f32; 3]]) -> (usize, f32, usize) {
|
||||
let mut degen = 0usize;
|
||||
let mut agree = 0usize;
|
||||
let mut counted = 0usize;
|
||||
for t in idx.chunks_exact(3) {
|
||||
let (a, b, c) = (t[0] as usize, t[1] as usize, t[2] as usize);
|
||||
if a == b || b == c || a == c {
|
||||
degen += 1;
|
||||
continue;
|
||||
}
|
||||
if a >= pos.len() || b >= pos.len() || c >= pos.len() {
|
||||
continue;
|
||||
}
|
||||
let e1 = [pos[b][0] - pos[a][0], pos[b][1] - pos[a][1], pos[b][2] - pos[a][2]];
|
||||
let e2 = [pos[c][0] - pos[a][0], pos[c][1] - pos[a][1], pos[c][2] - pos[a][2]];
|
||||
let f = [
|
||||
e1[1] * e2[2] - e1[2] * e2[1],
|
||||
e1[2] * e2[0] - e1[0] * e2[2],
|
||||
e1[0] * e2[1] - e1[1] * e2[0],
|
||||
];
|
||||
if nrm.len() <= a {
|
||||
continue;
|
||||
}
|
||||
let n = nrm[a];
|
||||
let dot = f[0] * n[0] + f[1] * n[1] + f[2] * n[2];
|
||||
counted += 1;
|
||||
if dot > 0.0 {
|
||||
agree += 1;
|
||||
}
|
||||
}
|
||||
let frac = if counted == 0 { 0.0 } else { agree as f32 / counted as f32 };
|
||||
(degen, frac.max(1.0 - frac), counted)
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let a: Vec<String> = std::env::args().collect();
|
||||
let bytes = std::fs::read(&a[1]).expect("container");
|
||||
let filter = a.get(2).cloned().unwrap_or_default();
|
||||
|
||||
let models = Xbg7Model::stage_models(&bytes);
|
||||
println!(
|
||||
"{:<22} {:>6} {:>6} {:<26} {:<26} verdict",
|
||||
"resource", "verts", "idx", "pad 0 (what we decode)", "pad 2 (2 bytes earlier)"
|
||||
);
|
||||
let (mut better_p2, mut better_p0, mut tie) = (0usize, 0usize, 0usize);
|
||||
let mut hist_ok: Vec<(usize, String, usize)> = Vec::new();
|
||||
for m in &models {
|
||||
if !filter.is_empty() && !m.name.contains(&filter) {
|
||||
continue;
|
||||
}
|
||||
if m.meshes.len() != 1 {
|
||||
continue; // single-block path only; grouped pools use another formula
|
||||
}
|
||||
let sm = &m.meshes[0];
|
||||
let Some(vb) = sm.vbuf_offset else { continue };
|
||||
let n = sm.indices.len();
|
||||
if n < 6 || sm.normals.is_empty() || vb < n * 2 + 2 {
|
||||
continue;
|
||||
}
|
||||
let read_at = |start: usize| -> Vec<u32> { (0..n).map(|k| be16(&bytes, start + k * 2)).collect() };
|
||||
let l0 = read_at(vb - n * 2);
|
||||
let l2 = read_at(vb - n * 2 - 2);
|
||||
// Sanity: l0 must be what the decoder emitted, or the assumption that we
|
||||
// took pad 0 is wrong for this resource and the comparison is meaningless.
|
||||
let took_pad0 = l0 == sm.indices;
|
||||
if !took_pad0 {
|
||||
continue;
|
||||
}
|
||||
let (d0, c0, _) = score(&l0, &sm.positions, &sm.normals);
|
||||
let (d2, c2, _) = score(&l2, &sm.positions, &sm.normals);
|
||||
let max_i0 = l0.iter().max().copied().unwrap_or(0);
|
||||
let max_i2 = l2.iter().max().copied().unwrap_or(0);
|
||||
let v = sm.positions.len() as u32;
|
||||
// A shifted list typically also overruns the pool by one vertex or leaves
|
||||
// the last vertex unreferenced, so carry the coverage as evidence too.
|
||||
let verdict = if d2 < d0 && c2 >= c0 {
|
||||
better_p2 += 1;
|
||||
"pad 2 is the real one"
|
||||
} else if d0 < d2 || c0 > c2 {
|
||||
better_p0 += 1;
|
||||
"pad 0 fine"
|
||||
} else {
|
||||
tie += 1;
|
||||
"indistinguishable"
|
||||
};
|
||||
if verdict == "pad 0 fine" {
|
||||
hist_ok.push((d0, m.name.clone(), n));
|
||||
}
|
||||
if verdict != "pad 0 fine" || !filter.is_empty() {
|
||||
println!(
|
||||
"{:<22} {:>6} {:>6} degen {:>5} wind {:.3} max {:<5} degen {:>5} wind {:.3} max {:<5} {}",
|
||||
m.name, v, n, d0, c0, max_i0, d2, c2, max_i2, verdict
|
||||
);
|
||||
}
|
||||
}
|
||||
println!("\npad 2 wins: {better_p2} · pad 0 wins: {better_p0} · indistinguishable: {tie}");
|
||||
// Is "no degenerate triangle" a safe invariant for a correctly anchored
|
||||
// block? Distribution over the resources where pad 0 is the better choice.
|
||||
let zero = hist_ok.iter().filter(|(d, _, _)| *d == 0).count();
|
||||
println!(
|
||||
"of the {} pad-0-correct resources, {zero} have ZERO degenerate triangles",
|
||||
hist_ok.len()
|
||||
);
|
||||
let mut worst: Vec<_> = hist_ok.iter().filter(|(d, _, _)| *d > 0).collect();
|
||||
worst.sort_by_key(|(d, _, _)| std::cmp::Reverse(*d));
|
||||
for (d, n, idx) in worst.iter().take(10) {
|
||||
println!(" {n}: {d} degenerate of {} triangles", idx / 3);
|
||||
}
|
||||
}
|
||||
90
crates/sylpheed-formats/examples/live_offsets.rs
Normal file
@@ -0,0 +1,90 @@
|
||||
//! Where does each declared field sit inside the engine's live definition object?
|
||||
//!
|
||||
//! A live object carries no names, so the mapping has to be inferred: take a unit
|
||||
//! whose disc record SETS a field, look for that value in the dumped words of the
|
||||
//! matching live object, and keep the offsets that agree across several units.
|
||||
//!
|
||||
//! Run: live_offsets <disc-root> <live-dump.txt> <ID=0xVA> ...
|
||||
//! e.g. … UN_f106_TCAF_Destroyer=0xbd3ee300 UN_f105_TCAF_Cruiser=0xbd40e200
|
||||
use std::collections::BTreeMap;
|
||||
use sylpheed_formats::idxd::IdxdObject;
|
||||
use sylpheed_formats::pak::PakArchive;
|
||||
|
||||
fn main() {
|
||||
let mut a = std::env::args().skip(1);
|
||||
let disc = a.next().expect("disc root");
|
||||
let dump = a.next().expect("live dump");
|
||||
let pairs: Vec<(String, String)> = a
|
||||
.filter_map(|s| s.split_once('=').map(|(i, v)| (i.to_string(), v.to_string())))
|
||||
.collect();
|
||||
|
||||
// live: va -> offset -> f32
|
||||
let mut live: BTreeMap<String, BTreeMap<usize, f32>> = BTreeMap::new();
|
||||
let mut cur = String::new();
|
||||
for line in std::fs::read_to_string(&dump).expect("dump").lines() {
|
||||
if let Some(rest) = line.strip_prefix("=== ") {
|
||||
cur = rest.trim().to_string();
|
||||
continue;
|
||||
}
|
||||
let f: Vec<&str> = line.split_whitespace().collect();
|
||||
if f.len() >= 4 && f[1].starts_with('+') {
|
||||
if let (Ok(off), Ok(val)) =
|
||||
(usize::from_str_radix(&f[1][1..], 16), f[3].parse::<f32>())
|
||||
{
|
||||
live.entry(cur.clone()).or_default().insert(off, val);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let pak = PakArchive::open(format!("{disc}/dat/GP_MAIN_GAME_E.pak")).expect("pak");
|
||||
// key -> offset -> how many units agree
|
||||
let mut votes: BTreeMap<String, BTreeMap<usize, usize>> = BTreeMap::new();
|
||||
let mut units = 0usize;
|
||||
for entry in pak.entries() {
|
||||
let Ok(bytes) = pak.read(entry) else { continue };
|
||||
let Ok(obj) = IdxdObject::parse(&bytes) else { continue };
|
||||
let Some(id) = obj.get_raw("ID") else { continue };
|
||||
let Some((_, va)) = pairs.iter().find(|(i, _)| i == id) else { continue };
|
||||
let Some(words) = live.get(va) else { continue };
|
||||
units += 1;
|
||||
let mut numeric = 0usize;
|
||||
let mut hit = 0usize;
|
||||
for key in obj.tokens() {
|
||||
let Some(v) = obj.get_f32(key) else { continue };
|
||||
if !v.is_finite() || v == 0.0 {
|
||||
continue; // zero matches everywhere and says nothing
|
||||
}
|
||||
numeric += 1;
|
||||
let mut found = false;
|
||||
for (off, w) in words {
|
||||
if (*w - v).abs() <= v.abs() * 1e-6 {
|
||||
*votes.entry(key.clone()).or_default().entry(*off).or_default() += 1;
|
||||
found = true;
|
||||
}
|
||||
}
|
||||
if found {
|
||||
hit += 1;
|
||||
}
|
||||
}
|
||||
eprintln!(" {id}: {numeric} numeric fields set on disc, {hit} found in the dumped window");
|
||||
}
|
||||
println!("{units} units correlated against their live objects\n");
|
||||
println!("{:<28} {:>8} offsets agreeing (votes)", "field", "unique?");
|
||||
for (key, offs) in &votes {
|
||||
let best = offs.iter().max_by_key(|(_, n)| **n).unwrap();
|
||||
if *best.1 < units.max(2) {
|
||||
continue; // needs every correlated unit to agree
|
||||
}
|
||||
let list: Vec<String> = offs
|
||||
.iter()
|
||||
.filter(|(_, n)| **n == *best.1)
|
||||
.map(|(o, n)| format!("+{o:03x}×{n}"))
|
||||
.collect();
|
||||
println!(
|
||||
"{:<28} {:>8} {}",
|
||||
key,
|
||||
if list.len() == 1 { "unique" } else { "ambig" },
|
||||
list.join(" ")
|
||||
);
|
||||
}
|
||||
}
|
||||
53
crates/sylpheed-formats/examples/locate_draw.rs
Normal file
@@ -0,0 +1,53 @@
|
||||
//! How many copies of a captured buffer does a container hold?
|
||||
//!
|
||||
//! The twins showed that two resources decoding to one buffer can mean the
|
||||
//! container really holds two (mirrored) copies and our scan found only one.
|
||||
//! This asks that question for any draw: give it a `vbase`, and it reports every
|
||||
//! offset whose leading vertices match the draw's dumped positions — directly, and
|
||||
//! X-mirrored.
|
||||
//!
|
||||
//! Usage: locate_draw <container.xpr> <capture.log> <vbase-hex>...
|
||||
use sylpheed_formats::ship_capture::{parse_capture, parse_drawlog};
|
||||
|
||||
fn main() {
|
||||
let a: Vec<String> = std::env::args().collect();
|
||||
let bytes = std::fs::read(&a[1]).expect("container");
|
||||
let text = std::fs::read_to_string(&a[2]).expect("log");
|
||||
let mut draws = parse_capture(&text);
|
||||
if draws.is_empty() {
|
||||
draws = parse_drawlog(&text);
|
||||
}
|
||||
let be = |at: usize| f32::from_be_bytes(bytes[at..at + 4].try_into().unwrap());
|
||||
|
||||
for want in &a[3..] {
|
||||
let vb = u32::from_str_radix(want.trim_start_matches("0x"), 16).expect("hex vbase");
|
||||
let Some(d) = draws.iter().find(|d| d.vbase == vb) else {
|
||||
println!("vbase 0x{vb:08X}: not in this log");
|
||||
continue;
|
||||
};
|
||||
let n = d.pos.len().min(8);
|
||||
let mut direct = Vec::new();
|
||||
let mut mirror = Vec::new();
|
||||
for o in (0..bytes.len().saturating_sub(12 + 64 * 24)).step_by(4) {
|
||||
for (flip, out) in [(1.0f32, &mut direct), (-1.0f32, &mut mirror)] {
|
||||
let hit = (0..n).all(|k| {
|
||||
let at = o + k * 24;
|
||||
(be(at) - flip * d.pos[k][0]).abs() <= 1e-4
|
||||
&& (be(at + 4) - d.pos[k][1]).abs() <= 1e-4
|
||||
&& (be(at + 8) - d.pos[k][2]).abs() <= 1e-4
|
||||
});
|
||||
if hit {
|
||||
out.push(o);
|
||||
}
|
||||
}
|
||||
}
|
||||
println!(
|
||||
"vbase 0x{vb:08X} vcount={}: {} direct copy/copies {:x?}, {} mirrored {:x?}",
|
||||
d.vcount,
|
||||
direct.len(),
|
||||
&direct[..],
|
||||
mirror.len(),
|
||||
&mirror[..]
|
||||
);
|
||||
}
|
||||
}
|
||||
86
crates/sylpheed-formats/examples/miss_targets.rs
Normal file
@@ -0,0 +1,86 @@
|
||||
//! Which container should the next runtime capture aim at?
|
||||
//!
|
||||
//! The 2026-08-13 stage-05 capture showed that a mission keeps several containers
|
||||
//! resident and that its OWN `Stage_SNN.xpr` is among them (the f101 ACROPOLIS was
|
||||
//! drawn from `Stage_S05.xpr`), so a capture can be aimed by choosing the mission.
|
||||
//! This ranks the targets: for every XBG7 resource on the disc it records the
|
||||
//! containers where it decodes and the ones where it does not, then reports
|
||||
//!
|
||||
//! * resources that decode **nowhere** — the real gaps, where a capture is the
|
||||
//! only ground truth left;
|
||||
//! * per container, how many of those it holds (fly that mission);
|
||||
//! * resources that miss in one container but decode in another — a defect worth
|
||||
//! fixing, but the geometry is already recoverable, so a capture is not needed.
|
||||
//!
|
||||
//! Usage: miss_targets <resource3d_dir>
|
||||
use sylpheed_formats::mesh::{xbg7_resource_names, Xbg7Model};
|
||||
use std::collections::{BTreeMap, BTreeSet};
|
||||
|
||||
fn main() {
|
||||
let dir = std::env::args().nth(1).expect("resource3d dir");
|
||||
let mut files: Vec<_> = std::fs::read_dir(&dir)
|
||||
.unwrap()
|
||||
.flatten()
|
||||
.map(|e| e.path())
|
||||
.filter(|p| p.extension().and_then(|s| s.to_str()) == Some("xpr"))
|
||||
.collect();
|
||||
files.sort();
|
||||
|
||||
// resource -> (containers where it decodes, containers where it misses)
|
||||
let mut ok: BTreeMap<String, BTreeSet<String>> = BTreeMap::new();
|
||||
let mut miss: BTreeMap<String, BTreeSet<String>> = BTreeMap::new();
|
||||
for f in &files {
|
||||
let Ok(bytes) = std::fs::read(f) else { continue };
|
||||
let where_ = f.file_name().unwrap().to_string_lossy().to_string();
|
||||
let declared: BTreeSet<String> = xbg7_resource_names(&bytes).into_iter().collect();
|
||||
if declared.is_empty() {
|
||||
continue;
|
||||
}
|
||||
let decoded: BTreeSet<String> =
|
||||
Xbg7Model::stage_models(&bytes).into_iter().map(|m| m.name).collect();
|
||||
for n in &declared {
|
||||
if decoded.contains(n) {
|
||||
ok.entry(n.clone()).or_default().insert(where_.clone());
|
||||
} else {
|
||||
miss.entry(n.clone()).or_default().insert(where_.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let never: BTreeMap<&String, &BTreeSet<String>> =
|
||||
miss.iter().filter(|(n, _)| !ok.contains_key(*n)).collect();
|
||||
let recoverable: Vec<&String> = miss.keys().filter(|n| ok.contains_key(*n)).collect();
|
||||
|
||||
println!("resources that decode NOWHERE: {}", never.len());
|
||||
println!("resources that miss somewhere but decode elsewhere: {}\n", recoverable.len());
|
||||
|
||||
// Rank containers by how many never-decoding resources they hold.
|
||||
let mut per_container: BTreeMap<&String, Vec<&String>> = BTreeMap::new();
|
||||
for (n, wheres) in &never {
|
||||
for w in wheres.iter() {
|
||||
per_container.entry(w).or_default().push(n);
|
||||
}
|
||||
}
|
||||
let mut ranked: Vec<_> = per_container.iter().collect();
|
||||
ranked.sort_by_key(|(_, v)| std::cmp::Reverse(v.len()));
|
||||
println!("container never-decoding resources it holds");
|
||||
for (w, v) in ranked.iter().take(14) {
|
||||
let sample: Vec<&str> = v.iter().take(6).map(|s| s.as_str()).collect();
|
||||
println!("{:<26} {:>3} {}", w, v.len(), sample.join(", "));
|
||||
}
|
||||
|
||||
// Exclusives: a never-decoding resource that only ONE container holds is only
|
||||
// reachable through whatever loads that container.
|
||||
let excl: Vec<(&&String, &&BTreeSet<String>)> =
|
||||
never.iter().filter(|(_, w)| w.len() == 1).collect();
|
||||
println!("\nof the never-decoding, {} live in exactly one container", excl.len());
|
||||
let mut by_one: BTreeMap<&String, Vec<&String>> = BTreeMap::new();
|
||||
for (n, w) in &excl {
|
||||
by_one.entry(w.iter().next().unwrap()).or_default().push(n);
|
||||
}
|
||||
let mut b: Vec<_> = by_one.iter().collect();
|
||||
b.sort_by_key(|(_, v)| std::cmp::Reverse(v.len()));
|
||||
for (w, v) in b.iter().take(10) {
|
||||
println!(" {:<24} {:>3} {}", w, v.len(), v.iter().take(5).map(|s| s.as_str()).collect::<Vec<_>>().join(", "));
|
||||
}
|
||||
}
|
||||
20
crates/sylpheed-formats/examples/node_scale.rs
Normal file
@@ -0,0 +1,20 @@
|
||||
//! Dump a composite's scene nodes with their scale — the transform the static
|
||||
//! assembler copies verbatim into a placement.
|
||||
//! Usage: node_scale <container.xpr> <composite name>
|
||||
use sylpheed_formats::mesh::scene_world_nodes;
|
||||
fn main() {
|
||||
let a: Vec<String> = std::env::args().collect();
|
||||
let bytes = std::fs::read(&a[1]).expect("container");
|
||||
for n in scene_world_nodes(&bytes, &a[2]) {
|
||||
// Row norms of `m`: a rotation has all three at 1.0; anything else is a
|
||||
// scale baked into the matrix, which `s` does not show.
|
||||
let norm = |r: [f32; 3]| (r[0] * r[0] + r[1] * r[1] + r[2] * r[2]).sqrt();
|
||||
println!(
|
||||
"{:<26} s[{:7.3}{:7.3}{:7.3}] |m rows|[{:7.3}{:7.3}{:7.3}] t[{:9.1}{:9.1}{:9.1}]",
|
||||
n.resource,
|
||||
n.s[0], n.s[1], n.s[2],
|
||||
norm(n.m[0]), norm(n.m[1]), norm(n.m[2]),
|
||||
n.t[0], n.t[1], n.t[2]
|
||||
);
|
||||
}
|
||||
}
|
||||
129
crates/sylpheed-formats/examples/pad_shift_audit.rs
Normal file
@@ -0,0 +1,129 @@
|
||||
//! How much geometry on the disc was being read one index element late?
|
||||
//!
|
||||
//! The pad-scoring fix (2026-08-13) makes `anchor_pool_mesh` choose the pad whose
|
||||
//! index run is cleanest instead of the first that validates. This measures the
|
||||
//! blast radius: per container, how many single-block resources now decode from a
|
||||
//! run that is NOT the old first-match (`pad 0`) one, and how many decoded runs
|
||||
//! still contain degenerate triangles (the shift signature) afterwards.
|
||||
//!
|
||||
//! Usage: pad_shift_audit <resource3d_dir>
|
||||
use sylpheed_formats::mesh::Xbg7Model;
|
||||
|
||||
fn be16(b: &[u8], at: usize) -> u32 {
|
||||
((b[at] as u32) << 8) | b[at + 1] as u32
|
||||
}
|
||||
|
||||
/// Fraction of triangles whose face normal agrees with the stored normals,
|
||||
/// folded to `max(na, 1-na)` so both authored windings read as ≈1.
|
||||
fn winding(idx: &[u32], pos: &[[f32; 3]], nrm: &[[f32; 3]]) -> f32 {
|
||||
let (mut agree, mut n) = (0usize, 0usize);
|
||||
for t in idx.chunks_exact(3) {
|
||||
let (a, b, c) = (t[0] as usize, t[1] as usize, t[2] as usize);
|
||||
if a == b || b == c || a == c || a.max(b).max(c) >= pos.len() || a >= nrm.len() {
|
||||
continue;
|
||||
}
|
||||
let e1 = [pos[b][0] - pos[a][0], pos[b][1] - pos[a][1], pos[b][2] - pos[a][2]];
|
||||
let e2 = [pos[c][0] - pos[a][0], pos[c][1] - pos[a][1], pos[c][2] - pos[a][2]];
|
||||
let f = [
|
||||
e1[1] * e2[2] - e1[2] * e2[1],
|
||||
e1[2] * e2[0] - e1[0] * e2[2],
|
||||
e1[0] * e2[1] - e1[1] * e2[0],
|
||||
];
|
||||
let nv = nrm[a];
|
||||
n += 1;
|
||||
if f[0] * nv[0] + f[1] * nv[1] + f[2] * nv[2] > 0.0 {
|
||||
agree += 1;
|
||||
}
|
||||
}
|
||||
if n == 0 {
|
||||
return 1.0;
|
||||
}
|
||||
let fr = agree as f32 / n as f32;
|
||||
fr.max(1.0 - fr)
|
||||
}
|
||||
|
||||
fn degenerate(idx: &[u32]) -> usize {
|
||||
idx.chunks_exact(3)
|
||||
.filter(|t| t[0] == t[1] || t[1] == t[2] || t[0] == t[2])
|
||||
.count()
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let dir = std::env::args().nth(1).expect("resource3d dir");
|
||||
let mut files: Vec<_> = std::fs::read_dir(&dir)
|
||||
.unwrap()
|
||||
.flatten()
|
||||
.map(|e| e.path())
|
||||
.filter(|p| p.extension().and_then(|s| s.to_str()) == Some("xpr"))
|
||||
.collect();
|
||||
files.sort();
|
||||
|
||||
let (mut total, mut moved, mut still_degen) = (0usize, 0usize, 0usize);
|
||||
let mut weak = 0usize;
|
||||
let mut weak_wind: Vec<(f32, f32, String)> = Vec::new();
|
||||
let mut worst: Vec<(usize, String, String)> = Vec::new();
|
||||
for f in &files {
|
||||
let Ok(bytes) = std::fs::read(f) else { continue };
|
||||
let where_ = f.file_name().unwrap().to_string_lossy().to_string();
|
||||
let mut c_moved = 0usize;
|
||||
for m in Xbg7Model::stage_models(&bytes) {
|
||||
// Degeneracy is counted over EVERY sub-mesh (grouped pools included —
|
||||
// they have their own pad choice); the pad-0 comparison below only
|
||||
// applies to single-block resources, whose run starts at `vb - 2n`.
|
||||
for sm in &m.meshes {
|
||||
let d = degenerate(&sm.indices);
|
||||
if d > 0 {
|
||||
still_degen += 1;
|
||||
worst.push((d, m.name.clone(), where_.clone()));
|
||||
}
|
||||
}
|
||||
if m.meshes.len() != 1 {
|
||||
continue;
|
||||
}
|
||||
let sm = &m.meshes[0];
|
||||
let Some(vb) = sm.vbuf_offset else { continue };
|
||||
let n = sm.indices.len();
|
||||
if n < 6 || vb < n * 2 {
|
||||
continue;
|
||||
}
|
||||
total += 1;
|
||||
let pad0: Vec<u32> = (0..n).map(|k| be16(&bytes, vb - n * 2 + k * 2)).collect();
|
||||
if pad0 != sm.indices {
|
||||
moved += 1;
|
||||
c_moved += 1;
|
||||
// How strong was the evidence for moving? A pad-0 run with
|
||||
// degenerate triangles is a decisive shift signature; one with
|
||||
// none moved on winding alone, which is weaker.
|
||||
if degenerate(&pad0) == 0 {
|
||||
weak += 1;
|
||||
// Record how far apart the two runs' winding is, to see
|
||||
// whether these look like the same shift signature as the
|
||||
// decisive cases (pad-0 winding drifting toward 0.5) or like
|
||||
// noise between two equally clean readings.
|
||||
let w0 = winding(&pad0, &sm.positions, &sm.normals);
|
||||
let w2: f32 = {
|
||||
let l2: Vec<u32> = (0..n).map(|k| be16(&bytes, vb - n * 2 - 2 + k * 2)).collect();
|
||||
winding(&l2, &sm.positions, &sm.normals)
|
||||
};
|
||||
weak_wind.push((w0, w2, m.name.clone()));
|
||||
}
|
||||
}
|
||||
}
|
||||
if c_moved > 0 {
|
||||
println!("{where_:<24} {c_moved} resources read from a non-pad-0 index run");
|
||||
}
|
||||
}
|
||||
println!("\n{moved} of {total} single-block resources moved off the pad-0 run");
|
||||
println!("{still_degen} decoded runs still contain a degenerate triangle");
|
||||
println!("of the moved, {weak} had a pad-0 run with no degenerate triangle (moved on winding alone)");
|
||||
let clear = weak_wind.iter().filter(|(w0, w2, _)| *w2 - *w0 > 0.15).count();
|
||||
let close = weak_wind.iter().filter(|(w0, w2, _)| (*w2 - *w0).abs() <= 0.05).count();
|
||||
println!(" of those: {clear} show the shift signature (pad-2 winding > pad-0 by >0.15), {close} are within 0.05 (noise)");
|
||||
for (w0, w2, n) in weak_wind.iter().take(8) {
|
||||
println!(" {n}: pad0 wind {w0:.3} -> pad2 {w2:.3}");
|
||||
}
|
||||
worst.sort_by_key(|(d, _, _)| std::cmp::Reverse(*d));
|
||||
for (d, n, w) in worst.iter().take(15) {
|
||||
println!(" {n} ({w}): {d} degenerate triangles");
|
||||
}
|
||||
}
|
||||
13
crates/sylpheed-formats/examples/params.rs
Normal file
@@ -0,0 +1,13 @@
|
||||
fn main(){
|
||||
let a:Vec<String>=std::env::args().collect();
|
||||
let bytes=std::fs::read(&a[1]).unwrap();
|
||||
let filter=a.get(2).cloned().unwrap_or_default();
|
||||
for n in sylpheed_formats::mesh::xbg7_resource_names(&bytes) {
|
||||
if !filter.is_empty() && !n.contains(&filter) { continue }
|
||||
if let Some((m,stride))=sylpheed_formats::mesh::debug_resource_params(&bytes,&n) {
|
||||
let strides = sylpheed_formats::mesh::debug_decl_strides(&bytes, &n);
|
||||
println!("{n:<28} decl-stride {stride} markers {:?} per-sub-mesh strides {:?}",
|
||||
&m[..m.len().min(4)], strides);
|
||||
}
|
||||
}
|
||||
}
|
||||
29
crates/sylpheed-formats/examples/pool_window.rs
Normal file
@@ -0,0 +1,29 @@
|
||||
//! Raw token sequence around a key, for one record — the ground truth for
|
||||
//! "is this field actually defaulted, or is our reader missing it?"
|
||||
//! Run: pool_window <disc-root> <ID substring> <key>
|
||||
use sylpheed_formats::idxd::IdxdObject;
|
||||
use sylpheed_formats::pak::PakArchive;
|
||||
fn main() {
|
||||
let disc = std::env::args().nth(1).unwrap();
|
||||
let want = std::env::args().nth(2).unwrap();
|
||||
let key = std::env::args().nth(3).unwrap();
|
||||
let pak = PakArchive::open(format!("{disc}/dat/GP_MAIN_GAME_E.pak")).unwrap();
|
||||
for e in pak.entries() {
|
||||
let Ok(b) = pak.read(e) else { continue };
|
||||
let Ok(o) = IdxdObject::parse(&b) else { continue };
|
||||
let Some(id) = o.get_raw("ID") else { continue };
|
||||
if !id.contains(&want) { continue; }
|
||||
let t = o.tokens();
|
||||
println!("=== {id} get_f32({key}) = {:?}", o.get_f32(&key));
|
||||
for (i, tok) in t.iter().enumerate() {
|
||||
if tok == &key {
|
||||
let lo = i.saturating_sub(6);
|
||||
let hi = (i + 7).min(t.len());
|
||||
for j in lo..hi {
|
||||
println!(" [{j}]{} {:?}", if j == i { " <-- key" } else { " " }, t[j]);
|
||||
}
|
||||
println!();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
6
crates/sylpheed-formats/examples/probe_anchor.rs
Normal file
@@ -0,0 +1,6 @@
|
||||
fn main() {
|
||||
let a: Vec<String> = std::env::args().collect();
|
||||
let bytes = std::fs::read(&a[1]).unwrap();
|
||||
let vb: usize = a[3].parse().unwrap();
|
||||
println!("{:?}", sylpheed_formats::mesh::debug_try_anchor(&bytes, &a[2], vb, 3));
|
||||
}
|
||||
60
crates/sylpheed-formats/examples/roster_target.rs
Normal file
@@ -0,0 +1,60 @@
|
||||
//! Which mission should the next unit harvest fly?
|
||||
//!
|
||||
//! Unit definitions are instantiated per stage, so coverage grows by visiting
|
||||
//! missions — but only if the mission fields units we have not read yet. This
|
||||
//! ranks every stage by how many of its roster units are missing from the
|
||||
//! harvested CSV.
|
||||
//!
|
||||
//! Usage: roster_target <disc-root> <unit-runtime-fields.csv>
|
||||
use sylpheed_formats::hash::name_hash;
|
||||
use sylpheed_formats::idxd::IdxdObject;
|
||||
use sylpheed_formats::pak::PakArchive;
|
||||
use std::collections::{BTreeMap, BTreeSet};
|
||||
|
||||
fn main() {
|
||||
let a: Vec<String> = std::env::args().collect();
|
||||
let pak = PakArchive::open(format!("{}/dat/GP_MAIN_GAME_E.pak", a[1])).expect("pak");
|
||||
let have: BTreeSet<String> = std::fs::read_to_string(&a[2])
|
||||
.expect("csv")
|
||||
.lines()
|
||||
.skip(1)
|
||||
.filter_map(|l| l.split(',').next().map(str::to_string))
|
||||
.collect();
|
||||
// Label each roster by STAGE: the TOC stores a hash of the original path, and
|
||||
// the table names are known (`EnumUnit_SNN.tbl`), so hashing the candidates
|
||||
// maps entries back to stages. Most rosters carry no `UN_S<NN>_` prop id, which
|
||||
// is all `UnitRoster::stage` can infer from.
|
||||
let mut by_hash: BTreeMap<u32, String> = BTreeMap::new();
|
||||
// The TOC hashes a PATH, and these tables live under a directory — try the
|
||||
// known schemes (see hash::TOC_NAME_SCHEMES) rather than the bare name.
|
||||
for i in 1..=29 {
|
||||
let stage = format!("S{i:02}");
|
||||
for pre in ["", "unit\\", "battle\\", "stage\\", "enemy\\"] {
|
||||
for suf in [".tbl", ""] {
|
||||
by_hash.insert(name_hash(&format!("{pre}EnumUnit_{stage}{suf}")), stage.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
let mut rows: Vec<(usize, String, Vec<String>)> = Vec::new();
|
||||
for e in pak.entries() {
|
||||
let Some(stage) = by_hash.get(&e.name_hash).cloned() else { continue };
|
||||
let Ok(b) = pak.read(e) else { continue };
|
||||
let Ok(o) = IdxdObject::parse(&b) else { continue };
|
||||
let mut units: Vec<String> = Vec::new();
|
||||
for id in o.tokens().iter().filter(|s| s.starts_with("UN_")) {
|
||||
let prop = id.contains("Asteroid") || id.contains("cmesh") || id.contains("_Box");
|
||||
if !prop && !units.iter().any(|u| u == id) {
|
||||
units.push(id.clone());
|
||||
}
|
||||
}
|
||||
let missing: Vec<String> = units.iter().filter(|u| !have.contains(*u)).cloned().collect();
|
||||
rows.push((missing.len(), stage, missing));
|
||||
}
|
||||
rows.sort_by_key(|(n, _, _)| std::cmp::Reverse(*n));
|
||||
println!("units already harvested: {}\n", have.len());
|
||||
println!("stage missing roster units not yet read");
|
||||
for (n, stage, missing) in rows.iter().take(12) {
|
||||
let s: Vec<&str> = missing.iter().take(6).map(|s| s.as_str()).collect();
|
||||
println!("{stage:<8} {n:>5} {}", s.join(", "));
|
||||
}
|
||||
}
|
||||
111
crates/sylpheed-formats/examples/screen_layout.rs
Normal file
@@ -0,0 +1,111 @@
|
||||
//! Dump a UI screen's full layout from one RATC bundle: the element declaration
|
||||
//! table (what is drawn, in back-to-front order, with pivots and parent links)
|
||||
//! plus the placement region that follows it (per element: a keyframe group of
|
||||
//! scale / tint / X / Y).
|
||||
//!
|
||||
//! See docs/re/structures/ui-rat-layout.md. Run:
|
||||
//! cargo run -p sylpheed-formats --example screen_layout -- <PAK> [0xHASH]
|
||||
//! With no hash, the largest RATC entry in the pak is used.
|
||||
|
||||
use sylpheed_formats::pak::PakArchive;
|
||||
|
||||
fn be32(b: &[u8], o: usize) -> u32 {
|
||||
u32::from_be_bytes([b[o], b[o + 1], b[o + 2], b[o + 3]])
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let mut args = std::env::args().skip(1);
|
||||
let pak = args.next().expect("usage: screen_layout <pak> [0xHASH]");
|
||||
let want = args.next();
|
||||
let arc = PakArchive::open(&pak).expect("open pak");
|
||||
|
||||
let bytes = match want {
|
||||
Some(h) if h.starts_with("0x") => {
|
||||
let h = u32::from_str_radix(h.trim_start_matches("0x"), 16).expect("hash");
|
||||
arc.read_by_hash(h).expect("entry").expect("decompress")
|
||||
}
|
||||
Some(name) => arc.read_by_name(&name).expect("entry present").expect("decompress"),
|
||||
None => arc
|
||||
.entries()
|
||||
.iter()
|
||||
.filter_map(|e| arc.read(e).ok())
|
||||
.filter(|b| b.len() > 16 && &b[..4] == b"RATC")
|
||||
.max_by_key(|b| b.len())
|
||||
.expect("no RATC entry"),
|
||||
};
|
||||
assert_eq!(&bytes[..4], b"RATC", "not a RATC bundle");
|
||||
|
||||
let count = be32(&bytes, 0x14) as usize;
|
||||
let mut names = Vec::with_capacity(count);
|
||||
let mut meta = Vec::with_capacity(count);
|
||||
for i in 0..count {
|
||||
let e = &bytes[0x20 + i * 60..0x20 + (i + 1) * 60];
|
||||
let name = String::from_utf8_lossy(&e[..28])
|
||||
.trim_end_matches('\0')
|
||||
.trim_end_matches(char::from(0))
|
||||
.to_string();
|
||||
let parent = be32(e, 32);
|
||||
let kind = be32(e, 40);
|
||||
let (px, py) = (be32(e, 48), be32(e, 52));
|
||||
names.push(name);
|
||||
meta.push((parent, kind, px, py));
|
||||
}
|
||||
|
||||
// Placement region: groups of (u32 element index, u32 keyframe count) then
|
||||
// `count` 40-byte keyframes; the X/Y sit 12 bytes into the scale/tint block.
|
||||
let mut pos = 0x20 + count * 60;
|
||||
// X/Y are SIGNED: off-screen animation starts are negative (e.g. -516).
|
||||
let mut placements: Vec<Vec<(i32, i32, u32)>> = vec![Vec::new(); count];
|
||||
for _ in 0..count {
|
||||
if pos + 8 > bytes.len() { break }
|
||||
let idx = be32(&bytes, pos) as usize;
|
||||
let frames = be32(&bytes, pos + 4) as usize;
|
||||
if idx >= count || frames == 0 || frames > 4096 { break }
|
||||
let first = pos + 28; // header + lead-in, verified on the pause bundles
|
||||
let mut group = Vec::with_capacity(frames);
|
||||
for k in 0..frames {
|
||||
let blk = first + k * 40;
|
||||
if blk + 20 > bytes.len() { break }
|
||||
group.push((be32(&bytes, blk + 12) as i32, be32(&bytes, blk + 16) as i32, be32(&bytes, blk + 20)));
|
||||
}
|
||||
placements[idx] = group;
|
||||
pos = first + frames * 40 - 20;
|
||||
}
|
||||
|
||||
println!("{} elements", count);
|
||||
println!("{:<3} {:<30} {:>7} {:>8} {:>12} {:>4} placement", "#", "element", "parent", "kind", "pivot", "kf");
|
||||
for i in 0..count {
|
||||
let (parent, kind, px, py) = meta[i];
|
||||
let p = &placements[i];
|
||||
// A group is an in → hold → out animation, so neither the first nor the
|
||||
// last keyframe is where the element sits: report the MAX-DWELL one
|
||||
// (longest gap to the next keyframe's time).
|
||||
let shown = if p.is_empty() {
|
||||
"—".into()
|
||||
} else if p.len() == 1 {
|
||||
format!("({},{})", p[0].0, p[0].1)
|
||||
} else {
|
||||
let mut best = (0usize, 0u32);
|
||||
for k in 0..p.len() - 1 {
|
||||
let d = p[k + 1].2.saturating_sub(p[k].2);
|
||||
if d > best.1 { best = (k, d) }
|
||||
}
|
||||
let r = p[best.0];
|
||||
format!(
|
||||
"rest ({},{}) t={}..{} [{}]",
|
||||
r.0, r.1, r.2, p[best.0 + 1].2,
|
||||
p.iter().map(|(x, y, t)| format!("{t}:{x},{y}")).collect::<Vec<_>>().join(" ")
|
||||
)
|
||||
};
|
||||
println!(
|
||||
"{:<3} {:<30} {:>7} {:>8} {:>12} {:>4} {}",
|
||||
i,
|
||||
names[i],
|
||||
if parent == u32::MAX { "-".into() } else { parent.to_string() },
|
||||
format!("{kind:#x}"),
|
||||
format!("({px},{py})"),
|
||||
p.len(),
|
||||
shown
|
||||
);
|
||||
}
|
||||
}
|
||||
262
crates/sylpheed-formats/examples/shared_vbase_check.rs
Normal file
@@ -0,0 +1,262 @@
|
||||
//! Ask the runtime capture whether two resources that our decoder gives the
|
||||
//! **same geometry** really are the same geometry.
|
||||
//!
|
||||
//! Our XBG7 anchor scan sometimes lands two different resource names on one
|
||||
//! vertex buffer. Statics cannot separate "the container genuinely reuses a
|
||||
//! buffer" from "the scan picked the wrong candidate" — but a capture can: the
|
||||
//! engine uploads a buffer per resource and reuses one only 3.4 % of the time
|
||||
//! (see docs/re/structures/xbg7-mesh.md), so a group of `k` resources our
|
||||
//! decoder collapses onto one buffer should show up as `k` distinct `vbase`s
|
||||
//! carrying that same vertex count and those same positions. Fewer means at
|
||||
//! most one member of the group is really that geometry.
|
||||
//!
|
||||
//! Usage:
|
||||
//! cargo run --release --example shared_vbase_check -- \
|
||||
//! <Stage_SNN.xpr> <capture.log>...
|
||||
use sylpheed_formats::mesh::Xbg7Model;
|
||||
use sylpheed_formats::ship_capture::{parse_capture, parse_drawlog, CapturedDraw};
|
||||
use std::collections::{BTreeMap, BTreeSet};
|
||||
|
||||
/// Quantised position key — the logs print 4 decimals, so compare at that scale.
|
||||
fn key(p: [f32; 3]) -> (i64, i64, i64) {
|
||||
(
|
||||
(p[0] as f64 * 1e4).round() as i64,
|
||||
(p[1] as f64 * 1e4).round() as i64,
|
||||
(p[2] as f64 * 1e4).round() as i64,
|
||||
)
|
||||
}
|
||||
|
||||
/// Where in the container does a captured buffer live? POSITION is `f32×3` big
|
||||
/// endian at vertex offset 0, so a draw's dumped positions are a literal byte
|
||||
/// pattern: find the first one, then confirm the next few at a fixed stride.
|
||||
/// This turns a capture into ground truth for a resource we mis-anchored.
|
||||
fn locate_run(bytes: &[u8], pos: &[[f32; 3]]) -> Vec<(usize, usize)> {
|
||||
if pos.len() < 4 {
|
||||
return Vec::new();
|
||||
}
|
||||
// The log prints 4 decimals, so match on value with the printing tolerance
|
||||
// rather than on bytes.
|
||||
let be = |b: &[u8], at: usize| f32::from_be_bytes(b[at..at + 4].try_into().unwrap());
|
||||
let same = |b: &[u8], at: usize, p: [f32; 3]| {
|
||||
at + 12 <= b.len() && (0..3).all(|c| (be(b, at + c * 4) - p[c]).abs() <= 1e-4)
|
||||
};
|
||||
let mut out = Vec::new();
|
||||
for o in (0..bytes.len().saturating_sub(12)).step_by(4) {
|
||||
if !same(bytes, o, pos[0]) {
|
||||
continue;
|
||||
}
|
||||
for stride in (12..=64).step_by(4) {
|
||||
if (1..4).all(|k| same(bytes, o + k * stride, pos[k])) {
|
||||
out.push((o, stride));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let args: Vec<String> = std::env::args().collect();
|
||||
if args.len() < 3 {
|
||||
eprintln!("usage: shared_vbase_check <Stage_SNN.xpr> <capture.log>...");
|
||||
std::process::exit(2);
|
||||
}
|
||||
let bytes = std::fs::read(&args[1]).expect("read container");
|
||||
|
||||
// Every draw from every log, keyed by vertex count.
|
||||
// Keep the logs apart: each is its own emulator run, so a `vbase` only
|
||||
// means something within one log.
|
||||
let mut logs: Vec<(String, Vec<CapturedDraw>)> = Vec::new();
|
||||
for log in args[2..].iter().filter(|a| !a.starts_with("--")) {
|
||||
let text = std::fs::read_to_string(log).expect("read log");
|
||||
let mut d = parse_capture(&text);
|
||||
if d.is_empty() {
|
||||
d = parse_drawlog(&text);
|
||||
}
|
||||
eprintln!("{log}: {} draws", d.len());
|
||||
logs.push((log.rsplit('/').next().unwrap_or(log).to_string(), d));
|
||||
}
|
||||
|
||||
// `--map`: is a draw's guest `vbase` just the container file offset plus a
|
||||
// constant? If the container is uploaded contiguously it is — and then a
|
||||
// capture names the exact offset of every buffer the engine drew, which is
|
||||
// ground truth the anchor scan currently has to guess at.
|
||||
if args.iter().any(|a| a == "--map") {
|
||||
for (log, draws) in &logs {
|
||||
let mut seen: BTreeSet<u32> = BTreeSet::new();
|
||||
let mut delta: BTreeMap<i64, usize> = BTreeMap::new();
|
||||
let mut unfound = 0usize;
|
||||
for d in draws {
|
||||
if d.pos.len() < 8 || d.vcount < 20 || !seen.insert(d.vbase) {
|
||||
continue;
|
||||
}
|
||||
let at = locate_run(&bytes, &d.pos);
|
||||
if at.is_empty() {
|
||||
unfound += 1;
|
||||
continue;
|
||||
}
|
||||
for (o, _) in at {
|
||||
*delta.entry(d.vbase as i64 - o as i64).or_default() += 1;
|
||||
}
|
||||
}
|
||||
let mut top: Vec<_> = delta.iter().collect();
|
||||
top.sort_by_key(|(_, n)| std::cmp::Reverse(**n));
|
||||
println!("{log}: {} distinct vbases located, {unfound} not in this container", seen.len() - unfound);
|
||||
for (d, n) in top.iter().take(5) {
|
||||
println!(" vbase - offset = 0x{:X} ×{n}", d);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Decode the container and group resources by the exact geometry they got.
|
||||
let models = Xbg7Model::anchor_models_cancellable(&bytes, 0.0, &|| false);
|
||||
|
||||
// `--truth <base>`: with the container's guest load address (from `--map`),
|
||||
// every draw names a file offset. Print it against the offset our anchor
|
||||
// scan chose for each resource — a direct read-out of what we got wrong.
|
||||
if let Some(a) = args.iter().find_map(|a| a.strip_prefix("--truth=")) {
|
||||
let base = u32::from_str_radix(a.trim_start_matches("0x"), 16).expect("base");
|
||||
// Where the anchor scan actually put each sub-mesh — exact, from the
|
||||
// decoder, not inferred by searching for its leading vertices (the same
|
||||
// leading run occurs at several offsets in a container, so a search
|
||||
// cannot tell where a resource was anchored).
|
||||
let mut ours: BTreeMap<usize, Vec<(String, usize)>> = BTreeMap::new();
|
||||
for m in &models {
|
||||
for sub in &m.meshes {
|
||||
if let Some(o) = sub.vbuf_offset {
|
||||
ours.entry(o).or_default().push((m.name.clone(), sub.positions.len()));
|
||||
}
|
||||
}
|
||||
}
|
||||
if args.iter().any(|a| a == "--anchors") {
|
||||
println!("{:<12} where our decode put each resource", "file offset");
|
||||
for (o, v) in &ours {
|
||||
for (n, c) in v {
|
||||
println!("0x{o:<10x} {n} ({c} verts)");
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
let mut drawn: BTreeMap<usize, u32> = BTreeMap::new();
|
||||
for (_, draws) in &logs {
|
||||
for d in draws {
|
||||
let off = d.vbase.wrapping_sub(base) as usize;
|
||||
if off < bytes.len() && d.vcount >= 20 {
|
||||
drawn.insert(off, d.vcount);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Which resources have the drawn vertex count, wherever we put them?
|
||||
// Right size + wrong place is a different bug from never finding it.
|
||||
let mut by_count: BTreeMap<usize, Vec<String>> = BTreeMap::new();
|
||||
for m in &models {
|
||||
let n: usize = m.meshes.iter().map(|s| s.positions.len()).sum();
|
||||
by_count.entry(n).or_default().push(m.name.clone());
|
||||
}
|
||||
// Is a capture-proven offset even a candidate the scan considers?
|
||||
// Absent ⇒ the run scan misses it; present ⇒ selection picked another.
|
||||
let starts: BTreeSet<usize> =
|
||||
sylpheed_formats::mesh::debug_vertex_run_starts(&bytes, 24).into_iter().collect();
|
||||
eprintln!("{} stride-24 candidate starts in this container", starts.len());
|
||||
println!("{:<12} {:>7} {:>9} {:<44} our resources with that vcount", "file offset", "vcount", "candidate", "claimed by our decode");
|
||||
for (off, vcount) in &drawn {
|
||||
let who = ours
|
||||
.get(off)
|
||||
.map(|v| {
|
||||
v.iter().map(|(n, c)| format!("{n}({c})")).collect::<Vec<_>>().join(", ")
|
||||
})
|
||||
.unwrap_or_else(|| "— NOBODY".into());
|
||||
let same = by_count
|
||||
.get(&(*vcount as usize))
|
||||
.map(|v| v.join(", "))
|
||||
.unwrap_or_else(|| "— none".into());
|
||||
// Nearest resource we anchored at or before this offset — the
|
||||
// likely owner of a buffer nobody claims.
|
||||
let near = ours
|
||||
.range(..=*off)
|
||||
.next_back()
|
||||
.map(|(o, v)| format!("{} @ -0x{:x}", v[0].0, off - o))
|
||||
.unwrap_or_default();
|
||||
let cand = if starts.contains(off) { "yes" } else { "NO" };
|
||||
println!("0x{off:<10x} {vcount:>7} {cand:>9} {who:<44} {same:<34} {near}");
|
||||
}
|
||||
return;
|
||||
}
|
||||
let mut groups: BTreeMap<Vec<(i64, i64, i64)>, Vec<String>> = BTreeMap::new();
|
||||
for m in &models {
|
||||
let pos: Vec<(i64, i64, i64)> =
|
||||
m.meshes.iter().flat_map(|s| s.positions.iter().copied()).map(key).collect();
|
||||
if pos.is_empty() {
|
||||
continue;
|
||||
}
|
||||
groups.entry(pos).or_default().push(m.name.clone());
|
||||
}
|
||||
let shared: Vec<_> = groups.iter().filter(|(_, n)| n.len() > 1).collect();
|
||||
eprintln!(
|
||||
"{} models, {} distinct geometries, {} shared by >1 resource",
|
||||
models.len(),
|
||||
groups.len(),
|
||||
shared.len()
|
||||
);
|
||||
|
||||
for (pos, names) in shared {
|
||||
let vcount = pos.len() as u32;
|
||||
// A draw belongs to this geometry if every dumped position is one of
|
||||
// the decoded ones (the log dumps at most the first 64).
|
||||
let want: BTreeSet<(i64, i64, i64)> = pos.iter().copied().collect();
|
||||
println!("\n{} ({vcount} verts, {} resources)", names.join(" ≡ "), names.len());
|
||||
for (log, draws) in &logs {
|
||||
let hits: Vec<&CapturedDraw> = draws.iter().filter(|d| d.vcount == vcount).collect();
|
||||
let all: BTreeSet<u32> = hits.iter().map(|d| d.vbase).collect();
|
||||
let matching: Vec<&&CapturedDraw> = hits
|
||||
.iter()
|
||||
.filter(|d| !d.pos.is_empty() && d.pos.iter().all(|p| want.contains(&key(*p))))
|
||||
.collect();
|
||||
let ok: BTreeSet<u32> = matching.iter().map(|d| d.vbase).collect();
|
||||
// A buffer we do NOT match may still be the mirrored twin: same
|
||||
// geometry with x negated. That is the case our assembler papers
|
||||
// over with `apply_twin_mirrors`.
|
||||
let mirrored: BTreeSet<u32> = hits
|
||||
.iter()
|
||||
.filter(|d| !ok.contains(&d.vbase))
|
||||
.filter(|d| {
|
||||
!d.pos.is_empty()
|
||||
&& d.pos.iter().all(|p| want.contains(&key([-p[0], p[1], p[2]])))
|
||||
})
|
||||
.map(|d| d.vbase)
|
||||
.collect();
|
||||
println!(
|
||||
" {log:32} draws={:<5} vbases@vcount={:<3} ours={} mirrored={} other={}",
|
||||
hits.len(),
|
||||
all.len(),
|
||||
ok.len(),
|
||||
mirrored.len(),
|
||||
all.len() - ok.len() - mirrored.len()
|
||||
);
|
||||
// Where does each captured buffer live in the container? One
|
||||
// representative draw per vbase is enough.
|
||||
let mut done: BTreeSet<u32> = BTreeSet::new();
|
||||
for d in &hits {
|
||||
if d.pos.len() < 8 || !done.insert(d.vbase) {
|
||||
continue;
|
||||
}
|
||||
let kind = if ok.contains(&d.vbase) {
|
||||
"ours"
|
||||
} else if mirrored.contains(&d.vbase) {
|
||||
"mirror"
|
||||
} else {
|
||||
"other"
|
||||
};
|
||||
let at = locate_run(&bytes, &d.pos);
|
||||
let shown: Vec<String> =
|
||||
at.iter().take(4).map(|(o, s)| format!("0x{o:x}/stride{s}")).collect();
|
||||
println!(
|
||||
" vbase=0x{:08X} [{kind:6}] in container at: {}",
|
||||
d.vbase,
|
||||
if shown.is_empty() { "NOT FOUND".into() } else { shown.join(" ") }
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
74
crates/sylpheed-formats/examples/slab_screen.rs
Normal file
@@ -0,0 +1,74 @@
|
||||
//! Screen every ship family for a part that is wildly out of scale with its
|
||||
//! siblings — the "slab" signature of a mis-anchored block.
|
||||
//!
|
||||
//! Rendering `e106` found such a part (`bdy_03`, 600×1600×998 beside parts of
|
||||
//! ~250) that coverage, cross-container consistency, the capture oracle and the
|
||||
//! twin invariant were all blind to. Eyeballing does not scale to 166 containers;
|
||||
//! this does the same comparison numerically.
|
||||
//!
|
||||
//! Usage: slab_screen <resource3d_dir> [factor]
|
||||
use sylpheed_formats::mesh::Xbg7Model;
|
||||
use sylpheed_formats::ship::{is_base_part, ship_id_of};
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
fn main() {
|
||||
let dir = std::env::args().nth(1).expect("resource3d dir");
|
||||
let factor: f32 = std::env::args().nth(2).and_then(|s| s.parse().ok()).unwrap_or(4.0);
|
||||
let mut files: Vec<_> = std::fs::read_dir(&dir)
|
||||
.unwrap()
|
||||
.flatten()
|
||||
.map(|e| e.path())
|
||||
.filter(|p| p.extension().and_then(|s| s.to_str()) == Some("xpr"))
|
||||
.collect();
|
||||
files.sort();
|
||||
|
||||
let mut flagged = 0usize;
|
||||
for f in &files {
|
||||
let Ok(bytes) = std::fs::read(f) else { continue };
|
||||
let mut by_ship: BTreeMap<String, Vec<(String, f32)>> = BTreeMap::new();
|
||||
for m in Xbg7Model::anchor_models_cancellable(&bytes, 0.0, &|| false) {
|
||||
if !is_base_part(&m.name) {
|
||||
continue;
|
||||
}
|
||||
let Some(id) = ship_id_of(&m.name) else { continue };
|
||||
let (mut lo, mut hi) = ([f32::MAX; 3], [f32::MIN; 3]);
|
||||
for s in &m.meshes {
|
||||
for q in &s.positions {
|
||||
for k in 0..3 {
|
||||
lo[k] = lo[k].min(q[k]);
|
||||
hi[k] = hi[k].max(q[k]);
|
||||
}
|
||||
}
|
||||
}
|
||||
if lo[0] == f32::MAX {
|
||||
continue;
|
||||
}
|
||||
// Compare the part's SMALLEST axis span, not its diagonal. A long
|
||||
// thin part (an antenna, a 100 000-unit tether on `f002`) is
|
||||
// legitimately huge in one axis and would swamp a diagonal test; a
|
||||
// mis-anchored block is bulky in all three, which is what the e106
|
||||
// slab looked like (600×1600×998 beside siblings of ~250).
|
||||
let thin = (hi[0] - lo[0]).min(hi[1] - lo[1]).min(hi[2] - lo[2]);
|
||||
by_ship.entry(id.to_string()).or_default().push((m.name.clone(), thin));
|
||||
}
|
||||
for (id, parts) in &by_ship {
|
||||
if parts.len() < 3 {
|
||||
continue; // no meaningful median
|
||||
}
|
||||
let mut d: Vec<f32> = parts.iter().map(|(_, x)| *x).collect();
|
||||
d.sort_by(|a, b| a.partial_cmp(b).unwrap());
|
||||
let median = d[d.len() / 2];
|
||||
for (name, diag) in parts {
|
||||
if *diag > median * factor {
|
||||
flagged += 1;
|
||||
println!(
|
||||
"{:<22} {name:<22} min-axis {diag:>8.0} vs ship median {median:>8.0} ({:.1}×)",
|
||||
f.file_name().unwrap().to_string_lossy(),
|
||||
diag / median
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
println!("{flagged} parts flagged at {factor}× the ship median");
|
||||
}
|
||||
13
crates/sylpheed-formats/examples/starts.rs
Normal file
@@ -0,0 +1,13 @@
|
||||
use sylpheed_formats::mesh::{debug_resource_params, debug_vertex_run_starts};
|
||||
fn main() {
|
||||
let a: Vec<String> = std::env::args().collect();
|
||||
let bytes = std::fs::read(&a[1]).unwrap();
|
||||
let (markers, stride) = debug_resource_params(&bytes, &a[2]).expect("resource");
|
||||
println!("{}: stride={stride} markers={markers:?}", a[2]);
|
||||
let starts = debug_vertex_run_starts(&bytes, stride);
|
||||
println!("{} candidate starts at stride {stride}", starts.len());
|
||||
for off in &a[3..] {
|
||||
let o = usize::from_str_radix(off.trim_start_matches("0x"), 16).unwrap();
|
||||
println!(" 0x{o:x} in candidate list: {}", starts.binary_search(&o).is_ok());
|
||||
}
|
||||
}
|
||||
7
crates/sylpheed-formats/examples/starts_probe.rs
Normal file
@@ -0,0 +1,7 @@
|
||||
fn main(){let a:Vec<String>=std::env::args().collect();let b=std::fs::read(&a[1]).unwrap();
|
||||
let stride:usize=a[2].parse().unwrap(); let want:usize=a[3].parse().unwrap();
|
||||
let s=sylpheed_formats::mesh::debug_vertex_run_starts(&b,stride);
|
||||
println!("{} candidate starts at stride {}", s.len(), stride);
|
||||
println!("contains {:#x}: {}", want, s.contains(&want));
|
||||
let near:Vec<String>=s.iter().filter(|&&o| o.abs_diff(want)<0x200).map(|o|format!("{o:#x}")).collect();
|
||||
println!("nearby: {}", near.join(" "));}
|
||||
34
crates/sylpheed-formats/examples/submesh_dump.rs
Normal file
@@ -0,0 +1,34 @@
|
||||
//! Per-sub-mesh vertex/index/coverage dump for one resource.
|
||||
//! Usage: submesh_dump <container.xpr> <resource>...
|
||||
use sylpheed_formats::mesh::{debug_resource_params, Xbg7Model};
|
||||
use std::collections::HashSet;
|
||||
fn main() {
|
||||
let a: Vec<String> = std::env::args().collect();
|
||||
let bytes = std::fs::read(&a[1]).expect("container");
|
||||
let want: HashSet<String> = a[2..].iter().cloned().collect();
|
||||
for m in Xbg7Model::models_named(&bytes, &want, &|| false) {
|
||||
let markers = debug_resource_params(&bytes, &m.name).map(|(mk, _)| mk).unwrap_or_default();
|
||||
println!("{} — {} sub-meshes decoded, {} markers declared", m.name, m.meshes.len(), markers.len());
|
||||
for (i, s) in m.meshes.iter().enumerate() {
|
||||
let max_idx = s.indices.iter().max().copied().unwrap_or(0) as usize;
|
||||
let (mut lo, mut hi) = ([f32::MAX; 3], [f32::MIN; 3]);
|
||||
for q in &s.positions {
|
||||
for k in 0..3 {
|
||||
lo[k] = lo[k].min(q[k]);
|
||||
hi[k] = hi[k].max(q[k]);
|
||||
}
|
||||
}
|
||||
println!(
|
||||
" #{i:<2} at 0x{:<9x} verts {:<6} idx {:<6} max_idx {:<6} slack {} span [{:.0} {:.0} {:.0}]",
|
||||
s.vbuf_offset.unwrap_or(0),
|
||||
s.positions.len(),
|
||||
s.indices.len(),
|
||||
max_idx,
|
||||
s.positions.len() as i64 - 1 - max_idx as i64,
|
||||
hi[0] - lo[0],
|
||||
hi[1] - lo[1],
|
||||
hi[2] - lo[2]
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
17
crates/sylpheed-formats/examples/try_anchor.rs
Normal file
@@ -0,0 +1,17 @@
|
||||
//! Does the capture-proven offset validate for the resource that should own it?
|
||||
use sylpheed_formats::mesh::debug_try_anchor;
|
||||
fn main() {
|
||||
let a: Vec<String> = std::env::args().collect();
|
||||
let bytes = std::fs::read(&a[1]).unwrap();
|
||||
// The production scan tries pads 0..=3; pass a bigger one to ask whether the
|
||||
// block would validate at all with a wider index/vertex gap.
|
||||
let max_pad: usize = std::env::var("MAX_PAD").ok().and_then(|v| v.parse().ok()).unwrap_or(3);
|
||||
for pair in a[2..].iter() {
|
||||
let (name, off) = pair.split_once('@').unwrap();
|
||||
let off = usize::from_str_radix(off.trim_start_matches("0x"), 16).unwrap();
|
||||
match debug_try_anchor(&bytes, name, off, max_pad) {
|
||||
Some((v, i, pad)) => println!("{name:22} @ 0x{off:x} ACCEPTED v={v} idx={i} pad={pad}"),
|
||||
None => println!("{name:22} @ 0x{off:x} rejected"),
|
||||
}
|
||||
}
|
||||
}
|
||||
119
crates/sylpheed-formats/examples/twin_mirror_audit.rs
Normal file
@@ -0,0 +1,119 @@
|
||||
//! Do port/starboard twins decode to mirror images of each other?
|
||||
//!
|
||||
//! A runtime capture proved the container stores both halves of the `e106` hull
|
||||
//! as separate, X-reflected buffers. That gives a **capture-free invariant**:
|
||||
//! a `…_01`/`…_02` pair of equal vertex count should decode to geometry that is
|
||||
//! an exact X-mirror — never to identical geometry (that is the collapse the
|
||||
//! distinct-assignment fix targets), and never to something unrelated (that is a
|
||||
//! mis-anchor no count-based metric can see).
|
||||
//!
|
||||
//! Usage: twin_mirror_audit <resource3d_dir>
|
||||
use sylpheed_formats::mesh::Xbg7Model;
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
fn key(p: [f32; 3]) -> (i64, i64, i64) {
|
||||
(
|
||||
(p[0] * 1e3).round() as i64,
|
||||
(p[1] * 1e3).round() as i64,
|
||||
(p[2] * 1e3).round() as i64,
|
||||
)
|
||||
}
|
||||
|
||||
/// Elementwise equality with a tolerance. Truncating keys is too strict for a
|
||||
/// mirrored pair: the halves are authored, not bit-negated, so they differ in
|
||||
/// the last digits and an exact key test reports them as unrelated.
|
||||
fn near(p: [f32; 3], q: [f32; 3]) -> bool {
|
||||
(0..3).all(|c| (p[c] - q[c]).abs() <= 1e-3 * (1.0 + q[c].abs()))
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let dir = std::env::args().nth(1).expect("resource3d dir");
|
||||
let mut files: Vec<_> = std::fs::read_dir(&dir)
|
||||
.unwrap()
|
||||
.flatten()
|
||||
.map(|e| e.path())
|
||||
.filter(|p| p.extension().and_then(|s| s.to_str()) == Some("xpr"))
|
||||
.collect();
|
||||
files.sort();
|
||||
|
||||
let (mut same, mut mirrored, mut unrelated, mut related) = (0usize, 0usize, 0usize, 0usize);
|
||||
let mut examples: Vec<String> = Vec::new();
|
||||
for f in &files {
|
||||
let Ok(bytes) = std::fs::read(f) else { continue };
|
||||
let models = Xbg7Model::anchor_models_cancellable(&bytes, 0.0, &|| false);
|
||||
let by_name: BTreeMap<&str, &Xbg7Model> =
|
||||
models.iter().map(|m| (m.name.as_str(), m)).collect();
|
||||
for m in &models {
|
||||
let Some(stem) = m.name.strip_suffix("_01") else { continue };
|
||||
let Some(t) = by_name.get(format!("{stem}_02").as_str()) else { continue };
|
||||
let a: Vec<[f32; 3]> = m.meshes.iter().flat_map(|s| s.positions.clone()).collect();
|
||||
let b: Vec<[f32; 3]> = t.meshes.iter().flat_map(|s| s.positions.clone()).collect();
|
||||
if a.len() != b.len() || a.is_empty() {
|
||||
continue;
|
||||
}
|
||||
let ident = a.iter().zip(&b).all(|(p, q)| near(*p, *q));
|
||||
let mirr = a.iter().zip(&b).all(|(p, q)| near([-p[0], p[1], p[2]], *q));
|
||||
// A pair that is neither may still be RELATED: mirrored on another
|
||||
// axis, or the same point cloud in a different vertex order. Only a
|
||||
// pair that is none of these is evidence of a mis-anchor.
|
||||
let mirr_y = a.iter().zip(&b).all(|(p, q)| near([p[0], -p[1], p[2]], *q));
|
||||
let mirr_z = a.iter().zip(&b).all(|(p, q)| near([p[0], p[1], -p[2]], *q));
|
||||
let set = |v: &Vec<[f32; 3]>| {
|
||||
let mut s: Vec<_> = v.iter().map(|p| key(*p)).collect();
|
||||
s.sort_unstable();
|
||||
s
|
||||
};
|
||||
let same_cloud = set(&a) == set(&b);
|
||||
// …and the same point cloud after mirroring, for a pair whose
|
||||
// halves are authored in different vertex order.
|
||||
let mirrored_cloud = {
|
||||
let am: Vec<[f32; 3]> = a.iter().map(|p| [-p[0], p[1], p[2]]).collect();
|
||||
set(&am) == set(&b)
|
||||
};
|
||||
if !ident && !mirr && (mirr_y || mirr_z || same_cloud || mirrored_cloud) {
|
||||
related += 1;
|
||||
continue;
|
||||
}
|
||||
// Identical GEOMETRY is only a collapse if it also comes from the
|
||||
// same buffer: a container may store a part twice unmirrored, and
|
||||
// then two twins on two copies decode identically and correctly.
|
||||
let shared_buffer = m.meshes[0].vbuf_offset.is_some()
|
||||
&& m.meshes[0].vbuf_offset == t.meshes[0].vbuf_offset;
|
||||
if ident && !shared_buffer {
|
||||
related += 1;
|
||||
continue;
|
||||
}
|
||||
if ident {
|
||||
same += 1;
|
||||
if examples.len() < 6 {
|
||||
examples.push(format!(
|
||||
"identical: {} / {}_02 in {}",
|
||||
m.name,
|
||||
stem,
|
||||
f.file_name().unwrap().to_string_lossy()
|
||||
));
|
||||
}
|
||||
} else if mirr {
|
||||
mirrored += 1;
|
||||
} else {
|
||||
unrelated += 1;
|
||||
if examples.len() < 6 {
|
||||
examples.push(format!(
|
||||
"unrelated: {} / {}_02 in {}",
|
||||
m.name,
|
||||
stem,
|
||||
f.file_name().unwrap().to_string_lossy()
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
println!("twin pairs of equal vertex count: {}", same + mirrored + unrelated + related);
|
||||
println!(" exact X-mirror (expected) : {mirrored}");
|
||||
println!(" IDENTICAL, one buffer (collapse) : {same}");
|
||||
println!(" related other way (Y/Z mirror, reordered): {related}");
|
||||
println!(" unrelated (mis-anchor?) : {unrelated}");
|
||||
for e in examples {
|
||||
println!(" e.g. {e}");
|
||||
}
|
||||
}
|
||||
56
crates/sylpheed-formats/examples/undecoded.rs
Normal file
@@ -0,0 +1,56 @@
|
||||
//! Which XBG7 resources never decode, and how big is that population?
|
||||
//!
|
||||
//! Coverage has been reported as "resources decoded" without a denominator. This
|
||||
//! prints both, per container and in total, and names the misses so the gate
|
||||
//! attribution (`why_rejected`) has a work list.
|
||||
use sylpheed_formats::mesh::{debug_resource_params, xbg7_resource_names, Xbg7Model};
|
||||
use std::collections::HashSet;
|
||||
|
||||
fn main() {
|
||||
let dir = std::env::args().nth(1).expect("resource3d dir");
|
||||
let show = std::env::args().nth(2).is_some();
|
||||
let mut files: Vec<_> = std::fs::read_dir(&dir)
|
||||
.unwrap()
|
||||
.flatten()
|
||||
.map(|e| e.path())
|
||||
.filter(|p| p.extension().and_then(|s| s.to_str()) == Some("xpr"))
|
||||
.collect();
|
||||
files.sort();
|
||||
|
||||
let (mut total, mut decoded, mut no_decl) = (0usize, 0usize, 0usize);
|
||||
let mut misses: Vec<String> = Vec::new();
|
||||
for f in &files {
|
||||
let Ok(bytes) = std::fs::read(f) else { continue };
|
||||
let names = xbg7_resource_names(&bytes);
|
||||
if names.is_empty() {
|
||||
continue;
|
||||
}
|
||||
let got: HashSet<String> = Xbg7Model::anchor_models_cancellable(&bytes, 0.0, &|| false)
|
||||
.into_iter()
|
||||
.map(|m| m.name)
|
||||
.collect();
|
||||
for n in &names {
|
||||
total += 1;
|
||||
if got.contains(n) {
|
||||
decoded += 1;
|
||||
} else if debug_resource_params(&bytes, n).is_none() {
|
||||
// No vertex declaration / no index markers: the anchor scan
|
||||
// never even considers these, so they are a different question
|
||||
// from "searched and not found".
|
||||
no_decl += 1;
|
||||
} else {
|
||||
misses.push(format!("{}|{n}", f.file_name().unwrap().to_string_lossy()));
|
||||
}
|
||||
}
|
||||
}
|
||||
println!(
|
||||
"XBG7 resources: {total} total, {decoded} decoded ({:.1}%), {no_decl} without a usable descriptor, {} searched-and-missed",
|
||||
100.0 * decoded as f64 / total as f64,
|
||||
misses.len()
|
||||
);
|
||||
if show {
|
||||
for m in &misses {
|
||||
println!("{m}");
|
||||
}
|
||||
}
|
||||
}
|
||||
31
crates/sylpheed-formats/examples/unit_fields.rs
Normal file
@@ -0,0 +1,31 @@
|
||||
//! Every key/value a named unit record actually sets on disc.
|
||||
//!
|
||||
//! The live definition object in guest RAM has no field names; to find where a
|
||||
//! field like `HQRatio` sits inside it, anchor on a unit whose disc record DOES
|
||||
//! set that field and look for the value. This prints those anchors.
|
||||
//! Run: unit_fields <disc-root> <substring of ID>...
|
||||
use sylpheed_formats::idxd::IdxdObject;
|
||||
use sylpheed_formats::pak::PakArchive;
|
||||
|
||||
fn main() {
|
||||
let disc = std::env::args().nth(1).expect("disc root");
|
||||
let want: Vec<String> = std::env::args().skip(2).collect();
|
||||
let pak = PakArchive::open(format!("{disc}/dat/GP_MAIN_GAME_E.pak")).expect("pak");
|
||||
for entry in pak.entries() {
|
||||
let Ok(bytes) = pak.read(entry) else { continue };
|
||||
let Ok(obj) = IdxdObject::parse(&bytes) else { continue };
|
||||
let Some(id) = obj.get_raw("ID") else { continue };
|
||||
if !want.iter().any(|w| id.contains(w.as_str())) {
|
||||
continue;
|
||||
}
|
||||
println!("=== {id} (schema {:08x})", obj.schema_hash);
|
||||
// The pool interleaves VALUE before KEY (see the module docs), so the
|
||||
// pairs read (t[i] = value, t[i+1] = key).
|
||||
let t = obj.tokens();
|
||||
let mut i = 0;
|
||||
while i + 1 < t.len() {
|
||||
println!(" {:<30} {}", t[i + 1], t[i]);
|
||||
i += 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
33
crates/sylpheed-formats/examples/unit_signatures.rs
Normal file
@@ -0,0 +1,33 @@
|
||||
//! (HP, Size_X, Size_Y, Size_Z) for every craft unit and vessel on the disc.
|
||||
//!
|
||||
//! A live definition object in guest RAM carries HP at `+0x54` and the sizes at
|
||||
//! `+0x30/34/38` but no name in its first words, so identity has to come from the
|
||||
//! values themselves: this is the lookup table for that match.
|
||||
//! Run: unit_signatures <disc-root>
|
||||
use sylpheed_formats::game_data::{load_units, load_vessels};
|
||||
use sylpheed_formats::PakArchive;
|
||||
fn main() {
|
||||
let disc = std::env::args().nth(1).expect("disc root");
|
||||
let pak = PakArchive::open(format!("{disc}/dat/GP_MAIN_GAME_E.pak")).expect("pak");
|
||||
println!("{:<34} {:>9} {:>8} {:>8} {:>8} kind", "id", "hp", "size_x", "size_y", "size_z");
|
||||
for u in load_units(&pak) {
|
||||
println!(
|
||||
"{:<34} {:>9} {:>8} {:>8} {:>8} unit",
|
||||
u.id.unwrap_or_default(),
|
||||
u.hp.map(|v| v.to_string()).unwrap_or("—".into()),
|
||||
u.size_x.map(|v| v.to_string()).unwrap_or("—".into()),
|
||||
u.size_y.map(|v| v.to_string()).unwrap_or("—".into()),
|
||||
u.size_z.map(|v| v.to_string()).unwrap_or("—".into()),
|
||||
);
|
||||
}
|
||||
for v in load_vessels(&pak) {
|
||||
println!(
|
||||
"{:<34} {:>9} {:>8} {:>8} {:>8} vessel",
|
||||
v.id.unwrap_or_default(),
|
||||
v.hp.map(|x| x.to_string()).unwrap_or("—".into()),
|
||||
v.size_x.map(|x| x.to_string()).unwrap_or("—".into()),
|
||||
v.size_y.map(|x| x.to_string()).unwrap_or("—".into()),
|
||||
v.size_z.map(|x| x.to_string()).unwrap_or("—".into()),
|
||||
);
|
||||
}
|
||||
}
|
||||
22
crates/sylpheed-formats/examples/unit_values.rs
Normal file
@@ -0,0 +1,22 @@
|
||||
use sylpheed_formats::idxd::IdxdObject;
|
||||
use sylpheed_formats::pak::PakArchive;
|
||||
fn main() {
|
||||
let disc = std::env::args().nth(1).unwrap();
|
||||
let want = std::env::args().nth(2).unwrap();
|
||||
let pak = PakArchive::open(format!("{disc}/dat/GP_MAIN_GAME_E.pak")).unwrap();
|
||||
for e in pak.entries() {
|
||||
let Ok(b) = pak.read(e) else { continue };
|
||||
let Ok(o) = IdxdObject::parse(&b) else { continue };
|
||||
let Some(id) = o.get_raw("ID") else { continue };
|
||||
if !id.contains(&want) { continue; }
|
||||
println!("=== {id}");
|
||||
let mut seen = std::collections::BTreeSet::new();
|
||||
for k in o.tokens() {
|
||||
if let Some(v) = o.get_f32(k) {
|
||||
if v != 0.0 && seen.insert(k.clone()) {
|
||||
println!(" {:<28} {:<12} 0x{:08x}", k, v, v.to_bits());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
70
crates/sylpheed-formats/examples/verify_fieldmap.rs
Normal file
@@ -0,0 +1,70 @@
|
||||
//! Check the code-derived offset→field map against the live dump, then read out
|
||||
//! the runtime value of every field the disc record leaves defaulted.
|
||||
//! Run: verify_fieldmap <disc-root> <live-dump> <ID=0xVA>...
|
||||
use std::collections::BTreeMap;
|
||||
use sylpheed_formats::idxd::IdxdObject;
|
||||
use sylpheed_formats::pak::PakArchive;
|
||||
|
||||
// Offsets read out of sub_82341A20's own key strings (stfs stores only).
|
||||
// Offsets and names recovered from `sub_82341A20` itself: each key is built as
|
||||
// `addi r4, r30, -N` (r30 = 0x82088f94, so the name is a string in the image),
|
||||
// and the value lands in the first store into the object after the accessor
|
||||
// call. 125 float-typed fields.
|
||||
const MAP: &[(usize, &str)] = &[(48, "Size_X"), (52, "Size_Y"), (56, "Size_Z"), (64, "Color_R"), (68, "Color_G"), (72, "Color_B"), (80, "Size_Radius"), (84, "HP"), (88, "HQRatio"), (92, "ShieldRatio"), (96, "ThrusterRatio"), (116, "ResistanceToOptics"), (120, "ResistanceToShell"), (124, "ResistanceToExplosion"), (128, "ResistanceToPlayer"), (132, "ResistanceParalyze"), (156, "MinimumVelocity"), (160, "MaximumVelocity"), (164, "CruisingVelocity"), (168, "Acceleration"), (172, "Deceleration"), (176, "AV_PitchPlus_Max"), (180, "AV_PitchPlus_Min"), (184, "AA_PitchPlus_Max"), (188, "AA_PitchPlus_Min"), (192, "AV_PitchMinus_Max"), (196, "AV_PitchMinus_Min"), (200, "AA_PitchMinus_Max"), (204, "AA_PitchMinus_Min"), (208, "AV_Yaw_Max"), (212, "AV_Yaw_Min"), (216, "AA_Yaw_Max"), (220, "AA_Yaw_Min"), (224, "AV_Roll_Max"), (228, "AV_Roll_Min"), (232, "AA_Roll_Max"), (236, "AA_Roll_Min"), (248, "SideThrustVelocity_Max"), (252, "SideThrustAcceleration"), (256, "MaximumBank_Normal"), (260, "YawDragFactor"), (264, "PitchDragFactor"), (268, "RollDragFactor"), (272, "DragFactorThreshold"), (276, "ArterBurner_Vc"), (280, "ReverseThrust_Vc"), (284, "ArterBurner_Acc"), (288, "ReverseThrust_Acc"), (292, "AccPitchFactor"), (296, "DecPitchFactor"), (300, "AV_AxisMode_Max"), (304, "AV_AxisMode_Min"), (308, "AA_AxisMode_Max"), (312, "AA_AxisMode_Min"), (316, "PowerCutConsumeShield"), (320, "PowerCutDeceleration"), (324, "AB_ConsumeShield_Begin"), (328, "AB_ConsumeShield"), (332, "AB_AV_PitchPlus"), (336, "AB_AA_PitchPlus"), (340, "AB_AV_PitchMinus"), (344, "AB_AA_PitchMinus"), (348, "AB_AV_Yaw"), (352, "AB_AA_Yaw"), (356, "AB_AV_Roll"), (360, "AB_AA_Roll"), (368, "SideRoll_Time"), (372, "SideRoll_Length"), (380, "BarrelRoll_CountMinimum"), (384, "BarrelRoll_CountMaximum"), (388, "BarrelRoll_Time"), (392, "BarrelRoll_Radius"), (400, "TurnAttack_CutoffRatio"), (404, "TurnAttack_DoubleRatio"), (408, "CutoffTimeMin"), (412, "CutoffTimeMax"), (416, "TurnAttack_DoubleTimeMin"), (420, "TurnAttack_DoubleTimeMax"), (428, "Turn_AngularVelocity"), (432, "TurnAway_Time_Minimum"), (436, "TurnAway_Time_Maximum"), (444, "BoostAway_Time_Minimum"), (448, "BoostAway_Time_Maximum"), (456, "HoldPosition_LengthMin"), (460, "HoldPosition_LengthMax"), (464, "HoldPosition_MinimumTime"), (468, "HoldPosition_MaximumTime"), (472, "HoldPosition_SideRatio"), (476, "HoldPosition_BackRatio"), (480, "HoldPosition_CutoffRatio"), (484, "HoldPosition_CancelTime"), (492, "Slalom_CutoffRatio"), (496, "Slalom_TurnCount_Min"), (500, "Slalom_TurnCount_Max"), (508, "Through_CutoffRatio"), (512, "Through_AngleMinimum"), (516, "Through_AngleMaximum"), (520, "Through_Time1Max"), (524, "Through_Time1Min"), (528, "Through_Time2Max"), (532, "Through_Time2Min"), (536, "Through_LengthMinimum"), (540, "Through_LengthMaximum"), (548, "SolidCutoff_Ratio"), (552, "SolidCutoff_LengthMin"), (556, "SolidCutoff_LengthMax"), (560, "HomingResistAdjustment"), (564, "UsingChaffRatio"), (568, "MaxValue"), (572, "ChargeDelay"), (576, "ChargeDelay_Break"), (580, "ChargeSpeed"), (584, "Delay"), (588, "DelayAdjustment"), (624, "DestroyMotionTime"), (628, "DryMass"), (632, "GrossMass"), (664, "LowerHPThresholdRatio"), (668, "SELength"), (672, "RadarRange"), (676, "FCSRange"), (680, "FiringRange"), (692, "AttackVesselPoint"), (696, "AttackCraftPoint"), (700, "DefencePoint")];
|
||||
|
||||
fn main() {
|
||||
let mut a = std::env::args().skip(1);
|
||||
let disc = a.next().unwrap();
|
||||
let dump = a.next().unwrap();
|
||||
let pairs: Vec<(String, String)> =
|
||||
a.filter_map(|s| s.split_once('=').map(|(i, v)| (i.into(), v.into()))).collect();
|
||||
|
||||
let mut live: BTreeMap<String, BTreeMap<usize, f32>> = BTreeMap::new();
|
||||
let mut cur = String::new();
|
||||
for line in std::fs::read_to_string(&dump).unwrap().lines() {
|
||||
if let Some(r) = line.strip_prefix("=== ") { cur = r.trim().into(); continue; }
|
||||
let f: Vec<&str> = line.split_whitespace().collect();
|
||||
// dump columns: addr +off hex u32 f32 -- the float is f[4], not f[3]
|
||||
if f.len() >= 5 && f[1].starts_with('+') {
|
||||
if let (Ok(o), Ok(v)) = (usize::from_str_radix(&f[1][1..], 16), f[4].parse::<f32>()) {
|
||||
live.entry(cur.clone()).or_default().insert(o, v);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let pak = PakArchive::open(format!("{disc}/dat/GP_MAIN_GAME_E.pak")).unwrap();
|
||||
let (mut agree, mut disagree) = (0, 0);
|
||||
let mut defaults: BTreeMap<String, Vec<(String, f32)>> = BTreeMap::new();
|
||||
for e in pak.entries() {
|
||||
let Ok(b) = pak.read(e) else { continue };
|
||||
let Ok(o) = IdxdObject::parse(&b) else { continue };
|
||||
let Some(id) = o.get_raw("ID") else { continue };
|
||||
let Some((_, va)) = pairs.iter().find(|(i, _)| i == id) else { continue };
|
||||
let Some(w) = live.get(va) else { continue };
|
||||
for (off, key) in MAP {
|
||||
let Some(got) = w.get(off) else { continue };
|
||||
match o.get_f32(key) {
|
||||
Some(want) => {
|
||||
// Angle fields are stored in RADIANS at runtime and degrees on
|
||||
// disc, so a match is either the raw value or its conversion.
|
||||
let rad = want * std::f32::consts::PI / 180.0;
|
||||
let ok = (want - got).abs() <= want.abs() * 1e-4
|
||||
|| (rad - got).abs() <= rad.abs() * 1e-4;
|
||||
if ok { agree += 1 } else {
|
||||
disagree += 1;
|
||||
println!(" MISMATCH {id} {key}: disc {want}, live +{off} = {got}");
|
||||
}
|
||||
}
|
||||
None => defaults.entry((*key).into()).or_default().push((id.into(), *got)),
|
||||
}
|
||||
}
|
||||
}
|
||||
println!("\nmap check: {agree} fields agree with the disc, {disagree} disagree\n");
|
||||
println!("runtime value where the disc record DEFAULTS the field:");
|
||||
for (key, hits) in &defaults {
|
||||
let vals: Vec<String> = hits.iter().map(|(_, v)| format!("{v}")).collect();
|
||||
let uniq: std::collections::BTreeSet<&String> = vals.iter().collect();
|
||||
println!(" {:<22} {:<28} ({} units)", key,
|
||||
uniq.iter().map(|s| s.as_str()).collect::<Vec<_>>().join(", "), hits.len());
|
||||
}
|
||||
}
|
||||
19
crates/sylpheed-formats/examples/why_missed.rs
Normal file
@@ -0,0 +1,19 @@
|
||||
//! Why does a named resource never decode? Reports the furthest gate its best
|
||||
//! candidate reached. Usage: why_missed <container.xpr> [name-substring]
|
||||
use sylpheed_formats::mesh::{debug_best_rejection, xbg7_resource_names, Xbg7Model};
|
||||
use std::collections::HashSet;
|
||||
fn main() {
|
||||
let a: Vec<String> = std::env::args().collect();
|
||||
let bytes = std::fs::read(&a[1]).unwrap();
|
||||
let filter = a.get(2).cloned().unwrap_or_default();
|
||||
let decoded: HashSet<String> = Xbg7Model::stage_models(&bytes).into_iter().map(|m| m.name).collect();
|
||||
for n in xbg7_resource_names(&bytes) {
|
||||
if decoded.contains(&n) || (!filter.is_empty() && !n.contains(&filter)) {
|
||||
continue;
|
||||
}
|
||||
match debug_best_rejection(&bytes, &n) {
|
||||
Some((rank, why)) => println!("{n:<28} rank {rank} {why}"),
|
||||
None => println!("{n:<28} (no candidate reached any gate / skipped before anchoring)"),
|
||||
}
|
||||
}
|
||||
}
|
||||
14
crates/sylpheed-formats/examples/why_rejected.rs
Normal file
@@ -0,0 +1,14 @@
|
||||
//! Why does the decoder refuse a grouped-pool resource at a given pool start?
|
||||
//! Usage: why_rejected <container.xpr> <resource>@<vb0-hex>...
|
||||
use sylpheed_formats::mesh::debug_grouped_report;
|
||||
fn main() {
|
||||
let a: Vec<String> = std::env::args().collect();
|
||||
let bytes = std::fs::read(&a[1]).expect("container");
|
||||
for pair in &a[2..] {
|
||||
let (name, off) = pair.split_once('@').expect("name@hex");
|
||||
let vb0 = usize::from_str_radix(off.trim_start_matches("0x"), 16).expect("hex");
|
||||
for line in debug_grouped_report(&bytes, name, vb0) {
|
||||
println!("{line}");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -68,6 +68,10 @@ pub mod localization;
|
||||
// Whole-ship assembly from XBG7 part families (capital ships as split parts).
|
||||
pub mod ship;
|
||||
|
||||
/// The runtime layout of a unit / vessel definition object, read out of the
|
||||
/// title's loader and verified against a live mission (see the module docs).
|
||||
pub mod unit_layout;
|
||||
|
||||
// Exact capital-ship placement from a runtime F10 ship-capture (ground truth).
|
||||
pub mod ship_capture;
|
||||
|
||||
|
||||
@@ -1,9 +1,18 @@
|
||||
//! `LSTA` — a sprite list: a header followed by N inline [`T8aD`](crate::t8ad)
|
||||
//! frames concatenated back-to-back.
|
||||
//! `LSTA` — a display list: a header followed by N inline elements concatenated
|
||||
//! back-to-back, each either a [`T8aD`](crate::t8ad) sprite or a `PRMD`
|
||||
//! primitive (a flat coloured quad — the same primitive the UI bundles use to
|
||||
//! dim a scene).
|
||||
//!
|
||||
//! A `count` lives at `@0x04`, but a few entries disagree with the actual frame
|
||||
//! count, so we walk by the `T8aD` magic instead (robust) and decode each frame
|
||||
//! from its slice up to the next frame (or end).
|
||||
//! The `count` at `@0x04` is **exact, and counts both kinds**: across all 64
|
||||
//! lists on the disc, `count == T8aD frames + PRMD primitives` with no
|
||||
//! exceptions (measured 2026-08-11). An earlier note here said "a few entries
|
||||
//! disagree with the actual frame count" — they do not; that comparison was
|
||||
//! counting sprites against a total that includes primitives.
|
||||
//!
|
||||
//! [`parse`] walks by magic and returns the **sprites**, deliberately skipping
|
||||
//! `PRMD` entries, so its result length is `count` only for lists that hold no
|
||||
//! primitives. Six lists do (in `GP_DEBRIEFING_PILOTLOG`, `GP_MISSION_SELECT`),
|
||||
//! each with exactly one.
|
||||
|
||||
use crate::t8ad::{self, T8adImage, T8AD_MAGIC};
|
||||
|
||||
@@ -15,8 +24,9 @@ pub fn is_lsta(bytes: &[u8]) -> bool {
|
||||
bytes.len() >= 4 && bytes[0..4] == LSTA_MAGIC
|
||||
}
|
||||
|
||||
/// Decode all inline T8aD frames. Frames that don't decode (unsupported T8aD
|
||||
/// variant) are skipped. Returns `None` only for non-LSTA input.
|
||||
/// Decode the inline T8aD sprites, skipping `PRMD` primitives. Returns `None`
|
||||
/// only for non-LSTA input. Every one of the 1 281 sprite frames on the disc
|
||||
/// decodes (measured 2026-08-11, after the T8aD rectangle-list fix).
|
||||
pub fn parse(bytes: &[u8]) -> Option<Vec<T8adImage>> {
|
||||
if !is_lsta(bytes) {
|
||||
return None;
|
||||
@@ -48,12 +58,21 @@ pub fn parse(bytes: &[u8]) -> Option<Vec<T8adImage>> {
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
/// A faithful one-rectangle T8aD frame: base header, a 1-entry offset table,
|
||||
/// then the rectangle header (dst 0,0, size w×h) and its pixels. (Before
|
||||
/// 2026-08-11 this fixture wrote no offset-table entry at all and the decoder
|
||||
/// read "pixels" from inside the header — the test only ever checked the
|
||||
/// dimensions, so it passed anyway.)
|
||||
fn t8ad_frame(w: u32, h: u32) -> Vec<u8> {
|
||||
let mut b = vec![0u8; 64];
|
||||
let mut b = vec![0u8; 0x2c];
|
||||
b[0..4].copy_from_slice(&T8AD_MAGIC);
|
||||
b[0x14..0x18].copy_from_slice(&w.to_be_bytes());
|
||||
b[0x18..0x1c].copy_from_slice(&h.to_be_bytes());
|
||||
b[0x1c..0x20].copy_from_slice(&1u32.to_be_bytes());
|
||||
b.extend_from_slice(&0x30u32.to_be_bytes()); // offset table → rect at 0x30
|
||||
for v in [0u32, 0, w, h] {
|
||||
b.extend_from_slice(&v.to_be_bytes()); // dst X, dst Y, width, height
|
||||
}
|
||||
b.extend_from_slice(&vec![0x80u8; (w * h * 4) as usize]);
|
||||
b
|
||||
}
|
||||
|
||||
@@ -615,18 +615,44 @@ mod tests {
|
||||
}
|
||||
}
|
||||
}
|
||||
// ── Extras: the direction this test could not previously fail in. ──
|
||||
// The loop above walks the CAPTURE's parts and looks each up in ours, so
|
||||
// a static placement with no counterpart was invisible to it — which is
|
||||
// how a resource decoded 100x too large (`e303_wep_01`, 2026-08-12) sat
|
||||
// here unnoticed. Pin the set instead: the capture legitimately misses
|
||||
// repeated instances of a shared resource (vbase dedup), so `e303_wep_01`
|
||||
// is expected; anything else appearing only in the static assembly is a
|
||||
// regression.
|
||||
let captured: std::collections::BTreeSet<&str> =
|
||||
cap.parts.iter().map(|p| p.part.as_str()).collect();
|
||||
let extra: std::collections::BTreeSet<&str> = placed
|
||||
.iter()
|
||||
.map(|p| p.resource.as_str())
|
||||
.filter(|r| !captured.contains(r))
|
||||
.collect();
|
||||
let allowed: std::collections::BTreeSet<&str> = ["e303_wep_01"].into_iter().collect();
|
||||
assert_eq!(
|
||||
extra, allowed,
|
||||
"static placements with no counterpart in the runtime capture"
|
||||
);
|
||||
|
||||
// Multi-instance coverage the capture couldn't see (vbase dedup).
|
||||
let count = |res: &str| placed.iter().filter(|p| p.resource == res).count();
|
||||
assert_eq!(count("e106_eng_01"), 2, "both engine nacelles placed");
|
||||
assert_eq!(count("e303_wep_01"), 2, "both shared turrets placed");
|
||||
// The mirrored starboard hull reflects (det < 0), the port one doesn't.
|
||||
// NEITHER hull reflects: the twins' geometry is mirrored on the disc,
|
||||
// so both placements are proper rotations. This flipped on 2026-08-12 —
|
||||
// while both twins decoded to one buffer, `apply_twin_mirrors` had to
|
||||
// synthesise the reflection here; a runtime capture showed the container
|
||||
// holds both halves, and distinct anchor assignment now hands each twin
|
||||
// its own (see docs/re/structures/xbg7-mesh.md).
|
||||
let det = |m: &[[f32; 3]; 3]| {
|
||||
m[0][0] * (m[1][1] * m[2][2] - m[1][2] * m[2][1])
|
||||
- m[0][1] * (m[1][0] * m[2][2] - m[1][2] * m[2][0])
|
||||
+ m[0][2] * (m[1][0] * m[2][1] - m[1][1] * m[2][0])
|
||||
};
|
||||
let one = |res: &str| placed.iter().find(|p| p.resource == res).unwrap();
|
||||
assert!(det(&one("e106_bdy_02").m) < 0.0, "starboard hull mirrored");
|
||||
assert!(det(&one("e106_bdy_02").m) > 0.0, "starboard hull plain (mirror is in the data)");
|
||||
assert!(det(&one("e106_bdy_01").m) > 0.0, "port hull plain");
|
||||
}
|
||||
|
||||
|
||||
@@ -49,6 +49,33 @@ pub struct CapturedDraw {
|
||||
/// First few LOCAL vertex positions dumped with the draw (buffer order).
|
||||
/// Used to disambiguate same-vcount twins (mirrored port/starboard parts).
|
||||
pub pos: Vec<[f32; 3]>,
|
||||
/// The draw's INDEX buffer, when the capture recorded one (`ib base=…`,
|
||||
/// added 2026-08-13): guest base address, index count, and the min/max index
|
||||
/// value the emulator read out of guest memory. `None` for older logs and
|
||||
/// for auto-index draws. This is ground truth for two things the offline
|
||||
/// decoder can only assume — where a block's index buffer lives relative to
|
||||
/// its vertex buffer, and how much of the vertex pool a draw really covers.
|
||||
pub ib: Option<CapturedIndexBuffer>,
|
||||
}
|
||||
|
||||
/// The index buffer a captured draw used. See [`CapturedDraw::ib`].
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub struct CapturedIndexBuffer {
|
||||
/// Guest base address of the index data.
|
||||
pub ibase: u32,
|
||||
/// Number of indices the draw issued (== `VGT_DRAW_INITIATOR.num_indices`).
|
||||
pub icount: u32,
|
||||
/// Lowest index value in the buffer.
|
||||
pub imin: u32,
|
||||
/// Highest index value in the buffer — with `vcount` this says whether the
|
||||
/// draw covers its whole vertex pool or only a sub-range.
|
||||
pub imax: u32,
|
||||
/// The first indices, verbatim (the capture prints up to 24). Byte-level
|
||||
/// ground truth for the offline index decode: a matched block's decoded
|
||||
/// index prefix must equal this run.
|
||||
pub head: [u32; 24],
|
||||
/// How many of `head` the capture actually carried.
|
||||
pub head_len: u8,
|
||||
}
|
||||
|
||||
/// A ship part to match against the capture. `part` is the **base** part name
|
||||
@@ -100,13 +127,16 @@ pub fn parse_capture(text: &str) -> Vec<CapturedDraw> {
|
||||
let mut vcount = 0u32;
|
||||
let mut pos: Vec<[f32; 3]> = Vec::new();
|
||||
let mut consts: Vec<(usize, [f64; 4])> = Vec::new();
|
||||
let mut ib: Option<CapturedIndexBuffer> = None;
|
||||
|
||||
let flush = |vbase: u32,
|
||||
vcount: u32,
|
||||
pos: &mut Vec<[f32; 3]>,
|
||||
ib: &mut Option<CapturedIndexBuffer>,
|
||||
consts: &[(usize, [f64; 4])],
|
||||
out: &mut Vec<CapturedDraw>| {
|
||||
let pos = std::mem::take(pos);
|
||||
let ib = ib.take();
|
||||
if vbase == 0 {
|
||||
return;
|
||||
}
|
||||
@@ -115,18 +145,47 @@ pub fn parse_capture(text: &str) -> Vec<CapturedDraw> {
|
||||
return; // no WorldView for this draw — skip it
|
||||
};
|
||||
if let Some((r, t)) = normalize_wvp([c0, c1, c2]) {
|
||||
out.push(CapturedDraw { vbase, vcount, r, t, pos });
|
||||
out.push(CapturedDraw { vbase, vcount, r, t, pos, ib });
|
||||
}
|
||||
};
|
||||
|
||||
for line in text.lines() {
|
||||
let l = line.trim();
|
||||
if let Some(rest) = l.strip_prefix("DRAW ") {
|
||||
flush(vbase, vcount, &mut pos, &consts, &mut out);
|
||||
flush(vbase, vcount, &mut pos, &mut ib, &consts, &mut out);
|
||||
consts.clear();
|
||||
let f = |k: &str| rest.split_whitespace().find_map(|t| t.strip_prefix(k));
|
||||
vbase = f("vbase=0x").and_then(|s| u32::from_str_radix(s, 16).ok()).unwrap_or(0);
|
||||
vcount = f("vcount=").and_then(|s| s.parse().ok()).unwrap_or(0);
|
||||
} else if let Some(rest) = l.strip_prefix("ib base=0x") {
|
||||
// `ib base=0x… count=N fmt=u16 endian=E len=L delta_vb=D min=a max=b idx: …`
|
||||
let f = |k: &str| rest.split_whitespace().find_map(|t| t.strip_prefix(k));
|
||||
let base = rest
|
||||
.split_whitespace()
|
||||
.next()
|
||||
.and_then(|s| u32::from_str_radix(s, 16).ok());
|
||||
if let (Some(ibase), Some(icount)) = (base, f("count=").and_then(|s| s.parse().ok())) {
|
||||
let mut head = [0u32; 24];
|
||||
let mut head_len = 0u8;
|
||||
if let Some((_, list)) = l.split_once("idx:") {
|
||||
for tok in list.split_whitespace() {
|
||||
let Ok(v) = tok.parse::<u32>() else { break };
|
||||
if head_len as usize >= head.len() {
|
||||
break;
|
||||
}
|
||||
head[head_len as usize] = v;
|
||||
head_len += 1;
|
||||
}
|
||||
}
|
||||
ib = Some(CapturedIndexBuffer {
|
||||
ibase,
|
||||
icount,
|
||||
imin: f("min=").and_then(|s| s.parse().ok()).unwrap_or(0),
|
||||
imax: f("max=").and_then(|s| s.parse().ok()).unwrap_or(0),
|
||||
head,
|
||||
head_len,
|
||||
});
|
||||
}
|
||||
} else if l.starts_with("pos:") || l.starts_with("positions:") {
|
||||
pos = parse_pos_line(l, 8);
|
||||
} else if l.starts_with("vsconst") {
|
||||
@@ -147,7 +206,7 @@ pub fn parse_capture(text: &str) -> Vec<CapturedDraw> {
|
||||
}
|
||||
}
|
||||
}
|
||||
flush(vbase, vcount, &mut pos, &consts, &mut out);
|
||||
flush(vbase, vcount, &mut pos, &mut ib, &consts, &mut out);
|
||||
out
|
||||
}
|
||||
|
||||
@@ -242,7 +301,9 @@ pub fn parse_drawlog(text: &str) -> Vec<CapturedDraw> {
|
||||
let get = |i: usize| consts.iter().find(|(k, _)| *k == i).map(|(_, v)| *v);
|
||||
let (Some(c0), Some(c1), Some(c2)) = (get(0), get(1), get(2)) else { return };
|
||||
if let Some((r, t)) = normalize_wvp([c0, c1, c2]) {
|
||||
out.push(CapturedDraw { vbase: base, vcount: size / stride, r, t, pos });
|
||||
// The draw-logger format carries an index base too, but it de-dups
|
||||
// by vertex declaration, so it never lines up per part — left None.
|
||||
out.push(CapturedDraw { vbase: base, vcount: size / stride, r, t, pos, ib: None });
|
||||
}
|
||||
};
|
||||
|
||||
@@ -717,10 +778,17 @@ mod tests {
|
||||
assert_eq!(e106.reference, "e106_bdy_04");
|
||||
assert_eq!(e106.parts.len(), 8, "all 8 e106 parts placed");
|
||||
let get = |p: &str| e106.parts.iter().find(|x| x.part == p).unwrap();
|
||||
// Port/starboard hull pair: X = ∓264, the starboard copy mirrored.
|
||||
// Port/starboard hull pair: X = ∓264, **both plain**. The mirror is
|
||||
// baked into the disc data, not into the placement: a runtime capture
|
||||
// shows the container carrying two 119-vertex buffers whose contents are
|
||||
// exact X-reflections, each drawn from its own address (see
|
||||
// docs/re/structures/xbg7-mesh.md). Until 2026-08-12 both twins decoded
|
||||
// to ONE buffer and this row carried diag(-1,1,1) to compensate; with
|
||||
// distinct anchor assignment they decode to their own, and re-emitting
|
||||
// from the capture produces identity here.
|
||||
assert!((get("e106_bdy_01").t[0] + 264.0).abs() < 0.1);
|
||||
assert!((get("e106_bdy_02").t[0] - 264.0).abs() < 0.1);
|
||||
assert_eq!(get("e106_bdy_02").m[0][0], -1.0);
|
||||
assert_eq!(get("e106_bdy_02").m[0][0], 1.0);
|
||||
assert_eq!(get("e106_bdy_01").m[0][0], 1.0);
|
||||
// Bridge: centreline, above and aft of the hull reference.
|
||||
let brg = get("e106_brg_01");
|
||||
|
||||
@@ -1,20 +1,28 @@
|
||||
//! `T8aD` — the game's 2D UI/HUD texture format.
|
||||
//!
|
||||
//! A 32bpp **A8R8G8B8** (Xbox byte order) surface stored as **256×256 raster
|
||||
//! tiles in row-major order** — each tile prefixed by a 16-byte tile header, edge
|
||||
//! tiles clipped to the image bounds. Fully reversed 2026-07-17 from the file
|
||||
//! header and verified against the running game (title screen).
|
||||
//! A 32bpp **A8R8G8B8** (Xbox byte order) surface stored as a list of
|
||||
//! **arbitrary sub-rectangles**, each with its own destination origin and size.
|
||||
//! Reversed 2026-07-17 (verified against the running game's title screen) and
|
||||
//! **corrected 2026-08-11**, when the per-tile header turned out to carry the
|
||||
//! rectangle's placement rather than being opaque flags.
|
||||
//!
|
||||
//! ```text
|
||||
//! 0x00 4 Magic "T8aD"
|
||||
//! 0x14 4 width (BE u32)
|
||||
//! 0x14 4 width (BE u32) the full surface
|
||||
//! 0x18 4 height (BE u32)
|
||||
//! 0x1c 4 tile count (BE u32) = ceil(w/256) * ceil(h/256)
|
||||
//! 0x2c tiles*4 offset table: absolute byte offset of each row-major tile
|
||||
//! <off> 16 per-tile header (flags + tile w/h), then:
|
||||
//! <off+16> tile_w * tile_h * 4 bytes of A8R8G8B8 pixels, row-major
|
||||
//! 0x1c 4 rectangle count (BE u32) — NOT ceil(w/256)*ceil(h/256)
|
||||
//! 0x2c count*4 offset table: absolute byte offset of each rectangle
|
||||
//! <off> 16 rectangle header, four BE u32: dst X, dst Y, width, height
|
||||
//! <off+16> width * height * 4 bytes of A8R8G8B8 pixels, row-major
|
||||
//! ```
|
||||
//!
|
||||
//! Most surfaces happen to be stored as full-width 256-tall bands, which is why
|
||||
//! treating the file as a 256×256 grid decoded 96 % of the disc correctly. It is
|
||||
//! not the model, though: a dialogue strip declares 524×63 and stores **one**
|
||||
//! 173×25 rectangle at (175,20), and `pdmes010` stores two — (59,6,256,54) and
|
||||
//! (315,6,149,54), the second beginning exactly `16 + 256*54*4` bytes after the
|
||||
//! first. Anything the rectangles do not cover stays transparent.
|
||||
//!
|
||||
//! Surfaces ≤256px wide are a single tile column, so the first tile's pixels sit
|
||||
//! at `44 + tiles*4 + 16 = 64` — which is why the old "type→header size 64/84/…"
|
||||
//! rule (header = 44 + tiles*20) happened to decode small textures correctly: for
|
||||
@@ -43,18 +51,11 @@ fn be32(b: &[u8], off: usize) -> u32 {
|
||||
}
|
||||
|
||||
/// Side of the square storage tile, in texels, and the per-tile header size.
|
||||
const TILE: usize = 256;
|
||||
const TILE_HDR: usize = 16;
|
||||
/// Bytes of per-rectangle header before its pixels: dst X, dst Y, w, h.
|
||||
const RECT_HDR: usize = 16;
|
||||
|
||||
/// Decode a T8aD surface from a slice whose first bytes ARE the magic. Returns
|
||||
/// `None` for non-T8aD input or a variant we can't decode as RGBA (never guesses).
|
||||
///
|
||||
/// Layout (reversed from the header + verified against the running game):
|
||||
/// a 44-byte base header, then a `tiles`-entry big-endian u32 **offset table** at
|
||||
/// `0x2c`, where `tiles` = the field at `0x1c` = `ceil(w/256) * ceil(h/256)`.
|
||||
/// Each entry is the absolute byte offset of a **row-major** 256×256 tile; every
|
||||
/// tile is a 16-byte tile header followed by `tile_w*tile_h*4` A8R8G8B8 pixels,
|
||||
/// edge tiles clipped to the image bounds.
|
||||
/// `None` if any rectangle fails to fit the surface or the file — never guesses.
|
||||
pub fn parse(bytes: &[u8]) -> Option<T8adImage> {
|
||||
if !is_t8ad(bytes) || bytes.len() < 0x40 {
|
||||
return None;
|
||||
@@ -64,33 +65,38 @@ pub fn parse(bytes: &[u8]) -> Option<T8adImage> {
|
||||
if !(1..=4096).contains(&width) || !(1..=4096).contains(&height) {
|
||||
return None;
|
||||
}
|
||||
let tiles = be32(bytes, 0x1c) as usize;
|
||||
let cols = width.div_ceil(TILE);
|
||||
let rows = height.div_ceil(TILE);
|
||||
// The field at 0x1c must be the tile count; otherwise it's a variant we don't
|
||||
// decode (e.g. DXT / palettized) — defer rather than misdecode.
|
||||
if tiles == 0 || tiles != cols * rows {
|
||||
let rects = be32(bytes, 0x1c) as usize;
|
||||
if rects == 0 || rects > 4096 {
|
||||
return None;
|
||||
}
|
||||
const TABLE: usize = 0x2c;
|
||||
if bytes.len() < TABLE + tiles * 4 {
|
||||
if bytes.len() < TABLE + rects * 4 {
|
||||
return None;
|
||||
}
|
||||
|
||||
// Anything no rectangle covers stays transparent.
|
||||
let mut rgba = vec![0u8; width * height * 4];
|
||||
for ty in 0..rows {
|
||||
for tx in 0..cols {
|
||||
let tile = ty * cols + tx;
|
||||
let pixels = be32(bytes, TABLE + tile * 4) as usize + TILE_HDR;
|
||||
let tw = TILE.min(width - tx * TILE);
|
||||
let th = TILE.min(height - ty * TILE);
|
||||
if pixels + tw * th * 4 > bytes.len() {
|
||||
return None; // truncated / not the layout we expect
|
||||
for r in 0..rects {
|
||||
let at = be32(bytes, TABLE + r * 4) as usize;
|
||||
if at + RECT_HDR > bytes.len() {
|
||||
return None;
|
||||
}
|
||||
for row in 0..th {
|
||||
let mut s = pixels + row * tw * 4;
|
||||
let mut d = ((ty * TILE + row) * width + tx * TILE) * 4;
|
||||
for _ in 0..tw {
|
||||
let dx = be32(bytes, at) as usize;
|
||||
let dy = be32(bytes, at + 4) as usize;
|
||||
let rw = be32(bytes, at + 8) as usize;
|
||||
let rh = be32(bytes, at + 12) as usize;
|
||||
// Never guess: a rectangle must fit the surface and its pixels the file.
|
||||
if rw == 0 || rh == 0 || dx + rw > width || dy + rh > height {
|
||||
return None;
|
||||
}
|
||||
let pixels = at + RECT_HDR;
|
||||
if pixels + rw * rh * 4 > bytes.len() {
|
||||
return None;
|
||||
}
|
||||
for row in 0..rh {
|
||||
let mut s = pixels + row * rw * 4;
|
||||
let mut d = ((dy + row) * width + dx) * 4;
|
||||
for _ in 0..rw {
|
||||
// A8R8G8B8 → RGBA8.
|
||||
rgba[d] = bytes[s + 1];
|
||||
rgba[d + 1] = bytes[s + 2];
|
||||
@@ -101,7 +107,6 @@ pub fn parse(bytes: &[u8]) -> Option<T8adImage> {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Some(T8adImage {
|
||||
width: width as u32,
|
||||
height: height as u32,
|
||||
@@ -123,7 +128,11 @@ mod tests {
|
||||
b[0x18..0x1c].copy_from_slice(&h.to_be_bytes());
|
||||
b[0x1c..0x20].copy_from_slice(&1u32.to_be_bytes()); // 1 tile
|
||||
b.extend_from_slice(&0x30u32.to_be_bytes()); // offset table: tile 0 @ 0x30
|
||||
b.extend_from_slice(&[0u8; 16]); // 16-byte tile header → pixels at 0x40
|
||||
// rectangle header: dst (0,0), size w×h
|
||||
b.extend_from_slice(&0u32.to_be_bytes());
|
||||
b.extend_from_slice(&0u32.to_be_bytes());
|
||||
b.extend_from_slice(&w.to_be_bytes());
|
||||
b.extend_from_slice(&h.to_be_bytes());
|
||||
for i in 0..(w * h) {
|
||||
b.extend_from_slice(&[(i & 0xff) as u8, 0x24, 0x63, 0xB2]); // A, R, G, B
|
||||
}
|
||||
@@ -143,8 +152,8 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn assembles_row_major_tiles_via_offset_table() {
|
||||
// 300×1 → 2 tiles: (0,0)=256×1 red, (1,0)=44×1 blue, each +16-byte header.
|
||||
fn assembles_rectangles_via_offset_table() {
|
||||
// 300×1 → 2 rectangles: (0,0) 256×1 red, then (256,0) 44×1 blue.
|
||||
let (w, h): (u32, u32) = (300, 1);
|
||||
let mut b = vec![0u8; 0x2c];
|
||||
b[0..4].copy_from_slice(&T8AD_MAGIC);
|
||||
@@ -155,9 +164,9 @@ mod tests {
|
||||
let off1 = off0 + 16 + 256 * 4; // tile-0 header + its 256 pixels
|
||||
b.extend_from_slice(&(off0 as u32).to_be_bytes());
|
||||
b.extend_from_slice(&(off1 as u32).to_be_bytes());
|
||||
b.extend_from_slice(&[0u8; 16]);
|
||||
for v in [0u32, 0, 256, 1] { b.extend_from_slice(&v.to_be_bytes()) } // dst(0,0) 256×1
|
||||
b.extend_from_slice(&[0xFF, 0xFF, 0, 0].repeat(256)); // A,R,G,B red
|
||||
b.extend_from_slice(&[0u8; 16]);
|
||||
for v in [256u32, 0, 44, 1] { b.extend_from_slice(&v.to_be_bytes()) } // dst(256,0) 44×1
|
||||
b.extend_from_slice(&[0xFF, 0, 0, 0xFF].repeat(44)); // A,R,G,B blue
|
||||
let img = parse(&b).expect("decodes");
|
||||
assert_eq!((img.width, img.height), (300, 1));
|
||||
@@ -166,10 +175,11 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rejects_wrong_tilecount_and_short() {
|
||||
// tile-count field that isn't ceil(w/256)*ceil(h/256) → None
|
||||
fn rejects_out_of_range_rect_and_short() {
|
||||
// a rectangle that does not fit the declared surface → None, never guess
|
||||
let mut b = synth(2, 2);
|
||||
b[0x1c..0x20].copy_from_slice(&7u32.to_be_bytes());
|
||||
let at = 0x2c + 4;
|
||||
b[at + 8..at + 12].copy_from_slice(&99u32.to_be_bytes()); // width 99 > 2
|
||||
assert!(parse(&b).is_none());
|
||||
// truncated pixel data → None
|
||||
let b = synth(64, 64);
|
||||
|
||||
105
crates/sylpheed-formats/src/unit_layout.rs
Normal file
@@ -0,0 +1,105 @@
|
||||
//! The runtime layout of a unit / vessel definition object.
|
||||
//!
|
||||
//! The game parses an [`crate::idxd`] record into a fixed 880-byte object whose
|
||||
//! field offsets are **not** guessable from the disc data: the record is a
|
||||
//! reflective key/value pool, and the loader assigns each key to a member by
|
||||
//! name. This table is that assignment, read out of the loader itself
|
||||
//! (`sub_82341A20` — every key is built as `addi r4, r30, -N`, so the field name
|
||||
//! for each store is a string in the executable image), and verified against
|
||||
//! objects dumped from a running mission: **406 values agree with the disc
|
||||
//! records, 0 disagree**, over 11 objects covering both schemas.
|
||||
//!
|
||||
//! Why a reimplementation wants it:
|
||||
//!
|
||||
//! - it names the field behind every word of a live definition object, so a
|
||||
//! memory snapshot can be read directly;
|
||||
//! - it says which fields a record leaves **defaulted**, and what the loader
|
||||
//! leaves there — the float accessor returns `0.0` on a pool miss;
|
||||
//! - it carries two conventions that are invisible on disc: **angles are degrees
|
||||
//! in the data and radians in the object**, and **`Size_Y` takes `Size_X`**
|
||||
//! when omitted.
|
||||
//!
|
||||
//! See `docs/re/live-unit-definitions.md` for the derivation and the measured
|
||||
//! default values.
|
||||
|
||||
/// How a field is stored in the definition object.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum Kind {
|
||||
/// IEEE-754 single, big-endian.
|
||||
F32,
|
||||
/// Pointer to a string.
|
||||
Str,
|
||||
/// 32-bit word (bool / enum / count / id).
|
||||
Word,
|
||||
}
|
||||
|
||||
/// One field of the definition object.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub struct Field {
|
||||
/// Byte offset from the start of the object.
|
||||
pub offset: usize,
|
||||
/// Field name, exactly as the disc record spells it.
|
||||
pub name: &'static str,
|
||||
/// Storage kind.
|
||||
pub kind: Kind,
|
||||
}
|
||||
|
||||
const TABLE: &str = include_str!("../data/unit_definition_layout.txt");
|
||||
|
||||
/// Every mapped field, in offset order.
|
||||
pub fn fields() -> Vec<Field> {
|
||||
TABLE
|
||||
.lines()
|
||||
.filter(|l| !l.trim_start().starts_with('#') && !l.trim().is_empty())
|
||||
.filter_map(|l| {
|
||||
let mut it = l.split_whitespace();
|
||||
let offset = it.next()?.parse().ok()?;
|
||||
let kind = match it.next()? {
|
||||
"f32" => Kind::F32,
|
||||
"str" => Kind::Str,
|
||||
_ => Kind::Word,
|
||||
};
|
||||
// `name` is a &'static str because TABLE is 'static.
|
||||
let name = it.next()?;
|
||||
let name: &'static str = TABLE.get(
|
||||
TABLE.find(name).map(|s| s..s + name.len())?,
|
||||
)?;
|
||||
Some(Field { offset, name, kind })
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// The field at `offset`, if one is mapped there.
|
||||
pub fn field_at(offset: usize) -> Option<Field> {
|
||||
fields().into_iter().find(|f| f.offset == offset)
|
||||
}
|
||||
|
||||
/// The offset of `name`, if it is mapped.
|
||||
pub fn offset_of(name: &str) -> Option<usize> {
|
||||
fields().into_iter().find(|f| f.name == name).map(|f| f.offset)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn table_parses_and_is_ordered() {
|
||||
let f = fields();
|
||||
assert!(f.len() > 150, "expected the full map, got {}", f.len());
|
||||
assert!(f.windows(2).all(|w| w[0].offset < w[1].offset), "offsets must be strictly increasing");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn known_fields_sit_where_the_loader_puts_them() {
|
||||
// Spot-checks from the verified map; these four also anchor the
|
||||
// identification of a live object (see docs/re/live-unit-definitions.md).
|
||||
assert_eq!(offset_of("Size_X"), Some(48));
|
||||
assert_eq!(offset_of("Size_Y"), Some(52));
|
||||
assert_eq!(offset_of("Size_Z"), Some(56));
|
||||
assert_eq!(offset_of("HP"), Some(84));
|
||||
assert_eq!(offset_of("HQRatio"), Some(88));
|
||||
assert_eq!(field_at(96).map(|f| f.name), Some("ThrusterRatio"));
|
||||
assert_eq!(field_at(48).map(|f| f.kind), Some(Kind::F32));
|
||||
}
|
||||
}
|
||||
144
crates/sylpheed-formats/tests/mesh_consistency_disc.rs
Normal file
@@ -0,0 +1,144 @@
|
||||
//! Cross-container consistency for XBG7 geometry.
|
||||
//!
|
||||
//! A geometry resource shared by several stage containers must decode to the
|
||||
//! same bounds in each. This needs no ground truth, and on 2026-08-11 it found
|
||||
//! **125 of 681** shared resources decoding to different bounds while reporting
|
||||
//! identical vertex and triangle counts — the anchor scan locating a different
|
||||
//! buffer of the same size (see `docs/re/structures/xbg7-mesh.md`).
|
||||
//!
|
||||
//! The test is `#[ignore]`d because the decoder does not satisfy it yet. It is
|
||||
//! written as the *target* state so that fixing the anchor scan makes it pass,
|
||||
//! rather than as a snapshot of the bug.
|
||||
|
||||
use std::collections::BTreeMap;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use sylpheed_formats::mesh::Xbg7Model;
|
||||
|
||||
fn disc_root() -> Option<PathBuf> {
|
||||
if let Ok(p) = std::env::var("SYLPHEED_DISC") {
|
||||
let p = PathBuf::from(p);
|
||||
if p.join("dat").is_dir() {
|
||||
return Some(p);
|
||||
}
|
||||
}
|
||||
let default = Path::new(
|
||||
"/home/fabi/RE - Project Sylpheed/Project Sylpheed - Arc of Deception (USA, Europe) (En,Ja)",
|
||||
);
|
||||
if default.join("dat").is_dir() {
|
||||
return Some(default.to_path_buf());
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
/// Rounded (w, h, d) of a model's own geometry.
|
||||
fn span(m: &Xbg7Model) -> Option<[i64; 3]> {
|
||||
let (mut lo, mut hi) = ([f32::MAX; 3], [f32::MIN; 3]);
|
||||
for s in &m.meshes {
|
||||
for q in &s.positions {
|
||||
for k in 0..3 {
|
||||
lo[k] = lo[k].min(q[k]);
|
||||
hi[k] = hi[k].max(q[k]);
|
||||
}
|
||||
}
|
||||
}
|
||||
if lo[0] == f32::MAX {
|
||||
return None;
|
||||
}
|
||||
Some([
|
||||
(hi[0] - lo[0]).round() as i64,
|
||||
(hi[1] - lo[1]).round() as i64,
|
||||
(hi[2] - lo[2]).round() as i64,
|
||||
])
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore = "known-failing: 62 of 714 shared resources decode inconsistently (was 125 of 681; distinct anchor assignment + the 0.42 connectivity cap fixed the rest). Note this metric is the WEAKER witness — a systematic mis-anchor is consistent — see docs/re/structures/xbg7-mesh.md"]
|
||||
fn shared_resources_decode_identically_in_every_container() {
|
||||
let Some(root) = disc_root() else {
|
||||
eprintln!("SKIP: extracted disc not found (set SYLPHEED_DISC to enable)");
|
||||
return;
|
||||
};
|
||||
let dir = root.join("hidden/resource3d");
|
||||
let mut files: Vec<PathBuf> = std::fs::read_dir(&dir)
|
||||
.expect("resource3d/")
|
||||
.flatten()
|
||||
.map(|e| e.path())
|
||||
.filter(|p| p.extension().and_then(|s| s.to_str()) == Some("xpr"))
|
||||
.collect();
|
||||
files.sort();
|
||||
|
||||
// name -> (verts, tris) -> set of spans seen
|
||||
let mut seen: BTreeMap<String, Vec<([i64; 3], usize, usize, String)>> = BTreeMap::new();
|
||||
for f in &files {
|
||||
let Ok(bytes) = std::fs::read(f) else { continue };
|
||||
let where_ = f.file_name().unwrap().to_string_lossy().to_string();
|
||||
for m in Xbg7Model::anchor_models_cancellable(&bytes, 0.0, &|| false) {
|
||||
let Some(sp) = span(&m) else { continue };
|
||||
let v: usize = m.meshes.iter().map(|s| s.positions.len()).sum();
|
||||
let t: usize = m.meshes.iter().map(|s| s.indices.len() / 3).sum();
|
||||
seen.entry(m.name.clone()).or_default().push((sp, v, t, where_.clone()));
|
||||
}
|
||||
}
|
||||
|
||||
let mut bad: Vec<String> = Vec::new();
|
||||
for (name, list) in &seen {
|
||||
if list.len() < 2 {
|
||||
continue;
|
||||
}
|
||||
// Only compare decodes that agree on how much geometry they found;
|
||||
// a differing vertex/triangle count is a different question.
|
||||
if !list.iter().all(|e| e.1 == list[0].1 && e.2 == list[0].2) {
|
||||
continue;
|
||||
}
|
||||
let spans: std::collections::BTreeSet<[i64; 3]> = list.iter().map(|e| e.0).collect();
|
||||
if spans.len() > 1 && bad.len() < 10 {
|
||||
bad.push(format!("{name}: {spans:?}"));
|
||||
}
|
||||
}
|
||||
assert!(
|
||||
bad.is_empty(),
|
||||
"{} shared resources decode to different bounds; first: {bad:?}",
|
||||
bad.len()
|
||||
);
|
||||
}
|
||||
|
||||
/// Port/starboard twins must not decode to the *same* buffer.
|
||||
///
|
||||
/// A runtime capture showed the container stores both halves of the `e106` hull
|
||||
/// as separate X-reflected buffers, so a `…_01`/`…_02` pair of equal vertex
|
||||
/// count should come out mirrored (or related by another axis / vertex order) —
|
||||
/// never identical, which is the collapse distinct assignment fixes. Since that
|
||||
/// fix reached grouped pools too, this holds for **every** twin pair on the disc
|
||||
/// — including `n206`, which was the last exception.
|
||||
#[test]
|
||||
fn twin_pairs_do_not_share_a_buffer() {
|
||||
let Some(root) = disc_root() else {
|
||||
eprintln!("SKIP: extracted disc not found (set SYLPHEED_DISC to enable)");
|
||||
return;
|
||||
};
|
||||
let mut files: Vec<PathBuf> = std::fs::read_dir(root.join("hidden/resource3d"))
|
||||
.expect("resource3d/")
|
||||
.flatten()
|
||||
.map(|e| e.path())
|
||||
.filter(|p| p.extension().and_then(|s| s.to_str()) == Some("xpr"))
|
||||
.collect();
|
||||
files.sort();
|
||||
|
||||
let mut collapsed: Vec<String> = Vec::new();
|
||||
for f in &files {
|
||||
let Ok(bytes) = std::fs::read(f) else { continue };
|
||||
let models = Xbg7Model::anchor_models_cancellable(&bytes, 0.0, &|| false);
|
||||
let by_name: BTreeMap<&str, &Xbg7Model> =
|
||||
models.iter().map(|m| (m.name.as_str(), m)).collect();
|
||||
for m in &models {
|
||||
let Some(stem) = m.name.strip_suffix("_01") else { continue };
|
||||
let Some(t) = by_name.get(format!("{stem}_02").as_str()) else { continue };
|
||||
let (a, b) = (m.meshes[0].vbuf_offset, t.meshes[0].vbuf_offset);
|
||||
if a.is_some() && a == b {
|
||||
collapsed.push(format!("{}/{stem}_02 in {}", m.name, f.file_name().unwrap().to_string_lossy()));
|
||||
}
|
||||
}
|
||||
}
|
||||
assert!(collapsed.is_empty(), "twin pairs sharing one buffer: {collapsed:?}");
|
||||
}
|
||||
@@ -17,7 +17,7 @@ fn res3d_dir() -> Option<PathBuf> {
|
||||
}
|
||||
}
|
||||
let default =
|
||||
PathBuf::from("/home/fabi/RE Project Sylpheed/sylph_extract/hidden/resource3d");
|
||||
PathBuf::from("/home/fabi/RE - Project Sylpheed/sylph_extract/hidden/resource3d");
|
||||
default.is_dir().then_some(default)
|
||||
}
|
||||
|
||||
@@ -321,7 +321,7 @@ fn hero_ship_grouped_pool_decodes() {
|
||||
fn stage_models_decode() {
|
||||
use sylpheed_formats::mesh::Xbg7Model;
|
||||
let dir = std::env::var("SYLPHEED_RES3D").unwrap_or_else(|_| {
|
||||
"/home/fabi/RE Project Sylpheed/sylph_extract/hidden/resource3d".to_string()
|
||||
"/home/fabi/RE - Project Sylpheed/sylph_extract/hidden/resource3d".to_string()
|
||||
});
|
||||
let path = format!("{dir}/Stage_S10.xpr");
|
||||
let bytes = std::fs::read(&path).expect("read Stage_S10");
|
||||
@@ -359,7 +359,7 @@ fn stage_models_sweep() {
|
||||
use sylpheed_formats::mesh::Xbg7Model;
|
||||
use std::time::Instant;
|
||||
let dir = std::env::var("SYLPHEED_RES3D").unwrap_or_else(|_| {
|
||||
"/home/fabi/RE Project Sylpheed/sylph_extract/hidden/resource3d".to_string()
|
||||
"/home/fabi/RE - Project Sylpheed/sylph_extract/hidden/resource3d".to_string()
|
||||
});
|
||||
let mut names: Vec<_> = std::fs::read_dir(&dir)
|
||||
.unwrap()
|
||||
@@ -386,7 +386,7 @@ fn stage_models_sweep() {
|
||||
fn stage_models_quality_audit() {
|
||||
use sylpheed_formats::mesh::Xbg7Model;
|
||||
let dir = std::env::var("SYLPHEED_RES3D").unwrap_or_else(|_| {
|
||||
"/home/fabi/RE Project Sylpheed/sylph_extract/hidden/resource3d".to_string()
|
||||
"/home/fabi/RE - Project Sylpheed/sylph_extract/hidden/resource3d".to_string()
|
||||
});
|
||||
let bytes = std::fs::read(format!("{dir}/Stage_S07.xpr")).unwrap();
|
||||
let models = Xbg7Model::stage_models(&bytes);
|
||||
@@ -424,3 +424,59 @@ fn stage_models_quality_audit() {
|
||||
assert!(huge < models.len() / 20, "few huge (skybox-plane) models");
|
||||
assert!(worst_deg < 0.35, "no model should be mostly-degenerate");
|
||||
}
|
||||
|
||||
/// A correctly located index run has **no degenerate triangles**. That is the
|
||||
/// signature the 2026-08-13 pad-scoring fix keys on: an index list read one
|
||||
/// element late still passes every count-based gate but wires arbitrary
|
||||
/// vertices, which produces triangles with a repeated index. Before the fix
|
||||
/// **579** decoded runs on the disc carried such triangles (and a runtime
|
||||
/// capture confirmed 17 of 93 index batches disagreed with the GPU); after it and
|
||||
/// the two follow-ups (pad scoring in the grouped path, and preferring a
|
||||
/// degenerate-free candidate over an earlier dirty one), exactly **one** does.
|
||||
/// Locking the
|
||||
/// number in, because the defect is invisible to coverage and to the anchor
|
||||
/// oracle: every count stays correct while the geometry is mis-wired.
|
||||
#[test]
|
||||
#[ignore = "requires extracted disc models — set SYLPHEED_RES3D"]
|
||||
fn decoded_index_runs_have_almost_no_degenerate_triangles() {
|
||||
let Some(dir) = res3d_dir() else {
|
||||
eprintln!("SKIP: resource3d dir not found (set SYLPHEED_RES3D)");
|
||||
return;
|
||||
};
|
||||
let mut files: Vec<PathBuf> = std::fs::read_dir(&dir)
|
||||
.expect("resource3d/")
|
||||
.flatten()
|
||||
.map(|e| e.path())
|
||||
.filter(|p| p.extension().and_then(|s| s.to_str()) == Some("xpr"))
|
||||
.collect();
|
||||
files.sort();
|
||||
|
||||
let mut offenders: Vec<(String, String, usize)> = Vec::new();
|
||||
for f in &files {
|
||||
let Ok(bytes) = std::fs::read(f) else { continue };
|
||||
let where_ = f.file_name().unwrap().to_string_lossy().to_string();
|
||||
for m in Xbg7Model::stage_models(&bytes) {
|
||||
for sm in &m.meshes {
|
||||
let d = sm
|
||||
.indices
|
||||
.chunks_exact(3)
|
||||
.filter(|t| t[0] == t[1] || t[1] == t[2] || t[0] == t[2])
|
||||
.count();
|
||||
if d > 0 {
|
||||
offenders.push((m.name.clone(), where_.clone(), d));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// The one at default settings: `_rou_f402_dead` in `Stage_S09`, whose
|
||||
// degenerate-free block is claimed by `e_rou_f003_Near` — both 24-vertex
|
||||
// bounding boxes, the identity class that needs descriptor-level data. (With
|
||||
// `XBG7_SUBMESH_DECLS=1` four newly decoded `ptc_pack` `.dat` composites join
|
||||
// it; that knob is off by default.) Anything else here is a regression.
|
||||
assert!(
|
||||
offenders.len() <= 1,
|
||||
"{} decoded index runs contain degenerate triangles (expected ≤ 1): {:?}",
|
||||
offenders.len(),
|
||||
&offenders[..offenders.len().min(20)]
|
||||
);
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ fn iso_path() -> Option<PathBuf> {
|
||||
}
|
||||
}
|
||||
let default = PathBuf::from(
|
||||
"/home/fabi/RE Project Sylpheed/Project Sylpheed - Arc of Deception (USA, Europe) (En,Ja).iso",
|
||||
"/home/fabi/RE - Project Sylpheed/Project Sylpheed - Arc of Deception (USA, Europe) (En,Ja).iso",
|
||||
);
|
||||
default.is_file().then_some(default)
|
||||
}
|
||||
|
||||
152
crates/sylpheed-formats/tests/ui_surfaces_disc.rs
Normal file
@@ -0,0 +1,152 @@
|
||||
//! Disc-wide guards for the 2D surface formats, locking in what was measured on
|
||||
//! 2026-08-11. Skipped (as no-ops) when the extracted disc is absent.
|
||||
//!
|
||||
//! These assert *disc-wide invariants* rather than one hand-picked file, because
|
||||
//! each of the findings they guard was originally missed by reasoning from a
|
||||
//! sample: T8aD's "~15 % unsupported variants" were a wrong model, and LSTA's
|
||||
//! "a few entries disagree with the count" was a miscount.
|
||||
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use sylpheed_formats::{lsta, pak::PakArchive, ratc, t8ad};
|
||||
|
||||
fn disc_root() -> Option<PathBuf> {
|
||||
if let Ok(p) = std::env::var("SYLPHEED_DISC") {
|
||||
let p = PathBuf::from(p);
|
||||
if p.join("dat").is_dir() {
|
||||
return Some(p);
|
||||
}
|
||||
}
|
||||
let default = Path::new(
|
||||
"/home/fabi/RE - Project Sylpheed/Project Sylpheed - Arc of Deception (USA, Europe) (En,Ja)",
|
||||
);
|
||||
if default.join("dat").is_dir() {
|
||||
return Some(default.to_path_buf());
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
macro_rules! skip_without_disc {
|
||||
($root:ident) => {
|
||||
let Some($root) = disc_root() else {
|
||||
eprintln!("SKIP: extracted disc not found (set SYLPHEED_DISC to enable)");
|
||||
return;
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
/// Every entry of every pak, plus every RATC child, as raw bytes.
|
||||
fn for_each_blob(root: &Path, mut f: impl FnMut(&str, &str, &[u8])) {
|
||||
let mut paks: Vec<PathBuf> = std::fs::read_dir(root.join("dat"))
|
||||
.expect("dat/")
|
||||
.flatten()
|
||||
.map(|e| e.path())
|
||||
.filter(|p| p.extension().and_then(|s| s.to_str()) == Some("pak"))
|
||||
.collect();
|
||||
paks.sort();
|
||||
for p in &paks {
|
||||
let pak_name = p.file_name().unwrap().to_string_lossy().to_string();
|
||||
let Ok(arc) = PakArchive::open(p) else { continue };
|
||||
for e in arc.entries() {
|
||||
let Ok(bytes) = arc.read(e) else { continue };
|
||||
f(&pak_name, &format!("{:08x}", e.name_hash), &bytes);
|
||||
if ratc::is_ratc(&bytes) {
|
||||
if let Some(kids) = ratc::parse(&bytes) {
|
||||
for k in &kids {
|
||||
if k.offset + k.size <= bytes.len() {
|
||||
f(&pak_name, &k.name, &bytes[k.offset..k.offset + k.size]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A T8aD surface is a list of sub-rectangles, and on this disc **every** one
|
||||
/// decodes. Regressing the rectangle model would show up here as a decode gap,
|
||||
/// which is exactly how the old 256-grid reading looked (96 %, not 100 %).
|
||||
#[test]
|
||||
fn every_t8ad_on_the_disc_decodes() {
|
||||
skip_without_disc!(root);
|
||||
let (mut total, mut ok) = (0usize, 0usize);
|
||||
let mut first_failure = None;
|
||||
for_each_blob(&root, |pak, name, b| {
|
||||
if !t8ad::is_t8ad(b) || b.len() < 0x40 {
|
||||
return;
|
||||
}
|
||||
total += 1;
|
||||
if t8ad::parse(b).is_some() {
|
||||
ok += 1;
|
||||
} else if first_failure.is_none() {
|
||||
first_failure = Some(format!("{pak}:{name}"));
|
||||
}
|
||||
});
|
||||
assert!(total > 19_000, "expected the disc's ~19 216 surfaces, saw {total}");
|
||||
assert_eq!(ok, total, "first failure: {first_failure:?}");
|
||||
}
|
||||
|
||||
/// An LSTA's header count is exact and counts **both** kinds of element: T8aD
|
||||
/// sprites and `PRMD` primitives. (It was long read as unreliable because the
|
||||
/// comparison ignored primitives.)
|
||||
#[test]
|
||||
fn lsta_count_equals_sprites_plus_primitives() {
|
||||
skip_without_disc!(root);
|
||||
let count_magic = |b: &[u8], magic: &[u8; 4]| {
|
||||
let (mut n, mut i) = (0usize, 4usize);
|
||||
while i + 4 <= b.len() {
|
||||
if &b[i..i + 4] == magic {
|
||||
n += 1;
|
||||
i += 4;
|
||||
} else {
|
||||
i += 1;
|
||||
}
|
||||
}
|
||||
n
|
||||
};
|
||||
let (mut lists, mut exact) = (0usize, 0usize);
|
||||
let mut bad = Vec::new();
|
||||
for_each_blob(&root, |pak, name, b| {
|
||||
if !lsta::is_lsta(b) || b.len() < 8 {
|
||||
return;
|
||||
}
|
||||
lists += 1;
|
||||
let declared = u32::from_be_bytes([b[4], b[5], b[6], b[7]]) as usize;
|
||||
let sprites = count_magic(b, b"T8aD");
|
||||
let prims = count_magic(b, b"PRMD");
|
||||
if declared == sprites + prims {
|
||||
exact += 1;
|
||||
} else if bad.len() < 4 {
|
||||
bad.push(format!("{pak}:{name} declared {declared} != {sprites}+{prims}"));
|
||||
}
|
||||
});
|
||||
assert!(lists >= 60, "expected the disc's 64 LSTA lists, saw {lists}");
|
||||
assert_eq!(exact, lists, "mismatches: {bad:?}");
|
||||
}
|
||||
|
||||
/// Nested RATC records are **leaves**: they carry no child list of their own.
|
||||
/// "One level deep" describes the data, not a parser limit.
|
||||
#[test]
|
||||
fn ratc_nesting_is_exactly_one_level() {
|
||||
skip_without_disc!(root);
|
||||
let mut grandchildren = 0usize;
|
||||
let mut bundles = 0usize;
|
||||
for_each_blob(&root, |_, _, b| {
|
||||
if !ratc::is_ratc(b) {
|
||||
return;
|
||||
}
|
||||
let Some(kids) = ratc::parse(b) else { return };
|
||||
bundles += 1;
|
||||
for k in &kids {
|
||||
if k.offset + k.size > b.len() {
|
||||
continue;
|
||||
}
|
||||
let sub = &b[k.offset..k.offset + k.size];
|
||||
if ratc::is_ratc(sub) {
|
||||
grandchildren += ratc::parse(sub).map(|g| g.len()).unwrap_or(0);
|
||||
}
|
||||
}
|
||||
});
|
||||
assert!(bundles > 2_000, "expected thousands of RATC bundles, saw {bundles}");
|
||||
assert_eq!(grandchildren, 0, "a nested RATC record listed children");
|
||||
}
|
||||
96
crates/sylpheed-formats/tests/unit_layout_disc.rs
Normal file
@@ -0,0 +1,96 @@
|
||||
//! The definition-object layout must keep agreeing with the disc records.
|
||||
//!
|
||||
//! `unit_layout` was read out of the game's loader and checked against objects
|
||||
//! dumped from a running Stage 02. This pins that check: every mapped float
|
||||
//! field of every identified object must equal the value its disc record sets.
|
||||
//! It needs no emulator — the dump is checked in under `docs/re/captures/`.
|
||||
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use sylpheed_formats::idxd::IdxdObject;
|
||||
use sylpheed_formats::pak::PakArchive;
|
||||
use sylpheed_formats::unit_layout::{fields, Kind};
|
||||
|
||||
/// Live objects identified in the dump, with the disc record each one is.
|
||||
/// `bf001` is here because full-record agreement is what identified it: the
|
||||
/// four-value signature also fitted `UN_be005_ADAN_SpaceFortress`, which
|
||||
/// disagrees on `Color_R`/`Color_G`.
|
||||
const IDENTIFIED: &[(&str, &str)] = &[
|
||||
("UN_f106_TCAF_Destroyer", "0xbd3ee300"),
|
||||
("UN_f105_TCAF_Cruiser", "0xbd40e200"),
|
||||
("UN_e105_ADAN_Cruiser", "0xbd3fd800"),
|
||||
("UN_e106_ADAN_Destroyer", "0xbd3e6f80"),
|
||||
("UN_f101_TCAF_Acropolis", "0xbd3e1f00"),
|
||||
("UN_bf001_TCAF_SchlosBase", "0xbd3b6a00"),
|
||||
("UN_e007_ADAN_Turret", "0xbd3dea80"),
|
||||
("UN_e010_ADAN_Attacker_S", "0xbd3faa80"),
|
||||
("UN_f003_TCAF_ArrowHead", "0xbd3dbd00"),
|
||||
("UN_f001_TCAF_DeltaSaber_T", "0xbd3dfc00"),
|
||||
("UN_f001_TCAF_DeltaSaber_T_Player", "0xbd3da100"),
|
||||
];
|
||||
|
||||
const DUMP: &str = include_str!("../../../docs/re/captures/stage02-live-unit-definitions-deep.txt");
|
||||
|
||||
fn disc_root() -> Option<String> {
|
||||
if let Ok(p) = std::env::var("SYLPHEED_DISC") {
|
||||
if std::path::Path::new(&p).join("dat").is_dir() {
|
||||
return Some(p);
|
||||
}
|
||||
}
|
||||
let d = "/home/fabi/RE - Project Sylpheed/Project Sylpheed - Arc of Deception (USA, Europe) (En,Ja)";
|
||||
std::path::Path::new(d).join("dat").is_dir().then(|| d.to_string())
|
||||
}
|
||||
|
||||
/// `va -> offset -> value`, from the dump's `addr +off hex u32 f32` columns.
|
||||
fn live() -> BTreeMap<String, BTreeMap<usize, f32>> {
|
||||
let mut out: BTreeMap<String, BTreeMap<usize, f32>> = BTreeMap::new();
|
||||
let mut cur = String::new();
|
||||
for line in DUMP.lines() {
|
||||
if let Some(rest) = line.strip_prefix("=== ") {
|
||||
cur = rest.trim().to_string();
|
||||
continue;
|
||||
}
|
||||
let f: Vec<&str> = line.split_whitespace().collect();
|
||||
if f.len() >= 5 && f[1].starts_with('+') {
|
||||
if let (Ok(off), Ok(val)) = (usize::from_str_radix(&f[1][1..], 16), f[4].parse::<f32>())
|
||||
{
|
||||
out.entry(cur.clone()).or_default().insert(off, val);
|
||||
}
|
||||
}
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mapped_fields_match_the_disc_records() {
|
||||
let Some(disc) = disc_root() else {
|
||||
eprintln!("SKIP: extracted disc not found (set SYLPHEED_DISC to enable)");
|
||||
return;
|
||||
};
|
||||
let pak = PakArchive::open(format!("{disc}/dat/GP_MAIN_GAME_E.pak")).expect("main pak");
|
||||
let live = live();
|
||||
let floats: Vec<_> = fields().into_iter().filter(|f| f.kind == Kind::F32).collect();
|
||||
|
||||
let (mut agree, mut bad) = (0usize, Vec::new());
|
||||
for entry in pak.entries() {
|
||||
let Ok(bytes) = pak.read(entry) else { continue };
|
||||
let Ok(obj) = IdxdObject::parse(&bytes) else { continue };
|
||||
let Some(id) = obj.get_raw("ID") else { continue };
|
||||
let Some((_, va)) = IDENTIFIED.iter().find(|(i, _)| *i == id) else { continue };
|
||||
let Some(words) = live.get(*va) else { continue };
|
||||
for f in &floats {
|
||||
let (Some(want), Some(got)) = (obj.get_f32(f.name), words.get(&f.offset)) else {
|
||||
continue;
|
||||
};
|
||||
// Angle fields are degrees on disc and radians in the object.
|
||||
let rad = want * std::f32::consts::PI / 180.0;
|
||||
if (want - got).abs() <= want.abs() * 1e-4 || (rad - got).abs() <= rad.abs() * 1e-4 {
|
||||
agree += 1;
|
||||
} else {
|
||||
bad.push(format!("{id}.{} disc {want} vs live +{} = {got}", f.name, f.offset));
|
||||
}
|
||||
}
|
||||
}
|
||||
assert!(bad.is_empty(), "{} fields disagree: {bad:?}", bad.len());
|
||||
assert!(agree >= 400, "expected ≥400 agreeing fields, got {agree}");
|
||||
}
|
||||
@@ -3677,6 +3677,7 @@ fn exhaust_cone_mesh() -> sylpheed_formats::mesh::GameMesh {
|
||||
uvs: Vec::new(),
|
||||
indices,
|
||||
name: Some("exhaust".to_string()),
|
||||
vbuf_offset: None, // procedural, not read from a container
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -8,8 +8,30 @@ unknown, what evidence exists, and what the first step would be. Move an item in
|
||||
|
||||
## Capital ships assemble wrong in the viewer
|
||||
|
||||
**Reported:** 2026-07-30, by the user. **Status:** 🔎 **diagnosed 2026-08-10 — the
|
||||
format layer is exonerated.** Runtime captures of three classes (`f105`, `e105`,
|
||||
**Reported:** 2026-07-30, by the user. **Status:** ✅ **format-side cause found and
|
||||
fixed 2026-08-12** — see below for the 2026-08-10 diagnosis this supersedes.
|
||||
|
||||
The remaining format-side defect this entry pointed at (a shared turret decoding
|
||||
~100× too large in some containers) was real and is gone. `e303_wep_01` decoded
|
||||
as a 1600×2100×4800 block in `Stage_S02`, swallowing the `e106` hull; requiring an
|
||||
index buffer to cover its vertex pool **exactly** moved it to the block every
|
||||
other container agrees on, and it now decodes 49×23×42 everywhere and places at
|
||||
±179 on the hull. The same fix repaired `e106_bdy_03` (a 600×1600×998 slab) and
|
||||
moved 29 anchors disc-wide, 22 of which had been carrying **another resource's
|
||||
geometry under their own name**. See
|
||||
[`structures/xbg7-mesh.md`](structures/xbg7-mesh.md).
|
||||
|
||||
Two things are worth carrying forward rather than closing:
|
||||
- the assembler was **audited and exonerated** — every composite node carries
|
||||
scale 1.0 and an orthonormal matrix, so nothing on that side inflates a part;
|
||||
- **no metric caught this.** Coverage, cross-container consistency, the capture
|
||||
oracle and the twin invariant were all green while a 1 600-unit slab sat through
|
||||
the ship. It was found by *rendering the ship and looking at it*, and the
|
||||
numeric screens written afterwards to automate that check both failed.
|
||||
|
||||
The 2026-08-10 diagnosis follows, and its viewer-side pointers still stand.
|
||||
|
||||
**Status (2026-08-10):** 🔎 **the format layer is exonerated.** Runtime captures of three classes (`f105`, `e105`,
|
||||
`e106`) at controlled range reproduce `assemble_ship` to ≤0.43 units in translation
|
||||
and to 0.000 in rotation for every part that does not move; see
|
||||
[`ship-placement-capture-generalisation.md`](ship-placement-capture-generalisation.md)
|
||||
@@ -58,3 +80,225 @@ class cannot hide behind `e106` passing.
|
||||
handedness, node-instance recursion) is not re-breaking a correct assembly — compare
|
||||
the viewer's placement against `assemble_ship`'s output directly before blaming the
|
||||
format layer.
|
||||
|
||||
---
|
||||
|
||||
## Viewer: `include_external` is already on — that hypothesis is dead
|
||||
|
||||
**Checked 2026-08-11.** The item above names "first that it passes
|
||||
`include_external = true` (`iso_loader.rs:4012`)" as the cheap first step. It
|
||||
does: `ShipBrowser::show_external` defaults to `true`
|
||||
(`iso_loader.rs:643`), the checkbox reads it (`ui.rs:1593`) and it is threaded
|
||||
through `RequestShipRender` → `build_ship_model` → `assemble_ship` unchanged
|
||||
(`ui.rs:1689`, `iso_loader.rs:4012`). So a ship rendered by the viewer is the
|
||||
full external assembly, not the bare hull.
|
||||
|
||||
The viewer also does not have a transform stack of its own to blame: it bakes
|
||||
`ScenePart::apply` straight into the vertices and rotates normals by the same
|
||||
`p.m` (`iso_loader.rs:4030-4062`), so its placement is `assemble_ship`'s output
|
||||
by construction. What remains unexcluded, in order of cheapness: the mirror
|
||||
handling (`det < 0` reverses triangle winding only — a reflected part keeps its
|
||||
reflected geometry), `Xbg7Model::models_named` resolving the wrong sub-model when
|
||||
a resource name repeats, and the exhaust cones. **Next step is a visual**: the
|
||||
diagnosis has run out of things it can settle by reading, so the viewer needs to
|
||||
be run against a known-good class (`e106`) and its render compared with
|
||||
`ship_render`'s.
|
||||
|
||||
---
|
||||
|
||||
## Viewer: the duplicate-resource-name hypothesis is dead too
|
||||
|
||||
**Checked 2026-08-11.** The diagnosis above left three candidates for why capital
|
||||
ships assemble wrong in the viewer: mirror handling, `Xbg7Model::models_named`
|
||||
resolving the wrong sub-model when a resource name repeats, and the exhaust
|
||||
cones. The second is now **refuted**, and comprehensively.
|
||||
|
||||
`build_ship_model` resolves each placement with
|
||||
`base.iter().find(|m| m.name == p.resource)` (`iso_loader.rs:4041`) — first match
|
||||
wins — so a repeated resource name inside a container would silently draw the
|
||||
wrong geometry. It cannot happen: decoding **every** XBG7 resource in **all 22
|
||||
stage containers** gives **4 603 resources and zero repeated names**.
|
||||
|
||||
```
|
||||
Stage_S01 62/62 Stage_S07 323/323 Stage_S13 290/290 Stage_S25 351/351
|
||||
Stage_S02 304/304 Stage_S08 388/388 Stage_S14 22/22 Stage_S26 318/318
|
||||
Stage_S03 214/214 Stage_S09 316/316 Stage_S15 386/386 Stage_S27 321/321
|
||||
Stage_S04 179/179 Stage_S10 7/7 Stage_S16 65/65 Stage_S28 118/118
|
||||
Stage_S05 92/92 Stage_S11 157/157 Stage_S24 162/162 Stage_S29 386/386
|
||||
Stage_S06 266/266 Stage_S12 376/376
|
||||
```
|
||||
|
||||
Per-ship it is tighter still: `e106` wants 9 distinct names and decodes exactly
|
||||
9 models for 11 placements; `e105` 9 for 9; `f105` 5 for 6. Every placement
|
||||
resolves to the one model it names.
|
||||
|
||||
**So two of the three candidates are gone** (this one and `include_external`),
|
||||
leaving **mirror handling** and **the exhaust cones** — and the still-untried
|
||||
visual comparison, which remains the right next step.
|
||||
|
||||
---
|
||||
|
||||
## Viewer: mirror handling and the exhaust cones are cleared too — the static avenue is exhausted
|
||||
|
||||
**Checked 2026-08-11.** Both remaining candidates were tested across every ship
|
||||
on the disc, and neither shows the reported signature.
|
||||
|
||||
**Mirror handling.** The concern was that `ScenePart::apply` bakes `R·(S·v)+T`
|
||||
while the viewer takes its winding-flip decision from `det(m)` alone and rotates
|
||||
normals by `m` alone — both ignoring `s`. A mirror encoded as a *negative scale*
|
||||
would then reflect geometry without flipping winding, drawing the part
|
||||
inside-out. It never happens: across **1 485 assembled parts** in all 22
|
||||
containers there are **22 mirrored parts, every one with `det(m) < 0`**, and
|
||||
**zero** parts with a negative scale or a non-uniform one. `apply_twin_mirrors`
|
||||
writes the reflection into `m` (negating its X column), so the viewer's flip
|
||||
always fires, and ignoring `s` for normals is harmless because `s` is always
|
||||
uniform.
|
||||
|
||||
**Exhaust cones.** These are the one piece of geometry the viewer *invents* — a
|
||||
cone at each `GN_Jet`/`GN_SJet` frame, because the real engine geometry is
|
||||
recessed and the game draws FX there instead. If they landed wrongly they would
|
||||
read exactly as "a part in the wrong place". Across **335 assembled ships, 192 of
|
||||
which have exhaust frames, not one cone sits outside its hull's bounding box**
|
||||
(tolerance 10 % of the axis span).
|
||||
|
||||
**Caveat, stated rather than glossed:** "inside the hull box" does not prove a
|
||||
cone is *right* — orientation and size are untested, and a cone could be wrong
|
||||
while still inside. What it does rule out is the reported symptom for that part.
|
||||
|
||||
So every mechanism this diagnosis proposed is now eliminated: `include_external`,
|
||||
duplicate resource names, mirror handling, and cones-in-the-wrong-place. The
|
||||
format and assembly layers pass every static test available, and **the visual
|
||||
comparison is no longer merely the next step — it is the only remaining one.**
|
||||
Render `e106` in the viewer beside `ship_render`'s output of the same
|
||||
`assemble_ship` result; if they agree, the bug is in neither and the original
|
||||
report needs re-grounding against a specific ship and a specific expectation.
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ DIAGNOSED 2026-08-12 — a mis-decode; the locality fix was written, then withdrawn
|
||||
|
||||
> Resolution at the end of this entry. Kept in full because the two wrong turns
|
||||
> along the way (a "stray volume", then "monotonic anchoring") are the useful part.
|
||||
|
||||
## ⚠️ The format layer is NOT exonerated — but the cause is a MIS-DECODE, not a stray volume
|
||||
|
||||
**Found 2026-08-11 by finally doing the visual**, which the notes above kept
|
||||
naming as the next step. It overturns their conclusion.
|
||||
|
||||
Render `e106` from the static assembly and from the baked runtime capture and
|
||||
compare — `ship_render` does both:
|
||||
|
||||
| | placements | parts |
|
||||
|---|---|---|
|
||||
| runtime capture (ground truth) | **8** | `bdy_01…04`, `brg_01`, `eng_01`, `eng_02`, `wep_02_01` |
|
||||
| `assemble_ship(--static)` | **11** | the same 8, **plus `e303_wep_01` ×2** and a second `e106_eng_01` |
|
||||
|
||||
The render makes it obvious: the destroyer sits inside a white slab that dwarfs
|
||||
it ([capture](captures/e106-static-assembly-volume-bug.png)). That slab is
|
||||
`e303_wep_01`, and its own geometry is:
|
||||
|
||||
```
|
||||
e303_wep_01 172 verts, 110 tris bounds X[-1000, 600] Y[-1050, 1050] Z[-2400, 2400] 1600 x 2100 x 4800
|
||||
e106_wep_02_01 1002 verts, 772 tris 269 x 179 x 417 ← what a real e106 turret looks like
|
||||
e106_brg_01 202 verts, 202 tris 105 x 76 x 305
|
||||
```
|
||||
|
||||
**110 triangles, perfectly round axis-aligned bounds, and bigger than the ship it
|
||||
is mounted on.**
|
||||
|
||||
### CORRECTION (same day, one iteration later): it is not a volume — it is a bad decode
|
||||
|
||||
The first reading of this was that `e303_wep_01` is a collision/trigger volume
|
||||
the assembler wrongly draws. **That is wrong, and the evidence that settles it is
|
||||
decoding the same resource from every container that holds it:**
|
||||
|
||||
```
|
||||
Stage_S01 172 verts 110 tris X[-24.5, 24.5] Y[0.0, 23.4] Z[-20.8, 20.8] ← 49 × 23 × 42, a turret
|
||||
Stage_S02 172 verts 110 tris X[-1000, 600] Y[±1050] Z[±2400] ← 1600 × 2100 × 4800
|
||||
Stage_S03… 172 verts 110 tris 49 × 23 × 42 (correct)
|
||||
Stage_S08 … 1600 × 2100 × 4800
|
||||
Stage_S26 … 1600 × 2100 × 4800
|
||||
```
|
||||
|
||||
Same resource, same vertex and triangle count, **decoding correctly in eleven
|
||||
containers and wrongly in exactly three** (`Stage_S02`, `S08`, `S26`). So:
|
||||
|
||||
- the **placement is legitimate** — `e303_wep_01` is a small shared turret,
|
||||
cross-mounted on `e101` and `e106`, and at its true size it is unremarkable;
|
||||
- the original author's explanation of the capture's silence (**vbase dedup**)
|
||||
stands, and my "dedup would show one, not zero" objection does not survive:
|
||||
with the correct decode the turret is small, ordinary geometry;
|
||||
- **the defect is in the mesh decoder**, which resolved this resource's vertex
|
||||
data differently in three containers.
|
||||
|
||||
The render and the symptom are real; the cause named in the first version of this
|
||||
entry was not.
|
||||
|
||||
### The part that matters more than this one resource
|
||||
|
||||
**The decoder can produce wrong geometry without declining.** The
|
||||
[XBG7 audit](structures/xbg7-mesh.md) counted 814 resources it *refuses* — a
|
||||
visible, honest failure. This is the other kind: `e303_wep_01` decodes "fine" in
|
||||
`Stage_S02` and is silently 100× too large. Screening for the signature (bounds
|
||||
that are exact multiples of 50 with a span over 1000) flags 22–32 models in each
|
||||
of `S02`, `S03`, `S08`, `S26`, `S27` — **but that screen also catches legitimate
|
||||
`e_rou_*` composite proxies**, so it is a candidate list, not a count of bugs.
|
||||
|
||||
**Next:** diff the anchor scan's chosen `vb0` for `e303_wep_01` between
|
||||
`Stage_S01` (correct) and `Stage_S02` (wrong) — same resource, two outcomes, so
|
||||
the divergence is directly observable — then use whatever distinguishes them to
|
||||
add a post-decode sanity check, so a silent 100× mis-decode becomes a decline.
|
||||
|
||||
### Why this was missed
|
||||
|
||||
`assemble_ship` treats **every** `rou_*` node in the composite as a drawable
|
||||
part, and the doc comment states the cross-id mount as intended behaviour —
|
||||
`"INCLUDING repeated instances and cross-id turret mounts (rou_e303_wep_01_root
|
||||
×2 on the e106 hull)"` — with
|
||||
`ship::tests::static_assembly_matches_runtime_capture` asserting
|
||||
`count("e303_wep_01") == 2`. The absence from the capture was explained away as
|
||||
vbase dedup, but **dedup would show one instance, not zero**.
|
||||
|
||||
The test cannot catch it either: it walks the capture's parts and looks each up
|
||||
in the static output, so **extra** static placements are invisible to it. That is
|
||||
the same shape of gap as the earlier `include_external` hypothesis — a test that
|
||||
can only fail one way.
|
||||
|
||||
### Scope, stated carefully
|
||||
|
||||
Sweeping all 335 assembled ships for the signature *ship-scale span with under
|
||||
400 triangles* flags **20 ships and 58 placements** over 28 distinct resources
|
||||
(`e005_ant_*`, `f001_ant_*`, `f002_bdy_*`, `f301_barrel`, `f303_body`,
|
||||
`e303_wep_01`, …). **Only the `e106`/`e303_wep_01` case is proven** — by render,
|
||||
by capture absence, and by geometry. Some of the others may be legitimately large
|
||||
low-poly parts, and each needs the same three checks before being called a bug.
|
||||
|
||||
**Still true, and independent of the correction above:**
|
||||
`static_assembly_matches_runtime_capture` walks the capture's parts and looks each
|
||||
up in the static output, so **extra static placements can never fail it**. That is
|
||||
worth fixing regardless — it is the same one-way-test shape as the earlier
|
||||
`include_external` hypothesis.
|
||||
|
||||
Also unchanged: only **two** cross-id placements exist fleet-wide (`e303_wep_01`
|
||||
on `e101` ×24 and `e106` ×36, across 335 assembled ships), so cross-id mounting is
|
||||
a narrow, real feature rather than a systemic guess.
|
||||
|
||||
---
|
||||
|
||||
## Resolution (2026-08-12)
|
||||
|
||||
`anchor_pool_mesh` took the **first** candidate in file order from a
|
||||
container-global scan, so a resource could be handed another resource's block
|
||||
whenever both shared `(stride, vertex count, index count)`. Fixed by anchoring
|
||||
each resource near its **descriptor neighbours** (two-pass: learn, then re-anchor).
|
||||
|
||||
- it took inconsistency **125 → 51** with coverage unchanged, and made `e106`
|
||||
render correctly ([after](captures/e106-static-assembly-fixed.png))
|
||||
- **but it flipped the `e106` twin-mirror decision**, which
|
||||
`static_assembly_matches_runtime_capture` (ISO-gated, so it skips in a plain
|
||||
`cargo test`) catches against the runtime capture — so it was **reverted**
|
||||
- the user-reported "capital ships assemble wrong" is therefore **diagnosed, not
|
||||
yet fixed**; see [xbg7](structures/xbg7-mesh.md) for what the real fix needs
|
||||
|
||||
Still open from this entry: `static_assembly_matches_runtime_capture` walks only
|
||||
the capture's parts, so **extra** static placements still cannot fail it.
|
||||
|
||||
@@ -13,17 +13,19 @@ Promote to a prose `structures/…md` file when a format needs behavioural notes
|
||||
| IPFB `.pak` archive | ✅ | `sylpheed-formats/src/pak.rs` + `tests/pak_idxd_disc.rs` | header + 12-byte TOC, Z1/zlib payloads |
|
||||
| name-hash (TOC keys) | ✅ | `sylpheed-formats/src/hash.rs` | Barrett-reduction hash; recovers original paths |
|
||||
| IDXD object/table | ✅ | `sylpheed-formats/src/idxd.rs` | self-describing; ship/weapon stats verified vs known values |
|
||||
| XPR2 texture + cubemap | 🟡 | `sylpheed-formats/src/texture.rs` | de-tile + A8R8G8B8; **colours unverified** (dynamic item) |
|
||||
| T8aD 2D texture | 🟡 | `sylpheed-formats/src/t8ad.rs` | ~85% decode; **colours ✅ CONFIRMED** ([k8888](structures/texture-color-k8888.md)); ~15% variants deferred |
|
||||
| RATC bundle | 🟡 | `sylpheed-formats/src/ratc.rs` | child listing confirmed; one level deep |
|
||||
| LSTA sprite list | 🟡 | `sylpheed-formats/src/lsta.rs` | inline T8aD frames |
|
||||
| IXUD subtitle | 🟡 | `sylpheed-formats/src/ixud.rs` | timed cues; **movie↔track link unknown** (dynamic item) |
|
||||
| XPR2 texture + cubemap | 🟡/✅ | `sylpheed-formats/src/texture.rs` + [colour check](xpr2-colour-check.md) | de-tile + A8R8G8B8 and DXT1. **Channel order ✅ confirmed against the running game**: the Delta Saber's decoded atlas is orange-dominant (median saturated hue 23.3°, *zero* cool pixels) and the game renders the same hull at 9.3° — a red↔blue swap would sit at ≈200°. Exact fidelity (gamma/sRGB curve, premultiplied alpha, per-channel scale) is 🟡 untested, since a hue comparison cannot see it; cubemap face ordering ❔ |
|
||||
| T8aD 2D texture | ✅ | `sylpheed-formats/src/t8ad.rs` | **100 % of the disc decodes** (19 216/19 216, measured). The "~15 % deferred variants" were a wrong model, not a variant: a surface is a list of **arbitrary sub-rectangles**, each with a 16-byte header of `dst X, dst Y, width, height`, not a 256×256 grid — `0x1c` is the **rectangle count**. Uncovered area stays transparent. **Colours ✅ CONFIRMED** ([k8888](structures/texture-color-k8888.md)) |
|
||||
| RATC bundle | ✅ | `sylpheed-formats/src/ratc.rs` | child listing confirmed. **"One level deep" is not a limitation — there is nothing deeper**: 2 859 bundles hold 18 002 children at depth 1 and **0 at depth 2**, with no parse failures. Nested RATC blobs are **leaf records that reference siblings by name** (`opt `, the sprite name): 3 311 leaves, all embedding sibling names, **10 144 of 10 148 references resolve**. The 4 that do not are one dangling asset — `pmbase.rat` → `pmbase.t32` in `GP_STAGE_CLEAR.pak`'s four language builds, and `pmbase.t32` is **on the disc nowhere** |
|
||||
| LSTA sprite list | ✅ | `sylpheed-formats/src/lsta.rs` | A display list of inline elements: **T8aD sprites and `PRMD` primitives**. The `count` at `0x04` is **exact and counts both** — `count == T8aD + PRMD` for **64/64** lists on the disc, which retires the old "a few entries disagree" note (it compared sprites against a total including primitives). **All 1 281 sprite frames decode** after the T8aD rectangle-list fix |
|
||||
| IXUD subtitle | 🟡/✅ | `sylpheed-formats/src/ixud.rs` + [movie link](movie-subtitle-link.md) | timed cues. **The movie↔subtitle↔voice link is solved — statically**, from the movie config record in `tables.pak` (schema `0x067025b9`), not from the running game as this row previously assumed: [101 movies mapped](captures/movie-subtitle-voice-map.csv), 94 with subtitles, 83 with voice, 21 with a telop overlay. 93 of 94 subtitle refs resolve in the language paks; **`SUBTITLE_S12B.tbl` is missing from all six languages** — a dangling reference on the disc. Naming is `SUBTITLE_<base>.tbl` / `VOICE_<base>` with six documented exceptions. The record's ~104 **script ids** are ❔ — positional pairing drifts by three because the IDXD pool dedupes repeated values |
|
||||
| Fonts (ttf/otf/ttc) | ✅ | `sylpheed-formats/src/font.rs` | standard OpenType, parsed via ttf-parser |
|
||||
| XBG7 mesh | 🟡/❔ | `sylpheed-formats/src/mesh.rs` + `tests/mesh_disc.rs` ([xbg7](structures/xbg7-mesh.md)) | weapons/props: declaration-driven variable stride (36 models), GPU-confirmed. **Stage containers: 5662 sub-models across 22 stages** via content-anchored grouped pools (`stage_models`). Quantized hero bodies (DeltaSaber `f004`) still declined |
|
||||
| Capital-ship part placement | 🟡 | `sylpheed-formats/src/ship.rs` (static) + [runtime capture](ship-placement-runtime-capture.md) | hull placement static-exact; external parts approximate statically. **Runtime capture** (Canary F10 → VS-constant WorldView) gives ground truth — validated on `e106` destroyer; not yet baked into the viewer |
|
||||
| XBG7 mesh | ✅/🟡 | `sylpheed-formats/src/mesh.rs` + `tests/mesh_disc.rs` ([xbg7](structures/xbg7-mesh.md)) | **6 294 resources, 6 209 decode (98.7 %), 82 searched-and-missed** (2026-08-12, up from 5 480 / 87.1 %). Five evidence-driven fixes got there: **distinct anchor assignment** (no two resources may claim one buffer — proved by a capture showing the container holds both mirrored `e106` hull halves), the connectivity cap replaced by a **winding-consistency gate at 0.70**, **structural requirements on pre-pivot sub-meshes** (index range, then exact pool coverage), and **filtering after the assignment** so a subset query cannot differ from the full decode. Validated against a runtime capture that names the file offset of every buffer the engine drew: **46/46 drawn buffers claimed, 45 anchored exactly**. **No real mesh now decodes differently in different containers** — all 89 remaining cross-container disagreements are interchangeable 24-vertex bounding boxes, which no anchoring rule can pin (monotone order re-tested and refuted). Remaining misses attribute to the degeneracy/extent gate (42), winding (31) and coverage (9); the first was probed and its "obvious" fix refuted. Every decoded sub-mesh covers its own vertex pool. **The `[index buffer][vertex buffer]` layout is now runtime-verified** (2026-08-13): with the F10 capture extended to log each draw's index buffer, all **42** drawn `Stage_S02` buffers match our decoded index count exactly, all 42 have their index union cover the pool exactly, and the 30 single-block cases all sit at `pad ≤ 3` — so `e106_eng_02_l`'s old rejection was the connectivity gate, not a misplaced index buffer. The `indices=` mystery was the capture keeping only the **first of several index batches** per buffer. **And comparing index VALUES found the biggest silent defect yet**: the anchor took the first `pad` that validated, so a block whose index data sits at pad 2 was read **one element late** — 76/93 captured runs matched, all 17 differences a one-element shift. Scoring pads by degenerate triangles + winding fixes it: **93/93** captured runs now match byte for byte, disc-wide degenerate runs **582 → 1** (the grouped path had the same bug; and two resources were anchored on a degenerate lookalike earlier in file order), **590 of 8 850** sub-meshes re-wired with 10 vertex anchors moved, resources decoded unchanged at 6 209. Cross-container minority decodes 89 → 96 — *because* the decoder improved: `_rou_f402_dead` now has a majority (32×25×8) so its seven wrong copies are named instead of hidden. One dirty run remains, blocked by distinct assignment on a 24-vertex box. **Then the descriptor gave up its last structural secret**: it declares a vertex layout **per sub-mesh** (`n201_01` → strides 24/24/24/**28**, capture-confirmed), and grouped selection must prefer the candidate explaining the **whole** pool rather than the first whose pivot validates — together they take never-decoding resources **85 → 47** (**6 247 / 6 294 = 99.25 %** decode), put `n201_01` on all four capture-proven offsets and raise the stage-05 capture oracle to **128/128**. The residual 47 is 30 pose/proxy composites (0.010-unit marker boxes), 6 `.DAT` particle composites, 8 damage/LOD variants and 3 props — not a threshold away |
|
||||
| Capital-ship part placement | ✅ | `sylpheed-formats/src/ship.rs` (static) + [runtime capture](ship-placement-runtime-capture.md) | Placement is **sound** (hull static-exact against the `e106` capture; cross-id mounting genuinely narrow, 2 pairs across 335 ships). The XBG7 mis-decode this row used to blame for "ships assemble wrong" — a shared turret ~100× too large in some containers — is **fixed** (2026-08-12, the exact-coverage requirement): `e303_wep_01` now decodes 49×23×42 everywhere and places at ±179 on the `e106` hull, and no real mesh disagrees across containers. A composite-node audit confirmed the assembler itself never applied a bad scale (all nodes scale 1.0, orthonormal). Still open: `static_assembly_matches_runtime_capture` walks capture parts only, so **extra** static placements cannot fail it |
|
||||
| Weapon fields defaulted on disc | ✅ | [runtime struct](structures/weapon-struct-runtime.md) · [DATA SHEET route](weapon-datasheet-runtime.md) | **Solved.** Canary maps guest RAM into `/dev/shm`, so the parsed `Weapon`/`Shell` objects are readable live; their layout is solved against disc ground truth (zero contradictions over 100+ records). All 126 weapons, exact numbers, no story progress needed — [4 393 values](captures/weapon-runtime-fields.csv) the disc does not carry. Supersedes the letter-bucket limit of the DATA SHEET route, which now serves as the independent cross-check |
|
||||
| Unit (craft/vessel) fields defaulted on disc | ✅/🟡 | [runtime struct](structures/unit-struct-runtime.md) | The parsed `unit\UN_*.tbl` definition object, vtable `0x820af844`, ≥`0x380` bytes, one per unit — **discovered, not assumed** (`unit_discover.py`), and distinguished from the spawned-entity class `0x820af030` by being one-per-ID and byte-constant within a run. Across runs only pointer words move — `--crosscheck` proves **no reported field offset is run-dependent** (two words, `+0x2c8`/`+0x2d0`, are stage-dependent and remain unidentified). 27 fields ✅ (21 units, 7 runs); the `Maneuver` block is **schema declaration order, 4 bytes/field, base `0x9c` with a two-slot gap after `AA_Roll_Min`** (29 anchors, 0 conflicts), which also pins 5 fields *no* disc record ever values. Angles are **radians at runtime, degrees on disc**. Unlike weapons, unit definitions are instantiated **per stage**, so coverage (21/110) grows by visiting missions — [values](captures/unit-runtime-fields.csv) |
|
||||
| UI screen layout (`.rat`) | ✅/🟡 | [ui-rat-layout](structures/ui-rat-layout.md) | One pak per UI screen; each RATC = one (context × language) build; every `<name>.t32` sprite has a `<name>.rat` **layout record** (BE u32; 1280×720 design space; scale/tint/X/Y, keyframes for animated elements, `opt ` link to the focused state). **The tutorial PAUSE menu and the title main menu both rebuild pixel-accurately from the disc.** `loop1.rat` (screen-level draw order) not yet decoded |
|
||||
| Unit (craft/vessel) fields defaulted on disc | ✅/🟡 | [runtime struct](structures/unit-struct-runtime.md) | The parsed `unit\UN_*.tbl` definition object, vtable `0x820af844`, ≥`0x380` bytes, one per unit — **discovered, not assumed** (`unit_discover.py`), and distinguished from the spawned-entity class `0x820af030` by being one-per-ID and byte-constant within a run. Across runs only pointer words move — `--crosscheck` proves **no reported field offset is run-dependent** (two words, `+0x2c8`/`+0x2d0`, are stage-dependent and remain unidentified). 27 fields ✅ (21 units, 7 runs); the `Maneuver` block is **schema declaration order, 4 bytes/field, base `0x9c` with a two-slot gap after `AA_Roll_Min`** (29 anchors, 0 conflicts), which also pins 5 fields *no* disc record ever values. Angles are **radians at runtime, degrees on disc**. **Re-derived independently 2026-08-13 from the loader's own key strings** (`sub_82341A20`; the field name for each store is a string in the image): **159 fields**, agreeing with this solver on **25 of 25 shared offsets**, verified at **406 values matching the disc and 0 disagreeing** over 11 live objects spanning UNIT and VESSEL — landed as `data/unit_definition_layout.txt` + `sylpheed_formats::unit_layout` + a no-emulator test, with **121 defaulted fields** read out ([live-unit-definitions](live-unit-definitions.md)). Unlike weapons, unit definitions are instantiated **per stage**, so coverage (21/110) grows by visiting missions — but a defaulted field is **not** a global constant: `Size_Y` provably inherits `Size_X` (7 independent units, 6 distinct values), and three more sibling rules are recorded ❔, recovering 65 values in units never visited — [values](captures/unit-runtime-fields.csv) |
|
||||
| Arsenal develop economy | ✅/❔ | [arsenal-develop-economy](arsenal-develop-economy.md) + [conditions](captures/arsenal-develop-conditions.csv) | The Arsenal reads `weapon.tbl` (item ids, in the 8-category display order) and `strings.tbl` (names, descriptions, and a **"Conditions to obtain"** block per item) out of `GP_HANGAR_ARSENAL.pak`. All **60** conditions are extracted: gates are stage completion, a predecessor item, or an **ace kill**; costs run 3 000–350 000 P and **20 items are free** once gated. `weapon.tbl`'s first record reproduces the in-game DATA SHEET exactly (Range D / Power E / Speed – / Weight 0.3 = Light / 4000 P) — later records are unreadable from the string pool alone because IDXD **dedupes repeated values**. Used to identify the save blob's index space, now **solved**: the blob follows **`strings.tbl`'s** order — the display order *plus* the cut items only the localisation file lists (`Adhesive Mine B2A`, `Ballista GSH`, …) — pinned by four hand-written probe saves (9 Stiletto, 21 Falcon, 39 Tomahawk, 48 Jamming System) and closing exactly at index 53. `weapon.tbl`'s id list is **not** the index space; that it is also 54 long is a coincidence, and the two agree only to index 32. The retail save's five unexplained owned entries are the cut items, shipped owned and never rendered |
|
||||
| UI screen layout (`.rat`) | ✅/🟡 | [ui-rat-layout](structures/ui-rat-layout.md) | One pak per UI screen; each RATC = one (context × language) build; every `<name>.t32` sprite has a `<name>.rat` **layout record** (BE u32; 1280×720 design space; scale/tint/X/Y, keyframes for animated elements, `opt ` link to the focused state). **The tutorial PAUSE menu and the title main menu both rebuild pixel-accurately from the disc.** `loop1.rat` is decoded — it is a **looping sprite animation**, not a composition. The **screen's draw list is the RATC bundle's own declaration table** (elements in back-to-front order, including the `eff*`/`deli*`/`msg` sprites that have no `.rat`, and excluding focused button variants reached via `opt `); its entry also carries a **parent element index** at `+32`. **A screen is fully reconstructible from its bundle**: the placement region right after the declaration table gives every element a keyframe group (header = element index + keyframe count, then 40-byte blocks of scale/tint/X/Y), including the `.rat`-less sprites — verified 11/11 on the tutorial pause bundle, with `pgp_ttrl_btn10`'s inline (546,288) matching its own record exactly |
|
||||
| Save file (`savedata`) | ✅/❔ | [savegame-format](structures/savegame-format.md) + [`tools/re-capture/savegame.py`](../../tools/re-capture/savegame.py) | `GDHA` container, zlib payload, chunk stream (`GDAA` / phase name / `GHAD` 122 B progress block / 16×20 B slot table / trailer). **Container and layout read off the title's own serializer `0x822C00E8` and verified by a byte-identical round-trip**; the whole save is 545 B. Payload offsets are also the live save object's offsets (`save+8` GHAD, `save+136` slots). A second save made in-game names **Points** (+24), **flight time in ms** (+4) and **clear ratio %** (+8) off the game's own Details panel; the payload is a **pure function of game state** (same state saved twice = byte-identical, only the header FILETIME and its uninitialised pointer padding move), and the 16 `SHAB` records are **not** the UI's 20 save slots. Difficulty vs stage is undecided — three fields hold 2. **A third save, taken after developing exactly one Arsenal weapon** (Light Machine Gun MG I, 4000 P), moves exactly three things: `+24` Points 4101→101 (which **separates it from `+28`**, that did not move), `+8` clear ratio 5→6 (so the ratio counts *collection*, not only stages), and two entries of the 54-byte blob — `2→4` for the item bought and `0→2` for the successor the game announced as newly developable, giving the blob its alphabet ✅ *0 locked / 2 developable / 4 developed* (only the `4`s are stored — `2` is re-derived at load). **Saves can also be written back**: three derived header fields (length at `+0x30`, payload length at `+0x8c`, `adler32` at `+0x8e`) are all that stand between a parse and a hand-written save that the title loads, and [`savegame_edit.py`](../../tools/re-capture/savegame_edit.py) re-wraps a real save byte-identically. That turned the blob's index space from blocked-on-story-progress into four probe saves — see the [economy note](arsenal-develop-economy.md) |
|
||||
|
||||
## Runtime / dynamic-capture technique
|
||||
|
||||
@@ -34,6 +36,7 @@ Promote to a prose `structures/…md` file when a format needs behavioural notes
|
||||
| Live entity state, anchored on the definition | ✅ | [`tools/re-capture/own_state.py`](../../tools/re-capture/own_state.py) · [autopilot](autopilot-memory-driven.md) | An undamaged craft holds its definition's own numbers, so a *solved definition field* locates the matching live field without a value scan: definition `HP` (1500) → **hull at `position+0x154`**, confirmed by a trace across a death (30/60/90 per hit, negative at 0). Reusable for any live counter whose maximum the definition carries |
|
||||
| Mission / escort state, every entity's hull | ✅ | [`tools/re-capture/mission_state.py`](../../tools/re-capture/mission_state.py) · [escort state](mission-escort-state.md) | `hull = position + 0x154` is a property of the **entity class**, not of the player object: at t=0 it equals each entity's own definition `HP` across 7 classes and 5 distinct HP values (turret 100, fighter 500, destroyer 10000, cruiser 30000, **ACROPOLIS 25000**), falls under fire (780 damage events in 240 s), goes negative at death, and the object then leaves the heap. So an escort objective is scoreable live — `UN_f101_TCAF_Acropolis` measured at 25000 → 23038 over 240 s, attack starting only at t≈170 s. `REMAINING OB` counts objectives, not hostiles (012 on the HUD vs 118 live ADAN); its address is still ❔ |
|
||||
| In-flight control mapping | ✅/🟡 | [`tools/re-capture/fire_probe.sh`](../../tools/re-capture/fire_probe.sh) · [controls](flight-controls-runtime.md) | Measured by holding each pad input and photographing the HUD ammo counters: **`RB` = nose gun** (6000→5956 in 4 s, ~11 rounds/s, HEAT rises), **`Y` = main mount** (missiles, 300→299), d-pad = **tactical map** overlay, nothing else moves a counter. No target-cycle input exists — the `TARGET` marker is present with nothing pressed, so targeting is automatic and a missile lock is **time-on-target**. That, not target choice or ballistics, is what caps lethality at 2 kills per 98 missiles |
|
||||
| Throttle → speed law | ✅/🟡 | [flight-speed-law](flight-speed-law.md) | **The throttle selects a TARGET SPEED**, it does not add thrust: no input settles at ~420 (`CruisingVelocity` 350), `RT` at ~1 530 (`MaximumVelocity` 1200), `LT` at ~125 (`MinimumVelocity` 100), and releasing either returns to cruise. `Acceleration`/`Deceleration` govern the convergence rate (measured ~440–560 units/s² against 500/600). 🟡 measured world speeds run ≈1.2–1.3× the definition numbers while the HUD shows the definition value exactly, so world units are a constant (~1.25) multiple of the definition's velocity unit |
|
||||
| Input → dynamics calibration | ✅ | [`tools/re-capture/ctrl_probe.py`](../../tools/re-capture/ctrl_probe.py) · [`binq.py`](../../tools/re-capture/binq.py) | Hold each pad input in turn and measure the craft's speed as displacement/s of its own position triple — no speed field needed first. Settled the throttle: **`RT` accelerates, `LT` brakes, and the setting persists** (488 → 1510 → 174 units/s), overturning an earlier field-scan conclusion |
|
||||
|
||||
## Functions / code paths
|
||||
|
||||
69
docs/re/SESSION-2026-08-11.md
Normal file
@@ -0,0 +1,69 @@
|
||||
# Session log — 2026-08-11 (autonomous run)
|
||||
|
||||
What changed today, in the order a reviewer would want it, with the one item
|
||||
that needs **your decision** called out first.
|
||||
|
||||
## Needs a decision from you
|
||||
|
||||
**Editing save slot 01.** Save files can now be written back
|
||||
([savegame_edit.py](../../tools/re-capture/savegame_edit.py) re-wraps a real save
|
||||
**byte-identically**), and hand-written saves load. That unblocked several
|
||||
questions by probing throwaway slots. But the **title-menu screens read the
|
||||
auto-save (slot 01)**, so the remaining save-field questions (the difficulty
|
||||
enum, `SHAB.b`) need slot 01 itself edited — the only save with real progress.
|
||||
Not done. Recoverable (full content backup + slot 01's bytes archived), but it is
|
||||
your call. Everything else below was done in throwaway slots.
|
||||
|
||||
## Findings, by area
|
||||
|
||||
### Save format
|
||||
- **The develop differential**: buying one Arsenal weapon moves exactly three
|
||||
fields — Points `+24` (which **separates it from `+28`**), clear ratio `+8`
|
||||
(so the ratio counts *collection*, not stages), and two entries of the 54-byte
|
||||
blob. Blob alphabet: `0` locked / `2` developable / `4` owned; only the `4`s
|
||||
are stored, `2` is re-derived at load.
|
||||
- **The blob's index space is solved** — it follows `strings.tbl`'s item order,
|
||||
**cut items included**, pinned by four probe saves and closing exactly at 53.
|
||||
- **`SHAB` is the per-stage record table**: record 0 = Stage 01, and `c` is its
|
||||
best clear time in ms (`324773` = the `05:24.77` MISSION SELECT shows).
|
||||
- **Refuted**: every GHAD scalar as stage/difficulty (16 elements probed), and
|
||||
"stage = filled `SHAB` count + 1".
|
||||
|
||||
### Formats
|
||||
- **T8aD 96 % → 100 %** of the disc: a surface is a list of **sub-rectangles**
|
||||
(`dst X, dst Y, w, h` per rectangle), not a 256 grid. The "~15 % variants" were
|
||||
a wrong model.
|
||||
- **LSTA**: the count was never unreliable — it counts **sprites *and* `PRMD`
|
||||
primitives** (64/64 exact).
|
||||
- **RATC**: "one level deep" is not a limitation; nested records are **leaves
|
||||
that reference siblings by name** (10 144/10 148 resolve).
|
||||
- **UI screens are fully reconstructible from their bundle** — element list +
|
||||
order (declaration table, with a **parent index**) and placement/animation
|
||||
(keyframe region). **Validated against the running game to ±2 px.**
|
||||
- **Movie ↔ subtitle ↔ voice link solved statically** (101 movies) — `INDEX` had
|
||||
it filed as needing the running game.
|
||||
- **XPR2 channel order confirmed** against the game (hue 23.3° vs 9.3°).
|
||||
|
||||
### Meshes — the user-reported viewer bug
|
||||
- Root-caused, **after one wrong turn I corrected in place**: the giant slab on
|
||||
`e106` is not a stray volume but an **XBG7 mis-decode** — the same resource is a
|
||||
49×23×42 turret in 11 containers and 1600×2100×4800 in 3.
|
||||
- **New detector**: a resource shared across containers must decode to the same
|
||||
bounds. **125 of 681** shared resources fail that check — a measured lower
|
||||
bound on *silent* mis-decodes (distinct from the 814 honest declines).
|
||||
|
||||
## Two disc defects found
|
||||
`SUBTITLE_S12B.tbl` (referenced by the movie table, present in **no** language)
|
||||
and `pmbase.t32` (placed by `pmbase.rat`, present **nowhere**). A reimplementation
|
||||
should skip a missing asset rather than treat it as a decode failure.
|
||||
|
||||
## Process notes worth keeping
|
||||
- Several "the data is inconsistent" comments meant **our model was incomplete**
|
||||
(T8aD variants, the LSTA count). Re-test such notes rather than routing around
|
||||
them.
|
||||
- A test that walks the *oracle's* items and looks each up in our output **can
|
||||
only fail one way** — it never sees extras. That shape hid the ship bug and,
|
||||
earlier, `include_external`.
|
||||
- When a diagnosis has eliminated every hypothesis and the last step is *a
|
||||
visual*, do the visual first. Three iterations went by before a 30-second
|
||||
render showed it.
|
||||
255
docs/re/arsenal-develop-economy.md
Normal file
@@ -0,0 +1,255 @@
|
||||
# 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](structures/savegame-format.md#the-develop-differential-one-weapon-three-fields) —
|
||||
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 — verified item-for-item against the running game (see the all-owned probe
|
||||
below). Note one **coincidence that cost time**: the run `No_Equipment … Wep_83`
|
||||
is also 54 entries, the save blob's length, which invites the conclusion that this
|
||||
list *is* the blob's index space. It is not — the two agree only up to index 32.
|
||||
The blob follows `strings.tbl`'s order, which carries items this list omits.
|
||||
|
||||
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`](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**
|
||||
(`Tomahawk Alpha Rail Gun — Complete Stage 1`). Those are still *bought*, for
|
||||
**0 P** — see the probe below, which caught one at `0 P` — which is why they
|
||||
read as `Developed` in a list the player never spent points in;
|
||||
- two items are anomalies worth flagging: `Adhesive Mine B2A` says "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.
|
||||
|
||||
## How the first 32 entries were 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** `2`s sit
|
||||
at WEAPONS-order indices **1, 5, 10, 12, 27, 31**, which are those six items, in
|
||||
order. Nothing had to be fitted.
|
||||
|
||||
Indices 0–32 are the same item in either order, so this table stands unchanged
|
||||
under the final mapping.
|
||||
|
||||
| blob | value | item | 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.
|
||||
|
||||
## The full index space ✅ — it is the **localisation** order, cut items included
|
||||
|
||||
Written after the probes below, which settled it. The blob is **not** indexed by
|
||||
`weapon.tbl`'s `WEAPONS` id list. It is indexed by the order the items appear in
|
||||
`strings.tbl` — the same order, *plus* the handful of items that exist only there:
|
||||
`Adhesive Mine B2A` (a "Conditions to obtain" block whose cost is missing) and the
|
||||
four late entries with a conditions *header* and no conditions. Those never appear
|
||||
in the Arsenal, but they **hold slots in the save**, and that is the whole reason
|
||||
the tail looked broken.
|
||||
|
||||
| blob | block | items (display names, in order) |
|
||||
|---|---|---|
|
||||
| 0 | — | `No_Equipment` |
|
||||
| 1–8 | GUN | Light Machine Gun MG1, Light Swivel MG2H, Heavy MG3, Heavy Swivel MG5H, Broad Sword SG1, Swing Sword SG2H, Twin Sword SG3II, Flail GP37 |
|
||||
| 9–16 | BEAM | **Stiletto BG1** ⌾, Dagger BG2, Rapier BG4H, Pilum BP, Multi Pike BP, Spear HBP, Long Spear HBP, Gray Head T76H |
|
||||
| 17–20 | LASER | Saber LG1, Twin Saber LG2H, Needle L1GP, Fire Arrow L2GP |
|
||||
| 21–25 | MPM | **Falcon 9AM** ⌾, Buzzard 10AM, Hawk 75AM, Condor 105AM, Eagle 120AM |
|
||||
| 26–30 | ASM | Terrier SMH, Hound SMH, Pointer SM, Piranha T53, White Shark T53R |
|
||||
| 31–38 | BOMB/ROCKET | **Dart 23 Rocket** ⌾, Arrow 27 Rocket, *Adhesive Mine B2A — never displayed*, EMP Mine B8E, Laser Mine B9L, Cauldron 50 Rocket, Cluster Mine B10, Maelstrom Bomb |
|
||||
| 39–47 | CANNON | **Tomahawk Alpha Rail Gun** ⌾, Sling 75KG, Glaive 120mm Cannon, Arbalest 155KG, Monoceros Long Cannon, Grav Cannon XGS, *+ three never-displayed slots* |
|
||||
| 48–53 | SPECIAL | **Jamming System** ⌾, Shield Doubler, Booster, Fire Control System, Regenerator, Cartridge Holder |
|
||||
|
||||
⌾ = pinned by an anchor: `9`, `21`, `39` and `48` were each **written into a save by
|
||||
hand** and read back off the screen; `31` is the Dart 23 Rocket's price row in the
|
||||
untouched save. The array ends exactly at 53 with the last SPECIAL item, with
|
||||
nothing left over — 8+8+4+5+5+8+9+6 plus `No_Equipment` is 54.
|
||||
|
||||
The three never-displayed CANNON-block slots are three of `Ballista GSH`,
|
||||
`Designator LH`, `Smoke Marker Launcher`, `Bumble UV` — the four cut entries
|
||||
`strings.tbl` lists between Grav Cannon XGS and Jamming System. **Which three** is
|
||||
🟡 undetermined and does not matter for reading a save; nothing displays them.
|
||||
|
||||
**This explains every anomaly in the retail save.** Its `4`s at 33 and at 45/46/47
|
||||
are the cut items — `Adhesive Mine B2A` and three of the four above — shipped
|
||||
flagged as owned. They never render, so no player ever sees them, and they are
|
||||
exactly the five "unexplained" owned entries that made the tail look wrong. The
|
||||
Tomahawk reading `Developed` at `blob[39]` while `weapon.tbl` puts it at 38 was
|
||||
the same off-by-N, seen from the other side.
|
||||
|
||||
## How the tail was actually settled (and what was wrong first)
|
||||
|
||||
Past index 31 the `weapon.tbl` alignment fails, and it fails against the screen,
|
||||
not against a theory — this is the evidence that sent the investigation to the
|
||||
localisation order above:
|
||||
|
||||
- `Tomahawk_a_Rail_Gun` is WEAPONS index **38** and the CANNON list shows it
|
||||
`Developed` (it is free once Stage 1 is complete) — but **blob[38] = 0**.
|
||||
- The blob's remaining `4`s are at **33, 39, 45, 46, 47**. In WEAPONS order those
|
||||
are `EMP_Mine_B8E`, `Sling_75KG`, `Shield_Doubler`, `Booster`,
|
||||
`FireControlSystem` — and **every one of them is shown as not developed**:
|
||||
BOMB/ROCKET lists only `DART 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 `4`s 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`](../../tools/re-capture/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 shows `STILETTO BG1 Developed`; `21` →
|
||||
MULTIPURPOSE MISSILE shows `FALCON 9AM Developed`. So a hand-written `4` does
|
||||
reach the screen, and indices 9 and 21 are those items — now by experiment,
|
||||
not by correlation.
|
||||
- **The tail probes rendered nothing.** `33` should have made a BOMB/ROCKET row
|
||||
`Developed` (row 3 of that list, on screen, not below the fold) — it stayed
|
||||
dashed. `45` should have made a SPECIAL row `Developed` — the SPECIAL tab
|
||||
stayed entirely empty. And `38` should have been the Tomahawk — instead the
|
||||
CANNON list showed `TOMAHAWK ALPHA RAIL GUN — 0 P`, i.e. *not owned*.
|
||||
- **Clearing the real save's tail `4`s cost the Tomahawk its status.** In the
|
||||
unedited save the Tomahawk reads `Developed` while `blob[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 `+1` shift past index 32 would explain that, but it fails elsewhere:
|
||||
under it the real save's `45/46/47` are Chaff Flare Dispencer / Shield Doubler
|
||||
/ Booster, and the SPECIAL tab is empty in that same save.
|
||||
|
||||
Three further probes settled it, and the reasoning is worth keeping because the
|
||||
first read of the same evidence was wrong.
|
||||
|
||||
1. **Every slot owned.** With all 54 entries set to `4`, every tab filled and the
|
||||
lists **wrap**, which gives exact counts: GUN 8, BEAM 8, LASER 4, MPM 5, ASM 5,
|
||||
BOMB/ROCKET **7**, CANNON 6, SPECIAL 6 — 43 items — and the display order is
|
||||
`weapon.tbl`'s order, item for item. Note what this probe *cannot* do: with
|
||||
everything owned, any surjective mapping looks the same. It bounds, it does not
|
||||
pin. It also killed the "gate-unmet flags are suppressed" idea outright —
|
||||
Maelstrom Bomb (Stage 6) and Grav Cannon XGS (Stage 12) both rendered
|
||||
`Developed` at a Stage-2 save.
|
||||
2. **One slot owned: `39`.** `blob = 0` except `9` (control) and `39` → BEAM showed
|
||||
`STILETTO BG1 Developed` **and CANNON showed `TOMAHAWK ALPHA RAIL GUN
|
||||
Developed`**. So the CANNON block starts at 39, not 38: exactly one slot more
|
||||
than the display between Dart 23 (31) and the Tomahawk.
|
||||
3. **One slot owned: `48`.** `blob = 0` except `39` and `48` → SPECIAL showed
|
||||
`JAMMING SYSTEM Developed`. That fixes the SPECIAL base, and with six SPECIAL
|
||||
items the array closes exactly at 53.
|
||||
|
||||
The earlier probe's `45 = 4` leaving SPECIAL empty — which had looked like the
|
||||
mapping failing — is simply a cut item: 45 is in the CANNON block's
|
||||
never-displayed tail, not in SPECIAL at all.
|
||||
|
||||
### Two behaviours the probe exposed
|
||||
|
||||
- **`2` is 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; writing `2` is pointless, and writing `0` over an
|
||||
item whose conditions are met is undone at load.
|
||||
- **The free items are bought for zero, not granted.** `TOMAHAWK ALPHA RAIL GUN
|
||||
— 0 P` is what an unowned no-cost item looks like. That is the real reason a
|
||||
save where the player never spent points still shows items as `Developed`
|
||||
(this note previously said "granted" — the mechanism is a 0 P purchase).
|
||||
|
||||
## Evidence
|
||||
|
||||
- [`captures/arsenal-develop-conditions.csv`](captures/arsenal-develop-conditions.csv) — all 60 conditions blocks.
|
||||
- [`captures/arsenal-categories-all.png`](captures/arsenal-categories-all.png) — the eight category tabs.
|
||||
- [`captures/arsenal-gun-below-fold.png`](captures/arsenal-gun-below-fold.png) — Broad Sword SG1, the entry that is not visible without scrolling.
|
||||
- [`captures/arsenal-probe-save-result.png`](captures/arsenal-probe-save-result.png) — the hand-written save's result: controls at 9/21 render `Developed`, the tail probes render nothing, and the Tomahawk shows `0 P`.
|
||||
- [`captures/arsenal-gun-list-predevelop.png`](captures/arsenal-gun-list-predevelop.png) · [`captures/arsenal-mg1-developed.png`](captures/arsenal-mg1-developed.png) — the differential itself.
|
||||
BIN
docs/re/captures/arsenal-all-owned-br-cannon.png
Normal file
|
After Width: | Height: | Size: 590 KiB |
BIN
docs/re/captures/arsenal-categories-all.png
Normal file
|
After Width: | Height: | Size: 860 KiB |
61
docs/re/captures/arsenal-develop-conditions.csv
Normal file
@@ -0,0 +1,61 @@
|
||||
item,stage_required,points_cost,other_conditions,raw_text
|
||||
Light Machine Gun MG1,1,4000,,Conditions to obtain Light Machine Gun MG1 - Complete Stage 1 - Must spend 4000 points to develop
|
||||
Light Swivel Machine Gun MG2H,,3000,Completed development of the Light Machine Gun MG1,Conditions to obtain Light Swivel Machine Gun MG2H - Completed development of the Light Machine Gun MG1 - Must spend 3000 points to develop
|
||||
Heavy Machine Gun MG3,6,6000,,Conditions to obtain Heavy Machine Gun MG3 - Complete Stage 6 - Must spend 6000 points to develop
|
||||
Heavy Swivel Machine Gun MG5H,,10000,Completed development of the Heavy Machine Gun MG3,Conditions to obtain Heavy Swivel Machine Gun MG5H - Completed development of the Heavy Machine Gun MG3 - Must spend 10000 points to develop
|
||||
Broad Sword SG1,1,5000,,Conditions to obtain Broad Sword SG1 - Complete Stage 1 - Must spend 5000 points to develop
|
||||
Swing Sword SG2H,,10000,Completed development of the Twin Sword SG3II,Conditions to obtain Swing Sword SG2H - Completed development of the Twin Sword SG3II - Must spend 10000 points to develop
|
||||
Twin Sword SG3II,,8000,Completed development of the Broad Sword SG1,Conditions to obtain Twin Sword SG3II - Completed development of the Broad Sword SG1 - Must spend 8000 points to develop
|
||||
Flail GP37,6,,,Conditions to obtain Flail GP37 - Complete Stage 6
|
||||
Stiletto BG1,,,Initially Mounted,Conditions to obtain Stiletto BG1 - Initially Mounted
|
||||
Dagger BG2,,5000,Development Possible,Conditions to obtain Dagger BG2 - Development Possible - Must spend 5000 points to develop
|
||||
Rapier BG4H,,12000,Completed development of the Dagger BG2,Conditions to obtain Rapier BG4H - Completed development of the Dagger BG2 - Must spend 12000 points to develop
|
||||
Pilum BP,,6000,Development Possible,Conditions to obtain Pilum BP - Development Possible - Must spend 6000 points to develop
|
||||
Multi Pike BP,,30000,Completed development of the Pilum BP,Conditions to obtain Multi Pike BP - Completed development of the Pilum BP - Must spend 30000 points to develop
|
||||
Spear HBP,,10000,Completed development of the Multi Pike BP,Conditions to obtain Spear HBP - Completed development of the Multi Pike BP - Must spend 10000 points to develop
|
||||
Long Spear HBP,7,350000,Completed development of the Gray Head T76H,Conditions to obtain Long Spear HBP - Complete Stage 7 - Completed development of the Gray Head T76H - Must spend 350000 points to develop
|
||||
Gray Head T76H,,25000,Completed development of the Spear HBP,Conditions to obtain Gray Head T76H - Completed development of the Spear HBP - Must spend 25000 points to develop
|
||||
Saber LG1,14,,,Conditions to obtain Saber LG1 - Complete Stage 14
|
||||
Twin Saber LG2H,15,,,Conditions to obtain Twin Saber LG2H - Complete Stage 15
|
||||
Needle L1GP,6,10000,,Conditions to obtain Needle L1GP - Complete Stage 6 - Must spend 10000 points to develop
|
||||
Fire Arrow L2GP,,20000,Completed development of the Needle L1GP,Conditions to obtain Fire Arrow L2GP - Completed development of the Needle L1GP - Must spend 20000 points to develop
|
||||
Falcon 9AM,,,Initially Mounted,Conditions to obtain Falcon 9AM - Initially Mounted
|
||||
Buzzard 10AM,1,,,Conditions to obtain Buzzard 10AM - Complete Stage 1
|
||||
Hawk 75AM,6,10000,Possess Buzzard 10AM,Conditions to obtain Hawk 75AM - Complete Stage 6 - Possess Buzzard 10AM - Must spend 10000 points to develop
|
||||
Condor 105AM,6,,,Conditions to obtain Condor 105AM - Complete Stage 6
|
||||
Eagle 120AM,12,15000,Completed development of the Hawk 75AM,Conditions to obtain Eagle 120AM - Complete Stage 12 - Completed development of the Hawk 75AM - Must spend 15000 points to develop
|
||||
Terrier SMH,1,,,Conditions to obtain Terrier SMH - Complete Stage 1
|
||||
Hound SMH,,10000,Development Possible,Conditions to obtain Hound SMH - Development Possible - Must spend 10000 points to develop
|
||||
Pointer SM,,40000,Completed development of Piranha T53,Conditions to obtain Pointer SM - Completed development of Piranha T53 - Must spend 40000 points to develop
|
||||
Piranha T53,,25000,Obtain White Shark T53R,Conditions to obtain Piranha T53 - Obtain White Shark T53R - Must spend 25000 points to develop
|
||||
White Shark T53R,6,,,Conditions to obtain White Shark T53R - Complete Stage 6
|
||||
Dart 23 Rocket,1,3500,,Conditions to obtain Dart 23 Rocket - Complete Stage 1 - Must spend 3500 points to develop
|
||||
Arrow 27 Rocket,2,5000,,Conditions to obtain Arrow 27 Rocket - Complete Stage 2 - Must spend 5000 points to develop
|
||||
Adhesive Mine B2A,3,MISSING,,Conditions to obtain Adhesive Mine B2A - Complete Stage 3 - Must spend points to develop
|
||||
EMP Mine B8E,6,50000,,Conditions to obtain EMP Mine B8E - Complete Stage 6 - Must spend 50000 points to develop
|
||||
Laser Mine B9L,6,15000,,Conditions to obtain Laser Mine B9L - Complete Stage 6 - Must spend 15000 points to develop
|
||||
Cauldron 50 Rocket,14,,,Conditions to obtain Cauldron 50 Rocket - Complete Stage 14
|
||||
Cluster Mine B10,6,5000,,Conditions to obtain Cluster Mine B10 - Complete Stage 6 - Must spend 5000 points to develop
|
||||
Maelstrom Bomb,6,200000,,Conditions to obtain Maelstrom Bomb - Complete Stage 6 - Must spend 200000 points to develop
|
||||
Tomahawk Alpha Rail Gun,1,,,Conditions to obtain Tomahawk Alpha Rail Gun - Complete Stage 1
|
||||
Sling 75KG,2,5000,,Conditions to obtain Sling 75KG - Complete Stage 2 - Must spend 5000 points to develop
|
||||
Glaive 120mm Cannon,6,,,Conditions to obtain Glaive 120mm Cannon - Complete Stage 6
|
||||
Arbalest 155KG,6,25000,,Conditions to obtain Arbalest 155KG - Complete Stage 6 - Must spend 25000 points to develop
|
||||
Monoceros Long Cannon,,,Shoot down the ace pilot in Stage 14,Conditions to obtain Monoceros Long Cannon - Shoot down the ace pilot in Stage 14
|
||||
Grav Cannon XGS,12,60000,,Conditions to obtain Grav Cannon XGS - Complete Stage 12 - Must spend 60000 points to develop
|
||||
Ballista GSH,,,,Conditions to obtain Ballista GSH
|
||||
Designator LH,,,,Conditions to obtain Designator LH
|
||||
Smoke Marker Launcher,,,,Conditions to obtain Smoke Marker Launcher
|
||||
Bumble UV,,,,Conditions to obtain Bumble UV
|
||||
Jamming System,3,15000,,Conditions to obtain Jamming System - Complete Stage 3 - Must spend 15000 points to develop
|
||||
Shield Doubler,12,50000,,Conditions to obtain Shield Doubler - Complete Stage 12 - Must spend 50000 points to develop
|
||||
Booster,,,Shoot down the ace pilot in Stage 11,Conditions to obtain Booster - Shoot down the ace pilot in Stage 11
|
||||
Fire Control System,6,15000,,Conditions to obtain Fire Control System - Complete Stage 6 - Must spend 15000 points to develop
|
||||
Regenerator,12,100000,,Conditions to obtain Regenerator - Complete Stage 12 - Must spend 100000 points to develop
|
||||
Cartridge Holder,,,Shoot down the ace pilot in Stage 13,Conditions to obtain Cartridge Holder - Shoot down the ace pilot in Stage 13
|
||||
Ballista 24 Rocket Launcher,,,,Conditions to obtain Ballista 24 Rocket Launcher
|
||||
Thrush 220AM,,,,Conditions to obtain Thrush 220AM
|
||||
Divider L3GP,,,,Conditions to obtain Divider L3GP
|
||||
Mace GP25H,,,,Conditions to obtain Mace GP25H
|
||||
Thor Gun System,,,,Conditions to obtain Thor Gun System
|
||||
Spitfire BX,,,,Conditions to obtain Spitfire BX
|
||||
|
BIN
docs/re/captures/arsenal-gun-below-fold.png
Normal file
|
After Width: | Height: | Size: 158 KiB |
BIN
docs/re/captures/arsenal-gun-list-predevelop.png
Normal file
|
After Width: | Height: | Size: 764 KiB |
BIN
docs/re/captures/arsenal-layout-validation.png
Normal file
|
After Width: | Height: | Size: 620 KiB |
50
docs/re/captures/arsenal-main-screen-layout.txt
Normal file
@@ -0,0 +1,50 @@
|
||||
=== eng\prmain_scr.prt ===
|
||||
23 elements
|
||||
# element parent kind pivot kf placement
|
||||
0 preff01.t32 - 0x0 (164,156) 5 rest (256,214) t=6..22 [6:256,214 22:256,214 31:256,214 42:256,214 1:256,214]
|
||||
1 preff02.t32 - 0x0 (155,175) 7 rest (156,121) t=8..37 [4:305,240 6:193,150 7:165,128 8:156,121 37:155,120 44:155,120 2:305,240]
|
||||
2 preff03.t32 - 0x0 (155,175) 7 rest (374,269) t=8..37 [4:225,150 6:337,240 7:365,262 8:374,269 37:375,270 44:375,270 3:225,150]
|
||||
3 preff04.t32 - 0x0 (247,237) 6 rest (173,133) t=8..37 [4:173,133 6:173,133 8:173,133 37:173,133 44:173,133 4:173,133]
|
||||
4 prwinbase.t32 - 0x0 (136,130) 5 rest (284,240) t=14..35 [6:284,240 14:284,240 35:284,240 43:284,240 5:284,240]
|
||||
5 prbtn1.rat - 0x3002 (28,14) 5 rest (242,166) t=16..33 [10:242,166 16:242,166 33:242,166 37:242,166 6:242,166]
|
||||
6 prbtn2.rat - 0x3002 (43,14) 5 rest (242,220) t=17..33 [11:242,220 17:242,220 33:242,220 37:242,220 7:242,220]
|
||||
7 prbtn3.rat - 0x3002 (58,14) 5 rest (242,276) t=18..33 [12:242,276 18:242,276 33:242,276 37:242,276 8:242,276]
|
||||
8 prbtn4.rat - 0x3002 (89,14) 5 rest (242,331) t=19..33 [13:242,331 19:242,331 33:242,331 37:242,331 9:242,331]
|
||||
9 prbtn5.rat - 0x3002 (89,14) 5 rest (242,385) t=20..33 [14:242,385 20:242,385 33:242,385 37:242,385 10:242,385]
|
||||
10 prbtn6.rat - 0x3002 (104,14) 5 rest (242,441) t=21..33 [15:242,441 21:242,441 33:242,441 37:242,441 11:242,441]
|
||||
11 prbtn7.rat - 0x3002 (58,14) 5 rest (242,496) t=22..33 [16:242,496 22:242,496 33:242,496 37:242,496 12:242,496]
|
||||
12 prbtn8.rat - 0x3002 (30,14) 5 rest (242,551) t=23..33 [17:242,551 23:242,551 33:242,551 37:242,551 13:242,551]
|
||||
13 prexp1.t32 - 0x0 (277,30) 8 rest (726,143) t=31..37 [21:726,143 25:1286,143 27:866,143 28:756,143 29:731,143 31:726,143 37:726,143 14:1286,143]
|
||||
14 prexp1a.t32 13 0x1 (277,30) 3 rest (1126,143) t=31..37 [31:1126,143 37:1126,143 15:1126,143]
|
||||
15 prexp3.t32 - 0x0 (205,5) 5 rest (726,381) t=10..29 [10:726,381 29:726,381 31:726,381 39:726,381 16:726,381]
|
||||
16 prmsg.t32 - 0x0 (330,19) 5 rest (151,645) t=21..29 [21:151,645 29:151,645 31:151,645 37:151,645 17:151,645]
|
||||
17 prexp3.t32 - 0x4 (205,5) 5 rest (726,415) t=10..29 [10:726,415 29:726,415 31:726,415 39:726,415 18:726,415]
|
||||
18 prexp3.t32 - 0x4 (205,5) 5 rest (726,449) t=10..29 [10:726,449 29:726,449 31:726,449 39:726,449 19:726,449]
|
||||
19 prexp3.t32 - 0x4 (205,5) 5 rest (726,483) t=10..29 [10:726,483 29:726,483 31:726,483 39:726,483 20:726,483]
|
||||
20 prexp3.t32 - 0x4 (205,5) 5 rest (726,517) t=10..29 [10:726,517 29:726,517 31:726,517 39:726,517 21:726,517]
|
||||
21 prexp3.t32 - 0x4 (205,5) 5 rest (726,551) t=10..29 [10:726,551 29:726,551 31:726,551 39:726,551 22:726,551]
|
||||
22 prexp3.t32 - 0x4 (205,5) 5 rest (726,585) t=39..1869640736 [10:726,585 29:726,585 31:726,585 39:726,585 1869640736:726,585]
|
||||
=== eng\prselect_scr.prt ===
|
||||
8 elements
|
||||
# element parent kind pivot kf placement
|
||||
0 prselect_win1.t32 - 0x0 (256,200) 10 rest (127,155) t=8..23 [4:-516,155 6:-71,155 7:81,155 8:127,155 23:134,155 24:134,155 25:127,155 27:81,155 31:-71,155 1:-516,155]
|
||||
1 prselect_win2.t32 0 0x1 (256,200) 1 (135,560)
|
||||
2 prselect_win1eff.t32 0 0x1 (256,200) 5 rest (71,92) t=2..14 [2:71,92 14:71,92 21:71,92 27:71,92 3:71,92]
|
||||
3 prselect_win2eff.t32 0 0x1 (256,200) 5 rest (71,499) t=2..14 [2:71,499 14:71,499 21:71,499 27:71,499 4:71,499]
|
||||
4 prselect_sbar.sbo 0 0x1 (256,200) 1 (614,172)
|
||||
5 prselect_type_line.t32 - 0x0 (260,24) 5 rest (123,142) t=14..21 [8:123,142 14:123,142 21:123,142 25:123,142 6:123,142]
|
||||
6 prselect_msg.t32 - 0x0 (468,19) 5 rest (151,645) t=11..19 [11:151,645 19:151,645 21:151,645 27:151,645 7:151,645]
|
||||
7 prselect_msgbtn1.t32 - 0x2 (128,19) 5 rest (809,645) t=27..1869640736 [11:809,645 19:809,645 21:809,645 27:809,645 1869640736:809,645]
|
||||
=== eng\prselect_win3.prt ===
|
||||
2 elements
|
||||
# element parent kind pivot kf placement
|
||||
0 prselect_win3.t32 - 0x0 (245,300) 4 rest (704,39) t=10..16 [8:704,39 10:704,39 16:704,39 1:704,39]
|
||||
1 prselect_win3eff.t32 - 0x0 (309,352) 4 rest (640,0) t=16..1869640736 [8:640,0 10:640,0 16:640,0 1869640736:640,0]
|
||||
=== eng\prtitle.prt ===
|
||||
5 elements
|
||||
# element parent kind pivot kf placement
|
||||
0 preff11.t32 - 0x0 (221,6) 6 rest (-160,82) t=16..35 [12:-440,82 14:-440,82 16:-160,82 35:0,82 41:0,82 1:-440,82]
|
||||
1 preff12.t32 - 0x0 (75,3) 5 rest (0,72) t=20..34 [14:0,72 20:0,72 34:0,72 38:0,72 2:0,72]
|
||||
2 preff13.t32 - 0x0 (53,3) 5 rest (407,78) t=22..33 [16:407,78 22:407,78 33:407,78 37:407,78 3:407,78]
|
||||
3 preff14.t32 - 0x0 (52,3) 5 rest (427,68) t=24..32 [18:427,68 24:427,68 32:427,68 36:427,68 4:427,68]
|
||||
4 prtitle.t32 - 0x0 (94,35) 7 rest (162,26) t=37..1869640736 [20:142,26 23:142,26 25:157,26 26:161,26 31:162,26 37:162,26 1869640736:142,26]
|
||||
BIN
docs/re/captures/arsenal-mg1-developed.png
Normal file
|
After Width: | Height: | Size: 478 KiB |
BIN
docs/re/captures/arsenal-probe-blob39-tomahawk.png
Normal file
|
After Width: | Height: | Size: 430 KiB |
BIN
docs/re/captures/arsenal-probe-blob48-jamming.png
Normal file
|
After Width: | Height: | Size: 148 KiB |
BIN
docs/re/captures/arsenal-probe-save-result.png
Normal file
|
After Width: | Height: | Size: 381 KiB |
115
docs/re/captures/disc-unit-signatures.txt
Normal file
@@ -0,0 +1,115 @@
|
||||
|
||||
|
||||
id hp size_x size_y size_z kind
|
||||
UN_e006_ADAN_Vindicator_MargrasF 6400 12 5 30 unit
|
||||
UN_be005_ADAN_SpaceFortress 10000 2500 3100 2600 unit
|
||||
UN_f001_TCAF_DeltaSaber_T 1000 10 7 29 unit
|
||||
UN_f002_TCAF_DeltaSaber_W 1000 10 7 29 unit
|
||||
UN_e009_ADAN_Phantom 1000 18 16 24 unit
|
||||
UN_e001_ADAN_Elan_EX4 500 25 10 30 unit
|
||||
UN_e011_ADAN_Attacker_B 1500 200 50 100 unit
|
||||
UN_f001_TCAF_DeltaSaber_T_Ttrl 1000 10 7 29 unit
|
||||
UN_f202_TCAF_Cargo 4000 1400 600 2000 unit
|
||||
UN_e007_ADAN_Turret_EX4 100 20 9 22 unit
|
||||
UN_e010_ADAN_Attacker_S 500 100 40 50 unit
|
||||
UN_be001_ADAN_TerrafoamingUnit 10000 2600 2200 — unit
|
||||
UN_e005_ADAN_ElanTypeQ_Margras 3200 24 15 38 unit
|
||||
UN_e001_ADAN_Elan_GR_Violeta 1200 25 10 30 unit
|
||||
UN_e004_ADAN_ElanPlus_N 800 24 10 30 unit
|
||||
UN_e201_ADAN_ISCMissile 3000 300 — 2800 unit
|
||||
UN_f001_TCAF_DeltaSaber_T_Player_Ttrl1 1500 10 7 29 unit
|
||||
UN_f001_TCAF_DeltaSaber_T_Player_Ttrl2 1500 10 7 29 unit
|
||||
UN_f003_TCAF_ArrowHead_EX4 300 10 5 20 unit
|
||||
UN_f003_TCAF_ArrowHead_EX5 300 10 5 20 unit
|
||||
UN_e011_ADAN_Attacker_B_HF 2250 200 50 100 unit
|
||||
UN_e015_ADAN_Puppy — 7 5.5 10 unit
|
||||
Ship_Test 100 20 14 29 unit
|
||||
UN_e010_ADAN_Attacker_S_HF 750 100 40 50 unit
|
||||
Test_ADAN_PrometheusDriver 10000 1 — — unit
|
||||
UN_f001_TCAF_DeltaSaber_T_EX5 1000 10 7 29 unit
|
||||
UN_e002_ADAN_Elan_N 800 20 10 30 unit
|
||||
UN_e008_ADAN_TurretPlus 300 24 9 35 unit
|
||||
UN_mn040_Asteroid_Big 10000 2000 3500 — unit
|
||||
UN_mn500_ADAN_FloatingMine 1000 200 — — unit
|
||||
UN_S04_Asteroid_cmesh_01 10000 46900 55500 13600 unit
|
||||
UN_S05_Asteroid_cmesh_01 10000 10400 13000 34400 unit
|
||||
UN_S04_Asteroid_cmesh_03 10000 42800 37700 47400 unit
|
||||
UN_S04_Asteroid_cmesh_04 10000 59000 56900 21800 unit
|
||||
UN_S05_Asteroid_cmesh_03 10000 8300 13000 19800 unit
|
||||
UN_S04_Asteroid_cmesh_05 — 26000 21800 72400 unit
|
||||
UN_S05_Asteroid_cmesh_04 10000 7400 15800 30100 unit
|
||||
UN_e011_ADAN_Attacker_B_EX4 1500 200 50 100 unit
|
||||
UN_e910_core_ADAN_GeneratorCore 10000 2000 — — unit
|
||||
UN_n001_TTRL_Box 100 0.1 — — unit
|
||||
UN_e006_ADAN_Vindicator_Margras 6400 12 5 30 unit
|
||||
UN_e010_ADAN_Attacker_S_EX4 500 100 40 50 unit
|
||||
UN_bf001_TCAF_SchlosBase 10000 2500 3100 2600 unit
|
||||
UN_f001_TCAF_DeltaSaber_T_EX5_el 1000 10 7 29 unit
|
||||
UN_n001_TTRL_Box_move 100 0.1 — — unit
|
||||
UN_e004_ADAN_ElanPlus_NF 800 24 10 30 unit
|
||||
Test_ADAN_PrometheusDriver_InsideP2_01 10000 1 — — unit
|
||||
UN_e001_ADAN_Elan 500 25 10 30 unit
|
||||
UN_e007_ADAN_Turret 100 20 9 22 unit
|
||||
UN_e003_ADAN_ElanPlus_Margras 1600 24 10 30 unit
|
||||
UN_e901_ADAN_Boss 10000 500 1700 — unit
|
||||
UN_e013_ADAN_ElanPlus_Taskent 1300 24 10 30 unit
|
||||
UN_e011_ADAN_Attacker_B_HF_Wayne 3000 200 50 100 unit
|
||||
UN_f003_TCAF_ArrowHead 300 10 5 20 unit
|
||||
UN_f004_TCAF_DeltaSaber_A_Player 1500 10 7 29 unit
|
||||
UN_e010_ADAN_Attacker_S_HF_Wayne 1000 100 40 50 unit
|
||||
UN_e015_ADAN_Puppy_2 — 7 5.5 10 unit
|
||||
UN_S01_Asteroid_cmesh_01a — 23000 21700 13400 unit
|
||||
UN_S01_Asteroid_cmesh_02a 10000 20900 24000 23800 unit
|
||||
UN_S01_Asteroid_cmesh_01b — 18000 20900 14200 unit
|
||||
UN_S01_Asteroid_cmesh_02b — 20400 21800 26700 unit
|
||||
UN_S01_Asteroid_cmesh_03a 10000 23600 11700 30300 unit
|
||||
UN_bf002_TCAF_TransitPlatform 10000 2500 3100 4600 unit
|
||||
UN_e001_ADAN_Elan_GR 800 25 10 30 unit
|
||||
UN_S13_Asteroid_cmesh_01a 10000 14200 10200 14700 unit
|
||||
UN_S01_Asteroid_cmesh_03b — 21800 18600 37200 unit
|
||||
UN_S01_Asteroid_cmesh_04a — 32800 19900 23300 unit
|
||||
UN_S13_Asteroid_cmesh_01b 10000 8000 5400 16400 unit
|
||||
UN_S13_Asteroid_cmesh_02a 10000 6200 5400 11300 unit
|
||||
UN_S01_Asteroid_cmesh_04b — 26400 23100 19600 unit
|
||||
UN_S04_Asteroid_cmesh_02a — 25100 20800 61400 unit
|
||||
UN_S13_Asteroid_cmesh_02b 10000 5100 6900 10500 unit
|
||||
UN_S05_Asteroid_cmesh_02a 10000 10100 13200 9100 unit
|
||||
UN_S04_Asteroid_cmesh_02b 10000 25200 19700 67000 unit
|
||||
UN_S05_Asteroid_cmesh_02b 10000 8700 14900 12300 unit
|
||||
UN_S08_Asteroid_cmesh_01a 10000 14700 14900 21300 unit
|
||||
UN_S08_Asteroid_cmesh_01b 10000 14900 14600 15800 unit
|
||||
UN_S08_Asteroid_cmesh_02a 10000 17400 13400 15200 unit
|
||||
UN_S04_Asteroid_cmesh_06a — 14400 20800 45900 unit
|
||||
UN_S08_Asteroid_cmesh_02b 10000 16800 10700 14200 unit
|
||||
UN_S08_Asteroid_cmesh_03a — 14000 10000 13700 unit
|
||||
UN_S04_Asteroid_cmesh_06b 10000 14100 16200 21500 unit
|
||||
UN_S04_Asteroid_cmesh_07a — 23200 14000 67100 unit
|
||||
UN_S08_Asteroid_cmesh_03b 10000 13900 15100 15700 unit
|
||||
UN_S04_Asteroid_cmesh_07b 10000 25300 4900 40600 unit
|
||||
UN_e008_ADAN_TurretPlus_EX4 300 24 9 35 unit
|
||||
UN_f001_TCAF_DeltaSaber_T_Player 1500 10 7 29 unit
|
||||
UN_f003_TCAF_ArrowHead_Yoji 400 10 5 20 unit
|
||||
UN_f002_TCAF_DeltaSaber_W_Player 1500 10 7 29 unit
|
||||
UN_e104_ADAN_Carrier 20000 2300 1000 3300 vessel
|
||||
UN_f106_TCAF_Destroyer 10000 200 — 2000 vessel
|
||||
UN_e105_ADAN_Cruiser 30000 600 — 3800 vessel
|
||||
UN_e101_ADAN_SDBattleship 100000 1900 2100 12300 vessel
|
||||
UN_f105_TCAF_Cruiser 30000 700 — 3800 vessel
|
||||
UN_f102_TCAF_LightCarrier 20000 500 — 1600 vessel
|
||||
UN_e102_ADAN_BattleshipEX 50000 1900 2300 6500 vessel
|
||||
UN_e107_ADAN_AAFrigate_EX4 4000 100 200 — vessel
|
||||
UN_e102_ADAN_Battleship 50000 1900 2300 6500 vessel
|
||||
UN_f104_TCAF_Battleship 30000 2000 — 6000 vessel
|
||||
UN_f105_TCAF_Cruiser_EX5 30000 700 — 3800 vessel
|
||||
UN_e108_ADAN_ASFrigateEX 4000 80 — 350 vessel
|
||||
UN_f106_TCAF_Destroyer_Inv 10000 200 — 2000 vessel
|
||||
UN_e107_ADAN_AAFrigate 4000 100 200 — vessel
|
||||
UN_f104_TCAF_Battleship_EX5 30000 2000 — 6000 vessel
|
||||
UN_f105_TCAF_Cruiser_Inv 30000 700 — 3800 vessel
|
||||
UN_f102_TCAF_LightCarrier_Inv 20000 500 — 1600 vessel
|
||||
UN_e108_ADAN_ASFrigate 4000 80 — 350 vessel
|
||||
UN_e106_ADAN_DestroyerEX 10000 300 — 2100 vessel
|
||||
UN_f101_TCAF_Acropolis 25000 400 — 1400 vessel
|
||||
UN_e105_ADAN_CruiserEX 30000 600 — 3800 vessel
|
||||
UN_e101_ADAN_SDBattleshipEX 100000 1900 2100 12300 vessel
|
||||
UN_e106_ADAN_Destroyer 10000 300 — 2100 vessel
|
||||
BIN
docs/re/captures/e106-cover-slack-before-after.png
Normal file
|
After Width: | Height: | Size: 43 KiB |
BIN
docs/re/captures/e106-static-assembly-fixed.png
Normal file
|
After Width: | Height: | Size: 26 KiB |
BIN
docs/re/captures/e106-static-assembly-volume-bug.png
Normal file
|
After Width: | Height: | Size: 15 KiB |
161
docs/re/captures/escort-decay-ab.csv
Normal file
@@ -0,0 +1,161 @@
|
||||
run,turret_rule,t_s,acropolis_hull,e007_alive,e007_killed_cum
|
||||
mission01,off,0,25000.0,0,0
|
||||
mission01,off,10,25000.0,5,0
|
||||
mission01,off,20,25000.0,6,0
|
||||
mission01,off,30,25000.0,6,0
|
||||
mission01,off,40,25000.0,6,0
|
||||
mission01,off,50,25000.0,8,0
|
||||
mission01,off,60,25000.0,11,0
|
||||
mission01,off,70,25000.0,11,3
|
||||
mission01,off,80,25000.0,8,3
|
||||
mission01,off,90,25000.0,8,3
|
||||
mission01,off,100,25000.0,10,3
|
||||
mission01,off,110,25000.0,14,3
|
||||
mission01,off,120,25000.0,16,3
|
||||
mission01,off,130,25000.0,17,3
|
||||
mission01,off,140,25000.0,20,3
|
||||
mission01,off,150,25000.0,23,3
|
||||
mission01,off,160,25000.0,26,4
|
||||
mission01,off,170,24509.5,33,4
|
||||
mission01,off,180,24509.5,37,4
|
||||
mission01,off,190,24419.5,38,5
|
||||
mission01,off,200,24212.5,41,5
|
||||
mission01,off,210,23447.5,40,5
|
||||
mission01,off,220,23447.5,40,5
|
||||
mission01,off,220,23447.5,40,5
|
||||
mission01,off,230,23447.5,41,5
|
||||
mission01,off,230,23447.5,41,5
|
||||
mission01,off,240,22912.0,43,5
|
||||
mission01,off,240,22867.0,43,5
|
||||
mission01,off,250,22667.0,42,6
|
||||
mission01,off,250,22647.0,42,6
|
||||
mission01,off,260,22334.5,44,6
|
||||
mission01,off,260,22177.9,44,6
|
||||
mission01,off,270,21101.2,46,6
|
||||
mission01,off,270,21101.2,45,6
|
||||
mission01,off,280,20292.6,46,6
|
||||
mission01,off,280,20279.1,46,6
|
||||
mission01,off,290,19598.5,58,6
|
||||
mission01,off,290,19598.5,58,6
|
||||
mission01,off,300,18725.2,58,6
|
||||
mission01,off,300,18658.5,47,6
|
||||
mission01,off,310,18231.9,47,6
|
||||
mission01,off,310,18231.9,47,6
|
||||
mission01,off,320,17425.2,47,6
|
||||
mission01,off,330,17046.9,49,6
|
||||
mission01,off,340,16044.8,49,7
|
||||
mission01,off,350,15461.5,50,7
|
||||
mission01,off,360,14956.3,51,7
|
||||
mission01,off,370,14425.5,51,8
|
||||
mission01,off,380,13910.5,51,8
|
||||
mission01,off,390,13418.9,49,11
|
||||
mission01,off,400,13218.9,45,12
|
||||
mission01,off,410,12517.9,44,12
|
||||
mission01,off,420,12161.3,44,13
|
||||
mission01,off,430,11601.3,44,13
|
||||
mission02,on,0,25000.0,0,0
|
||||
mission02,on,10,25000.0,5,0
|
||||
mission02,on,20,25000.0,6,0
|
||||
mission02,on,30,25000.0,5,0
|
||||
mission02,on,40,25000.0,5,1
|
||||
mission02,on,50,25000.0,7,1
|
||||
mission02,on,60,25000.0,10,1
|
||||
mission02,on,70,25000.0,7,4
|
||||
mission02,on,80,25000.0,7,4
|
||||
mission02,on,90,25000.0,7,4
|
||||
mission02,on,100,25000.0,7,4
|
||||
mission02,on,110,25000.0,9,4
|
||||
mission02,on,120,25000.0,14,4
|
||||
mission02,on,130,25000.0,16,4
|
||||
mission02,on,140,25000.0,19,4
|
||||
mission02,on,150,25000.0,19,4
|
||||
mission02,on,160,25000.0,23,5
|
||||
mission02,on,170,24730.0,28,5
|
||||
mission02,on,180,24730.0,32,5
|
||||
mission02,on,190,24730.0,36,5
|
||||
mission02,on,200,24640.0,43,6
|
||||
mission02,on,210,24190.0,46,6
|
||||
mission02,on,220,24055.0,47,6
|
||||
mission02,on,220,24055.0,47,6
|
||||
mission02,on,230,24055.0,49,6
|
||||
mission02,on,230,24055.0,49,6
|
||||
mission02,on,240,23474.5,52,6
|
||||
mission02,on,240,23474.5,52,6
|
||||
mission02,on,250,23474.5,53,6
|
||||
mission02,on,250,23474.5,53,6
|
||||
mission02,on,260,23474.5,54,6
|
||||
mission02,on,260,23474.5,54,6
|
||||
mission02,on,270,22697.0,54,6
|
||||
mission02,on,270,22697.0,54,6
|
||||
mission02,on,280,22091.2,56,6
|
||||
mission02,on,280,22091.2,55,7
|
||||
mission02,on,290,21577.9,55,7
|
||||
mission02,on,290,21555.4,55,7
|
||||
mission02,on,300,20799.1,56,7
|
||||
mission02,on,310,19781.1,55,7
|
||||
mission02,on,320,18925.8,56,8
|
||||
mission02,on,330,18592.5,56,8
|
||||
mission02,on,340,17432.5,57,8
|
||||
mission02,on,350,16932.6,59,8
|
||||
mission02,on,360,16171.6,58,9
|
||||
mission02,on,370,15017.6,58,11
|
||||
mission02,on,380,14217.2,59,11
|
||||
mission02,on,390,13322.2,59,12
|
||||
mission02,on,400,12808.9,58,13
|
||||
mission02,on,410,12090.6,61,16
|
||||
mission02,on,420,11440.6,66,19
|
||||
mission02,on,430,11127.3,55,19
|
||||
mission03,on,0,25000.0,0,0
|
||||
mission03,on,10,25000.0,3,0
|
||||
mission03,on,20,25000.0,8,0
|
||||
mission03,on,30,25000.0,5,0
|
||||
mission03,on,40,25000.0,6,0
|
||||
mission03,on,50,25000.0,8,0
|
||||
mission03,on,60,25000.0,9,0
|
||||
mission03,on,70,25000.0,16,0
|
||||
mission03,on,80,25000.0,10,0
|
||||
mission03,on,90,25000.0,11,0
|
||||
mission03,on,100,25000.0,10,2
|
||||
mission03,on,110,25000.0,15,2
|
||||
mission03,on,120,25000.0,12,2
|
||||
mission03,on,130,25000.0,10,4
|
||||
mission03,on,140,25000.0,11,6
|
||||
mission03,on,150,25000.0,14,6
|
||||
mission03,on,160,25000.0,17,7
|
||||
mission03,on,170,24685.0,24,7
|
||||
mission03,on,180,24640.0,23,7
|
||||
mission03,on,190,24640.0,27,7
|
||||
mission03,on,200,24460.0,26,8
|
||||
mission03,on,210,24100.0,28,8
|
||||
mission03,on,220,24100.0,29,9
|
||||
mission03,on,230,24100.0,28,11
|
||||
mission03,on,230,24100.0,28,11
|
||||
mission03,on,240,23810.0,35,11
|
||||
mission03,on,240,23810.0,36,11
|
||||
mission03,on,250,23630.0,36,12
|
||||
mission03,on,250,23630.0,36,12
|
||||
mission03,on,260,23630.0,38,13
|
||||
mission03,on,260,23630.0,38,13
|
||||
mission03,on,270,22998.0,39,13
|
||||
mission03,on,270,22908.0,39,13
|
||||
mission03,on,280,22197.5,40,13
|
||||
mission03,on,280,22188.5,40,13
|
||||
mission03,on,290,21602.4,38,13
|
||||
mission03,on,290,21602.4,38,13
|
||||
mission03,on,300,21042.4,39,13
|
||||
mission03,on,300,20975.8,39,13
|
||||
mission03,on,310,20529.1,41,15
|
||||
mission03,on,310,20529.1,41,15
|
||||
mission03,on,320,19812.5,38,17
|
||||
mission03,on,320,19767.5,38,17
|
||||
mission03,on,330,19434.2,39,18
|
||||
mission03,on,340,18785.9,37,19
|
||||
mission03,on,350,18339.2,37,19
|
||||
mission03,on,360,17528.1,38,19
|
||||
mission03,on,370,16901.5,37,19
|
||||
mission03,on,380,16260.7,35,19
|
||||
mission03,on,390,15708.2,37,19
|
||||
mission03,on,400,15346.2,37,19
|
||||
mission03,on,410,14412.1,36,20
|
||||
mission03,on,420,13951.9,37,22
|
||||
mission03,on,430,13211.9,35,23
|
||||
|
BIN
docs/re/captures/f101-index-shift-before-after.png
Normal file
|
After Width: | Height: | Size: 45 KiB |
BIN
docs/re/captures/hangar-loadout-screen.png
Normal file
|
After Width: | Height: | Size: 918 KiB |
BIN
docs/re/captures/mission-select-stage01-record.png
Normal file
|
After Width: | Height: | Size: 1.2 MiB |
BIN
docs/re/captures/mission01-t485.png
Normal file
|
After Width: | Height: | Size: 1.7 MiB |
BIN
docs/re/captures/mission03-turretrule-t430.png
Normal file
|
After Width: | Height: | Size: 1.3 MiB |
102
docs/re/captures/movie-subtitle-voice-map.csv
Normal file
@@ -0,0 +1,102 @@
|
||||
movie,telop,subtitle,voicetrack
|
||||
logo1.wmv,,,
|
||||
logo2.wmv,,,
|
||||
logo3.wmv,,,
|
||||
logo4.wmv,,,
|
||||
ADV.wmv,,,VOICE_ADV
|
||||
SYLPH_HD720p_8M-CBR_2ch.wmv,pwterop_s01a.prt,SYLPH_HD720p_8M-CBR_2ch.tbl,
|
||||
S00A.wmv,,SUBTITLE_S00A.tbl,VOICE_S00A
|
||||
S01A.wmv,,SUBTITLE_S01A.tbl,VOICE_S01A
|
||||
RT01A.wmv,pwrt01.prt,SUBTITLE_RT01A.tbl,VOICE_RT01A
|
||||
RT01B.wmv,,SUBTITLE_RT01B.tbl,VOICE_RT01B
|
||||
RT01C_1.wmv,,SUBTITLE_RT01C_1.tbl,VOICE_RT01C_1
|
||||
RT01C_2.wmv,,SUBTITLE_RT01C_2.tbl,VOICE_RT01C_2
|
||||
S02A.wmv,pwterop_s02a.prt,SUBTITLE_S02A.tbl,VOICE_S02A
|
||||
S02B.wmv,,SUBTITLE_S02B.tbl,VOICE_S02B
|
||||
S02C.wmv,,SUBTITLE_S02C.tbl,VOICE_S02C
|
||||
RT02A.wmv,pwrt02.prt,SUBTITLE_RT02A.tbl,VOICE_RT02A
|
||||
RT02B.wmv,,SUBTITLE_RT02B.tbl,VOICE_RT02B
|
||||
RT02C.wmv,,SUBTITLE_RT02C.tbl,VOICE_RT02C
|
||||
RT02D_1.wmv,,SUBTITLE_RT02D_1.tbl,VOICE_RT02D_1
|
||||
RT02D_2.wmv,,SUBTITLE_RT02D_2.tbl,VOICE_RT02D_2
|
||||
hokyu_LS_s02A.wmv,,SUBTITLE_hokyu_LS_s02A.tbl,VOICE_D_450
|
||||
hokyu_LS_s02H.wmv,,SUBTITLE_hokyu_LS_s02H.tbl,VOICE_D_453
|
||||
S03A.wmv,,SUBTITLE_S03A.tbl,VOICE_S03A
|
||||
RT03A.wmv,pwrt03.prt,SUBTITLE_RT03A.tbl,VOICE_RT03A
|
||||
RT03B.wmv,,SUBTITLE_RT03B.tbl,VOICE_RT03B
|
||||
RT03C.wmv,,SUBTITLE_RT03C.tbl,VOICE_RT03C
|
||||
RT03D.wmv,,SUBTITLE_RT03D.tbl,VOICE_RT03D
|
||||
hokyu_LS_s03A.wmv,,SUBTITLE_hokyu_LS_s03A.tbl,
|
||||
hokyu_LS_s03H.wmv,,SUBTITLE_hokyu_LS_s03H.tbl,
|
||||
S04A.wmv,,SUBTITLE_S04A.tbl,VOICE_S04A
|
||||
S04B.wmv,,SUBTITLE_S04B.tbl,VOICE_S04B
|
||||
RT04A.wmv,pwrt04.prt,SUBTITLE_RT04A.tbl,VOICE_RT04A
|
||||
RT04B.wmv,,SUBTITLE_RT04B.tbl,VOICE_RT04B
|
||||
hokyu_DS_s02A.wmv,,SUBTITLE_hokyu_DS_s02A.tbl,VOICE_D_452
|
||||
S05A.wmv,,SUBTITLE_S05A.tbl,VOICE_S05A
|
||||
RT05A.wmv,pwrt05.prt,SUBTITLE_RT05A.tbl,VOICE_RT05A
|
||||
RT05B.wmv,,SUBTITLE_RT05B.tbl,VOICE_RT05B
|
||||
RT05C.wmv,,SUBTITLE_RT05C.tbl,VOICE_RT05C
|
||||
S06A.wmv,pwterop_s06a.prt,SUBTITLE_S06A.tbl,VOICE_S06A
|
||||
S06B.wmv,,SUBTITLE_S06B.tbl,VOICE_S06B
|
||||
RT06A.wmv,pwrt06.prt,SUBTITLE_RT06A.tbl,VOICE_RT06A
|
||||
RT06B.wmv,,SUBTITLE_RT06B.tbl,VOICE_RT06B
|
||||
RT06C.wmv,,SUBTITLE_RT06C.tbl,VOICE_RT06C
|
||||
RT06D.wmv,,SUBTITLE_RT06D.tbl,VOICE_RT06D
|
||||
hokyu_LS_s06A.wmv,,SUBTITLE_hokyu_LS_s06A.tbl,
|
||||
hokyu_LS_s06H.wmv,,SUBTITLE_hokyu_LS_s06H.tbl,
|
||||
S07A.wmv,,SUBTITLE_S07A.tbl,VOICE_S07A
|
||||
S07B.wmv,,SUBTITLE_S07B.tbl,VOICE_S07B
|
||||
RT07A.wmv,pwrt07.prt,SUBTITLE_RT07A.tbl,VOICE_RT07A
|
||||
RT07B.wmv,,SUBTITLE_RT07B.tbl,VOICE_RT07B
|
||||
RT07C.wmv,,SUBTITLE_RT07C.tbl,VOICE_RT07C
|
||||
hokyu_DS_s07A.wmv,,SUBTITLE_hokyu_DS_s07A.tbl,
|
||||
hokyu_DS_s07H.wmv,,SUBTITLE_hokyu_DS_s07H.tbl,VOICE_D_454
|
||||
RT08A.wmv,pwrt08.prt,SUBTITLE_RT08A.tbl,VOICE_RT08A
|
||||
RT08B.wmv,,SUBTITLE_RT08B.tbl,VOICE_RT08B
|
||||
RT08C.wmv,,SUBTITLE_RT08C.tbl,VOICE_RT08C
|
||||
hokyu_DS_s08A.wmv,,SUBTITLE_hokyu_DS_s08A.tbl,
|
||||
S09B.wmv,,SUBTITLE_S09B.tbl,VOICE_S09B
|
||||
RT09A.wmv,pwrt09.prt,SUBTITLE_RT09A.tbl,VOICE_RT09A
|
||||
RT09B.wmv,,SUBTITLE_RT09B.tbl,VOICE_RT09B
|
||||
RT09C.wmv,,SUBTITLE_RT09C.tbl,VOICE_RT09C
|
||||
RT09D.wmv,,SUBTITLE_RT09D.tbl,VOICE_RT09D
|
||||
hokyu_LS_s09A.wmv,,SUBTITLE_hokyu_LS_s09A.tbl,VOICE_D_451
|
||||
hokyu_LS_s09H.wmv,,SUBTITLE_hokyu_LS_s09H.tbl,
|
||||
S10B.wmv,,SUBTITLE_S10B.tbl,VOICE_S10B
|
||||
RT10A.wmv,pwrt10.prt,SUBTITLE_RT10A.tbl,VOICE_RT10A
|
||||
RT10B.wmv,,SUBTITLE_RT10B.tbl,VOICE_RT10B
|
||||
S11A.wmv,,SUBTITLE_S11A.tbl,VOICE_S11A
|
||||
S11C.wmv,,SUBTITLE_S11C.tbl,VOICE_S11C
|
||||
RT11A.wmv,pwrt11.prt,SUBTITLE_RT11A.tbl,VOICE_RT11A
|
||||
RT11B.wmv,,SUBTITLE_RT11B.tbl,VOICE_RT11B
|
||||
RT11C.wmv,,SUBTITLE_RT11C.tbl,VOICE_RT11C
|
||||
hokyu_LS_s11A.wmv,,SUBTITLE_hokyu_LS_s11A.tbl,
|
||||
S12A.wmv,,SUBTITLE_S12A.tbl,VOICE_S12A
|
||||
S12B.wmv,,SUBTITLE_S12B.tbl,VOICE_S12B
|
||||
S12C.wmv,,SUBTITLE_S12C.tbl,VOICE_S12C
|
||||
RT12A.wmv,pwrt12.prt,SUBTITLE_RT12A.tbl,VOICE_RT12A
|
||||
RT12B_1.wmv,,SUBTITLE_RT12B_1.tbl,VOICE_RT12B_1
|
||||
RT12B_2.wmv,,SUBTITLE_RT12B_2.tbl,VOICE_RT12B_2
|
||||
S13A.wmv,,SUBTITLE_S13A.tbl,VOICE_S13A
|
||||
S13B.wmv,,SUBTITLE_S13B.tbl,VOICE_S13B
|
||||
RT13A.wmv,pwrt13.prt,SUBTITLE_RT13A.tbl,VOICE_RT13A
|
||||
RT13B_1.wmv,,SUBTITLE_RT13B_1.tbl,VOICE_RT13B_1
|
||||
RT13B_2.wmv,,SUBTITLE_RT13B_2.tbl,VOICE_RT13B_2
|
||||
hokyu_DS_s13A.wmv,,SUBTITLE_hokyu_DS_s13A.tbl,
|
||||
S14A.wmv,,SUBTITLE_S14A.tbl,VOICE_S14A
|
||||
RT14A.wmv,pwrt14.prt,SUBTITLE_RT14A.tbl,VOICE_RT14A
|
||||
RT14B.wmv,,SUBTITLE_RT14B.tbl,VOICE_RT14B
|
||||
RT14C.wmv,,SUBTITLE_RT14C.tbl,VOICE_RT14C
|
||||
hokyu_DS_s14H.wmv,,SUBTITLE_hokyu_DS_s14H.tbl,
|
||||
S15A.wmv,pwterop_s15a.prt,SUBTITLE_S15A.tbl,VOICE_S15A
|
||||
S15B.wmv,,SUBTITLE_S15B.tbl,VOICE_S15B
|
||||
S15C.wmv,,SUBTITLE_S15C.tbl,VOICE_S15C
|
||||
RT15A.wmv,pwrt15.prt,SUBTITLE_RT15A.tbl,VOICE_RT15A
|
||||
RT15B.wmv,,SUBTITLE_RT15B.tbl,VOICE_RT15B
|
||||
RT15C.wmv,,SUBTITLE_RT15C.tbl,VOICE_RT15C
|
||||
hokyu_LS_s15A.wmv,,SUBTITLE_hokyu_LS_s15A.tbl,
|
||||
S16A.wmv,pwterop_s16a.prt,SUBTITLE_S16A.tbl,VOICE_S16A
|
||||
RT16C.wmv,pwrt16.prt,SUBTITLE_RT16C.tbl,VOICE_RT16C
|
||||
hokyu_LS_s24A.wmv,,,
|
||||
hokyu_LS_s27A.wmv,,,
|
||||
|
13
docs/re/captures/pause-tutorial-screen-layout.txt
Normal file
@@ -0,0 +1,13 @@
|
||||
11 elements
|
||||
# element parent kind pivot kf placement
|
||||
0 pgp_ttrl_eff11.t32 - 0x0 (34,33) 7 rest (470,121) t=9..25 [3:603,121 6:603,121 8:503,121 9:470,121 25:463,121 30:463,121 1:493,121]
|
||||
1 pgp_ttrl_eff12.t32 - 0x0 (34,33) 7 rest (742,121) t=9..25 [3:609,121 6:609,121 8:709,121 9:742,121 25:749,121 30:749,121 2:719,121]
|
||||
2 pgp_ttrl_eff10.t32 - 0x0 (204,60) 5 rest (436,94) t=14..23 [8:436,94 14:436,94 23:436,94 30:436,94 3:436,94]
|
||||
3 pgp_ttrl_eff22.t32 - 0x0 (133,130) 5 rest (480,225) t=11..25 [5:480,225 11:480,225 25:460,225 30:460,225 4:490,225]
|
||||
4 pgp_ttrl_eff23.t32 - 0x0 (138,130) 5 rest (524,271) t=11..25 [5:524,271 11:524,271 25:544,271 30:544,271 5:514,271]
|
||||
5 pgp_ttrl_eff21.t32 - 0x0 (214,180) 5 rest (426,198) t=9..21 [9:426,198 21:426,198 23:426,198 30:426,198 6:426,198]
|
||||
6 pgp_ttrl_title.rat - 0x0 (101,36) 6 rest (540,119) t=13..23 [5:540,119 13:540,119 23:540,119 28:540,119 30:540,119 7:540,119]
|
||||
7 pgp_ttrl_btn10.rat - 0x3002 (43,21) 5 rest (546,288) t=15..23 [9:546,288 15:546,288 23:546,288 26:546,288 8:546,288]
|
||||
8 pgp_ttrl_btn11.rat - 0x3002 (86,22) 5 rest (546,358) t=11..17 [11:546,358 17:546,358 23:546,358 26:546,358 9:546,358]
|
||||
9 pgp_ttrl_btn12.rat - 0x3002 (110,21) 5 rest (546,428) t=13..19 [13:546,428 19:546,428 23:546,428 26:546,428 10:546,428]
|
||||
10 pgp_ttrl_msg.t32 - 0x0 (190,19) 5 rest (451,545) t=28..1869640736 [16:451,545 21:451,545 23:451,545 28:451,545 1869640736:451,545]
|
||||
314
docs/re/captures/pitch-gametime.csv
Normal file
@@ -0,0 +1,314 @@
|
||||
phase,t,fx,fy,fz
|
||||
slow,0.022,-0.9999999999976801,-5.364418029772711e-07,-2.0861625671338324e-06
|
||||
slow,0.072,-0.9999999999976801,-5.364418029772711e-07,-2.0861625671338324e-06
|
||||
slow,0.138,-0.9999999999976801,-5.364418029772711e-07,-2.0861625671338324e-06
|
||||
slow,0.188,-0.9999999999976801,-5.364418029772711e-07,-2.0861625671338324e-06
|
||||
slow,0.238,-0.9999996503301273,-0.000739351008976433,0.00039076810084039696
|
||||
slow,0.288,-0.9995667500505145,-0.025989011351613873,0.013816058859813869
|
||||
slow,0.338,-0.9991424233572718,-0.03655991231151712,0.01943683769373226
|
||||
slow,0.388,-0.9974614922372212,-0.06287386978474002,0.03342825155743701
|
||||
slow,0.441,-0.9974614922372212,-0.06287386978474002,0.03342825155743701
|
||||
slow,0.491,-0.9974614922372212,-0.06287386978474002,0.03342825155743701
|
||||
slow,0.542,-0.9974614922372212,-0.06287386978474002,0.03342825155743701
|
||||
slow,0.592,-0.9756937705208446,-0.19348862930198532,0.10287767735352636
|
||||
slow,0.642,-0.9599895265779043,-0.24725777991348272,0.13146748317735063
|
||||
slow,0.692,-0.9065968643570531,-0.3726025922234667,0.19811469861247516
|
||||
slow,0.742,-0.8880690175394421,-0.405900585102419,0.215819681910719
|
||||
slow,0.792,-0.8231976038572328,-0.5012983450542031,0.2665439443124295
|
||||
slow,0.845,-0.8111180643235866,-0.5164209971100269,0.2745848493122731
|
||||
slow,0.895,-0.8111180643235866,-0.5164209971100269,0.2745848493122731
|
||||
slow,0.946,-0.8111180643235866,-0.5164209971100269,0.2745848493122731
|
||||
slow,0.996,-0.8111180643235866,-0.5164209971100269,0.2745848493122731
|
||||
slow,1.047,-0.6225347328753619,-0.6909879300492386,0.3674046636749054
|
||||
slow,1.098,-0.4995761468930651,-0.7648708472261829,0.40668939071357624
|
||||
slow,1.148,-0.42441628069163945,-0.7994799112461082,0.425091392759005
|
||||
slow,1.198,-0.34635895158661695,-0.8282947460891245,0.440412636350244
|
||||
slow,1.249,-0.26597407096587283,-0.8511436226859083,0.4525619594430681
|
||||
slow,1.3,-0.22508987440157757,-0.8602890836759516,0.45742457405551984
|
||||
slow,1.35,-0.22508987440157757,-0.8602890836759516,0.45742457405551984
|
||||
slow,1.4,-0.22508987440157757,-0.8602890836759516,0.45742457405551984
|
||||
slow,1.45,0.0050435963756794286,-0.8829357634414114,0.4694667184920898
|
||||
slow,1.5,0.1113271185026686,-0.8774582682112876,0.4665546701443904
|
||||
slow,1.551,0.19530088938670107,-0.8659441420087264,0.46043273724349176
|
||||
slow,1.601,0.35806279594948665,-0.8244050055782095,0.4383462340826355
|
||||
slow,1.651,0.4357105639703388,-0.7947284845866424,0.4225670837053784
|
||||
slow,1.701,0.4357105639703388,-0.7947284845866424,0.4225670837053784
|
||||
slow,1.751,0.4357105639703388,-0.7947284845866424,0.4225670837053784
|
||||
slow,1.801,0.581059227173933,-0.7185957552011922,0.38208679003972373
|
||||
slow,1.854,0.5981621070188505,-0.707571139880663,0.3762249004714446
|
||||
slow,1.933,0.7102092219298712,-0.6215856943388454,0.3305058027918156
|
||||
slow,1.983,0.7936818073741404,-0.5371251153693617,0.28559726728869045
|
||||
slow,2.034,0.8306115980329357,-0.491670162790673,0.2614284304251665
|
||||
slow,2.085,0.8306115980329357,-0.491670162790673,0.2614284304251665
|
||||
slow,2.136,0.8306115980329357,-0.491670162790673,0.2614284304251665
|
||||
slow,2.186,0.8306115980329357,-0.491670162790673,0.2614284304251665
|
||||
slow,2.236,0.9439254674422402,-0.29151154402331125,0.15500236003072188
|
||||
slow,2.288,0.9782500298959619,-0.18314796371755346,0.09738430260911397
|
||||
slow,2.338,0.9923168633793271,-0.1092393189325843,0.05808626216377494
|
||||
slow,2.388,0.9999950320089501,0.0027841479666426932,-0.0014779978073891553
|
||||
slow,2.439,0.9961448118772039,0.07745674002692376,-0.04118212226842516
|
||||
slow,2.489,0.9885868780617736,0.1330186187164556,-0.07072504223287812
|
||||
slow,2.54,0.9885868780617736,0.1330186187164556,-0.07072504223287812
|
||||
slow,2.59,0.9885868780617736,0.1330186187164556,-0.07072504223287812
|
||||
slow,2.64,0.9885868780617736,0.1330186187164556,-0.07072504223287812
|
||||
slow,2.69,0.9104394306290844,0.3652252075952527,-0.1941921494108173
|
||||
slow,2.741,0.8718210936242394,0.4324898355933772,-0.22995765440673788
|
||||
slow,2.791,0.8021474529558832,0.5272312979999614,-0.28033305571349426
|
||||
slow,2.841,0.7483879225773887,0.5856251973581853,-0.31138183241718986
|
||||
slow,2.891,0.6415804884515123,0.6772700855747096,-0.3601106885722006
|
||||
slow,2.942,0.5565400067937,0.7335711833839508,-0.39004684301598735
|
||||
slow,2.992,0.5025832072518267,0.7633335327127708,-0.4058719473241637
|
||||
slow,3.042,0.5025832072518267,0.7633335327127708,-0.4058719473241637
|
||||
slow,3.092,0.5025832072518267,0.7633335327127708,-0.4058719473241637
|
||||
slow,3.142,0.5025832072518267,0.7633335327127708,-0.4058719473241637
|
||||
slow,3.193,0.26840150425248394,0.8505494121077833,-0.4522458734782528
|
||||
slow,3.243,0.14383384202445684,0.8737661032159637,-0.4645908121769882
|
||||
slow,3.293,0.017126773329819035,0.8828175004515181,-0.46940380753871486
|
||||
slow,3.344,-0.025233536073355507,0.8826657223955247,-0.46932343982079283
|
||||
slow,3.394,-0.1306276597599528,0.8753813567867831,-0.46545020646226887
|
||||
slow,3.444,-0.1306276597599528,0.8753813567867831,-0.46545020646226887
|
||||
slow,3.494,-0.1306276597599528,0.8753813567867831,-0.46545020646226887
|
||||
slow,3.544,-0.1306276597599528,0.8753813567867831,-0.46545020646226887
|
||||
slow,3.594,-0.3953899986357624,0.8109981935802458,-0.4312176700790331
|
||||
slow,3.644,-0.5092096812736444,0.7599012381904277,-0.40404901768702334
|
||||
slow,3.695,-0.6146085873298012,0.6964964366951696,-0.37033633099035523
|
||||
slow,3.745,-0.6475735623151888,0.6728076094689478,-0.35774069104743467
|
||||
slow,3.795,-0.7246428766140702,0.6084598143649699,-0.32352643736707504
|
||||
slow,3.845,-0.7246428766140702,0.6084598143649699,-0.32352643736707504
|
||||
slow,3.895,-0.7246428766140702,0.6084598143649699,-0.32352643736707504
|
||||
slow,3.946,-0.7246428766140702,0.6084598143649699,-0.32352643736707504
|
||||
slow,4.0,-0.8948032879220578,0.39420026288722865,-0.2096025492772329
|
||||
slow,4.05,-0.9371976061971167,0.3079700993685502,-0.16375306053115327
|
||||
slow,4.1,-0.9739646105616616,0.20016331274041113,-0.10643113081357014
|
||||
slow,4.151,-0.9925182516704475,0.1078037923584182,-0.057322442849038695
|
||||
slow,4.201,-0.9998661518865883,0.014444979973920849,-0.007682503827448022
|
||||
slow,4.252,-0.999988574147902,-0.004221603854607207,0.0022426846725771095
|
||||
slow,4.302,-0.999988574147902,-0.004221603854607207,0.0022426846725771095
|
||||
slow,4.352,-0.999988574147902,-0.004221603854607207,0.0022426846725771095
|
||||
slow,4.402,-0.999988574147902,-0.004221603854607207,0.0022426846725771095
|
||||
slow,4.454,-0.9481592609187346,-0.28059728776173093,0.1491951005723691
|
||||
slow,4.504,-0.8998604949330586,-0.3851225220144059,0.20477239241678594
|
||||
slow,4.554,-0.8483542366290391,-0.4674583762955424,0.24855131386636348
|
||||
slow,4.605,-0.8002653758276345,-0.5294561807103555,0.281516395542175
|
||||
slow,4.655,-0.7740407763904767,-0.5590258203543885,0.2972389756777044
|
||||
slow,4.705,-0.7320686741889714,-0.6014912262488834,0.31981832501721374
|
||||
slow,4.755,-0.7320686741889714,-0.6014912262488834,0.31981832501721374
|
||||
slow,4.805,-0.7320686741889714,-0.6014912262488834,0.31981832501721374
|
||||
slow,4.855,-0.7320686741889714,-0.6014912262488834,0.31981832501721374
|
||||
slow,4.905,-0.5000347671416738,-0.7646370203731946,0.4065654421177173
|
||||
slow,4.956,-0.38571043654168863,-0.8146245270401747,0.43314470917626546
|
||||
slow,5.033,-0.3059761515320789,-0.8406001527732009,0.44695634893274494
|
||||
slow,5.083,-0.14067269192255813,-0.8741670164828578,0.46480449765543375
|
||||
slow,5.136,-0.11971066366716597,-0.876597475120388,0.46609679640277224
|
||||
slow,5.186,-0.11971066366716597,-0.876597475120388,0.46609679640277224
|
||||
slow,5.236,-0.11971066366716597,-0.876597475120388,0.46609679640277224
|
||||
slow,5.286,0.09210780811437987,-0.8791932987194956,0.4674775878810623
|
||||
slow,5.336,0.1552569076145395,-0.8722401249754417,0.46378061302818196
|
||||
slow,5.389,0.29980078422712286,-0.8423325684075889,0.4478787045358116
|
||||
slow,5.439,0.39908806765876,-0.8095847039629167,0.43046639980569157
|
||||
slow,5.49,0.4751133264531701,-0.7769262478123317,0.4131016006829976
|
||||
slow,5.542,0.4751133264531701,-0.7769262478123317,0.4131016006829976
|
||||
slow,5.592,0.4751133264531701,-0.7769262478123317,0.4131016006829976
|
||||
slow,5.642,0.4751133264531701,-0.7769262478123317,0.4131016006829976
|
||||
slow,5.692,0.696657860237904,-0.633428595248907,0.3368026729254701
|
||||
slow,5.742,0.726585020550654,-0.6066514667740671,0.3225650411502751
|
||||
slow,5.792,0.8325182828011221,-0.48914972660411055,0.2600881653689665
|
||||
slow,5.843,0.8765739353060135,-0.4249327255756417,0.22594316691811042
|
||||
slow,5.893,0.9054260508840364,-0.37481374091028163,0.19929457093804306
|
||||
slow,5.943,0.9054260508840364,-0.37481374091028163,0.19929457093804306
|
||||
slow,5.993,0.9054260508840364,-0.37481374091028163,0.19929457093804306
|
||||
slow,6.043,0.9054260508840364,-0.37481374091028163,0.19929457093804306
|
||||
slow,6.093,0.9868879110680581,-0.14251307214027348,0.07577780187407064
|
||||
slow,6.144,0.9928589520370625,-0.10532958640717122,0.056006960167108814
|
||||
slow,6.194,0.9995721898785164,0.025825160128774418,-0.013729469246492699
|
||||
slow,6.244,0.9934893887944055,0.10059024435508387,-0.05348305426490678
|
||||
slow,6.294,0.9538232020213261,0.26521033194280885,-0.14101340048580407
|
||||
slow,6.344,0.947267673727074,0.2829352443494437,-0.1504380331452729
|
||||
slow,6.394,0.947267673727074,0.2829352443494437,-0.1504380331452729
|
||||
slow,6.445,0.947267673727074,0.2829352443494437,-0.1504380331452729
|
||||
slow,6.495,0.947267673727074,0.2829352443494437,-0.1504380331452729
|
||||
slow,6.545,0.8243733791338322,0.4997895512364342,-0.2657422364818033
|
||||
slow,6.597,0.7593203746728908,0.5745491212947682,-0.30549284087524925
|
||||
slow,6.647,0.7010074410559842,0.6296776548998042,-0.3348053441987208
|
||||
slow,6.697,0.6041754101078993,0.7035779205024607,-0.37409916279294836
|
||||
slow,6.747,0.5340870381020223,0.7464697028629991,-0.39690555355025536
|
||||
slow,6.797,0.4025055990782129,0.8082653740061208,-0.4297631067151235
|
||||
slow,6.847,0.3433573357356595,0.8292682687272306,-0.44093069577729704
|
||||
slow,6.898,0.3433573357356595,0.8292682687272306,-0.44093069577729704
|
||||
slow,6.948,0.3433573357356595,0.8292682687272306,-0.44093069577729704
|
||||
slow,6.999,0.3433573357356595,0.8292682687272306,-0.44093069577729704
|
||||
slow,7.049,0.09503184681410931,0.8789508919606488,-0.46734813320765445
|
||||
slow,7.1,-0.03242949378753749,0.8824823690120825,-0.4692261675519672
|
||||
slow,7.15,-0.07486618489555856,0.8804688348729269,-0.46815562068258304
|
||||
slow,7.202,-0.2217573682314415,0.8609628101781774,-0.4577845662810842
|
||||
slow,7.253,-0.3436636683706333,0.8291685841702975,-0.44087950969291334
|
||||
slow,7.306,-0.3436636683706333,0.8291685841702975,-0.44087950969291334
|
||||
slow,7.356,-0.3436636683706333,0.8291685841702975,-0.44087950969291334
|
||||
slow,7.406,-0.3436636683706333,0.8291685841702975,-0.44087950969291334
|
||||
slow,7.456,-0.5335192585826919,0.7467851744734487,-0.3970756904017614
|
||||
slow,7.506,-0.5863848618861591,0.715215075832045,-0.38028961207652284
|
||||
slow,7.557,-0.6691599892298212,0.6561333325626291,-0.3488752767311034
|
||||
slow,7.607,-0.7582631988698701,0.5756351874374445,-0.30607360589160554
|
||||
slow,7.657,-0.8108505756823166,0.51674664977568,-0.27476215871416254
|
||||
slow,7.707,-0.8575526461274808,0.4541837039893854,-0.24149662968711988
|
||||
slow,7.757,-0.8575526461274808,0.4541837039893854,-0.24149662968711988
|
||||
slow,7.808,-0.8575526461274808,0.4541837039893854,-0.24149662968711988
|
||||
slow,7.859,-0.8575526461274808,0.4541837039893854,-0.24149662968711988
|
||||
slow,7.909,-0.9532867456950477,0.2667081367420545,-0.1418137873329921
|
||||
slow,7.959,-0.9755224624568912,0.19415891248194872,-0.10323876183813074
|
||||
fast,0.031,-0.9183790521746182,-0.3493842945716818,0.1857701031745906
|
||||
fast,0.081,-0.9181941875656858,-0.34976292144045307,0.18597132226778823
|
||||
fast,0.131,-0.9157256739103099,-0.35477222000318137,0.18863499690015914
|
||||
fast,0.181,-0.9149015400332507,-0.3564260247285592,0.18951427635136814
|
||||
fast,0.231,-0.9149015400332507,-0.3564260247285592,0.18951427635136814
|
||||
fast,0.282,-0.9149015400332507,-0.3564260247285592,0.18951427635136814
|
||||
fast,0.334,-0.886987130221704,-0.40774025376731704,0.21679879215260833
|
||||
fast,0.392,-0.874422882615109,-0.42837502256975,0.22777063550301968
|
||||
fast,0.442,-0.8391761714215903,-0.4801996653842019,0.2553265256159302
|
||||
fast,0.492,-0.8087011271707248,-0.5193672154489234,0.27615246229144985
|
||||
fast,0.542,-0.7639193320090165,-0.5697763250541364,0.3029557617717493
|
||||
fast,0.592,-0.7142788381999624,-0.6179405632697418,0.3285653687861613
|
||||
fast,0.644,-0.7056326965418199,-0.6256368844308748,0.3326574610778302
|
||||
fast,0.694,-0.7056326965418199,-0.6256368844308748,0.3326574610778302
|
||||
fast,0.761,-0.7056326965418199,-0.6256368844308748,0.3326574610778302
|
||||
fast,0.811,-0.6045554154137983,-0.703323001692553,0.37396457717814224
|
||||
fast,0.861,-0.5641123049229769,-0.7290475533490074,0.38764284127313964
|
||||
fast,0.911,-0.5010910596413976,-0.7640973590830287,0.4062794306752468
|
||||
fast,0.961,-0.4574254856752507,-0.7851585926980168,0.4174780345925168
|
||||
fast,1.011,-0.40119022065553167,-0.8087745891735626,0.43003496457555734
|
||||
fast,1.062,-0.40119022065553167,-0.8087745891735626,0.43003496457555734
|
||||
fast,1.112,-0.40119022065553167,-0.8087745891735626,0.43003496457555734
|
||||
fast,1.162,-0.40119022065553167,-0.8087745891735626,0.43003496457555734
|
||||
fast,1.213,-0.2732721277804909,-0.8493387726086905,0.4516038014918455
|
||||
fast,1.263,-0.24847333019197224,-0.8552561875978043,0.45475032463867526
|
||||
fast,1.314,-0.13690544128361162,-0.8746327174969611,0.46505323311511404
|
||||
fast,1.364,-0.11190820705598208,-0.8774002355189958,0.46652479023598115
|
||||
fast,1.414,-0.04853388875408303,-0.8819058319940255,0.46892063842118525
|
||||
fast,1.464,-0.023325922091828814,-0.8827061212913064,0.4693461460302229
|
||||
fast,1.52,-0.023325922091828814,-0.8827061212913064,0.4693461460302229
|
||||
fast,1.57,-0.023325922091828814,-0.8827061212913064,0.4693461460302229
|
||||
fast,1.623,0.11590445397311018,-0.8769955308131724,0.4663099789656202
|
||||
fast,1.674,0.16796962344551228,-0.8704014300065923,0.46280401493726464
|
||||
fast,1.724,0.2673695301474367,-0.8508015956107015,0.4523827795739172
|
||||
fast,1.776,0.3040082366636887,-0.8411556467255445,0.44725403298613803
|
||||
fast,1.826,0.34060203879357387,-0.8301524613755806,0.44140360447296506
|
||||
fast,1.876,0.34060203879357387,-0.8301524613755806,0.44140360447296506
|
||||
fast,1.926,0.34060203879357387,-0.8301524613755806,0.44140360447296506
|
||||
fast,1.976,0.34060203879357387,-0.8301524613755806,0.44140360447296506
|
||||
fast,2.026,0.4800222448487086,-0.7745698100484196,0.41184979520689524
|
||||
fast,2.077,0.5027716590432529,-0.7632352767416622,0.4058233251058559
|
||||
fast,2.162,0.567685165746978,-0.726881238602736,0.3864934896711436
|
||||
fast,2.213,0.60896099881303,-0.7003525641308903,0.37238795340333464
|
||||
fast,2.263,0.60896099881303,-0.7003525641308903,0.37238795340333464
|
||||
fast,2.313,0.60896099881303,-0.7003525641308903,0.37238795340333464
|
||||
fast,2.363,0.60896099881303,-0.7003525641308903,0.37238795340333464
|
||||
fast,2.415,0.7138914376865056,-0.6182880840720885,0.3287534947226572
|
||||
fast,2.465,0.7492325438071801,-0.584780749889073,0.31093740505026973
|
||||
fast,2.515,0.7980625863701071,-0.5320401745258126,0.28289461098931906
|
||||
fast,2.566,0.8280000832233713,-0.4950917864992449,0.2632489033653826
|
||||
fast,2.618,0.8351255264037815,-0.48567212818960787,0.2582400802520477
|
||||
fast,2.669,0.8351255264037815,-0.48567212818960787,0.2582400802520477
|
||||
fast,2.719,0.8351255264037815,-0.48567212818960787,0.2582400802520477
|
||||
fast,2.769,0.8351255264037815,-0.48567212818960787,0.2582400802520477
|
||||
fast,2.82,0.9093870336730951,-0.36726148601053504,0.1952798604070126
|
||||
fast,2.871,0.9203632924764715,-0.3452879583192104,0.18359639348750836
|
||||
fast,2.921,0.9600488101430662,-0.247076202859695,0.13137591911502494
|
||||
fast,2.972,0.9669595427173864,-0.2250884627532023,0.1196846969465328
|
||||
fast,3.022,0.9813959442736294,-0.16952014269323645,0.09013834802422341
|
||||
fast,3.074,0.9837681894544645,-0.158438487459875,0.08424603912892294
|
||||
fast,3.124,0.9837681894544645,-0.158438487459875,0.08424603912892294
|
||||
fast,3.174,0.9837681894544645,-0.158438487459875,0.08424603912892294
|
||||
fast,3.224,0.9985768344211968,-0.04708834623109012,0.025039836392490022
|
||||
fast,3.276,0.9999091072744978,0.011905223183064872,-0.006327942041318827
|
||||
fast,3.326,0.9958087405962524,0.08075529499181068,-0.04293640044171679
|
||||
fast,3.376,0.9931046463689198,0.10350980586946733,-0.05503527459119239
|
||||
fast,3.426,0.9725948196320136,0.20529173779377843,-0.10915410765784948
|
||||
fast,3.476,0.9662434406366642,0.22747505904204074,-0.12094920810175326
|
||||
fast,3.526,0.9554895457575009,0.26049211070264017,-0.13850483099808442
|
||||
fast,3.577,0.9554895457575009,0.26049211070264017,-0.13850483099808442
|
||||
fast,3.627,0.9554895457575009,0.26049211070264017,-0.13850483099808442
|
||||
fast,3.677,0.9554895457575009,0.26049211070264017,-0.13850483099808442
|
||||
fast,3.727,0.8909286923979324,0.4009849131652504,-0.21320685841681314
|
||||
fast,3.778,0.8640929736842651,0.44441621107211554,-0.23629973374041036
|
||||
fast,3.829,0.8128494133306493,0.5142939755758894,-0.27345481881723305
|
||||
fast,3.879,0.7814528720839342,0.5509276830126654,-0.2929336047674737
|
||||
fast,3.929,0.7211597970721555,0.611678327920411,-0.3252355611542772
|
||||
fast,3.979,0.6841778570037976,0.6439468055268214,-0.34239329963872395
|
||||
fast,4.029,0.6746720278390272,0.65171893206941,-0.34652573992978153
|
||||
fast,4.082,0.6746720278390272,0.65171893206941,-0.34652573992978153
|
||||
fast,4.133,0.6746720278390272,0.65171893206941,-0.34652573992978153
|
||||
fast,4.183,0.6746720278390272,0.65171893206941,-0.34652573992978153
|
||||
fast,4.233,0.5182188187016136,0.7551386081987147,-0.4015158008736547
|
||||
fast,4.284,0.47114018545956854,0.7788105659042874,-0.41410267818618
|
||||
fast,4.335,0.37644461942850993,0.8179963583095251,-0.4349383936785522
|
||||
fast,4.385,0.32771191712873915,0.8341882735212928,-0.44354799480030493
|
||||
fast,4.435,0.22775844025010583,0.8597406189563827,-0.4571347296053267
|
||||
fast,4.485,0.20235015483807314,0.864681197867618,-0.4597617218638685
|
||||
fast,4.536,0.18963344038093657,0.8669254276051488,-0.4609549449359662
|
||||
fast,4.586,0.18963344038093657,0.8669254276051488,-0.4609549449359662
|
||||
fast,4.636,0.18963344038093657,0.8669254276051488,-0.4609549449359662
|
||||
fast,4.686,0.0616702782462941,0.8812657670818295,-0.46858022210791117
|
||||
fast,4.742,-0.033265594992281614,0.8824576094559936,-0.4692142023671305
|
||||
fast,4.793,-0.08561358744104564,0.8797044474697407,-0.46775035943057547
|
||||
fast,4.861,-0.16311988058380167,0.8711202450018558,-0.46318616484760533
|
||||
fast,4.911,-0.2647679339679234,0.8514356989074474,-0.4527197717886242
|
||||
fast,4.962,-0.31438943728704216,0.8381755148966445,-0.4456692584754773
|
||||
fast,5.012,-0.31438943728704216,0.8381755148966445,-0.4456692584754773
|
||||
fast,5.062,-0.31438943728704216,0.8381755148966445,-0.4456692584754773
|
||||
fast,5.112,-0.31438943728704216,0.8381755148966445,-0.4456692584754773
|
||||
fast,5.163,-0.4463483290724788,0.7901123971308208,-0.42011375725438266
|
||||
fast,5.213,-0.505148005766188,0.7620111594970173,-0.40517216719865473
|
||||
fast,5.263,-0.5274613820707995,0.7501322143205744,-0.3988560535612739
|
||||
fast,5.313,-0.6235599606983542,0.6902653205120604,-0.36702419908263495
|
||||
fast,5.364,-0.6535579280284671,0.6682807541755503,-0.3553348678496027
|
||||
fast,5.414,-0.6535579280284671,0.6682807541755503,-0.3553348678496027
|
||||
fast,5.464,-0.6535579280284671,0.6682807541755503,-0.3553348678496027
|
||||
fast,5.514,-0.6535579280284671,0.6682807541755503,-0.3553348678496027
|
||||
fast,5.564,-0.7542996656095252,0.5796803289795648,-0.3082251298247795
|
||||
fast,5.615,-0.8053480118610813,0.5234118913151391,-0.2783066147638474
|
||||
fast,5.665,-0.835317794193117,0.4854144024411218,-0.25810277140525667
|
||||
fast,5.715,-0.8756811392589173,0.4263650045332836,-0.22670559158419007
|
||||
fast,5.765,-0.8879541369536935,0.4060944713229071,-0.21592760598818767
|
||||
fast,5.816,-0.8938471875490723,0.3958873769547781,-0.21050033272771532
|
||||
fast,5.867,-0.8938471875490723,0.3958873769547781,-0.21050033272771532
|
||||
fast,5.917,-0.8938471875490723,0.3958873769547781,-0.21050033272771532
|
||||
fast,5.967,-0.8938471875490723,0.3958873769547781,-0.21050033272771532
|
||||
fast,6.017,-0.9568362261302265,0.2566077577804137,-0.1364437430290401
|
||||
fast,6.067,-0.9711494705384393,0.21055675115725614,-0.11195785106463185
|
||||
fast,6.118,-0.9868689841435945,0.1426150745455621,-0.07583237203037746
|
||||
fast,6.168,-0.9907414896934681,0.11986964152412082,-0.06373829022531322
|
||||
fast,6.218,-0.9974727159739508,0.06273297303862826,-0.03335798227238545
|
||||
fast,6.27,-0.9974727159739508,0.06273297303862826,-0.03335798227238545
|
||||
fast,6.321,-0.9974727159739508,0.06273297303862826,-0.03335798227238545
|
||||
fast,6.371,-0.9974727159739508,0.06273297303862826,-0.03335798227238545
|
||||
fast,6.421,-0.9962961796573708,-0.0759235377258525,0.04036754661010212
|
||||
fast,6.471,-0.9935876720124013,-0.09983054395307195,0.053079191016920685
|
||||
fast,6.521,-0.98151891503074,-0.1689659788258626,0.0898393980183376
|
||||
fast,6.572,-0.9761981791370964,-0.19149456258495987,0.1018182083412009
|
||||
fast,6.622,-0.9600279771648162,-0.24714097112937958,0.13140632956619028
|
||||
fast,6.673,-0.9600279771648162,-0.24714097112937958,0.13140632956619028
|
||||
fast,6.723,-0.9600279771648162,-0.24714097112937958,0.13140632956619028
|
||||
fast,6.773,-0.9600279771648162,-0.24714097112937958,0.13140632956619028
|
||||
fast,6.823,-0.9600279771648162,-0.24714097112937958,0.13140632956619028
|
||||
fast,6.874,-0.8987677690425633,-0.3871064780875767,0.20582777254997944
|
||||
fast,6.924,-0.8670890034724157,-0.43984325215975256,0.23386871014897564
|
||||
fast,6.974,-0.8537120733075257,-0.4597908235105943,0.24447514087206346
|
||||
fast,7.024,-0.7865457418615867,-0.545248184091142,0.2899141488522395
|
||||
fast,7.074,-0.7533129809628684,-0.5806809342120418,0.3087542798982184
|
||||
fast,7.127,-0.7447100018154831,-0.5892684722773185,0.31332041231930347
|
||||
fast,7.179,-0.7447100018154831,-0.5892684722773185,0.31332041231930347
|
||||
fast,7.229,-0.7447100018154831,-0.5892684722773185,0.31332041231930347
|
||||
fast,7.279,-0.7447100018154831,-0.5892684722773185,0.31332041231930347
|
||||
fast,7.329,-0.5992280106837283,-0.7068679873070793,0.3758502889881291
|
||||
fast,7.379,-0.5443899367788374,-0.7406440829377567,0.3938095213973664
|
||||
fast,7.431,-0.4771420994818272,-0.7759564582613324,0.4125857387072451
|
||||
fast,7.481,-0.41846538807715317,-0.801920794933833,0.42639155436538256
|
||||
fast,7.531,-0.35845204323781216,-0.8242729196805756,0.43827649558229453
|
||||
fast,7.581,-0.35845204323781216,-0.8242729196805756,0.43827649558229453
|
||||
fast,7.631,-0.35845204323781216,-0.8242729196805756,0.43827649558229453
|
||||
fast,7.681,-0.35845204323781216,-0.8242729196805756,0.43827649558229453
|
||||
fast,7.732,-0.23477000507402776,-0.8582687461538578,0.4563527189499649
|
||||
fast,7.783,-0.20781325308245005,-0.8636701935950999,0.4592248344097825
|
||||
fast,7.861,-0.10263756227815277,-0.8782831695934015,0.4669948659445483
|
||||
fast,7.911,-0.02463647965351885,-0.8826781186805318,0.4693318470686588
|
||||
fast,7.962,0.001224726571744081,-0.8829453951924657,0.4694740984902627
|
||||
|
314
docs/re/captures/roll-gametime.csv
Normal file
@@ -0,0 +1,314 @@
|
||||
phase,t,fx,fy,fz
|
||||
slow,0.046,5.36441810913306e-07,0.8829469811387421,-0.4694727132621171
|
||||
slow,0.124,-0.0037028938908542333,0.8829408973863202,-0.4694695520472787
|
||||
slow,0.175,-0.029433192564468726,0.8825644180942943,-0.4692693630414654
|
||||
slow,0.225,-0.047907995026344505,0.8819330671344229,-0.46893377902154315
|
||||
slow,0.276,-0.05494452988794276,0.8816131314630001,-0.4687636772056853
|
||||
slow,0.327,-0.05494452988794276,0.8816131314630001,-0.4687636772056853
|
||||
slow,0.377,-0.05494452988794276,0.8816131314630001,-0.4687636772056853
|
||||
slow,0.427,-0.05494452988794276,0.8816131314630001,-0.4687636772056853
|
||||
slow,0.478,-0.2805405845010285,0.8474894135274412,-0.4506203217862406
|
||||
slow,0.529,-0.3464401476183601,0.828267481843762,-0.440400048408674
|
||||
slow,0.579,-0.4933270963841563,0.7680254218881261,-0.4083691066997395
|
||||
slow,0.629,-0.5646325672888053,0.7287328660233411,-0.3874770624621025
|
||||
slow,0.679,-0.663984471662157,0.6602195443081346,-0.35104810881855686
|
||||
slow,0.729,-0.6795143538879752,0.6477839998044589,-0.3444359627819725
|
||||
slow,0.782,-0.6795143538879752,0.6477839998044589,-0.3444359627819725
|
||||
slow,0.832,-0.6795143538879752,0.6477839998044589,-0.3444359627819725
|
||||
slow,0.883,-0.6795143538879752,0.6477839998044589,-0.3444359627819725
|
||||
slow,0.933,-0.8636404059341753,0.44509990979031433,-0.23666710701427646
|
||||
slow,0.983,-0.9034235025038042,0.37856180077666735,-0.20128819666459757
|
||||
slow,1.033,-0.9208475303349151,0.34428010688112226,-0.18306019196431236
|
||||
slow,1.084,-0.9823258963987413,0.1652676638242847,-0.08787737227792251
|
||||
slow,1.134,-0.9946318488171882,0.09136350259329508,-0.048581845502070904
|
||||
slow,1.188,-0.9965908416220451,0.07284426607901787,-0.03873509125422061
|
||||
slow,1.238,-0.9965908416220451,0.07284426607901787,-0.03873509125422061
|
||||
slow,1.288,-0.9965908416220451,0.07284426607901787,-0.03873509125422061
|
||||
slow,1.338,-0.9965908416220451,0.07284426607901787,-0.03873509125422061
|
||||
slow,1.388,-0.9887775514835899,-0.13190930729666128,0.0701347868794932
|
||||
slow,1.438,-0.9556714469692192,-0.2599717478297875,0.13822726134190855
|
||||
slow,1.489,-0.8922483691415248,-0.39869133958378805,0.21198599837058513
|
||||
slow,1.539,-0.8508838444950022,-0.46386065972493185,0.24663732794724316
|
||||
slow,1.589,-0.7907927164969744,-0.5404386826976731,0.28735502393266027
|
||||
slow,1.64,-0.7777418936805442,-0.5550070379438635,0.29510122779646286
|
||||
slow,1.691,-0.7777418936805442,-0.5550070379438635,0.29510122779646286
|
||||
slow,1.741,-0.7777418936805442,-0.5550070379438635,0.29510122779646286
|
||||
slow,1.791,-0.5952358692207049,-0.7094937017300517,0.377243882917219
|
||||
slow,1.841,-0.5604049203743158,-0.7312739567343891,0.38882480042081646
|
||||
slow,1.892,-0.45017479352935263,-0.7884196750580088,0.4192100562393852
|
||||
slow,1.942,-0.3527042657551637,-0.8262047790273792,0.4393009947979067
|
||||
slow,1.993,-0.2515210843607131,-0.8545622769363128,0.454379201724106
|
||||
slow,2.048,-0.21020554253366847,-0.8632198721332542,0.4589826600672126
|
||||
slow,2.098,-0.21020554253366847,-0.8632198721332542,0.4589826600672126
|
||||
slow,2.148,-0.21020554253366847,-0.8632198721332542,0.4589826600672126
|
||||
slow,2.199,-0.21020554253366847,-0.8632198721332542,0.4589826600672126
|
||||
slow,2.249,0.06423144170165734,-0.8811238583231025,0.46850300766455694
|
||||
slow,2.3,0.12787323617667282,-0.8756985100693347,0.46561846498184756
|
||||
slow,2.35,0.2327336093496518,-0.858701653919306,0.45658135817763307
|
||||
slow,2.423,0.3547279040834424,-0.8255283263615021,0.43894315855170307
|
||||
slow,2.479,0.5260315144840211,-0.7509151987562808,0.3992708479797423
|
||||
slow,2.529,0.5961056688939317,-0.7089229256787852,0.3769433869423229
|
||||
slow,2.581,0.5961056688939317,-0.7089229256787852,0.3769433869423229
|
||||
slow,2.631,0.5961056688939317,-0.7089229256787852,0.3769433869423229
|
||||
slow,2.681,0.5961056688939317,-0.7089229256787852,0.3769433869423229
|
||||
slow,2.732,0.5961056688939317,-0.7089229256787852,0.3769433869423229
|
||||
slow,2.782,0.8295924682130262,-0.4930097696572142,0.26214061819328877
|
||||
slow,2.832,0.8741827941510458,-0.4287555674072037,0.22797610801923318
|
||||
slow,2.882,0.9439064628362973,-0.29155959525346875,0.15502771310802138
|
||||
slow,2.932,0.9571066736003423,-0.25582037216180104,0.1360248232371042
|
||||
slow,2.982,0.9861968747582776,-0.14619466320861924,0.07773573609560003
|
||||
slow,3.032,0.9966905224322332,-0.07177339798264935,0.03816519141555759
|
||||
slow,3.083,0.9992422961997571,-0.03436404433766335,0.01827418786692905
|
||||
slow,3.133,0.9992422961997571,-0.03436404433766335,0.01827418786692905
|
||||
slow,3.184,0.9992422961997571,-0.03436404433766335,0.01827418786692905
|
||||
slow,3.234,0.9992422961997571,-0.03436404433766335,0.01827418786692905
|
||||
slow,3.286,0.9667885938062846,0.22566239526859874,-0.11998457504073406
|
||||
slow,3.336,0.9415539672577656,0.2974325173987048,-0.15814557956209666
|
||||
slow,3.386,0.8910494227865149,0.4007762840154738,-0.21309457131181636
|
||||
slow,3.437,0.8493271610881868,0.46607942161808097,-0.24781716280765573
|
||||
slow,3.487,0.761860256786668,0.5719223062697806,-0.30409509157499987
|
||||
slow,3.537,0.748009652602088,0.586002088637891,-0.3115816293142661
|
||||
slow,3.587,0.748009652602088,0.586002088637891,-0.3115816293142661
|
||||
slow,3.637,0.748009652602088,0.586002088637891,-0.3115816293142661
|
||||
slow,3.688,0.591663947524308,0.7118191730405439,-0.3784801686902885
|
||||
slow,3.738,0.539041368473937,0.7436887413829951,-0.3954256681273002
|
||||
slow,3.789,0.40803721926756004,0.8061006144181252,-0.4286110441030633
|
||||
slow,3.839,0.36886970856393964,0.8206831516383724,-0.4363648733810532
|
||||
slow,3.89,0.24776263895916018,0.8554178600934551,-0.4548339910001527
|
||||
slow,3.94,0.16475926423798679,0.8708809062626716,-0.46305597065027154
|
||||
slow,3.992,0.14387633165971442,0.8737609544059406,-0.46458733919873835
|
||||
slow,4.042,0.14387633165971442,0.8737609544059406,-0.46458733919873835
|
||||
slow,4.093,0.14387633165971442,0.8737609544059406,-0.46458733919873835
|
||||
slow,4.143,0.14387633165971442,0.8737609544059406,-0.46458733919873835
|
||||
slow,4.196,-0.1733023236078747,0.869586949506304,-0.46236872934967393
|
||||
slow,4.247,-0.21515127447977428,0.86226908085857,-0.4584778743680502
|
||||
slow,4.297,-0.3579950095311995,0.8244282743035451,-0.4383578374794213
|
||||
slow,4.347,-0.3974270950667179,0.8102218773848151,-0.43080414751236507
|
||||
slow,4.397,-0.5648254471144367,0.7286167551161777,-0.3874142982079373
|
||||
slow,4.448,-0.6161812577464959,0.6954125799901635,-0.36975938283344584
|
||||
slow,4.498,-0.6161812577464959,0.6954125799901635,-0.36975938283344584
|
||||
slow,4.55,-0.6161812577464959,0.6954125799901635,-0.36975938283344584
|
||||
slow,4.6,-0.6161812577464959,0.6954125799901635,-0.36975938283344584
|
||||
slow,4.65,-0.7949436150360499,0.5356686009745564,-0.2848223987740045
|
||||
slow,4.706,-0.8320602692300713,0.4897569720337153,-0.26041086135787
|
||||
slow,4.756,-0.9052591628184888,0.37512792417883484,-0.1994614966213956
|
||||
slow,4.824,-0.9519766029552919,0.2703318015873179,-0.14374026741403262
|
||||
slow,4.874,-0.983175920401433,0.1612793283684674,-0.08575597812287565
|
||||
slow,4.925,-0.992798606527993,0.10577208861815067,-0.05624226298282666
|
||||
slow,4.975,-0.992798606527993,0.10577208861815067,-0.05624226298282666
|
||||
slow,5.025,-0.992798606527993,0.10577208861815067,-0.05624226298282666
|
||||
slow,5.075,-0.992798606527993,0.10577208861815067,-0.05624226298282666
|
||||
slow,5.125,-0.9909352442124001,-0.11861635420392577,0.06306744241746298
|
||||
slow,5.177,-0.971089850817662,-0.21077290020723444,0.11206822108502636
|
||||
slow,5.227,-0.9473137584186095,-0.2828150426309014,0.15037385002881865
|
||||
slow,5.278,-0.8692337732689488,-0.43653100062660777,0.23210629655517379
|
||||
slow,5.329,-0.8474958640779253,-0.4686708632974702,0.24919546999657635
|
||||
slow,5.379,-0.7866667561433955,-0.5451126777938068,0.2898406170420097
|
||||
slow,5.43,-0.7734621510586307,-0.5596500718853581,0.29757032432430053
|
||||
slow,5.48,-0.7734621510586307,-0.5596500718853581,0.29757032432430053
|
||||
slow,5.531,-0.7734621510586307,-0.5596500718853581,0.29757032432430053
|
||||
slow,5.581,-0.6062043205372735,-0.7022162337138214,0.3733747217886052
|
||||
slow,5.632,-0.5541510145645213,-0.734979908569316,0.39079558218658444
|
||||
slow,5.682,-0.42418330732008847,-0.7995763915406578,0.42514246539461586
|
||||
slow,5.732,-0.3852348981801229,-0.8148002884614528,0.4332373058120499
|
||||
slow,5.783,-0.243946035449459,-0.8562723940695657,0.45528883024171124
|
||||
slow,5.833,-0.16070912377840446,-0.8714704720337134,0.4633700399332204
|
||||
slow,5.883,-0.0975990723650993,-0.8787317852366855,0.4672310677686361
|
||||
slow,5.934,-0.0975990723650993,-0.8787317852366855,0.4672310677686361
|
||||
slow,5.984,-0.0975990723650993,-0.8787317852366855,0.4672310677686361
|
||||
slow,6.034,-0.0975990723650993,-0.8787317852366855,0.4672310677686361
|
||||
slow,6.085,0.1775602464395945,-0.8689168079517865,0.46201292161928603
|
||||
slow,6.135,0.2607328887465965,-0.8524065164179079,0.45323447738906825
|
||||
slow,6.185,0.36194842063416144,-0.8230812569371999,0.4376420743931222
|
||||
slow,6.235,0.4398997578876258,-0.7929274597606337,0.4216091158501638
|
||||
slow,6.285,0.5682409772730201,-0.7265432946265349,0.38631209246025844
|
||||
slow,6.336,0.6359981125758746,-0.6813614183433645,0.36228858440895406
|
||||
slow,6.386,0.7140739082487315,-0.6181244196428086,0.32866495919039396
|
||||
slow,6.436,0.7140739082487315,-0.6181244196428086,0.32866495919039396
|
||||
slow,6.487,0.7140739082487315,-0.6181244196428086,0.32866495919039396
|
||||
slow,6.539,0.7140739082487315,-0.6181244196428086,0.32866495919039396
|
||||
slow,6.589,0.7140739082487315,-0.6181244196428086,0.32866495919039396
|
||||
slow,6.639,0.9065680252911018,-0.37265567940138744,0.1981468145837393
|
||||
slow,6.691,0.9392685544638382,-0.3030117042642596,0.16111638549285018
|
||||
slow,6.741,0.965104771584189,-0.2312113232646016,0.12293943167130032
|
||||
slow,6.792,0.9973294370055789,-0.06448454199620153,0.034289035064803325
|
||||
slow,6.842,0.9999278161305012,0.010609508165090861,-0.005639225565755396
|
||||
slow,6.892,0.9930188796365541,0.1041492959551682,-0.055375345031933365
|
||||
slow,6.943,0.9930188796365541,0.1041492959551682,-0.055375345031933365
|
||||
slow,6.993,0.9930188796365541,0.1041492959551682,-0.055375345031933365
|
||||
slow,7.043,0.9930188796365541,0.1041492959551682,-0.055375345031933365
|
||||
slow,7.093,0.9386688966024551,0.30445842560916364,-0.16188196201775823
|
||||
slow,7.144,0.9311374675345754,0.32198368527382176,-0.17120024232104936
|
||||
slow,7.223,0.8558381442539132,0.4566995610987047,-0.24283035587065402
|
||||
slow,7.273,0.7831408512856074,0.5490564200014606,-0.29193741572952536
|
||||
slow,7.324,0.7560444192777931,0.5779075266990185,-0.30727793064834297
|
||||
slow,7.374,0.6509683314574719,0.6702500769547218,-0.3563777010163019
|
||||
slow,7.425,0.6509683314574719,0.6702500769547218,-0.3563777010163019
|
||||
slow,7.475,0.6509683314574719,0.6702500769547218,-0.3563777010163019
|
||||
slow,7.525,0.6509683314574719,0.6702500769547218,-0.3563777010163019
|
||||
slow,7.576,0.4196485397802218,0.8014397844240222,-0.42613304847512623
|
||||
slow,7.626,0.3806332297410603,0.8164846784795214,-0.43413259981839836
|
||||
slow,7.676,0.23917773281080557,0.8573206036655484,-0.4558458014044032
|
||||
slow,7.727,0.13481190457623585,0.8748870865403633,-0.46518634566100775
|
||||
slow,7.777,0.007896542705130004,0.8829196865934407,-0.4694576356169389
|
||||
slow,7.828,-0.055613100836188686,0.8815807152851224,-0.46874580046412767
|
||||
slow,7.878,-0.055613100836188686,0.8815807152851224,-0.46874580046412767
|
||||
slow,7.934,-0.055613100836188686,0.8815807152851224,-0.46874580046412767
|
||||
slow,7.984,-0.055613100836188686,0.8815807152851224,-0.46874580046412767
|
||||
fast,0.044,-0.468564632951404,0.7800211366930896,-0.4147459596658374
|
||||
fast,0.094,-0.468564632951404,0.7800211366930896,-0.4147459596658374
|
||||
fast,0.147,-0.6532919035228096,0.6684843531374839,-0.3554410758507007
|
||||
fast,0.197,-0.6740804102157376,0.6521956099667656,-0.346780170862553
|
||||
fast,0.253,-0.7406633321896331,0.593230936921181,-0.31542809613185546
|
||||
fast,0.303,-0.8067414530300762,0.521736964591286,-0.27741443318975423
|
||||
fast,0.354,-0.8364927453434133,0.4838350279608412,-0.25726164250811173
|
||||
fast,0.404,-0.8572846408021899,0.4545782991663938,-0.24170563620165925
|
||||
fast,0.454,-0.8572846408021899,0.4545782991663938,-0.24170563620165925
|
||||
fast,0.505,-0.8572846408021899,0.4545782991663938,-0.24170563620165925
|
||||
fast,0.555,-0.8572846408021899,0.4545782991663938,-0.24170563620165925
|
||||
fast,0.605,-0.9267948315707897,0.33160599382699485,-0.17632017760790408
|
||||
fast,0.656,-0.9368960066831657,0.30868477952212847,-0.16413281071288688
|
||||
fast,0.706,-0.9657135503823218,0.22922111730247924,-0.12188116339522719
|
||||
fast,0.756,-0.9779586730613649,0.18435730681493703,-0.09802661479413445
|
||||
fast,0.807,-0.9943971478546243,0.09333425614443461,-0.04962891262715385
|
||||
fast,0.857,-0.9977694134078435,0.058940059911640835,-0.031341139184466044
|
||||
fast,0.907,-0.9977694134078435,0.058940059911640835,-0.031341139184466044
|
||||
fast,0.957,-0.9977694134078435,0.058940059911640835,-0.031341139184466044
|
||||
fast,1.007,-0.9977694134078435,0.058940059911640835,-0.031341139184466044
|
||||
fast,1.057,-0.9960740012037427,-0.0781633335984552,0.041558120827859354
|
||||
fast,1.108,-0.9915869770276278,-0.11429142880777064,0.06076788864432451
|
||||
fast,1.158,-0.9830488997220782,-0.16188358013218335,0.08607303433014432
|
||||
fast,1.208,-0.9654882699749504,-0.22996141302144335,0.12227080216453375
|
||||
fast,1.258,-0.9421438137516855,-0.2959725872352957,0.15736982498139357
|
||||
fast,1.308,-0.9183006336633192,-0.3495452280775043,0.1858549965478247
|
||||
fast,1.359,-0.9183006336633192,-0.3495452280775043,0.1858549965478247
|
||||
fast,1.409,-0.9183006336633192,-0.3495452280775043,0.1858549965478247
|
||||
fast,1.459,-0.9183006336633192,-0.3495452280775043,0.1858549965478247
|
||||
fast,1.509,-0.852788869570223,-0.4611250554621998,0.24518325220561926
|
||||
fast,1.561,-0.8457836830587759,-0.47107600317640114,0.25047427153877355
|
||||
fast,1.611,-0.7990683904573951,-0.5308639797392242,0.28226431298919213
|
||||
fast,1.661,-0.7576916738262323,-0.5762231745752734,0.30638240892191654
|
||||
fast,1.712,-0.7039218688816332,-0.6271380249228601,0.3334544949555229
|
||||
fast,1.763,-0.6851467559103143,-0.6431437685849701,0.34196493357637503
|
||||
fast,1.813,-0.6560905528050488,-0.6663450550269401,0.3543013606537654
|
||||
fast,1.863,-0.6560905528050488,-0.6663450550269401,0.3543013606537654
|
||||
fast,1.913,-0.6560905528050488,-0.6663450550269401,0.3543013606537654
|
||||
fast,1.964,-0.6560905528050488,-0.6663450550269401,0.3543013606537654
|
||||
fast,2.014,-0.5163993954213524,-0.7561103236758572,0.40203089786638574
|
||||
fast,2.064,-0.4546015920025061,-0.7864369179669822,0.4181559118407643
|
||||
fast,2.142,-0.3807299710798536,-0.8164494585717856,0.4341140065920148
|
||||
fast,2.193,-0.2820279695634007,-0.8471052048351421,0.45041424969150085
|
||||
fast,2.244,-0.23157561174253732,-0.8589461445197458,0.4567102548232671
|
||||
fast,2.294,-0.1805137900613878,-0.8684427065467883,0.46175971786564723
|
||||
fast,2.344,-0.1805137900613878,-0.8684427065467883,0.46175971786564723
|
||||
fast,2.395,-0.1805137900613878,-0.8684427065467883,0.46175971786564723
|
||||
fast,2.445,-0.1805137900613878,-0.8684427065467883,0.46175971786564723
|
||||
fast,2.496,-0.024830674288472077,-0.8826749954107264,0.46932748703975774
|
||||
fast,2.55,0.016627909997720097,-0.8828251361547534,0.4694073833914936
|
||||
fast,2.6,0.111726663405662,-0.8774189869035709,0.46653303645652927
|
||||
fast,2.651,0.13780214292537876,-0.874523580821017,0.4649936300565413
|
||||
fast,2.701,0.2656103428096974,-0.8512318793390828,0.45260958163672177
|
||||
fast,2.751,0.31554354506824306,-0.8378380863926662,0.4454880606207544
|
||||
fast,2.801,0.34018885207653954,-0.8302853882293821,0.4414722177166199
|
||||
fast,2.851,0.34018885207653954,-0.8302853882293821,0.4414722177166199
|
||||
fast,2.901,0.34018885207653954,-0.8302853882293821,0.4414722177166199
|
||||
fast,2.952,0.34018885207653954,-0.8302853882293821,0.4414722177166199
|
||||
fast,3.003,0.4940800181283668,-0.7676483628437457,0.4081677678474743
|
||||
fast,3.053,0.5530560650928253,-0.7356216755844609,0.39113902806336104
|
||||
fast,3.103,0.5974397882275826,-0.7080469991770619,0.3764772853691691
|
||||
fast,3.154,0.6185047351113914,-0.6938025932733614,0.3689035838968582
|
||||
fast,3.206,0.6881042365676635,-0.6406762784960411,0.34065593461746135
|
||||
fast,3.256,0.7514898864756772,-0.5825188958027513,0.3097332506488721
|
||||
fast,3.306,0.7931845845529396,-0.5376975001965116,0.2859014045261112
|
||||
fast,3.357,0.8009798300561773,-0.5286119824350076,0.28107060299737585
|
||||
fast,3.407,0.8009798300561773,-0.5286119824350076,0.28107060299737585
|
||||
fast,3.458,0.8009798300561773,-0.5286119824350076,0.28107060299737585
|
||||
fast,3.508,0.8009798300561773,-0.5286119824350076,0.28107060299737585
|
||||
fast,3.566,0.9079615585910537,-0.37000119040604634,0.19673567856139682
|
||||
fast,3.618,0.9289873638512246,-0.3267879327407989,0.1737588122074601
|
||||
fast,3.668,0.951171266364975,-0.27253270223930237,0.14491082862158244
|
||||
fast,3.718,0.97558673482976,-0.1939070387891802,0.10310471925289769
|
||||
fast,3.768,0.9897808982513759,-0.1259042544228822,0.06694693551550773
|
||||
fast,3.819,0.9931410372296491,-0.10323546390051833,0.05489370786949623
|
||||
fast,3.869,0.9931410372296491,-0.10323546390051833,0.05489370786949623
|
||||
fast,3.943,0.9931410372296491,-0.10323546390051833,0.05489370786949623
|
||||
fast,3.993,0.9999376400959273,0.009861411882843249,-0.005241037594265275
|
||||
fast,4.045,0.998654861608302,0.04578216735335978,-0.024340512289846263
|
||||
fast,4.095,0.992907801560002,0.10497188750958387,-0.05581218893717397
|
||||
fast,4.145,0.9853453521738103,0.15060635068109599,-0.08007661383945096
|
||||
fast,4.195,0.9656808627762835,0.22933012769607466,-0.12193508026222302
|
||||
fast,4.245,0.9584524128068335,0.25186373612958607,-0.13391650685261142
|
||||
fast,4.295,0.9422656297525576,0.295670151705548,-0.15720891952250804
|
||||
fast,4.346,0.9422656297525576,0.295670151705548,-0.15720891952250804
|
||||
fast,4.396,0.9422656297525576,0.295670151705548,-0.15720891952250804
|
||||
fast,4.446,0.9422656297525576,0.295670151705548,-0.15720891952250804
|
||||
fast,4.496,0.872076362854367,0.43208850957625344,-0.22974406900477443
|
||||
fast,4.547,0.8580093568921251,0.45351256088827063,-0.24113544036935863
|
||||
fast,4.598,0.8132252153454199,0.5138310229603692,-0.2732076663819958
|
||||
fast,4.648,0.7815435931024417,0.5508278891839419,-0.29287923889836937
|
||||
fast,4.699,0.7210023605142103,0.6118235759463857,-0.32531140165864775
|
||||
fast,4.752,0.7119253398407421,0.6200549034607505,-0.32968807559104
|
||||
fast,4.802,0.7119253398407421,0.6200549034607505,-0.32968807559104
|
||||
fast,4.852,0.7119253398407421,0.6200549034607505,-0.32968807559104
|
||||
fast,4.902,0.7119253398407421,0.6200549034607505,-0.32968807559104
|
||||
fast,4.952,0.5717137662325098,0.7244172953808695,-0.38517911632551577
|
||||
fast,5.003,0.5489901411529138,0.7379931951737293,-0.392397590199244
|
||||
fast,5.053,0.4917898196021908,0.7687948374153668,-0.40877533107946273
|
||||
fast,5.104,0.42168183785580987,0.8006073549916201,-0.42569036958318496
|
||||
fast,5.154,0.37379846714844805,0.8189426822227043,-0.43543953563193305
|
||||
fast,5.204,0.33726198026341786,0.8312163487333023,-0.44196576594491466
|
||||
fast,5.255,0.33726198026341786,0.8312163487333023,-0.44196576594491466
|
||||
fast,5.306,0.33726198026341786,0.8312163487333023,-0.44196576594491466
|
||||
fast,5.357,0.33726198026341786,0.8312163487333023,-0.44196576594491466
|
||||
fast,5.407,0.33726198026341786,0.8312163487333023,-0.44196576594491466
|
||||
fast,5.459,0.13407634499561424,0.8749752241040766,-0.46523315758513817
|
||||
fast,5.509,0.1067327874409669,0.8779037141803553,-0.4667902962931195
|
||||
fast,5.561,0.02627950826709677,0.8826423614597066,-0.46930997134311153
|
||||
fast,5.611,-0.05263072139273397,0.8817235042090142,-0.4688215751125975
|
||||
fast,5.662,-0.10470500798344481,0.8780938562969806,-0.46689189416468185
|
||||
fast,5.712,-0.14352498907655858,0.8738056992414621,-0.46461185680491773
|
||||
fast,5.762,-0.14352498907655858,0.8738056992414621,-0.46461185680491773
|
||||
fast,5.812,-0.14352498907655858,0.8738056992414621,-0.46461185680491773
|
||||
fast,5.862,-0.14352498907655858,0.8738056992414621,-0.46461185680491773
|
||||
fast,5.912,-0.295103523640452,0.843625197837987,-0.4485648625425387
|
||||
fast,5.962,-0.33420756849144506,0.8321770526698412,-0.44247785726832983
|
||||
fast,6.013,-0.4219355057458717,0.8005024328118661,-0.42563632839932636
|
||||
fast,6.063,-0.44551813189673,0.790478394286142,-0.42030643858743166
|
||||
fast,6.113,-0.5364964099008817,0.7451210147911475,-0.396189696332547
|
||||
fast,6.163,-0.5798139907491607,0.719379824283596,-0.38250281638863076
|
||||
fast,6.213,-0.6213904831858549,0.6917895911437915,-0.3678328818786537
|
||||
fast,6.265,-0.6213904831858549,0.6917895911437915,-0.3678328818786537
|
||||
fast,6.316,-0.6213904831858549,0.6917895911437915,-0.3678328818786537
|
||||
fast,6.366,-0.6213904831858549,0.6917895911437915,-0.3678328818786537
|
||||
fast,6.416,-0.6213904831858549,0.6917895911437915,-0.3678328818786537
|
||||
fast,6.467,-0.7697685939313547,0.5636049336495432,-0.2996761427989206
|
||||
fast,6.517,-0.8188294015799512,0.5068436537072459,-0.26949568049397077
|
||||
fast,6.567,-0.8407379772940358,0.4780642012631092,-0.2541933771879171
|
||||
fast,6.618,-0.8928806068283204,0.39758464497224594,-0.21140168408076437
|
||||
fast,6.668,-0.904333371419307,0.3768648333400775,-0.20038475672801448
|
||||
fast,6.719,-0.9150935727512862,0.35604033442654803,-0.18931200007056712
|
||||
fast,6.769,-0.9150935727512862,0.35604033442654803,-0.18931200007056712
|
||||
fast,6.819,-0.9150935727512862,0.35604033442654803,-0.18931200007056712
|
||||
fast,6.87,-0.9150935727512862,0.35604033442654803,-0.18931200007056712
|
||||
fast,6.92,-0.9695072843499856,0.21637647177545588,-0.11505150174735601
|
||||
fast,6.97,-0.9840519374844526,0.15705915122674283,-0.08351171983039263
|
||||
fast,7.021,-0.9950416541582717,0.08781629148940584,-0.04669481169271156
|
||||
fast,7.071,-0.9994288190771032,0.029837554381306492,-0.015866819047602072
|
||||
fast,7.122,-0.9990051683116948,-0.03937536594069818,0.020934522768823672
|
||||
fast,7.172,-0.9983402523657028,-0.05085064211870806,0.027036136974427062
|
||||
fast,7.222,-0.9983402523657028,-0.05085064211870806,0.027036136974427062
|
||||
fast,7.272,-0.9983402523657028,-0.05085064211870806,0.027036136974427062
|
||||
fast,7.322,-0.9983402523657028,-0.05085064211870806,0.027036136974427062
|
||||
fast,7.372,-0.9773662273888087,-0.18679204232103552,0.09931762424315102
|
||||
fast,7.423,-0.9642049478456077,-0.23412130178924356,0.12448306952577433
|
||||
fast,7.473,-0.9484068324824643,-0.27994440727138714,0.14884760306451283
|
||||
fast,7.545,-0.9099733991029075,-0.36612944116866886,0.194673175436727
|
||||
fast,7.595,-0.8747228862138969,-0.42789759461639953,0.22751597934019752
|
||||
fast,7.646,-0.8618665613775525,-0.44777421714659554,0.23808460857011965
|
||||
fast,7.696,-0.8618665613775525,-0.44777421714659554,0.23808460857011965
|
||||
fast,7.746,-0.8618665613775525,-0.44777421714659554,0.23808460857011965
|
||||
fast,7.796,-0.8618665613775525,-0.44777421714659554,0.23808460857011965
|
||||
fast,7.847,-0.7556758579959296,-0.5782831810950133,0.3074777392019586
|
||||
fast,7.897,-0.7097083644940697,-0.6220330202188311,0.3307400174224341
|
||||
fast,7.948,-0.6909634215042433,-0.6382740408720933,0.3393756014977489
|
||||
fast,7.998,-0.6322370482773051,-0.6840859490030367,0.3637344211952114
|
||||
|
BIN
docs/re/captures/savedata-game02-samestate.bin
Normal file
BIN
docs/re/captures/savedata-game03-developed-mg1.bin
Normal file
BIN
docs/re/captures/savedata-stage02-5pct.bin
Normal file
BIN
docs/re/captures/savegame-details-slot02.png
Normal file
|
After Width: | Height: | Size: 900 KiB |
BIN
docs/re/captures/savegame-details-slot03-developed.png
Normal file
|
After Width: | Height: | Size: 908 KiB |
BIN
docs/re/captures/savegame-stage05-probe-details.png
Normal file
|
After Width: | Height: | Size: 906 KiB |
BIN
docs/re/captures/savegame-stage05-probe-inflight.png
Normal file
|
After Width: | Height: | Size: 1.1 MiB |
786
docs/re/captures/speed-law-throttle-phases.csv
Normal file
@@ -0,0 +1,786 @@
|
||||
phase,t,x,y,z
|
||||
idle,0.0,-1032.5526123046875,881.9141235351562,1004.4220581054688
|
||||
idle,0.05,-1038.362548828125,881.9141235351562,1004.4220581054688
|
||||
idle,0.1,-1038.362548828125,881.9141235351562,1004.4220581054688
|
||||
idle,0.162,-1038.362548828125,881.9141235351562,1004.4220581054688
|
||||
idle,0.212,-1090.8275146484375,881.9140625,1004.4219360351562
|
||||
idle,0.262,-1102.4825439453125,881.9140625,1004.4219360351562
|
||||
idle,0.312,-1143.2926025390625,881.9140625,1004.4218139648438
|
||||
idle,0.362,-1166.6025390625,881.9140625,1004.4217529296875
|
||||
idle,0.413,-1201.5675048828125,881.9140014648438,1004.4216918945312
|
||||
idle,0.463,-1201.5675048828125,881.9140014648438,1004.4216918945312
|
||||
idle,0.516,-1201.5675048828125,881.9140014648438,1004.4216918945312
|
||||
idle,0.566,-1201.5675048828125,881.9140014648438,1004.4216918945312
|
||||
idle,0.616,-1277.342529296875,881.9140014648438,1004.4215698242188
|
||||
idle,0.666,-1300.652587890625,881.9139404296875,1004.4215087890625
|
||||
idle,0.716,-1335.6175537109375,881.9139404296875,1004.4214477539062
|
||||
idle,0.767,-1358.927490234375,881.9139404296875,1004.42138671875
|
||||
idle,0.817,-1388.0474853515625,881.9139404296875,1004.4213256835938
|
||||
idle,0.867,-1388.0474853515625,881.9139404296875,1004.4213256835938
|
||||
idle,0.917,-1388.0474853515625,881.9139404296875,1004.4213256835938
|
||||
idle,0.968,-1388.0474853515625,881.9139404296875,1004.4213256835938
|
||||
idle,1.018,-1458.0125732421875,881.9138793945312,1004.4212036132812
|
||||
idle,1.068,-1492.9775390625,881.9138793945312,1004.421142578125
|
||||
idle,1.118,-1527.9425048828125,881.913818359375,1004.4210205078125
|
||||
idle,1.168,-1539.5975341796875,881.913818359375,1004.4210205078125
|
||||
idle,1.218,-1551.217529296875,881.913818359375,1004.4210205078125
|
||||
idle,1.269,-1551.217529296875,881.913818359375,1004.4210205078125
|
||||
idle,1.319,-1551.217529296875,881.913818359375,1004.4210205078125
|
||||
idle,1.369,-1603.6824951171875,881.913818359375,1004.4208984375
|
||||
idle,1.419,-1638.6474609375,881.9137573242188,1004.4208374023438
|
||||
idle,1.469,-1650.302490234375,881.9137573242188,1004.4207763671875
|
||||
idle,1.519,-1685.2674560546875,881.9137573242188,1004.4207153320312
|
||||
idle,1.572,-1691.0775146484375,881.9137573242188,1004.4207153320312
|
||||
idle,1.622,-1691.0775146484375,881.9137573242188,1004.4207153320312
|
||||
idle,1.672,-1691.0775146484375,881.9137573242188,1004.4207153320312
|
||||
idle,1.722,-1749.387451171875,881.9136962890625,1004.4205932617188
|
||||
idle,1.773,-1761.04248046875,881.9136962890625,1004.4205932617188
|
||||
idle,1.823,-1807.6624755859375,881.9136962890625,1004.4204711914062
|
||||
idle,1.873,-1819.3175048828125,881.9136962890625,1004.4204711914062
|
||||
idle,1.923,-1842.5924072265625,881.9136962890625,1004.42041015625
|
||||
idle,1.973,-1842.5924072265625,881.9136962890625,1004.42041015625
|
||||
idle,2.024,-1842.5924072265625,881.9136962890625,1004.42041015625
|
||||
idle,2.074,-1842.5924072265625,881.9136962890625,1004.42041015625
|
||||
idle,2.124,-1906.71240234375,881.9136352539062,1004.4202880859375
|
||||
idle,2.174,-1935.867431640625,881.9136352539062,1004.4202270507812
|
||||
idle,2.224,-1970.8323974609375,881.91357421875,1004.420166015625
|
||||
idle,2.275,-1982.4874267578125,881.91357421875,1004.4201049804688
|
||||
idle,2.325,-1994.107421875,881.91357421875,1004.4201049804688
|
||||
idle,2.375,-1994.107421875,881.91357421875,1004.4201049804688
|
||||
idle,2.425,-1994.107421875,881.91357421875,1004.4201049804688
|
||||
idle,2.475,-2058.262451171875,881.91357421875,1004.4199829101562
|
||||
idle,2.525,-2075.7275390625,881.91357421875,1004.419921875
|
||||
idle,2.575,-2116.537353515625,881.9135131835938,1004.4198608398438
|
||||
idle,2.625,-2151.50244140625,881.9135131835938,1004.4197998046875
|
||||
idle,2.675,-2169.00244140625,881.9135131835938,1004.4197387695312
|
||||
idle,2.726,-2174.8125,881.9135131835938,1004.4197387695312
|
||||
idle,2.776,-2174.8125,881.9135131835938,1004.4197387695312
|
||||
idle,2.826,-2174.8125,881.9135131835938,1004.4197387695312
|
||||
idle,2.876,-2238.932373046875,881.9134521484375,1004.4196166992188
|
||||
idle,2.927,-2279.742431640625,881.9134521484375,1004.4194946289062
|
||||
idle,2.977,-2291.3974609375,881.9134521484375,1004.4194946289062
|
||||
idle,3.027,-2320.517333984375,881.9133911132812,1004.41943359375
|
||||
idle,3.077,-2326.327392578125,881.9133911132812,1004.41943359375
|
||||
idle,3.128,-2326.327392578125,881.9133911132812,1004.41943359375
|
||||
idle,3.18,-2326.327392578125,881.9133911132812,1004.41943359375
|
||||
idle,3.23,-2390.482421875,881.9133911132812,1004.4193115234375
|
||||
idle,3.28,-2419.602294921875,881.9133911132812,1004.4192504882812
|
||||
idle,3.361,-2454.5673828125,881.913330078125,1004.419189453125
|
||||
idle,3.411,-2489.532470703125,881.913330078125,1004.4191284179688
|
||||
idle,3.464,-2501.15234375,881.913330078125,1004.4190673828125
|
||||
idle,3.514,-2501.15234375,881.913330078125,1004.4190673828125
|
||||
idle,3.564,-2501.15234375,881.913330078125,1004.4190673828125
|
||||
idle,3.615,-2547.7724609375,881.9132690429688,1004.4190063476562
|
||||
idle,3.665,-2559.427490234375,881.9132690429688,1004.4189453125
|
||||
idle,3.715,-2600.2373046875,881.9132690429688,1004.4188842773438
|
||||
idle,3.765,-2629.392333984375,881.9132690429688,1004.4188232421875
|
||||
idle,3.815,-2658.512451171875,881.9132080078125,1004.4187622070312
|
||||
idle,3.865,-2664.322265625,881.9132080078125,1004.4187622070312
|
||||
idle,3.916,-2664.322265625,881.9132080078125,1004.4187622070312
|
||||
idle,3.968,-2664.322265625,881.9132080078125,1004.4187622070312
|
||||
idle,4.019,-2728.4423828125,881.9132080078125,1004.4186401367188
|
||||
idle,4.069,-2740.097412109375,881.9132080078125,1004.4185791015625
|
||||
idle,4.119,-2792.562255859375,881.9131469726562,1004.41845703125
|
||||
idle,4.169,-2804.21728515625,881.9131469726562,1004.41845703125
|
||||
idle,4.221,-2815.83740234375,881.9131469726562,1004.41845703125
|
||||
idle,4.271,-2815.83740234375,881.9131469726562,1004.41845703125
|
||||
idle,4.324,-2815.83740234375,881.9131469726562,1004.41845703125
|
||||
idle,4.374,-2874.147216796875,881.9131469726562,1004.4183349609375
|
||||
idle,4.424,-2903.267333984375,881.9130859375,1004.4182739257812
|
||||
idle,4.475,-2938.232421875,881.9130859375,1004.418212890625
|
||||
idle,4.525,-2973.197265625,881.9130859375,1004.4180908203125
|
||||
idle,4.575,-2996.50732421875,881.9130249023438,1004.4180908203125
|
||||
idle,4.627,-3008.162353515625,881.9130249023438,1004.4180297851562
|
||||
idle,4.677,-3008.162353515625,881.9130249023438,1004.4180297851562
|
||||
idle,4.727,-3008.162353515625,881.9130249023438,1004.4180297851562
|
||||
idle,4.778,-3072.2822265625,881.9130249023438,1004.4179077148438
|
||||
idle,4.863,-3113.09228515625,881.9129638671875,1004.4178466796875
|
||||
idle,4.913,-3148.057373046875,881.9129638671875,1004.4177856445312
|
||||
idle,4.963,-3165.522216796875,881.9129638671875,1004.417724609375
|
||||
idle,5.013,-3165.522216796875,881.9129638671875,1004.417724609375
|
||||
idle,5.064,-3165.522216796875,881.9129638671875,1004.417724609375
|
||||
idle,5.114,-3223.832275390625,881.9129028320312,1004.4176025390625
|
||||
idle,5.164,-3241.29736328125,881.9129028320312,1004.4176025390625
|
||||
idle,5.214,-3276.26220703125,881.9129028320312,1004.41748046875
|
||||
idle,5.266,-3299.572265625,881.9129028320312,1004.41748046875
|
||||
idle,5.316,-3334.537353515625,881.912841796875,1004.4173583984375
|
||||
idle,5.367,-3340.34716796875,881.912841796875,1004.4173583984375
|
||||
idle,5.417,-3340.34716796875,881.912841796875,1004.4173583984375
|
||||
idle,5.467,-3340.34716796875,881.912841796875,1004.4173583984375
|
||||
idle,5.519,-3416.122314453125,881.912841796875,1004.417236328125
|
||||
idle,5.569,-3427.77734375,881.912841796875,1004.4171752929688
|
||||
idle,5.619,-3462.7421875,881.9127807617188,1004.4171142578125
|
||||
idle,5.669,-3491.897216796875,881.9127807617188,1004.4170532226562
|
||||
idle,5.719,-3521.017333984375,881.9127807617188,1004.4169921875
|
||||
idle,5.769,-3521.017333984375,881.9127807617188,1004.4169921875
|
||||
idle,5.819,-3521.017333984375,881.9127807617188,1004.4169921875
|
||||
idle,5.87,-3521.017333984375,881.9127807617188,1004.4169921875
|
||||
idle,5.92,-3585.13720703125,881.9127197265625,1004.4168701171875
|
||||
idle,5.97,-3608.447265625,881.9127197265625,1004.4168090820312
|
||||
idle,6.02,-3643.412109375,881.9127197265625,1004.416748046875
|
||||
idle,6.07,-3655.067138671875,881.9127197265625,1004.416748046875
|
||||
idle,6.12,-3672.567138671875,881.9126586914062,1004.4166870117188
|
||||
idle,6.173,-3672.567138671875,881.9126586914062,1004.4166870117188
|
||||
idle,6.223,-3672.567138671875,881.9126586914062,1004.4166870117188
|
||||
idle,6.273,-3736.687255859375,881.9126586914062,1004.4165649414062
|
||||
idle,6.323,-3771.652099609375,881.9126586914062,1004.41650390625
|
||||
idle,6.374,-3783.30712890625,881.9126586914062,1004.41650390625
|
||||
idle,6.424,-3829.92724609375,881.91259765625,1004.4163818359375
|
||||
idle,6.474,-3835.7373046875,881.91259765625,1004.4163818359375
|
||||
idle,6.531,-3835.7373046875,881.91259765625,1004.4163818359375
|
||||
idle,6.581,-3835.7373046875,881.91259765625,1004.4163818359375
|
||||
idle,6.633,-3894.047119140625,881.91259765625,1004.416259765625
|
||||
idle,6.683,-3905.7021484375,881.9125366210938,1004.416259765625
|
||||
idle,6.761,-3952.322265625,881.9125366210938,1004.4161376953125
|
||||
idle,6.811,-3981.442138671875,881.9125366210938,1004.4160766601562
|
||||
idle,6.863,-3987.252197265625,881.9125366210938,1004.4160766601562
|
||||
idle,6.913,-3987.252197265625,881.9125366210938,1004.4160766601562
|
||||
idle,6.963,-3987.252197265625,881.9125366210938,1004.4160766601562
|
||||
idle,7.013,-4039.71728515625,881.9124755859375,1004.4159545898438
|
||||
idle,7.064,-4063.027099609375,881.9124755859375,1004.4158935546875
|
||||
idle,7.114,-4097.9921875,881.9124755859375,1004.4158325195312
|
||||
idle,7.164,-4109.64697265625,881.9124755859375,1004.4158325195312
|
||||
idle,7.214,-4150.42236328125,881.9124145507812,1004.415771484375
|
||||
idle,7.264,-4156.23193359375,881.9124145507812,1004.4157104492188
|
||||
idle,7.315,-4156.23193359375,881.9124145507812,1004.4157104492188
|
||||
idle,7.365,-4156.23193359375,881.9124145507812,1004.4157104492188
|
||||
idle,7.415,-4220.35205078125,881.9124145507812,1004.4155883789062
|
||||
idle,7.466,-4243.662109375,881.912353515625,1004.41552734375
|
||||
idle,7.516,-4278.626953125,881.912353515625,1004.4154663085938
|
||||
idle,7.566,-4307.7822265625,881.912353515625,1004.4154052734375
|
||||
idle,7.616,-4336.90234375,881.912353515625,1004.4153442382812
|
||||
idle,7.667,-4336.90234375,881.912353515625,1004.4153442382812
|
||||
idle,7.717,-4336.90234375,881.912353515625,1004.4153442382812
|
||||
idle,7.767,-4336.90234375,881.912353515625,1004.4153442382812
|
||||
idle,7.82,-4401.02197265625,881.9122924804688,1004.4152221679688
|
||||
idle,7.87,-4424.33203125,881.9122924804688,1004.4152221679688
|
||||
idle,7.92,-4470.9521484375,881.9122314453125,1004.4151000976562
|
||||
idle,7.97,-4482.60693359375,881.9122314453125,1004.4151000976562
|
||||
RT,0.0,-4511.76220703125,881.9122314453125,1004.4150390625
|
||||
RT,0.05,-4511.76220703125,881.9122314453125,1004.4150390625
|
||||
RT,0.1,-4511.76220703125,881.9122314453125,1004.4150390625
|
||||
RT,0.151,-4591.10205078125,881.9121704101562,1004.4148559570312
|
||||
RT,0.203,-4631.14013671875,881.9121704101562,1004.414794921875
|
||||
RT,0.253,-4688.6787109375,881.9121704101562,1004.4146728515625
|
||||
RT,0.303,-4745.97314453125,881.912109375,1004.41455078125
|
||||
RT,0.353,-4758.11572265625,881.912109375,1004.4144897460938
|
||||
RT,0.404,-4758.11572265625,881.912109375,1004.4144897460938
|
||||
RT,0.454,-4758.11572265625,881.912109375,1004.4144897460938
|
||||
RT,0.504,-4894.6865234375,881.9120483398438,1004.4142456054688
|
||||
RT,0.554,-4976.0556640625,881.9119873046875,1004.4140625
|
||||
RT,0.605,-5047.275390625,881.9119262695312,1004.4139404296875
|
||||
RT,0.655,-5163.5263671875,881.911865234375,1004.4136962890625
|
||||
RT,0.705,-5203.26416015625,881.911865234375,1004.4136352539062
|
||||
RT,0.756,-5223.07373046875,881.911865234375,1004.41357421875
|
||||
RT,0.806,-5223.07373046875,881.911865234375,1004.41357421875
|
||||
RT,0.857,-5223.07373046875,881.911865234375,1004.41357421875
|
||||
RT,0.908,-5421.8828125,881.9117431640625,1004.4132080078125
|
||||
RT,0.989,-5541.0966796875,881.9116821289062,1004.4129638671875
|
||||
RT,1.039,-5700.048828125,881.91162109375,1004.41259765625
|
||||
RT,1.09,-5739.78662109375,881.9115600585938,1004.4125366210938
|
||||
RT,1.14,-5779.52490234375,881.9115600585938,1004.4124755859375
|
||||
RT,1.19,-5779.52490234375,881.9115600585938,1004.4124755859375
|
||||
RT,1.24,-5779.52490234375,881.9115600585938,1004.4124755859375
|
||||
RT,1.29,-5978.333984375,881.9114379882812,1004.4120483398438
|
||||
RT,1.341,-6097.54833984375,881.911376953125,1004.4118041992188
|
||||
RT,1.391,-6177.02392578125,881.9113159179688,1004.4116821289062
|
||||
RT,1.441,-6316.1669921875,881.9112548828125,1004.411376953125
|
||||
RT,1.491,-6355.90478515625,881.9112548828125,1004.4113159179688
|
||||
RT,1.541,-6455.3095703125,881.9111938476562,1004.4110717773438
|
||||
RT,1.591,-6455.3095703125,881.9111938476562,1004.4110717773438
|
||||
RT,1.641,-6455.3095703125,881.9111938476562,1004.4110717773438
|
||||
RT,1.691,-6455.3095703125,881.9111938476562,1004.4110717773438
|
||||
RT,1.742,-6713.666015625,881.9110717773438,1004.4105834960938
|
||||
RT,1.792,-6793.14208984375,881.9110107421875,1004.410400390625
|
||||
RT,1.842,-6912.35595703125,881.9109497070312,1004.41015625
|
||||
RT,1.892,-6952.09423828125,881.9109497070312,1004.4100952148438
|
||||
RT,1.942,-7011.6416015625,881.910888671875,1004.4099731445312
|
||||
RT,1.992,-7011.6416015625,881.910888671875,1004.4099731445312
|
||||
RT,2.042,-7011.6416015625,881.910888671875,1004.4099731445312
|
||||
RT,2.092,-7011.6416015625,881.910888671875,1004.4099731445312
|
||||
RT,2.144,-7269.998046875,881.9107666015625,1004.409423828125
|
||||
RT,2.194,-7369.40283203125,881.9107055664062,1004.4092407226562
|
||||
RT,2.244,-7488.61669921875,881.91064453125,1004.4089965820312
|
||||
RT,2.294,-7528.3544921875,881.91064453125,1004.408935546875
|
||||
RT,2.347,-7587.90185546875,881.9105834960938,1004.4088134765625
|
||||
RT,2.397,-7587.90185546875,881.9105834960938,1004.4088134765625
|
||||
RT,2.448,-7587.90185546875,881.9105834960938,1004.4088134765625
|
||||
RT,2.498,-7587.90185546875,881.9105834960938,1004.4088134765625
|
||||
RT,2.549,-7826.44921875,881.9104614257812,1004.4083251953125
|
||||
RT,2.599,-7965.591796875,881.910400390625,1004.4080200195312
|
||||
RT,2.65,-8084.80615234375,881.9103393554688,1004.4077758789062
|
||||
RT,2.7,-8124.5439453125,881.9102783203125,1004.40771484375
|
||||
RT,2.751,-8164.16259765625,881.9102783203125,1004.4076538085938
|
||||
RT,2.801,-8164.16259765625,881.9102783203125,1004.4076538085938
|
||||
RT,2.852,-8164.16259765625,881.9102783203125,1004.4076538085938
|
||||
RT,2.902,-8164.16259765625,881.9102783203125,1004.4076538085938
|
||||
RT,2.952,-8422.638671875,881.91015625,1004.4071044921875
|
||||
RT,3.004,-8502.1142578125,881.9100952148438,1004.4069213867188
|
||||
RT,3.054,-8621.3291015625,881.9100341796875,1004.4066772460938
|
||||
RT,3.104,-8661.06640625,881.9100341796875,1004.4066162109375
|
||||
RT,3.155,-8740.4228515625,881.9099731445312,1004.4064331054688
|
||||
RT,3.205,-8740.4228515625,881.9099731445312,1004.4064331054688
|
||||
RT,3.29,-8740.4228515625,881.9099731445312,1004.4064331054688
|
||||
RT,3.34,-8939.232421875,881.9098510742188,1004.4060668945312
|
||||
RT,3.39,-8998.7802734375,881.9098510742188,1004.4059448242188
|
||||
RT,3.44,-9137.9228515625,881.9097290039062,1004.4056396484375
|
||||
RT,3.49,-9177.66015625,881.9097290039062,1004.4055786132812
|
||||
RT,3.54,-9276.9462890625,881.90966796875,1004.4053955078125
|
||||
RT,3.592,-9296.7548828125,881.90966796875,1004.4053344726562
|
||||
RT,3.642,-9296.7548828125,881.90966796875,1004.4053344726562
|
||||
RT,3.692,-9296.7548828125,881.90966796875,1004.4053344726562
|
||||
RT,3.747,-9555.1123046875,881.9095458984375,1004.40478515625
|
||||
RT,3.797,-9614.7783203125,881.9094848632812,1004.4046630859375
|
||||
RT,3.847,-9733.9921875,881.909423828125,1004.4044189453125
|
||||
RT,3.898,-9793.6591796875,881.909423828125,1004.404296875
|
||||
RT,3.948,-9873.015625,881.9093627929688,1004.4041748046875
|
||||
RT,3.998,-9873.015625,881.9093627929688,1004.4041748046875
|
||||
RT,4.048,-9873.015625,881.9093627929688,1004.4041748046875
|
||||
RT,4.098,-9873.015625,881.9093627929688,1004.4041748046875
|
||||
RT,4.148,-10111.5634765625,881.9092407226562,1004.4036865234375
|
||||
RT,4.198,-10151.30078125,881.9092407226562,1004.4036254882812
|
||||
RT,4.249,-10310.2529296875,881.9091186523438,1004.4032592773438
|
||||
RT,4.3,-10349.9912109375,881.9091186523438,1004.4031982421875
|
||||
RT,4.35,-10469.205078125,881.9090576171875,1004.4029541015625
|
||||
RT,4.4,-10469.205078125,881.9090576171875,1004.4029541015625
|
||||
RT,4.451,-10469.205078125,881.9090576171875,1004.4029541015625
|
||||
RT,4.501,-10469.205078125,881.9090576171875,1004.4029541015625
|
||||
RT,4.551,-10727.5615234375,881.9088745117188,1004.4024047851562
|
||||
RT,4.601,-10727.5615234375,881.9088745117188,1004.4024047851562
|
||||
RT,4.651,-10906.4423828125,881.9088134765625,1004.4020385742188
|
||||
RT,4.704,-11005.8466796875,881.9087524414062,1004.40185546875
|
||||
RT,4.754,-11065.39453125,881.9087524414062,1004.4017333984375
|
||||
RT,4.804,-11065.39453125,881.9087524414062,1004.4017333984375
|
||||
RT,4.855,-11065.39453125,881.9087524414062,1004.4017333984375
|
||||
RT,4.905,-11065.39453125,881.9087524414062,1004.4017333984375
|
||||
RT,4.957,-11323.7509765625,881.9085693359375,1004.4012451171875
|
||||
RT,5.007,-11423.15625,881.9085083007812,1004.4010009765625
|
||||
RT,5.058,-11542.3701171875,881.908447265625,1004.4007568359375
|
||||
RT,5.108,-11621.845703125,881.908447265625,1004.400634765625
|
||||
RT,5.158,-11681.3935546875,881.9083862304688,1004.4005126953125
|
||||
RT,5.208,-11681.3935546875,881.9083862304688,1004.4005126953125
|
||||
RT,5.259,-11681.3935546875,881.9083862304688,1004.4005126953125
|
||||
RT,5.309,-11681.3935546875,881.9083862304688,1004.4005126953125
|
||||
RT,5.36,-11939.75,881.9082641601562,1004.3999633789062
|
||||
RT,5.41,-12058.9638671875,881.908203125,1004.3997192382812
|
||||
RT,5.489,-12178.177734375,881.9081420898438,1004.3994750976562
|
||||
RT,5.539,-12297.3916015625,881.9080810546875,1004.3992309570312
|
||||
RT,5.59,-12297.3916015625,881.9080810546875,1004.3992309570312
|
||||
RT,5.64,-12297.3916015625,881.9080810546875,1004.3992309570312
|
||||
RT,5.69,-12297.3916015625,881.9080810546875,1004.3992309570312
|
||||
RT,5.743,-12555.748046875,881.9078979492188,1004.3987426757812
|
||||
RT,5.793,-12595.486328125,881.9078979492188,1004.398681640625
|
||||
RT,5.844,-12714.7001953125,881.9078369140625,1004.3984375
|
||||
RT,5.894,-12814.10546875,881.9077758789062,1004.398193359375
|
||||
RT,5.944,-12913.390625,881.90771484375,1004.3980102539062
|
||||
RT,5.994,-12913.390625,881.90771484375,1004.3980102539062
|
||||
RT,6.044,-12913.390625,881.90771484375,1004.3980102539062
|
||||
RT,6.094,-12913.390625,881.90771484375,1004.3980102539062
|
||||
RT,6.144,-13132.0087890625,881.9075927734375,1004.3975830078125
|
||||
RT,6.195,-13171.7470703125,881.9075927734375,1004.3975219726562
|
||||
RT,6.246,-13350.6279296875,881.907470703125,1004.3971557617188
|
||||
RT,6.296,-13390.365234375,881.907470703125,1004.3970336914062
|
||||
RT,6.347,-13489.6513671875,881.9074096679688,1004.3968505859375
|
||||
RT,6.397,-13489.6513671875,881.9074096679688,1004.3968505859375
|
||||
RT,6.447,-13489.6513671875,881.9074096679688,1004.3968505859375
|
||||
RT,6.497,-13489.6513671875,881.9074096679688,1004.3968505859375
|
||||
RT,6.548,-13728.1982421875,881.9072875976562,1004.3963623046875
|
||||
RT,6.598,-13807.673828125,881.9072265625,1004.3961791992188
|
||||
RT,6.649,-13966.6259765625,881.9071655273438,1004.3958740234375
|
||||
RT,6.701,-14066.03125,881.9071044921875,1004.3956909179688
|
||||
RT,6.752,-14125.578125,881.9071044921875,1004.3955688476562
|
||||
RT,6.802,-14125.578125,881.9071044921875,1004.3955688476562
|
||||
RT,6.852,-14125.578125,881.9071044921875,1004.3955688476562
|
||||
RT,6.902,-14125.578125,881.9071044921875,1004.3955688476562
|
||||
RT,6.952,-14383.9345703125,881.9069213867188,1004.39501953125
|
||||
RT,7.002,-14443.6015625,881.9069213867188,1004.3948974609375
|
||||
RT,7.052,-14562.8154296875,881.9068603515625,1004.3946533203125
|
||||
RT,7.102,-14602.5537109375,881.9067993164062,1004.3945922851562
|
||||
RT,7.152,-14721.767578125,881.90673828125,1004.3943481445312
|
||||
RT,7.202,-14721.767578125,881.90673828125,1004.3943481445312
|
||||
RT,7.252,-14721.767578125,881.90673828125,1004.3943481445312
|
||||
RT,7.302,-14721.767578125,881.90673828125,1004.3943481445312
|
||||
RT,7.354,-14960.314453125,881.9066162109375,1004.3938598632812
|
||||
RT,7.404,-15059.7197265625,881.9065551757812,1004.3936767578125
|
||||
RT,7.455,-15178.93359375,881.906494140625,1004.3934326171875
|
||||
RT,7.505,-15278.337890625,881.9064331054688,1004.3932495117188
|
||||
RT,7.555,-15337.8857421875,881.9064331054688,1004.3931274414062
|
||||
RT,7.605,-15337.8857421875,881.9064331054688,1004.3931274414062
|
||||
RT,7.656,-15337.8857421875,881.9064331054688,1004.3931274414062
|
||||
RT,7.706,-15496.95703125,881.9063720703125,1004.3927612304688
|
||||
RT,7.756,-15596.2421875,881.9063110351562,1004.392578125
|
||||
RT,7.806,-15655.9091796875,881.90625,1004.3924560546875
|
||||
RT,7.857,-15775.123046875,881.9061889648438,1004.3922119140625
|
||||
RT,7.907,-15874.52734375,881.9061279296875,1004.3920288085938
|
||||
RT,7.957,-15894.3369140625,881.9061279296875,1004.3919677734375
|
||||
coast,0.0,-15894.3369140625,881.9061279296875,1004.3919677734375
|
||||
coast,0.05,-15894.3369140625,881.9061279296875,1004.3919677734375
|
||||
coast,0.1,-16132.259765625,881.906005859375,1004.3914794921875
|
||||
coast,0.15,-16228.6279296875,881.9059448242188,1004.3912963867188
|
||||
coast,0.201,-16341.4130859375,881.9058837890625,1004.3910522460938
|
||||
coast,0.251,-16414.818359375,881.9058837890625,1004.3909301757812
|
||||
coast,0.302,-16432.935546875,881.9058227539062,1004.390869140625
|
||||
coast,0.352,-16432.935546875,881.9058227539062,1004.390869140625
|
||||
coast,0.402,-16432.935546875,881.9058227539062,1004.390869140625
|
||||
coast,0.453,-16589.41015625,881.90576171875,1004.3905639648438
|
||||
coast,0.503,-16674.68359375,881.9057006835938,1004.390380859375
|
||||
coast,0.553,-16741.302734375,881.9057006835938,1004.3902587890625
|
||||
coast,0.603,-16838.55078125,881.9056396484375,1004.3900756835938
|
||||
coast,0.653,-16870.25390625,881.9056396484375,1004.3900146484375
|
||||
coast,0.705,-16885.966796875,881.9055786132812,1004.3899536132812
|
||||
coast,0.755,-16885.966796875,881.9055786132812,1004.3899536132812
|
||||
coast,0.805,-16885.966796875,881.9055786132812,1004.3899536132812
|
||||
coast,0.855,-17035.544921875,881.905517578125,1004.3896484375
|
||||
coast,0.906,-17122.61328125,881.9054565429688,1004.3894653320312
|
||||
coast,0.956,-17150.923828125,881.9054565429688,1004.389404296875
|
||||
coast,1.006,-17233.70703125,881.9053955078125,1004.3892822265625
|
||||
coast,1.056,-17260.5859375,881.9053955078125,1004.3892211914062
|
||||
coast,1.107,-17287.109375,881.9053955078125,1004.38916015625
|
||||
coast,1.157,-17287.109375,881.9053955078125,1004.38916015625
|
||||
coast,1.207,-17387.55859375,881.9053344726562,1004.3889770507812
|
||||
coast,1.259,-17424.537109375,881.9053344726562,1004.3888549804688
|
||||
coast,1.309,-17496.0703125,881.9052734375,1004.3887329101562
|
||||
coast,1.359,-17541.97265625,881.9052734375,1004.3886108398438
|
||||
coast,1.409,-17586.4453125,881.9052124023438,1004.3885498046875
|
||||
coast,1.459,-17629.4921875,881.9052124023438,1004.3884887695312
|
||||
coast,1.509,-17629.4921875,881.9052124023438,1004.3884887695312
|
||||
coast,1.559,-17629.4921875,881.9052124023438,1004.3884887695312
|
||||
coast,1.611,-17718.390625,881.9051513671875,1004.3883056640625
|
||||
coast,1.661,-17747.33984375,881.9051513671875,1004.3882446289062
|
||||
coast,1.737,-17802.796875,881.9050903320312,1004.3881225585938
|
||||
coast,1.787,-17855.041015625,881.9050903320312,1004.3880004882812
|
||||
coast,1.837,-17871.7421875,881.9050903320312,1004.387939453125
|
||||
coast,1.889,-17896.01171875,881.905029296875,1004.387939453125
|
||||
coast,1.942,-17896.01171875,881.905029296875,1004.387939453125
|
||||
coast,1.992,-17896.01171875,881.905029296875,1004.387939453125
|
||||
coast,2.042,-17976.451171875,881.905029296875,1004.3877563476562
|
||||
coast,2.098,-18003.77734375,881.905029296875,1004.3876953125
|
||||
coast,2.148,-18016.90625,881.9049682617188,1004.3876953125
|
||||
coast,2.198,-18071.580078125,881.9049682617188,1004.3875732421875
|
||||
coast,2.248,-18077.390625,881.9049682617188,1004.3875732421875
|
||||
coast,2.302,-18077.390625,881.9049682617188,1004.3875732421875
|
||||
coast,2.352,-18077.390625,881.9049682617188,1004.3875732421875
|
||||
coast,2.402,-18135.69921875,881.9049072265625,1004.387451171875
|
||||
coast,2.452,-18147.35546875,881.9049072265625,1004.3873901367188
|
||||
coast,2.537,-18193.974609375,881.9049072265625,1004.3873291015625
|
||||
coast,2.587,-18228.939453125,881.9049072265625,1004.3872680664062
|
||||
coast,2.638,-18234.75,881.9049072265625,1004.38720703125
|
||||
coast,2.688,-18234.75,881.9049072265625,1004.38720703125
|
||||
coast,2.738,-18234.75,881.9049072265625,1004.38720703125
|
||||
coast,2.788,-18293.060546875,881.9048461914062,1004.3870849609375
|
||||
coast,2.84,-18310.525390625,881.9048461914062,1004.3870849609375
|
||||
coast,2.89,-18345.490234375,881.9048461914062,1004.3870239257812
|
||||
coast,2.94,-18357.14453125,881.90478515625,1004.386962890625
|
||||
coast,2.99,-18386.265625,881.90478515625,1004.3869018554688
|
||||
coast,3.043,-18392.07421875,881.90478515625,1004.3869018554688
|
||||
coast,3.093,-18392.07421875,881.90478515625,1004.3869018554688
|
||||
coast,3.143,-18392.07421875,881.90478515625,1004.3869018554688
|
||||
coast,3.193,-18467.849609375,881.9047241210938,1004.3867797851562
|
||||
coast,3.243,-18479.505859375,881.9047241210938,1004.38671875
|
||||
coast,3.293,-18514.470703125,881.9047241210938,1004.3866577148438
|
||||
coast,3.343,-18537.779296875,881.9047241210938,1004.3865966796875
|
||||
coast,3.394,-18561.0546875,881.9047241210938,1004.3865966796875
|
||||
coast,3.445,-18561.0546875,881.9047241210938,1004.3865966796875
|
||||
coast,3.495,-18561.0546875,881.9047241210938,1004.3865966796875
|
||||
coast,3.546,-18561.0546875,881.9047241210938,1004.3865966796875
|
||||
coast,3.596,-18631.01953125,881.9046630859375,1004.3864135742188
|
||||
coast,3.646,-18654.330078125,881.9046630859375,1004.3863525390625
|
||||
coast,3.697,-18700.94921875,881.9046020507812,1004.3862915039062
|
||||
coast,3.747,-18712.60546875,881.9046020507812,1004.3862915039062
|
||||
coast,3.797,-18747.5703125,881.9046020507812,1004.3861694335938
|
||||
coast,3.847,-18747.5703125,881.9046020507812,1004.3861694335938
|
||||
coast,3.897,-18747.5703125,881.9046020507812,1004.3861694335938
|
||||
coast,3.947,-18747.5703125,881.9046020507812,1004.3861694335938
|
||||
coast,3.998,-18823.345703125,881.904541015625,1004.3860473632812
|
||||
coast,4.048,-18846.654296875,881.904541015625,1004.385986328125
|
||||
coast,4.098,-18881.619140625,881.904541015625,1004.3859252929688
|
||||
coast,4.148,-18904.9296875,881.904541015625,1004.3858642578125
|
||||
coast,4.199,-18910.740234375,881.904541015625,1004.3858642578125
|
||||
coast,4.249,-18910.740234375,881.904541015625,1004.3858642578125
|
||||
coast,4.299,-18910.740234375,881.904541015625,1004.3858642578125
|
||||
coast,4.349,-18910.740234375,881.904541015625,1004.3858642578125
|
||||
coast,4.399,-18980.705078125,881.9044799804688,1004.3857421875
|
||||
coast,4.449,-19021.515625,881.9044799804688,1004.3856201171875
|
||||
coast,4.499,-19056.48046875,881.9044189453125,1004.3855590820312
|
||||
coast,4.549,-19068.134765625,881.9044189453125,1004.3855590820312
|
||||
coast,4.6,-19073.9453125,881.9044189453125,1004.385498046875
|
||||
coast,4.65,-19073.9453125,881.9044189453125,1004.385498046875
|
||||
coast,4.7,-19073.9453125,881.9044189453125,1004.385498046875
|
||||
coast,4.751,-19138.099609375,881.9044189453125,1004.3853759765625
|
||||
coast,4.801,-19155.564453125,881.9043579101562,1004.3853759765625
|
||||
coast,4.851,-19190.529296875,881.9043579101562,1004.3853149414062
|
||||
coast,4.902,-19225.494140625,881.9043579101562,1004.3851928710938
|
||||
coast,4.952,-19237.150390625,881.9043579101562,1004.3851928710938
|
||||
coast,5.005,-19242.958984375,881.9043579101562,1004.3851928710938
|
||||
coast,5.055,-19242.958984375,881.9043579101562,1004.3851928710938
|
||||
coast,5.109,-19242.958984375,881.9043579101562,1004.3851928710938
|
||||
coast,5.159,-19312.92578125,881.904296875,1004.3850708007812
|
||||
coast,5.209,-19347.890625,881.904296875,1004.3849487304688
|
||||
coast,5.259,-19377.044921875,881.9042358398438,1004.3848876953125
|
||||
coast,5.31,-19412.009765625,881.9042358398438,1004.3848266601562
|
||||
coast,5.36,-19423.6640625,881.9042358398438,1004.3848266601562
|
||||
coast,5.411,-19423.6640625,881.9042358398438,1004.3848266601562
|
||||
coast,5.461,-19423.6640625,881.9042358398438,1004.3848266601562
|
||||
coast,5.537,-19481.974609375,881.9042358398438,1004.3847045898438
|
||||
coast,5.587,-19522.78515625,881.9041748046875,1004.3846435546875
|
||||
coast,5.637,-19534.439453125,881.9041748046875,1004.3845825195312
|
||||
coast,5.687,-19569.404296875,881.9041748046875,1004.384521484375
|
||||
coast,5.737,-19586.869140625,881.9041748046875,1004.3844604492188
|
||||
coast,5.787,-19586.869140625,881.9041748046875,1004.3844604492188
|
||||
coast,5.839,-19586.869140625,881.9041748046875,1004.3844604492188
|
||||
coast,5.889,-19586.869140625,881.9041748046875,1004.3844604492188
|
||||
coast,5.939,-19650.990234375,881.9041137695312,1004.3843383789062
|
||||
coast,5.989,-19685.955078125,881.9041137695312,1004.38427734375
|
||||
coast,6.039,-19697.609375,881.9041137695312,1004.38427734375
|
||||
coast,6.09,-19744.23046875,881.904052734375,1004.3841552734375
|
||||
coast,6.14,-19755.849609375,881.904052734375,1004.3841552734375
|
||||
coast,6.192,-19755.849609375,881.904052734375,1004.3841552734375
|
||||
coast,6.242,-19755.849609375,881.904052734375,1004.3841552734375
|
||||
coast,6.292,-19814.16015625,881.904052734375,1004.384033203125
|
||||
coast,6.343,-19831.625,881.9039916992188,1004.3839721679688
|
||||
coast,6.393,-19878.244140625,881.9039916992188,1004.3839111328125
|
||||
coast,6.443,-19889.900390625,881.9039916992188,1004.3838500976562
|
||||
coast,6.494,-19913.173828125,881.9039916992188,1004.3838500976562
|
||||
coast,6.544,-19913.173828125,881.9039916992188,1004.3838500976562
|
||||
coast,6.594,-19913.173828125,881.9039916992188,1004.3838500976562
|
||||
coast,6.644,-19913.173828125,881.9039916992188,1004.3838500976562
|
||||
coast,6.694,-19988.94921875,881.9039306640625,1004.3836669921875
|
||||
coast,6.744,-20006.44921875,881.9039306640625,1004.3836059570312
|
||||
coast,6.795,-20041.4140625,881.9039306640625,1004.383544921875
|
||||
coast,6.846,-20070.5703125,881.9038696289062,1004.3834838867188
|
||||
coast,6.896,-20088.03515625,881.9038696289062,1004.3834838867188
|
||||
coast,6.946,-20088.03515625,881.9038696289062,1004.3834838867188
|
||||
coast,6.996,-20088.03515625,881.9038696289062,1004.3834838867188
|
||||
coast,7.046,-20088.03515625,881.9038696289062,1004.3834838867188
|
||||
coast,7.097,-20152.154296875,881.9038696289062,1004.3833618164062
|
||||
coast,7.147,-20192.96484375,881.90380859375,1004.3832397460938
|
||||
coast,7.197,-20227.9296875,881.90380859375,1004.3831787109375
|
||||
coast,7.247,-20245.4296875,881.90380859375,1004.3831787109375
|
||||
coast,7.298,-20257.083984375,881.90380859375,1004.3831176757812
|
||||
coast,7.348,-20257.083984375,881.90380859375,1004.3831176757812
|
||||
coast,7.399,-20257.083984375,881.90380859375,1004.3831176757812
|
||||
coast,7.449,-20315.39453125,881.9037475585938,1004.3829956054688
|
||||
coast,7.499,-20344.515625,881.9037475585938,1004.3829345703125
|
||||
coast,7.55,-20367.82421875,881.9037475585938,1004.3828735351562
|
||||
coast,7.6,-20414.4453125,881.9036865234375,1004.3828125
|
||||
coast,7.65,-20426.099609375,881.9036865234375,1004.3828125
|
||||
coast,7.702,-20437.75390625,881.9036865234375,1004.3827514648438
|
||||
coast,7.752,-20437.75390625,881.9036865234375,1004.3827514648438
|
||||
coast,7.802,-20437.75390625,881.9036865234375,1004.3827514648438
|
||||
coast,7.852,-20507.71875,881.9036865234375,1004.3826293945312
|
||||
coast,7.902,-20542.685546875,881.9036254882812,1004.382568359375
|
||||
coast,7.952,-20554.33984375,881.9036254882812,1004.3825073242188
|
||||
LT,0.0,-20600.958984375,881.9036254882812,1004.3824462890625
|
||||
LT,0.05,-20600.958984375,881.9036254882812,1004.3824462890625
|
||||
LT,0.104,-20600.958984375,881.9036254882812,1004.3824462890625
|
||||
LT,0.175,-20641.892578125,881.903564453125,1004.38232421875
|
||||
LT,0.225,-20670.3359375,881.903564453125,1004.3822631835938
|
||||
LT,0.275,-20679.1328125,881.903564453125,1004.3822631835938
|
||||
LT,0.325,-20703.380859375,881.903564453125,1004.3822021484375
|
||||
LT,0.376,-20714.333984375,881.903564453125,1004.3822021484375
|
||||
LT,0.426,-20714.333984375,881.903564453125,1004.3822021484375
|
||||
LT,0.479,-20714.333984375,881.903564453125,1004.3822021484375
|
||||
LT,0.529,-20714.333984375,881.903564453125,1004.3822021484375
|
||||
LT,0.579,-20745.40234375,881.9035034179688,1004.3821411132812
|
||||
LT,0.629,-20758.935546875,881.9035034179688,1004.3821411132812
|
||||
LT,0.679,-20762.732421875,881.9035034179688,1004.382080078125
|
||||
LT,0.729,-20772.962890625,881.9035034179688,1004.382080078125
|
||||
LT,0.779,-20778.05078125,881.9035034179688,1004.382080078125
|
||||
LT,0.829,-20778.05078125,881.9035034179688,1004.382080078125
|
||||
LT,0.88,-20778.05078125,881.9035034179688,1004.382080078125
|
||||
LT,0.93,-20795.037109375,881.9035034179688,1004.3820190429688
|
||||
LT,0.981,-20800.125,881.9035034179688,1004.3820190429688
|
||||
LT,1.031,-20808.619140625,881.9035034179688,1004.3820190429688
|
||||
LT,1.081,-20817.11328125,881.9035034179688,1004.3820190429688
|
||||
LT,1.131,-20827.298828125,881.9035034179688,1004.3819580078125
|
||||
LT,1.183,-20828.990234375,881.9035034179688,1004.3819580078125
|
||||
LT,1.233,-20828.990234375,881.9035034179688,1004.3819580078125
|
||||
LT,1.283,-20828.990234375,881.9035034179688,1004.3819580078125
|
||||
LT,1.335,-20847.669921875,881.9035034179688,1004.3819580078125
|
||||
LT,1.386,-20854.4609375,881.9034423828125,1004.3818969726562
|
||||
LT,1.436,-20864.646484375,881.9034423828125,1004.3818969726562
|
||||
LT,1.486,-20868.041015625,881.9034423828125,1004.3818969726562
|
||||
LT,1.536,-20874.822265625,881.9034423828125,1004.3818969726562
|
||||
LT,1.586,-20874.822265625,881.9034423828125,1004.3818969726562
|
||||
LT,1.636,-20874.822265625,881.9034423828125,1004.3818969726562
|
||||
LT,1.686,-20874.822265625,881.9034423828125,1004.3818969726562
|
||||
LT,1.737,-20895.203125,881.9034423828125,1004.3818359375
|
||||
LT,1.787,-20903.697265625,881.9034423828125,1004.3818359375
|
||||
LT,1.837,-20913.8828125,881.9034423828125,1004.3817749023438
|
||||
LT,1.887,-20917.279296875,881.9034423828125,1004.3817749023438
|
||||
LT,1.937,-20924.068359375,881.9034423828125,1004.3817749023438
|
||||
LT,1.987,-20924.068359375,881.9034423828125,1004.3817749023438
|
||||
LT,2.037,-20924.068359375,881.9034423828125,1004.3817749023438
|
||||
LT,2.088,-20924.068359375,881.9034423828125,1004.3817749023438
|
||||
LT,2.139,-20946.14453125,881.9034423828125,1004.3817138671875
|
||||
LT,2.189,-20956.330078125,881.9034423828125,1004.3817138671875
|
||||
LT,2.239,-20966.515625,881.9034423828125,1004.3817138671875
|
||||
LT,2.289,-20969.91015625,881.9033813476562,1004.3817138671875
|
||||
LT,2.34,-20971.603515625,881.9033813476562,1004.3816528320312
|
||||
LT,2.39,-20971.603515625,881.9033813476562,1004.3816528320312
|
||||
LT,2.44,-20971.603515625,881.9033813476562,1004.3816528320312
|
||||
LT,2.492,-20991.974609375,881.9033813476562,1004.3816528320312
|
||||
LT,2.542,-21002.16015625,881.9033813476562,1004.381591796875
|
||||
LT,2.592,-21005.556640625,881.9033813476562,1004.381591796875
|
||||
LT,2.642,-21015.7421875,881.9033813476562,1004.381591796875
|
||||
LT,2.693,-21017.435546875,881.9033813476562,1004.381591796875
|
||||
LT,2.775,-21017.435546875,881.9033813476562,1004.381591796875
|
||||
LT,2.825,-21017.435546875,881.9033813476562,1004.381591796875
|
||||
LT,2.878,-21037.81640625,881.9033813476562,1004.3815307617188
|
||||
LT,2.928,-21048.001953125,881.9033813476562,1004.3815307617188
|
||||
LT,2.978,-21051.3984375,881.9033813476562,1004.3815307617188
|
||||
LT,3.029,-21061.583984375,881.9033813476562,1004.3814697265625
|
||||
LT,3.079,-21063.275390625,881.9033813476562,1004.3814697265625
|
||||
LT,3.129,-21063.275390625,881.9033813476562,1004.3814697265625
|
||||
LT,3.179,-21063.275390625,881.9033813476562,1004.3814697265625
|
||||
LT,3.229,-21081.955078125,881.9033203125,1004.3814697265625
|
||||
LT,3.279,-21090.44921875,881.9033203125,1004.3814697265625
|
||||
LT,3.329,-21097.240234375,881.9033203125,1004.3814086914062
|
||||
LT,3.379,-21100.634765625,881.9033203125,1004.3814086914062
|
||||
LT,3.429,-21110.8203125,881.9033203125,1004.3814086914062
|
||||
LT,3.48,-21110.8203125,881.9033203125,1004.3814086914062
|
||||
LT,3.53,-21110.8203125,881.9033203125,1004.3814086914062
|
||||
LT,3.58,-21110.8203125,881.9033203125,1004.3814086914062
|
||||
LT,3.631,-21132.89453125,881.9033203125,1004.38134765625
|
||||
LT,3.681,-21143.080078125,881.9033203125,1004.38134765625
|
||||
LT,3.731,-21153.267578125,881.9033203125,1004.3812866210938
|
||||
LT,3.781,-21160.056640625,881.9033203125,1004.3812866210938
|
||||
LT,3.831,-21163.453125,881.9033203125,1004.3812866210938
|
||||
LT,3.882,-21163.453125,881.9033203125,1004.3812866210938
|
||||
LT,3.932,-21163.453125,881.9033203125,1004.3812866210938
|
||||
LT,3.982,-21177.033203125,881.9033203125,1004.3812866210938
|
||||
LT,4.035,-21188.921875,881.9033203125,1004.3812255859375
|
||||
LT,4.085,-21199.107421875,881.9032592773438,1004.3812255859375
|
||||
LT,4.135,-21205.8984375,881.9032592773438,1004.3812255859375
|
||||
LT,4.187,-21207.591796875,881.9032592773438,1004.3812255859375
|
||||
LT,4.237,-21207.591796875,881.9032592773438,1004.3812255859375
|
||||
LT,4.287,-21207.591796875,881.9032592773438,1004.3812255859375
|
||||
LT,4.337,-21222.875,881.9032592773438,1004.3811645507812
|
||||
LT,4.387,-21229.666015625,881.9032592773438,1004.3811645507812
|
||||
LT,4.437,-21239.8515625,881.9032592773438,1004.3811645507812
|
||||
LT,4.487,-21244.94921875,881.9032592773438,1004.381103515625
|
||||
LT,4.537,-21251.73046875,881.9032592773438,1004.381103515625
|
||||
LT,4.587,-21251.73046875,881.9032592773438,1004.381103515625
|
||||
LT,4.638,-21251.73046875,881.9032592773438,1004.381103515625
|
||||
LT,4.688,-21251.73046875,881.9032592773438,1004.381103515625
|
||||
LT,4.741,-21272.111328125,881.9032592773438,1004.381103515625
|
||||
LT,4.791,-21284.0,881.9032592773438,1004.3810424804688
|
||||
LT,4.841,-21290.791015625,881.9032592773438,1004.3810424804688
|
||||
LT,4.891,-21297.58203125,881.9032592773438,1004.3810424804688
|
||||
LT,4.942,-21299.275390625,881.9032592773438,1004.3810424804688
|
||||
LT,4.992,-21299.275390625,881.9032592773438,1004.3810424804688
|
||||
LT,5.042,-21299.275390625,881.9032592773438,1004.3810424804688
|
||||
LT,5.092,-21316.26171875,881.9031982421875,1004.3809814453125
|
||||
LT,5.142,-21324.744140625,881.9031982421875,1004.3809814453125
|
||||
LT,5.192,-21331.53515625,881.9031982421875,1004.3809814453125
|
||||
LT,5.242,-21341.720703125,881.9031982421875,1004.3809204101562
|
||||
LT,5.292,-21346.818359375,881.9031982421875,1004.3809204101562
|
||||
LT,5.343,-21350.21484375,881.9031982421875,1004.3809204101562
|
||||
LT,5.393,-21350.21484375,881.9031982421875,1004.3809204101562
|
||||
LT,5.443,-21362.103515625,881.9031982421875,1004.3809204101562
|
||||
LT,5.493,-21370.5859375,881.9031982421875,1004.380859375
|
||||
LT,5.578,-21380.771484375,881.9031982421875,1004.380859375
|
||||
LT,5.628,-21390.95703125,881.9031982421875,1004.380859375
|
||||
LT,5.678,-21396.044921875,881.9031982421875,1004.3807983398438
|
||||
LT,5.728,-21396.044921875,881.9031982421875,1004.3807983398438
|
||||
LT,5.778,-21396.044921875,881.9031982421875,1004.3807983398438
|
||||
LT,5.828,-21413.03125,881.9031982421875,1004.3807983398438
|
||||
LT,5.882,-21418.119140625,881.9031982421875,1004.3807983398438
|
||||
LT,5.932,-21430.0078125,881.9031372070312,1004.3807373046875
|
||||
LT,5.983,-21436.798828125,881.9031372070312,1004.3807373046875
|
||||
LT,6.033,-21443.58984375,881.9031372070312,1004.3807373046875
|
||||
LT,6.085,-21445.283203125,881.9031372070312,1004.3807373046875
|
||||
LT,6.135,-21445.283203125,881.9031372070312,1004.3807373046875
|
||||
LT,6.185,-21445.283203125,881.9031372070312,1004.3807373046875
|
||||
LT,6.235,-21463.9609375,881.9031372070312,1004.3806762695312
|
||||
LT,6.285,-21469.060546875,881.9031372070312,1004.3806762695312
|
||||
LT,6.335,-21479.24609375,881.9031372070312,1004.3806762695312
|
||||
LT,6.385,-21482.640625,881.9031372070312,1004.3806762695312
|
||||
LT,6.438,-21489.431640625,881.9031372070312,1004.380615234375
|
||||
LT,6.488,-21489.431640625,881.9031372070312,1004.380615234375
|
||||
LT,6.538,-21489.431640625,881.9031372070312,1004.380615234375
|
||||
LT,6.588,-21506.41796875,881.9031372070312,1004.380615234375
|
||||
LT,6.638,-21514.900390625,881.9031372070312,1004.3805541992188
|
||||
LT,6.689,-21525.087890625,881.9031372070312,1004.3805541992188
|
||||
LT,6.739,-21535.2734375,881.9031372070312,1004.3805541992188
|
||||
LT,6.789,-21538.66796875,881.903076171875,1004.3805541992188
|
||||
LT,6.84,-21542.064453125,881.903076171875,1004.3805541992188
|
||||
LT,6.89,-21542.064453125,881.903076171875,1004.3805541992188
|
||||
LT,6.94,-21542.064453125,881.903076171875,1004.3805541992188
|
||||
LT,6.99,-21560.7421875,881.903076171875,1004.3804931640625
|
||||
LT,7.075,-21570.927734375,881.903076171875,1004.3804931640625
|
||||
LT,7.125,-21581.115234375,881.903076171875,1004.3804321289062
|
||||
LT,7.175,-21586.203125,881.903076171875,1004.3804321289062
|
||||
LT,7.225,-21586.203125,881.903076171875,1004.3804321289062
|
||||
LT,7.275,-21586.203125,881.903076171875,1004.3804321289062
|
||||
LT,7.325,-21603.189453125,881.903076171875,1004.3804321289062
|
||||
LT,7.376,-21608.27734375,881.903076171875,1004.38037109375
|
||||
LT,7.426,-21620.166015625,881.903076171875,1004.38037109375
|
||||
LT,7.476,-21620.166015625,881.903076171875,1004.38037109375
|
||||
LT,7.526,-21632.04296875,881.903076171875,1004.38037109375
|
||||
LT,7.577,-21633.736328125,881.903076171875,1004.38037109375
|
||||
LT,7.627,-21633.736328125,881.903076171875,1004.38037109375
|
||||
LT,7.677,-21633.736328125,881.903076171875,1004.38037109375
|
||||
LT,7.729,-21652.416015625,881.9030151367188,1004.3803100585938
|
||||
LT,7.779,-21659.20703125,881.9030151367188,1004.3803100585938
|
||||
LT,7.829,-21672.787109375,881.9030151367188,1004.3802490234375
|
||||
LT,7.879,-21676.18359375,881.9030151367188,1004.3802490234375
|
||||
LT,7.929,-21681.271484375,881.9030151367188,1004.3802490234375
|
||||
LT,7.979,-21681.271484375,881.9030151367188,1004.3802490234375
|
||||
coast2,0.0,-21699.958984375,881.9030151367188,1004.3801879882812
|
||||
coast2,0.05,-21709.087890625,881.9030151367188,1004.3801879882812
|
||||
coast2,0.1,-21722.490234375,881.9030151367188,1004.3801879882812
|
||||
coast2,0.15,-21733.17578125,881.9030151367188,1004.380126953125
|
||||
coast2,0.2,-21748.78125,881.9030151367188,1004.380126953125
|
||||
coast2,0.25,-21748.78125,881.9030151367188,1004.380126953125
|
||||
coast2,0.301,-21748.78125,881.9030151367188,1004.380126953125
|
||||
coast2,0.351,-21780.17578125,881.9029541015625,1004.3800659179688
|
||||
coast2,0.401,-21797.376953125,881.9029541015625,1004.3800048828125
|
||||
coast2,0.478,-21826.087890625,881.9029541015625,1004.3799438476562
|
||||
coast2,0.528,-21858.291015625,881.9029541015625,1004.3798828125
|
||||
coast2,0.579,-21875.611328125,881.9029541015625,1004.3798828125
|
||||
coast2,0.629,-21875.611328125,881.9029541015625,1004.3798828125
|
||||
coast2,0.68,-21875.611328125,881.9029541015625,1004.3798828125
|
||||
coast2,0.73,-21875.611328125,881.9029541015625,1004.3798828125
|
||||
coast2,0.78,-21951.38671875,881.9028930664062,1004.3796997070312
|
||||
coast2,0.83,-21986.3515625,881.9028930664062,1004.379638671875
|
||||
coast2,0.881,-21998.005859375,881.90283203125,1004.3795776367188
|
||||
coast2,0.932,-22044.626953125,881.90283203125,1004.3795166015625
|
||||
coast2,0.982,-22062.091796875,881.90283203125,1004.3794555664062
|
||||
coast2,1.032,-22062.091796875,881.90283203125,1004.3794555664062
|
||||
coast2,1.082,-22062.091796875,881.90283203125,1004.3794555664062
|
||||
coast2,1.132,-22062.091796875,881.90283203125,1004.3794555664062
|
||||
coast2,1.183,-22143.7109375,881.9027709960938,1004.3793334960938
|
||||
coast2,1.233,-22178.677734375,881.9027709960938,1004.3792114257812
|
||||
coast2,1.283,-22190.33203125,881.9027709960938,1004.3792114257812
|
||||
coast2,1.333,-22236.951171875,881.9027099609375,1004.379150390625
|
||||
coast2,1.383,-22242.76171875,881.9027099609375,1004.3790893554688
|
||||
coast2,1.433,-22242.76171875,881.9027099609375,1004.3790893554688
|
||||
coast2,1.483,-22242.76171875,881.9027099609375,1004.3790893554688
|
||||
coast2,1.533,-22289.416015625,881.9027099609375,1004.3790283203125
|
||||
coast2,1.583,-22318.537109375,881.9027099609375,1004.3789672851562
|
||||
coast2,1.634,-22353.501953125,881.9026489257812,1004.37890625
|
||||
coast2,1.684,-22365.15625,881.9026489257812,1004.3788452148438
|
||||
coast2,1.734,-22411.77734375,881.9026489257812,1004.3787841796875
|
||||
coast2,1.784,-22417.5859375,881.9026489257812,1004.3787841796875
|
||||
coast2,1.834,-22417.5859375,881.9026489257812,1004.3787841796875
|
||||
coast2,1.885,-22417.5859375,881.9026489257812,1004.3787841796875
|
||||
coast2,1.935,-22475.896484375,881.902587890625,1004.378662109375
|
||||
coast2,1.989,-22493.361328125,881.902587890625,1004.3786010742188
|
||||
coast2,2.042,-22539.982421875,881.902587890625,1004.3785400390625
|
||||
coast2,2.092,-22563.291015625,881.9025268554688,1004.3784790039062
|
||||
coast2,2.143,-22592.412109375,881.9025268554688,1004.37841796875
|
||||
coast2,2.194,-22598.22265625,881.9025268554688,1004.37841796875
|
||||
coast2,2.279,-22598.22265625,881.9025268554688,1004.37841796875
|
||||
coast2,2.329,-22598.22265625,881.9025268554688,1004.37841796875
|
||||
coast2,2.38,-22662.341796875,881.9025268554688,1004.3782348632812
|
||||
coast2,2.43,-22708.9609375,881.9024658203125,1004.378173828125
|
||||
coast2,2.48,-22708.9609375,881.9024658203125,1004.378173828125
|
||||
coast2,2.531,-22743.92578125,881.9024658203125,1004.3781127929688
|
||||
coast2,2.581,-22761.392578125,881.9024658203125,1004.3780517578125
|
||||
coast2,2.631,-22761.392578125,881.9024658203125,1004.3780517578125
|
||||
coast2,2.681,-22761.392578125,881.9024658203125,1004.3780517578125
|
||||
coast2,2.731,-22761.392578125,881.9024658203125,1004.3780517578125
|
||||
coast2,2.781,-22837.166015625,881.9024047851562,1004.3779296875
|
||||
coast2,2.832,-22872.130859375,881.9024047851562,1004.3778686523438
|
||||
coast2,2.882,-22883.787109375,881.9024047851562,1004.3778076171875
|
||||
coast2,2.933,-22930.40625,881.90234375,1004.3777465820312
|
||||
coast2,2.983,-22942.02734375,881.90234375,1004.377685546875
|
||||
coast2,3.033,-22942.02734375,881.90234375,1004.377685546875
|
||||
coast2,3.083,-22942.02734375,881.90234375,1004.377685546875
|
||||
coast2,3.133,-22994.52734375,881.90234375,1004.3775634765625
|
||||
coast2,3.183,-23011.9921875,881.90234375,1004.3775634765625
|
||||
coast2,3.234,-23046.95703125,881.9022827148438,1004.3775024414062
|
||||
coast2,3.284,-23058.611328125,881.9022827148438,1004.37744140625
|
||||
coast2,3.334,-23105.232421875,881.9022827148438,1004.3773803710938
|
||||
coast2,3.384,-23111.041015625,881.9022827148438,1004.3773803710938
|
||||
coast2,3.435,-23111.041015625,881.9022827148438,1004.3773803710938
|
||||
coast2,3.485,-23111.041015625,881.9022827148438,1004.3773803710938
|
||||
coast2,3.535,-23163.541015625,881.9022216796875,1004.3772583007812
|
||||
coast2,3.587,-23181.005859375,881.9022216796875,1004.377197265625
|
||||
coast2,3.677,-23221.81640625,881.9022216796875,1004.3771362304688
|
||||
coast2,3.728,-23256.78125,881.9021606445312,1004.3770751953125
|
||||
coast2,3.778,-23274.24609375,881.9021606445312,1004.3770141601562
|
||||
coast2,3.828,-23274.24609375,881.9021606445312,1004.3770141601562
|
||||
coast2,3.878,-23274.24609375,881.9021606445312,1004.3770141601562
|
||||
coast2,3.928,-23274.24609375,881.9021606445312,1004.3770141601562
|
||||
coast2,3.979,-23344.2109375,881.9021606445312,1004.3768920898438
|
||||
coast2,4.029,-23379.17578125,881.902099609375,1004.3768310546875
|
||||
coast2,4.079,-23390.83203125,881.902099609375,1004.3767700195312
|
||||
coast2,4.129,-23437.451171875,881.902099609375,1004.376708984375
|
||||
coast2,4.18,-23449.072265625,881.902099609375,1004.3766479492188
|
||||
coast2,4.23,-23449.072265625,881.902099609375,1004.3766479492188
|
||||
coast2,4.28,-23449.072265625,881.902099609375,1004.3766479492188
|
||||
coast2,4.33,-23449.072265625,881.902099609375,1004.3766479492188
|
||||
coast2,4.38,-23524.845703125,881.9020385742188,1004.3765258789062
|
||||
coast2,4.43,-23559.8125,881.9020385742188,1004.37646484375
|
||||
coast2,4.48,-23571.466796875,881.9020385742188,1004.3764038085938
|
||||
coast2,4.53,-23606.431640625,881.9019775390625,1004.3763427734375
|
||||
coast2,4.58,-23623.896484375,881.9019775390625,1004.3763427734375
|
||||
coast2,4.631,-23623.896484375,881.9019775390625,1004.3763427734375
|
||||
coast2,4.681,-23623.896484375,881.9019775390625,1004.3763427734375
|
||||
coast2,4.731,-23623.896484375,881.9019775390625,1004.3763427734375
|
||||
coast2,4.781,-23688.015625,881.9019775390625,1004.3761596679688
|
||||
coast2,4.832,-23722.982421875,881.9019165039062,1004.3760986328125
|
||||
coast2,4.882,-23746.291015625,881.9019165039062,1004.3760986328125
|
||||
coast2,4.934,-23781.255859375,881.9019165039062,1004.3759765625
|
||||
coast2,4.984,-23804.56640625,881.9019165039062,1004.3759765625
|
||||
coast2,5.034,-23822.06640625,881.90185546875,1004.3759155273438
|
||||
coast2,5.084,-23822.06640625,881.90185546875,1004.3759155273438
|
||||
coast2,5.134,-23822.06640625,881.90185546875,1004.3759155273438
|
||||
coast2,5.184,-23874.56640625,881.90185546875,1004.3757934570312
|
||||
coast2,5.234,-23915.341796875,881.90185546875,1004.375732421875
|
||||
coast2,5.284,-23938.65234375,881.9017944335938,1004.3756713867188
|
||||
coast2,5.334,-23985.271484375,881.9017944335938,1004.3756103515625
|
||||
coast2,5.385,-23996.92578125,881.9017944335938,1004.3755493164062
|
||||
coast2,5.438,-24008.546875,881.9017944335938,1004.3755493164062
|
||||
coast2,5.488,-24008.546875,881.9017944335938,1004.3755493164062
|
||||
coast2,5.539,-24008.546875,881.9017944335938,1004.3755493164062
|
||||
coast2,5.589,-24008.546875,881.9017944335938,1004.3755493164062
|
||||
coast2,5.639,-24095.9765625,881.9017333984375,1004.3753662109375
|
||||
coast2,5.689,-24125.130859375,881.9017333984375,1004.3753051757812
|
||||
coast2,5.739,-24160.095703125,881.9016723632812,1004.375244140625
|
||||
coast2,5.789,-24171.751953125,881.9016723632812,1004.3751831054688
|
||||
coast2,5.839,-24195.02734375,881.9016723632812,1004.3751831054688
|
||||
coast2,5.889,-24195.02734375,881.9016723632812,1004.3751831054688
|
||||
coast2,5.94,-24195.02734375,881.9016723632812,1004.3751831054688
|
||||
coast2,5.99,-24195.02734375,881.9016723632812,1004.3751831054688
|
||||
coast2,6.04,-24264.9921875,881.9016723632812,1004.375
|
||||
coast2,6.091,-24288.30078125,881.901611328125,1004.375
|
||||
coast2,6.141,-24334.921875,881.901611328125,1004.3748779296875
|
||||
coast2,6.192,-24358.232421875,881.901611328125,1004.3748168945312
|
||||
coast2,6.242,-24387.3515625,881.9015502929688,1004.374755859375
|
||||
coast2,6.292,-24393.162109375,881.9015502929688,1004.374755859375
|
||||
coast2,6.345,-24393.162109375,881.9015502929688,1004.374755859375
|
||||
coast2,6.395,-24393.162109375,881.9015502929688,1004.374755859375
|
||||
coast2,6.446,-24451.470703125,881.9015502929688,1004.3746337890625
|
||||
coast2,6.496,-24480.591796875,881.9015502929688,1004.3745727539062
|
||||
coast2,6.578,-24515.556640625,881.9014892578125,1004.37451171875
|
||||
coast2,6.628,-24568.021484375,881.9014892578125,1004.3743896484375
|
||||
coast2,6.678,-24579.67578125,881.9014892578125,1004.3743896484375
|
||||
coast2,6.728,-24591.33203125,881.9014892578125,1004.3743286132812
|
||||
coast2,6.778,-24591.33203125,881.9014892578125,1004.3743286132812
|
||||
coast2,6.829,-24591.33203125,881.9014892578125,1004.3743286132812
|
||||
coast2,6.879,-24655.451171875,881.9014282226562,1004.3742065429688
|
||||
coast2,6.929,-24690.416015625,881.9014282226562,1004.3741455078125
|
||||
coast2,6.979,-24702.072265625,881.9014282226562,1004.3741455078125
|
||||
coast2,7.031,-24737.037109375,881.9013671875,1004.3740844726562
|
||||
coast2,7.081,-24760.345703125,881.9013671875,1004.3740234375
|
||||
coast2,7.132,-24772.001953125,881.9013671875,1004.3739624023438
|
||||
coast2,7.182,-24772.001953125,881.9013671875,1004.3739624023438
|
||||
coast2,7.232,-24772.001953125,881.9013671875,1004.3739624023438
|
||||
coast2,7.284,-24830.310546875,881.9013671875,1004.3738403320312
|
||||
coast2,7.334,-24865.27734375,881.9013061523438,1004.373779296875
|
||||
coast2,7.384,-24894.431640625,881.9013061523438,1004.3737182617188
|
||||
coast2,7.434,-24929.396484375,881.9013061523438,1004.3736572265625
|
||||
coast2,7.484,-24952.70703125,881.9012451171875,1004.3735961914062
|
||||
coast2,7.534,-24958.515625,881.9012451171875,1004.3735961914062
|
||||
coast2,7.584,-24958.515625,881.9012451171875,1004.3735961914062
|
||||
coast2,7.635,-24958.515625,881.9012451171875,1004.3735961914062
|
||||
coast2,7.685,-24958.515625,881.9012451171875,1004.3735961914062
|
||||
coast2,7.736,-25028.48046875,881.9012451171875,1004.3734741210938
|
||||
coast2,7.786,-25057.63671875,881.9012451171875,1004.3734130859375
|
||||
coast2,7.837,-25092.6015625,881.9011840820312,1004.3733520507812
|
||||
coast2,7.887,-25104.255859375,881.9011840820312,1004.373291015625
|
||||
coast2,7.937,-25121.720703125,881.9011840820312,1004.373291015625
|
||||
coast2,7.987,-25121.720703125,881.9011840820312,1004.373291015625
|
||||
|
15
docs/re/captures/stage-s01-capture-truth-offsets.txt
Normal file
@@ -0,0 +1,15 @@
|
||||
file offset vcount candidate claimed by our decode our resources with that vcount
|
||||
0x38788 181 NO — NOBODY — none
|
||||
0x4b8b8 93 NO — NOBODY — none
|
||||
0xb6574 41 NO — NOBODY n042_bdy_m e007_bdy_01 @ -0x24864
|
||||
0xdbbac 77 NO — NOBODY — none _rou_e010_break @ -0x5050
|
||||
0x133da0 76 NO — NOBODY e303_wep_01_m, n041_bdy_l _rou_e010_break @ -0x407bc
|
||||
0x162840 60 NO — NOBODY — none _rou_e010_break @ -0x6f25c
|
||||
0x3b3ee8 119 yes e106_bdy_01_l(119), e106_bdy_02_l(119) e106_bdy_01_l, e106_bdy_02_l e106_bdy_01_l @ -0x0
|
||||
0x3c55d8 119 yes — NOBODY e106_bdy_01_l, e106_bdy_02_l e106_bdy_01_m @ -0x104e8
|
||||
0x3dd2c4 146 yes e106_bdy_03_l(146) e106_bdy_03_l e106_bdy_03_l @ -0x0
|
||||
0x40763c 179 yes e106_bdy_04_l(179) e106_bdy_04_l e106_bdy_04_l @ -0x0
|
||||
0x40e418 51 yes — NOBODY e106_brg_01_b_02, e106_brg_01_l e106_brg_01_b_02 @ -0x5d0
|
||||
0x444ccc 58 yes e106_eng_01_l(58) e106_eng_01_l e106_eng_01_l @ -0x0
|
||||
0x44a32c 44 yes — NOBODY e106_eng_02_l e106_eng_02 @ -0x35b8
|
||||
0x45705c 82 yes e106_wep_02_01_l(82) e106_wep_02_01_l e106_wep_02_01_l @ -0x0
|
||||
47
docs/re/captures/stage-s02-capture-truth-offsets.txt
Normal file
@@ -0,0 +1,47 @@
|
||||
file offset vcount candidate claimed by our decode our resources with that vcount
|
||||
0x135036c 240 yes _rou_f101(240) — none _rou_f101 @ -0x0
|
||||
0x13519ec 240 NO _rou_f101(240) — none _rou_f101 @ -0x0
|
||||
0x13fd6e8 25448 yes f101_bdy_01(25448) f101_bdy_01 f101_bdy_01 @ -0x0
|
||||
0x192961c 13172 yes f101_bdy_02(13172) f101_bdy_02 f101_bdy_02 @ -0x0
|
||||
0x1a0befc 2296 yes f101_bdy_03(2296) f101_bdy_03 f101_bdy_03 @ -0x0
|
||||
0x1a22c1c 7694 yes f101_eng_01(7694) f101_eng_01 f101_eng_01 @ -0x0
|
||||
0x1be5df4 628 yes f101_wep_01_m(628) f101_wep_01_m f101_wep_01_m @ -0x0
|
||||
0x1bf40f4 261 yes e_rob_f001(24), e_rou_f003_Near(24), e_rou_f101_wep_01(24), e_rou_f105(24), _rou_f105_break(261), e_rou_f106(24), e_rou_f302_barrel(24), e_rou_f302_base(24), e_rou_f303_barrel(24), e_rou_f303_base(24), e_rou_e007_Far(24), e_rou_e007_Near(24), e_rou_e010_Far(24), e_rou_e010_Near(24), e_rou_e105(24), e_rou_e105_wep_01(24), e_rou_e106(24), e_rou_e106_eng(24), e_rou_e106_wep_02_01(24), e_rou_e106_wep_02_joint(24), e_rou_e108_Missile_open(24), e_rou_e201(24), e_rou_e302_barrel(24), e_rou_e302_base(24), e_rou_e303_barrel_Near(24), e_rou_e303_base_Near(24), e_rou_e501(24) f106_eng_01_l e_rob_f001 @ -0x0
|
||||
0x1bf596c 2446 NO _rou_f105_break(2446) f105_bdy_01_m _rou_f105_break @ -0x0
|
||||
0x1c03ebc 1960 NO _rou_f105_break(1960) f105_bdy_02_m _rou_f105_break @ -0x0
|
||||
0x1c0f67c 82 NO _rou_f105_break(82) e106_wep_02_01_l _rou_f105_break @ -0x0
|
||||
0x1c0fe2c 2336 NO _rou_f105_break(2336) f105_bdy_03_m _rou_f105_break @ -0x0
|
||||
0x1c1d92c 220 NO _rou_f105_break(220) — none _rou_f105_break @ -0x0
|
||||
0x1c1edcc 80 NO _rou_f105_break(80) f001_bdy_24_m _rou_f105_break @ -0x0
|
||||
0x1c1f54c 148 NO _rou_f105_break(148) — none _rou_f105_break @ -0x0
|
||||
0x1c2032c 156 NO _rou_f105_break(156) e105_bdy_01_l _rou_f105_break @ -0x0
|
||||
0x1c211cc 1402 NO _rou_f105_break(1402) f105_eng_01_m _rou_f105_break @ -0x0
|
||||
0x1c2953c 84 NO _rou_f105_break(84) — none _rou_f105_break @ -0x0
|
||||
0x1c29d1c 192 NO _rou_f105_break(192) f105_sld_01_b01 _rou_f105_break @ -0x0
|
||||
0x1c2af1c 122 NO _rou_f105_break(122) e106_eng_02_m _rou_f105_break @ -0x0
|
||||
0x1c2ba8c 24 NO _rou_f105_break(24) e_rob_f001, e_rou_f003_Far, e_rou_f003_Near, e_rou_f101_wep_01, e_rou_f105, f105_sld_01_l, e_rou_f106, e_rou_f302_barrel, e_rou_f302_base, f302_01_l, e_rou_f303_barrel, e_rou_f303_base, _rou_f402_dead, e_rou_e007_Far, e_rou_e007_Near, e_rou_e010_Far, e_rou_e010_Near, e_rou_e105, e_rou_e105_wep_01, e_rou_e106, e_rou_e106_eng, e_rou_e106_wep_02_01, e_rou_e106_wep_02_joint, e_rou_e108_Missile_open, e_rou_e201, e201_bdy_03_m, e_rou_e302_barrel, e_rou_e302_base, e_rou_e303_barrel_Near, e_rou_e303_base_Near, e303_wep_02_l, e_rou_e501 _rou_f105_break @ -0x0
|
||||
0x227ac94 1692 yes f106_bdy_02_l(1692) f106_bdy_02_l f106_bdy_02_l @ -0x0
|
||||
0x22c2ef0 686 yes f106_bdy_03_l(686) f106_bdy_03_l f106_bdy_03_l @ -0x0
|
||||
0x22ed420 261 yes f106_eng_01_l(261) f106_eng_01_l f106_eng_01_l @ -0x0
|
||||
0x2326c9c 254 yes f106_sld_01_l(254) f106_sld_01_l, f106_sld_02_l, e303_wep_02_m f106_sld_01_l @ -0x0
|
||||
0x233b3fc 254 yes f106_sld_02_l(254) f106_sld_01_l, f106_sld_02_l, e303_wep_02_m f106_sld_02_l @ -0x0
|
||||
0x24ce5f4 32 yes f303_body_l(32) f303_body_l, e302_barrel_l f303_body_l @ -0x0
|
||||
0x24fa1c8 128 yes _rou_f402(128) _rou_f402 _rou_f402 @ -0x0
|
||||
0x28ea1d4 156 yes e105_bdy_01_l(156) e105_bdy_01_l e105_bdy_01_l @ -0x0
|
||||
0x28f7514 107 yes e105_bdy_02_l(107) e105_bdy_02_l, e105_brg_m e105_bdy_02_l @ -0x0
|
||||
0x291e1e8 185 yes e105_bdy_03_l(185) e105_bdy_03_l e105_bdy_03_l @ -0x0
|
||||
0x29a4788 181 yes e105_bdy_04_l(181) e105_bdy_04_l e105_bdy_04_l @ -0x0
|
||||
0x29b78b8 93 yes e105_bdy_05_l(93) e105_bdy_05_l e105_bdy_05_l @ -0x0
|
||||
0x2a22574 41 yes e105_bdy_06_l(41) e105_bdy_06_l e105_bdy_06_l @ -0x0
|
||||
0x2a47bac 77 yes e105_brg_l(77) e105_brg_l e105_brg_l @ -0x0
|
||||
0x2a9fda0 76 yes e105_eng_01_l(76) e105_eng_01_l, e303_wep_01_m e105_eng_01_l @ -0x0
|
||||
0x2ace840 60 yes e105_wep_01_l(60) e105_wep_01_l e105_wep_01_l @ -0x0
|
||||
0x2d1fee8 119 yes e106_bdy_01_l(119) e106_bdy_01_l, e106_bdy_02_l e106_bdy_01_l @ -0x0
|
||||
0x2d315d8 119 yes e106_bdy_02_l(119) e106_bdy_01_l, e106_bdy_02_l e106_bdy_02_l @ -0x0
|
||||
0x2d492c4 146 yes e106_bdy_03_l(146) e106_bdy_03_l e106_bdy_03_l @ -0x0
|
||||
0x2d7363c 179 yes e106_bdy_04_l(179) e106_bdy_04_l e106_bdy_04_l @ -0x0
|
||||
0x2d7a418 51 yes e106_brg_01_l(51) e106_brg_01_b_02, e106_brg_01_l e106_brg_01_l @ -0x0
|
||||
0x2db0ccc 58 yes e106_eng_01_l(58) e106_eng_01_l e106_eng_01_l @ -0x0
|
||||
0x2db632c 44 yes e106_eng_02_l(44) e106_eng_02_l, e302_base_m e106_eng_02_l @ -0x0
|
||||
0x2dc305c 82 yes e106_wep_02_01_l(82) e106_wep_02_01_l e106_wep_02_01_l @ -0x0
|
||||
0x329077c 6000 yes n006_02(6000) n006_02 n006_02 @ -0x0
|
||||
50
docs/re/captures/stage-s02-index-buffer-truth.txt
Normal file
@@ -0,0 +1,50 @@
|
||||
container load constant: vbase - file_offset = 0x17FE3FF4 (93 buffers agree)
|
||||
decoded 356 resources, 422 distinct vertex offsets
|
||||
|
||||
vb 0x13FD6E8 v=25448 batches 8 idx 52077 span 104154 gap 2 decpad 2 cover exact f101_bdy_01(v25448,i52077)
|
||||
vb 0x192961C v=13172 batches 2 idx 24816 span 49632 gap 0 decpad 0 cover exact f101_bdy_02(v13172,i24816)
|
||||
vb 0x1BF40F4 v=261 batches 3 idx 498 span 996 gap 41476 decpad 41476 cover exact e_rob_f001(v24,i36) e_rou_f003_Near(v24,i36) e_rou_f101_wep_01(v24,i36) e_rou_f105(v24,i36) _rou_f105_break(v261,i498) e_rou_f106(v24,i36) e_rou_f302_barrel(v24,i36) e_rou_f302_base(v24,i36) e_rou_f303_barrel(v24,i36) e_rou_f303_base(v24,i36) e_rou_e007_Far(v24,i36) e_rou_e007_Near(v24,i36) e_rou_e010_Far(v24,i36) e_rou_e010_Near(v24,i36) e_rou_e105(v24,i36) e_rou_e105_wep_01(v24,i36) e_rou_e106(v24,i36) e_rou_e106_eng(v24,i36) e_rou_e106_wep_02_01(v24,i36) e_rou_e106_wep_02_joint(v24,i36) e_rou_e108_Missile_open(v24,i36) e_rou_e201(v24,i36) e_rou_e302_barrel(v24,i36) e_rou_e302_base(v24,i36) e_rou_e303_barrel_Near(v24,i36) e_rou_e303_base_Near(v24,i36) e_rou_e501(v24,i36)
|
||||
vb 0x1C0F67C v=82 batches 1 idx 144 span 288 gap 133672 decpad 133672 cover exact _rou_f105_break(v82,i144)
|
||||
vb 0x1C1D92C v=220 batches 3 idx 384 span 768 gap 179872 decpad 179872 cover exact _rou_f105_break(v220,i384)
|
||||
vb 0x1C1EDCC v=80 batches 2 idx 132 span 264 gap 184888 decpad 184888 cover exact _rou_f105_break(v80,i132)
|
||||
vb 0x1C1F54C v=148 batches 3 idx 288 span 576 gap 186232 decpad 186232 cover exact _rou_f105_break(v148,i288)
|
||||
vb 0x1C2032C v=156 batches 1 idx 246 span 492 gap 189292 decpad 189292 cover exact _rou_f105_break(v156,i246)
|
||||
vb 0x1C2953C v=84 batches 1 idx 156 span 312 gap 220012 decpad 220012 cover exact _rou_f105_break(v84,i156)
|
||||
vb 0x1C2AF1C v=122 batches 3 idx 351 span 702 gap 224882 decpad 224882 cover exact _rou_f105_break(v122,i351)
|
||||
vb 0x1C2BA8C v=24 batches 2 idx 36 span 72 gap 227736 decpad 227736 cover exact _rou_f105_break(v24,i36)
|
||||
vb 0x1ECB4C0 v=2446 batches 4 idx 5466 span 10932 gap 0 decpad 0 cover exact f105_bdy_01_m(v2446,i5466)
|
||||
vb 0x227AC94 v=1692 batches 3 idx 3651 span 7302 gap 2 decpad 2 cover exact f106_bdy_02_l(v1692,i3651)
|
||||
vb 0x22C2EF0 v=686 batches 2 idx 1506 span 3012 gap 0 decpad 0 cover exact f106_bdy_03_l(v686,i1506)
|
||||
vb 0x22C8F50 v=1558 batches 3 idx 4098 span 8196 gap 0 decpad 0 cover exact f106_bdy_03_m(v1558,i4098)
|
||||
vb 0x22ED420 v=261 batches 4 idx 561 span 1122 gap 2 decpad 2 cover exact f106_eng_01_l(v261,i561)
|
||||
vb 0x22F0430 v=1162 batches 4 idx 3018 span 6036 gap 0 decpad 0 cover exact f106_eng_01_m(v1162,i3018)
|
||||
vb 0x2326C9C v=254 batches 2 idx 495 span 990 gap 2 decpad 2 cover exact f106_sld_01_l(v254,i495)
|
||||
vb 0x233B3FC v=254 batches 2 idx 495 span 990 gap 2 decpad 2 cover exact f106_sld_02_l(v254,i495)
|
||||
vb 0x233D700 v=627 batches 3 idx 1434 span 2868 gap 0 decpad 0 cover exact f106_sld_02_m(v627,i1434)
|
||||
vb 0x24CE5F4 v=32 batches 1 idx 60 span 120 gap 0 decpad 0 cover exact f303_body_l(v32,i60)
|
||||
vb 0x28EA1D4 v=156 batches 2 idx 228 span 456 gap 0 decpad 0 cover exact e105_bdy_01_l(v156,i228)
|
||||
vb 0x28F7514 v=107 batches 3 idx 180 span 360 gap 0 decpad 0 cover exact e105_bdy_02_l(v107,i180)
|
||||
vb 0x291E1E8 v=185 batches 2 idx 285 span 570 gap 2 decpad 2 cover exact e105_bdy_03_l(v185,i285)
|
||||
vb 0x29A4788 v=181 batches 3 idx 318 span 636 gap 0 decpad 0 cover exact e105_bdy_04_l(v181,i318)
|
||||
vb 0x29B78B8 v=93 batches 2 idx 117 span 234 gap 2 decpad 2 cover exact e105_bdy_05_l(v93,i117)
|
||||
vb 0x2A22574 v=41 batches 1 idx 51 span 102 gap 2 decpad 2 cover exact e105_bdy_06_l(v41,i51)
|
||||
vb 0x2A47BAC v=77 batches 1 idx 111 span 222 gap 2 decpad 2 cover exact e105_brg_l(v77,i111)
|
||||
vb 0x2A9FDA0 v=76 batches 1 idx 90 span 180 gap 0 decpad 0 cover exact e105_eng_01_l(v76,i90)
|
||||
vb 0x2ACE840 v=60 batches 1 idx 90 span 180 gap 0 decpad 0 cover exact e105_wep_01_l(v60,i90)
|
||||
vb 0x2D1FEE8 v=119 batches 2 idx 246 span 492 gap 0 decpad 0 cover exact e106_bdy_01_l(v119,i246)
|
||||
vb 0x2D315D8 v=119 batches 2 idx 246 span 492 gap 0 decpad 0 cover exact e106_bdy_02_l(v119,i246)
|
||||
vb 0x2D492C4 v=146 batches 2 idx 324 span 648 gap 0 decpad 0 cover exact e106_bdy_03_l(v146,i324)
|
||||
vb 0x2D7363C v=179 batches 2 idx 399 span 798 gap 2 decpad 2 cover exact e106_bdy_04_l(v179,i399)
|
||||
vb 0x2D7A418 v=51 batches 2 idx 126 span 252 gap 0 decpad 0 cover exact e106_brg_01_l(v51,i126)
|
||||
vb 0x2DB0CCC v=58 batches 2 idx 96 span 192 gap 0 decpad 0 cover exact e106_eng_01_l(v58,i96)
|
||||
vb 0x2DB632C v=44 batches 2 idx 72 span 144 gap 0 decpad 0 cover exact e106_eng_02_l(v44,i72)
|
||||
vb 0x2DC305C v=82 batches 2 idx 168 span 336 gap 0 decpad 0 cover exact e106_wep_02_01_l(v82,i168)
|
||||
vb 0x329077C v=6000 batches 1 idx 6000 span 12000 gap 0 decpad 0 cover exact n006_02(v6000,i6000)
|
||||
vb 0x3434030 v=4 batches 1 idx 6 span 12 gap 24 decpad 24 cover exact n301_02B(v4,i6)
|
||||
vb 0x3434090 v=4 batches 1 idx 6 span 12 gap 108 decpad 108 cover exact n301_02B(v4,i6)
|
||||
vb 0x34340F0 v=4 batches 1 idx 6 span 12 gap 192 decpad 192 cover exact n301_02B(v4,i6)
|
||||
|
||||
placed 42 drawn buffers in this container
|
||||
DECODER layout assumption — whole index buffer at vb - 2*idx_count - pad: pad 0 20 · pad 1..3 10 · elsewhere 12 · not decoded here 0
|
||||
index extent: our idx_count == sum of captured batches for 42 buffers, differs for 0
|
||||
vertex-pool coverage by the union of batches: exact 42 · short 0
|
||||
125
docs/re/captures/stage02-defaulted-field-runtime-values.txt
Normal file
@@ -0,0 +1,125 @@
|
||||
|
||||
map check: 406 fields agree with the disc, 0 disagree
|
||||
|
||||
runtime value where the disc record DEFAULTS the field:
|
||||
AA_AxisMode_Max 0 (10 units)
|
||||
AA_AxisMode_Min 0, 1.74533 (11 units)
|
||||
AA_PitchMinus_Max 0, 0.0628319, 0.174533 (8 units)
|
||||
AA_PitchMinus_Min 0, 0.0628319, 0.261799, 0.349066, 0.698132 (10 units)
|
||||
AA_PitchPlus_Max 0, 0.0628319 (6 units)
|
||||
AA_PitchPlus_Min 0, 0.0628319, 1.74533 (8 units)
|
||||
AA_Roll_Max 0, 0.0628319, 0.174533, 0.523599, 1.0472 (10 units)
|
||||
AA_Roll_Min 0, 0.0628319, 1.0472 (7 units)
|
||||
AA_Yaw_Max 0, 0.0628319, 0.0872665 (7 units)
|
||||
AA_Yaw_Min 0, 0.0628319, 0.261799 (7 units)
|
||||
AB_AA_PitchMinus 0, 0.174533 (11 units)
|
||||
AB_AA_PitchPlus 0 (10 units)
|
||||
AB_AA_Roll 0, 0.523599 (11 units)
|
||||
AB_AA_Yaw 0, 0.174533 (11 units)
|
||||
AB_AV_PitchMinus 0 (10 units)
|
||||
AB_AV_PitchPlus 0, 0.523599 (11 units)
|
||||
AB_AV_Roll 0, 0.698132 (11 units)
|
||||
AB_AV_Yaw 0, 0.279253 (11 units)
|
||||
AB_ConsumeShield 0, 10 (11 units)
|
||||
AB_ConsumeShield_Begin 0 (10 units)
|
||||
AV_AxisMode_Max 0, 0.174533 (11 units)
|
||||
AV_AxisMode_Min 0 (10 units)
|
||||
AV_PitchMinus_Max 0, 0.0628319, 0.349066 (7 units)
|
||||
AV_PitchMinus_Min 0, 0.0628319 (6 units)
|
||||
AV_PitchPlus_Max 0 (1 units)
|
||||
AV_PitchPlus_Min 0, 0.0628319, 1.74533 (7 units)
|
||||
AV_Roll_Max 0, 0.0628319, 0.349066, 1.0472, 2.0944 (10 units)
|
||||
AV_Roll_Min 0, 0.0628319, 1.0472 (7 units)
|
||||
AV_Yaw_Max 0, 0.0628319, 0.174533 (7 units)
|
||||
AV_Yaw_Min 0, 0.0628319, 0.349066 (7 units)
|
||||
AccPitchFactor 0, 30 (5 units)
|
||||
Acceleration 0 (1 units)
|
||||
ArterBurner_Acc 0, 2 (10 units)
|
||||
ArterBurner_Vc 0, 2 (6 units)
|
||||
AttackCraftPoint 0, 0.1, 0.5, 1 (9 units)
|
||||
AttackVesselPoint 0, 0.1 (3 units)
|
||||
BarrelRoll_CountMaximum 0, 5 (11 units)
|
||||
BarrelRoll_CountMinimum 0, 0.5 (11 units)
|
||||
BarrelRoll_Radius 0 (7 units)
|
||||
BarrelRoll_Time 0 (7 units)
|
||||
BoostAway_Time_Maximum 0, 2 (11 units)
|
||||
BoostAway_Time_Minimum 0, 1 (11 units)
|
||||
ChargeDelay 1, 3, 5 (10 units)
|
||||
ChargeDelay_Break 1, 10 (6 units)
|
||||
ChargeSpeed 25 (3 units)
|
||||
Color_B 0, 1 (3 units)
|
||||
Color_G 1 (1 units)
|
||||
CruisingVelocity 0 (1 units)
|
||||
CutoffTimeMax 0, 5 (10 units)
|
||||
CutoffTimeMin 0 (7 units)
|
||||
DecPitchFactor 0, 30 (11 units)
|
||||
Deceleration 0, 50 (2 units)
|
||||
DefencePoint 0, 0.1, 0.25 (5 units)
|
||||
Delay 0, 1.5, 3 (8 units)
|
||||
DelayAdjustment 0, 1, 2 (10 units)
|
||||
DestroyMotionTime 0 (1 units)
|
||||
DragFactorThreshold 0, 0.5 (10 units)
|
||||
DryMass 100, 10000 (8 units)
|
||||
FCSRange 0, 500000 (3 units)
|
||||
FiringRange 0 (11 units)
|
||||
GrossMass 10000, 200 (10 units)
|
||||
HQRatio 0.2, 1 (9 units)
|
||||
HoldPosition_BackRatio 0, 0.1, 0.3 (11 units)
|
||||
HoldPosition_CancelTime 0, 20 (10 units)
|
||||
HoldPosition_CutoffRatio 0, 0.5 (11 units)
|
||||
HoldPosition_LengthMax 0, 400 (8 units)
|
||||
HoldPosition_LengthMin 0, 150 (10 units)
|
||||
HoldPosition_MaximumTime 0, 10 (10 units)
|
||||
HoldPosition_MinimumTime 0, 5 (11 units)
|
||||
HoldPosition_SideRatio 0, 0.3 (11 units)
|
||||
HomingResistAdjustment 0, 0.5 (11 units)
|
||||
LowerHPThresholdRatio 0, 0.3 (7 units)
|
||||
MaxValue 100 (3 units)
|
||||
MaximumBank_Normal 0, 0.523599, 1.0472 (5 units)
|
||||
MaximumVelocity 0 (1 units)
|
||||
MinimumVelocity 0, 100 (7 units)
|
||||
PitchDragFactor 0, 2, 3 (11 units)
|
||||
PowerCutConsumeShield 0, 20 (11 units)
|
||||
PowerCutDeceleration 0, 10 (11 units)
|
||||
RadarRange 0 (1 units)
|
||||
ResistanceParalyze 1 (1 units)
|
||||
ResistanceToExplosion 0.1, 0.5, 1 (9 units)
|
||||
ResistanceToOptics 0.1, 1 (4 units)
|
||||
ResistanceToPlayer 0, 1 (10 units)
|
||||
ResistanceToShell 0.1, 1 (8 units)
|
||||
ReverseThrust_Acc 0, 0.25, 2 (9 units)
|
||||
ReverseThrust_Vc 0 (1 units)
|
||||
RollDragFactor 0, 2, 3 (11 units)
|
||||
SELength 2000 (11 units)
|
||||
ShieldRatio 1 (6 units)
|
||||
SideRoll_Length 0 (7 units)
|
||||
SideRoll_Time 0, 0.5, 1.5 (11 units)
|
||||
SideThrustAcceleration 0, 100, 1000 (5 units)
|
||||
SideThrustVelocity_Max 0, 100, 1000 (3 units)
|
||||
Size_Radius 10, 50 (4 units)
|
||||
Size_Y 200, 300, 400, 600, 700 (5 units)
|
||||
Slalom_CutoffRatio 0 (7 units)
|
||||
Slalom_TurnCount_Max 0 (11 units)
|
||||
Slalom_TurnCount_Min 0 (9 units)
|
||||
SolidCutoff_LengthMax 0 (7 units)
|
||||
SolidCutoff_LengthMin 0, 500 (9 units)
|
||||
SolidCutoff_Ratio 0, 0.5 (11 units)
|
||||
Through_AngleMaximum 0, 1.0472 (11 units)
|
||||
Through_AngleMinimum 0, 0.523599 (11 units)
|
||||
Through_CutoffRatio 0, 0.5 (11 units)
|
||||
Through_LengthMaximum 0, 500 (10 units)
|
||||
Through_LengthMinimum 0, 250 (11 units)
|
||||
Through_Time1Max 0, 2 (11 units)
|
||||
Through_Time1Min 0, 5 (11 units)
|
||||
Through_Time2Max 0, 1.5 (8 units)
|
||||
Through_Time2Min 0, 5 (11 units)
|
||||
ThrusterRatio 0.2, 1 (10 units)
|
||||
TurnAttack_CutoffRatio 0 (7 units)
|
||||
TurnAttack_DoubleRatio 0 (7 units)
|
||||
TurnAttack_DoubleTimeMax 0 (7 units)
|
||||
TurnAttack_DoubleTimeMin 0 (7 units)
|
||||
TurnAway_Time_Maximum 0, 2, 5 (11 units)
|
||||
TurnAway_Time_Minimum 0, 0.5 (11 units)
|
||||
Turn_AngularVelocity 0, 0.523599 (8 units)
|
||||
UsingChaffRatio 0 (11 units)
|
||||
YawDragFactor 0 (1 units)
|
||||