Add portable Windows build and update GC runtime

This commit is contained in:
2026-08-15 13:01:11 +02:00
parent cd300cd554
commit 623ef9a733
13 changed files with 580 additions and 60 deletions
+69 -8
View File
@@ -288,9 +288,11 @@ mixed as ordinary scalars.
### Up vector and roll
`FUN_005e0ad0` rebuilds `up` by projecting world-up `(0,1,0)` onto the plane
normal to `target-eye`. If the two directions are collinear it falls back to
`(0,0,1)`. `rotationB` then rotates that up vector around the normalized view
axis.
normal to `target-eye`. The Android `camera3D::BuildUpVector` uses `(0,0,1)`
when the normalized view direction's L1 distance from either Y pole is below
`0.001`; otherwise it uses `(0,1,0)`. `rotationB` then rotates that up vector
around the normalized view axis. This is not equivalent to a generic
dot-product parallelism threshold on near-vertical authored cameras.
### Gameplay projection
@@ -314,9 +316,13 @@ orthographic sections.
The distance is not clamped to the near plane. A zero-length camera therefore
also produces zero orthographic extents in the original. Likewise, its vector
normalizer returns `(0,0,0)` for a zero-length input instead of propagating a
NaN as `glm::normalize` does. The Linux port mirrors both edge cases and uses
explicit left-handed view/projection builders; the `NO` depth variant is the
OpenGL backend adaptation of the original D3D left-handed matrices.
NaN as `glm::normalize` does. The Linux port mirrors both edge cases.
The matrix convention is backend-specific. Arcade emits D3D matrices, while
Android's `matrix44::LookAt` uses `eye-target` and its perspective matrix has
`-1` at `m[2][3]`, the OpenGL right-handed/no-depth-remap convention. Because
Vectorail also renders through OpenGL, its GC path follows the Android RH/NO
builders rather than feeding D3D handedness directly to GLM.
The evaluator sets `projBlend` to `0` for `projType=0` and `1` for
`projType=1`. An `fMode=1` transition linearly interpolates the endpoint
@@ -349,11 +355,66 @@ The player now imports the raw camera keys (including non-finite sentinel
values) and ports the confirmed `aMode`,
`fMode`, orbit, up/roll, projection type/blend, FOV and clipping-plane
behavior. GC cameras are evaluated directly without the legacy follow-camera
smoothing. Remaining camera work is validation against captured original
frames.
smoothing. The runtime uses Android's `a + (b-a)*u` float operation order and
builds the GC view matrix without an extra host-side up-vector fallback.
An independent verifier reimplements the Android evaluator from decoded wire
records instead of calling the player's camera helpers. Across the current
2970-chart corpus it checked 171333 keys and 9113837 sampled times, including
every track vertex and the interior of every camera-key interval. All seven
`aMode` branches and `fMode` 0/1/2 were present:
```text
eye/target max error: 0
up max error: 0
view max error: 0
projection max error: 4.76837e-7
```
This pass found and corrected a sign error in the expanded quaternion's `qz`
term and replaced the previous approximate vertical-up threshold with the
exact Android `0.001` L1 test.
## Switch and Android cross-version validation
The Android reference used here comes from
`/home/au/Downloads/gc2offlinev4.xapk`. The outer package is an offline
installer; the original game is its nested `assets/groovecoaster.apk`:
```text
package: jp.co.taito.groovecoasterzero
version: 1.0.18 (versionCode 76)
native ABI: arm64-v8a
native code: lib/arm64-v8a/libtune.so
XAPK SHA-256: c6c394f7a1cc65331edc98aac94014668f9d5277ce17262fecf9aeeb1a2a3003
nested APK SHA-256: 62c9e739dc9b8c1bcbcb4b5234d78f154b20d1a930329b9c21a3c9236b92a0dc
libtune.so SHA-256: 689b2e4c0bc4479a3f309944b5070796878c66c0dfdbd283c11ec0bbeeea9efa
```
Unlike the arcade executable, this `libtune.so` retains C++ symbols. Relevant
ELF virtual addresses (before Ghidra's `+0x100000` image base) are:
```text
0x0933a0 TuneGameData::LoadGameData(bytearray*, bool)
0x0953bc TuneGameData::GetWayPosition(int)
0x095588 TuneGameData::GetCameraData(int, bool)
0x0ad1a4 GameScene::SetCommonParam()
0x0ad524 GameScene::CalcGameProjectionMatrix(camera3D&)
0x0b8410 GameScene::CalcGamePerspectiveMatrix(camera3D&)
0x0b847c GameScene::CalcGameOrthoMatrix(camera3D&)
0x102c78 matrix44::Perspective(float, float, float, float)
0x102d34 matrix44::LookAt(vector3 const&, vector3 const&, vector3 const&)
0x1031f0 camera3D::BuildUpVector(float)
0x103e48 RotateHPB::ToVector_Deg(float)
```
`TuneGameData::LoadGameData` independently confirms every wire read and the
59-byte camera record order. It expands each record to an aligned 0x48-byte
runtime entry. `GameScene::SetCommonParam` then calls `GetCameraData` for the
current chart time, builds the projection, and passes `eye`, `target`, and
`up` to `matrix44::LookAt`.
The base Switch executable retains `CTuneGameData` RTTI and the original GC
source filenames. Its stripped `CTuneGameData::GetWayPosition` and
`CTuneGameData::GetCameraData` implementations were matched to the named