Handle retail park streams and spline headers across all disc maps

This commit is contained in:
chazmm committed 2026-09-08 04:44:05 +01:00
1 parent ae107e2876
commit a7090f9ef3
4 files changed
+34 -5

No files matched your search

+9 -2
View File
@@ -102,8 +102,15 @@ pub(crate) fn primitives(map: Option<&SkateMap>) -> Result<Vec<Primitive>,String
let [a,b,c,d] = coefficients;
let end = std::array::from_fn(|j| (a[j]+b[j])+(c[j]+d[j]));
let length: f32 = (0..3).map(|j| (end[j]-d[j]).powi(2)).sum();
if !length.is_finite() || length <= 0.000001 {
return Err(format!("Spline segment {i} has a degenerate contact chord"));
if !length.is_finite() {
return Err(format!("Spline segment {i} has a non-finite contact chord"));
}
if length <= 0.000001 {
// Retail Downtown contains duplicate knots and sub-millimetre
// chords. They cannot provide a usable contact direction. Keep
// their cubic records and links in build(), but omit them from
// this adapter's independent contact-primitive array.
continue;
}
result.push(Primitive { start:d,end,owner:((word(r) as u64)<<32)|word(r+4) as u64 });
}
+16
View File
@@ -46,3 +46,19 @@ game process and closes the current session so physics and render resources
are recreated. A fresh launch defaults to University. Development checkouts
with `assets/private/game.json` retain their existing local assets and default
test world; `--assets DIRECTORY` also selects a prepared asset set explicitly.
## Conversion checks
The direct converter was checked against an extracted Xbox 360 disc containing
the three main districts, Skate School, and six separately stored parks:
Black Box, Downtown Skate Park, Industrial Skate Park, Maloof, Mega Park and
Start Park. All ten outputs passed the runtime's asset, spline and collision
load checks. This does not establish visual or gameplay parity, and the
original ISO extraction step still needs checking with an ISO.
Two retail spline details needed corrections. Downtown contains duplicate
knots and sub-millimetre chords. The native cubic records remain intact;
only unusable contact primitives are omitted. Skate School's first spline
header word is `0001 0002`, meaning two rails, not 65,538. TU3 function
`82C1EEF0` reads the count with `lhz +2` (also at `82C1EFBC`). The reader now
uses that halfword and retains the other halfword in the map metadata.
+3 -1
View File
@@ -98,7 +98,9 @@ def convert_map(archive,work,maps,stage,game_exe,log,report):
manifest_path=prepare(stream_directory=stream,output_root=district_work/'intermediate',
utt_root=TOOLS/'vendor/utt',district_name=district,map_name=label,
package_name='Skate 3 owned disc',cache_format='skate3-rust-map-v1',
texture_stream_names=('Tex',),excluded_normal_texture_ids=EXCLUDED_NORMAL_TEXTURE_IDS)
# Smaller parks keep their textures in Pres rather than a Tex stream.
texture_stream_names=('Tex',) if any(stream.glob('cTex_*.xsf')) else (),
excluded_normal_texture_ids=EXCLUDED_NORMAL_TEXTURE_IDS)
collision=district_work/'collision.rwcmset'
build_archive(manifest_path,collision)
final=maps/(label+'.skate')
@@ -168,8 +168,11 @@ def decode_grind_splines(data: bytes) -> list[dict[str, object]]:
):
raise ValueError("RX2 spline section extends beyond the file")
rail_count, segment_count, rail_table, segment_table = (
struct.unpack_from(">4I", data, section_offset)
# TU3 GrindData::Add (82C1EEF0) reads lhz +2, including its loop
# bound at 82C1EFBC. The first halfword is not part of the count:
# Skate School has header 0001 0002, two rails, table end 0x50.
header_halfword, rail_count, segment_count, rail_table, segment_table = (
struct.unpack_from(">HH3I", data, section_offset)
)
expected_segment_table = (
SPLINE_HEADER_SIZE + rail_count * SPLINE_RAIL_SIZE
@@ -271,6 +274,7 @@ def decode_grind_splines(data: bytes) -> list[dict[str, object]]:
{
"section_index": record_index,
"section_offset": section_offset,
"section_header_halfword": header_halfword,
"rail_index": rail_index,
"spline_id": f"0x{spline_id:016X}",
"type_signature": f"0x{type_signature:016X}",