Decode tfrag normals from the overlay's sine table
Normals are per-vertex, and the tfrag's light records store them as an azimuth and elevation *index* into a 256-entry sine table rather than as angles. This decodes that table and builds normals for the renderer. The reference reads the table from an ELF at BOOT_NORMAL_TABLE_VADDR (0x165500) and notes every level overlay carries a copy. The overlay has no ELF header -- it is a raw loadable image starting with code -- so there is no program header to map a virtual address through, and the offset is not fixed either: level 0 has the table at 0xb650c, level 5 at 0xcca30, level 26 at 0xb6284. So it is located by content. Every table starts with exactly (1.0, 0.0), angle zero, and the scan then requires all 256 entries to match cos(i*pi/128), sin(i*pi/128). Validating the whole curve is what makes the search safe: a lone (1.0, 0.0) pair of floats occurs by accident in a 2.7 MB executable, and a test plants one as a decoy. The values are read from the disc rather than recomputed, since those are the numbers the game indexes. A normal is (cos(az)cos(el), sin(az)cos(el), sin(el)) *negated* -- the stored normals point into the surface -- and then given the same Z-up to Y-up swap as positions, or the lighting comes out sideways. The azimuth and elevation fields are stored signed but index 0..=255, so entry 200 must be read as 200, not as -56; there is a test for that. Also frames and parses the level light bank from the gameplay extent, and the pipeline now writes overlay.bin. The per-vertex lighting evaluation is not written yet, so the renderer stays unlit by default. GC_ENGINE_LIT=1 substitutes a host directional light for inspection -- that is a stand-in, not the game's lighting, and it is deliberately not the default. The hash table is regenerated for the 27 new overlay.bin files: now 5,213 files and 5,762,497,559 bytes.
This commit is contained in:
@@ -611,13 +611,26 @@ pub struct TfragRgba {
|
||||
pub struct TfragLight {
|
||||
pub unknown_0: i8,
|
||||
pub intensity: i8,
|
||||
/// Normal azimuth, pi/128 radians per unit.
|
||||
/// Normal azimuth, as an *index* into the overlay's 256-entry sine table.
|
||||
pub azimuth: i8,
|
||||
/// Normal elevation, pi/128 radians per unit.
|
||||
/// Normal elevation, as an index into the same table.
|
||||
pub elevation: i8,
|
||||
pub color: i16,
|
||||
}
|
||||
|
||||
impl TfragLight {
|
||||
/// Azimuth as the unsigned table index. The field is stored signed but
|
||||
/// indexes 0..=255, so the byte is what matters, not its sign.
|
||||
pub fn azimuth_index(&self) -> u8 {
|
||||
self.azimuth as u8
|
||||
}
|
||||
|
||||
/// Elevation as the unsigned table index.
|
||||
pub fn elevation_index(&self) -> u8 {
|
||||
self.elevation as u8
|
||||
}
|
||||
}
|
||||
|
||||
/// Everything one tfrag contributes to a draw call.
|
||||
#[derive(Debug, Clone, Default, PartialEq)]
|
||||
pub struct TfragGeometry {
|
||||
|
||||
Reference in New Issue
Block a user