Add portable Windows build and update GC runtime
This commit is contained in:
+49
-10
@@ -70,20 +70,21 @@ The file is big-endian. The initial words are not all section offsets. Confirmed
|
||||
| 2 | track points |
|
||||
| 3 | notes |
|
||||
| 4 | camera |
|
||||
| 5 | particles |
|
||||
| 5 | `TuneBGEffectData` / FlowItem keys |
|
||||
| 6 | visualizer |
|
||||
| 7 | unknown section |
|
||||
| 7 | background texture names and image keys |
|
||||
| 8 | first color table |
|
||||
| 9 | objects |
|
||||
| 10 | scalar/unknown; often `0x30`, not an offset |
|
||||
| 11 | second color table |
|
||||
| 11 | points into the post-object extension offset table |
|
||||
| 12 | scalar/unknown; can accidentally look like an in-file offset |
|
||||
|
||||
This matters because treating every plausible header word as an offset can split the notes or camera section at a false boundary.
|
||||
|
||||
Older 11-word stage headers instead store the second color-table offset in
|
||||
slot 10. The scalar/offset/scalar arrangement in slots 10..12 belongs to the
|
||||
newer 13-word format used by the 4.71-era charts.
|
||||
The post-object extension base is the reader position after the variable
|
||||
object stream. Relative offset slot 1 extends background colors and slot 2
|
||||
stores object parents. Deriving that base from consumed bytes, as Android
|
||||
`LoadBGData` does, also handles the small mobile chart revision correctly.
|
||||
|
||||
## Note array: confirmed wire layout
|
||||
|
||||
@@ -199,8 +200,15 @@ BPM active at the note timestamp. With `beat_ms = 60000 / bpm`:
|
||||
| ---: | --- |
|
||||
| `+6` | signed marker-effect/UV selector; the note-head draw passes `value - 1` to effect 3 |
|
||||
| `+39` | appearance lead in beats; runtime `+0xbc = max(time - value * beat_ms, 0)` |
|
||||
| `+37` | enable positional fly-in |
|
||||
| `+38` | draw the additive fly-in trail |
|
||||
| `+43` | fly-in interpolation start, in beats after appearance |
|
||||
| `+47` | fly-in interpolation end, in beats before the hit time |
|
||||
| `+51` | duration in beats for types 3/4/5/10/15; runtime `+0x58` and `+0xac = time + value * beat_ms` |
|
||||
| `+55` | packed authored `RRGGBBAA` colour used by duration-target geometry |
|
||||
| `+55` | primary packed authored `RRGGBBAA` target colour; also used by duration geometry |
|
||||
| `+59` | secondary packed `RRGGBBAA` colour |
|
||||
| `+63` | integer fly-in oscillation count |
|
||||
| `+67` | fly-in oscillation endpoint/phase scalar |
|
||||
| `+71` | number of generated MERRY GO ROUND targets |
|
||||
| `+75` | MERRY GO ROUND spacing in beats |
|
||||
|
||||
@@ -333,7 +341,7 @@ and the displaced point with the camera evaluated at the note timestamp, then
|
||||
uses `RotateHPB::SetVector` (`atan2(screen_dx, screen_dy_down)`) and stores the
|
||||
resulting screen angle. It is computed before gameplay and remains fixed while
|
||||
the live camera moves. Effect 39 resolves to a 128x32 animated strip whose
|
||||
arrows extend to the sides of the 32x32 head; it uses the same `0.025` scale,
|
||||
arrows extend to the sides of the 32x32 head; it uses the same `0.025` scale
|
||||
colour and marker alpha. This is independent of the lower-right control
|
||||
helper, even though that helper also has a direction overlay.
|
||||
|
||||
@@ -366,8 +374,13 @@ synthetic pulsing rings are no longer used for decoded GC stages.
|
||||
|
||||
Duration bodies are also type-specific in the executable:
|
||||
|
||||
- HOLD calls `0x00647cf0` and SLIDE HOLD calls `0x00641fd0`; both emit ribbon
|
||||
triangles rather than an OpenGL-style line.
|
||||
- Android HOLD does not submit the prebuilt ribbon array. Its normal gameplay
|
||||
branch calls `DrawWay(max(current_ms, appear_ms), end_ms, color, color)` with
|
||||
additive blending and a beat-reactive width from 3 to 5 pixels. This exact
|
||||
moving timestamp boundary is why dropping whole prebuilt samples produces a
|
||||
visibly stepped disappearance. Arcade still constructs HOLD helper geometry,
|
||||
but it is not evidence for the mobile draw path.
|
||||
- SLIDE HOLD calls its prebuilt triangle-ribbon path.
|
||||
- SCRATCH is sampled every `0.15` world units. `0x005ebaa0` derives two
|
||||
opposing paths with radius `0.2` and rotates the offset by 45 degrees per
|
||||
sample; `0x00641d50` emits six vertices per segment for each path.
|
||||
@@ -391,6 +404,32 @@ Duration bodies are also type-specific in the executable:
|
||||
screen direction. Both endpoint layers inherit the long note's colour, fade alpha,
|
||||
billboard transform, beat-synchronised animation and `0.025` sprite scale.
|
||||
|
||||
### Positional fly-in
|
||||
|
||||
`GameScene::DrawMark` converts wire `+25/+29/+33` into direction vector `D` and
|
||||
starts from the authored route position `P`. With:
|
||||
|
||||
```text
|
||||
start = max(0, appear_ms + field_43 * beat_ms)
|
||||
end = max(0, note_ms - field_47 * beat_ms)
|
||||
u = 0 before start, (now-start)/(end-start) in between, 1 at/after end
|
||||
```
|
||||
|
||||
`0x005ebaa0` first stores runtime `+0xc0 = ROUND(appear_ms / frame_ms)`.
|
||||
`0x0064ab80` then computes the start from that stored integer, but computes the
|
||||
end from the still-fractional `note_ms / frame_ms`; each final boundary passes
|
||||
through another nearest-integer conversion at `0x0050a4a0`. The live clock is
|
||||
quantized the same way.
|
||||
Flag `+37` selects positional interpolation. With no oscillations the displayed
|
||||
position is `P + D*(1-u)`. When integer `cycles = +63` is positive, let
|
||||
`c = +67` for values up to 1 and `c = 2-(+67)` otherwise; then position is
|
||||
`P + D*(c - abs(sin(u*(asin(c) + cycles*2*pi))))`. Integer cycles guarantee
|
||||
that the marker lands back on `P` at `u=1`. The approach circle follows the
|
||||
same moved position. Flag `+38` draws an additive line from the initial external
|
||||
anchor to the moving marker before the hit time. The player preserves that
|
||||
nearest-frame 60 Hz quantization rather than smoothing the movement on the
|
||||
audio clock.
|
||||
|
||||
## Tap judgment timing
|
||||
|
||||
The system-config loader at `0x00635c90` lays out the timing overrides in four
|
||||
|
||||
@@ -0,0 +1,200 @@
|
||||
# Groove Coaster Android Runtime Reverse
|
||||
|
||||
This is a clean-room interoperability record for the offline Android package
|
||||
`gc2offlinev4.xapk`. It documents facts used to validate the GC stage loader
|
||||
and Vectorail runtime. Addresses below are ELF virtual addresses in
|
||||
`lib/arm64-v8a/libtune.so`; the Ghidra project uses an additional `0x100000`
|
||||
image-base offset.
|
||||
|
||||
## Provenance
|
||||
|
||||
```text
|
||||
XAPK c6c394f7a1cc65331edc98aac94014668f9d5277ce17262fecf9aeeb1a2a3003
|
||||
APK 62c9e739dc9b8c1bcbcb4b5234d78f154b20d1a930329b9c21a3c9236b92a0dc
|
||||
libtune 689b2e4c0bc4479a3f309944b5070796878c66c0dfdbd283c11ec0bbeeea9efa
|
||||
```
|
||||
|
||||
Stage and audio ZIPs use the password returned by
|
||||
`mtxc::ObbFile::getZipPassword`: `eiprblFFv69R83J5`.
|
||||
|
||||
The Android `ac_10pt8tion_{easy,normal,hard}{,_ext}.dat` payloads are binary
|
||||
identical to the corresponding files in `GC/data/stage`. For example both
|
||||
copies of `ac_10pt8tion_easy.dat` have SHA-256
|
||||
`86a75c86b91bbbe25cae78bcb51d4f7842b3fc4946e4b091f2ec5ba084497765`.
|
||||
The Android executable is therefore a valid independent specification for the
|
||||
arcade files consumed by Vectorail.
|
||||
|
||||
## Stage Selection And Containers
|
||||
|
||||
`TuneAppMain::LoadStageData` (`0x9da24`) loads six chart ids per song. The
|
||||
first three are serialized mobile ids. The other three are synthesized by
|
||||
prefixing those ids with `ac_`. `GameScene::makeFilenameStageDat` (`0xc4bac`)
|
||||
then appends `.dat` or `_ext.dat`.
|
||||
|
||||
The sampled offline package contains one-note `placeholder_bgm` mobile charts,
|
||||
while its `ac_` charts contain the complete arcade route, camera, notes and
|
||||
background scene. `GameScene::LoadStageData` (`0xc607c`) performs this order:
|
||||
|
||||
1. Load the selected main DAT from `<stage-pack>.zip`.
|
||||
2. Call `TuneGameData::LoadGameData` (`0x933a0`).
|
||||
3. Call `TuneGameData::LoadBGData` (`0x93aa4`) on the same bytes.
|
||||
4. Load `_ext.dat` only for an extra/arcade difficulty when arrange mode is off.
|
||||
5. Build runtime data and load stage resources.
|
||||
|
||||
For every selected `ac_` chart, `LoadExtData` starts at byte 6 of the matching
|
||||
`_ext.dat`, replaces the four timing lists, reads another array of 99-byte
|
||||
notes, and links each ext note to a same-time main note. If both effective
|
||||
types are FLICK, the main runtime type becomes `0x10` (dual flick).
|
||||
|
||||
The corpus contains 2927 valid sidecars with 18965 ext notes. Every sidecar
|
||||
parses with the layout described by `docs/stage_ext.pat`, and all ext note
|
||||
records are FLICK entries. One unusual main chart is itself named
|
||||
`SW_marianne_hard_ext.dat`, so its sidecar is
|
||||
`SW_marianne_hard_ext_ext.dat`. File discovery must check whether removing the
|
||||
suffix names an existing main chart instead of excluding every `_ext.dat`.
|
||||
|
||||
## Parsed Stage Sections
|
||||
|
||||
The parser now follows every section consumed by Android `LoadGameData` and
|
||||
`LoadBGData`:
|
||||
|
||||
| Header | Runtime data |
|
||||
| ---: | --- |
|
||||
| 0 | stage config, BPM and four timing tables |
|
||||
| 1 | route draw-distance keys |
|
||||
| 2 | route points |
|
||||
| 3 | 99-byte note records |
|
||||
| 4 | 59-byte camera records |
|
||||
| 5 | 44-byte `TuneBGEffectData` / FlowItem keys |
|
||||
| 6 | 12-byte visualizer keys |
|
||||
| 7 | background texture names and 20-byte image keys |
|
||||
| 8 | background color keys |
|
||||
| 9 | model/shader names and animated stage objects |
|
||||
|
||||
After the variable-length object stream, Android records the current read
|
||||
position and treats it as the base of four relative extension offsets. The
|
||||
third extension contains signed object parent indices. Header word 11 points
|
||||
inside this table on current charts; it is not the object-stream end. The
|
||||
parser now derives the base from the consumed object stream exactly as Android
|
||||
does, which also fixes the small mobile DAT revision.
|
||||
|
||||
## Timing, Route And Long Elements
|
||||
|
||||
`TuneTimingData::GetTime` selects the latest timing key at or before the note.
|
||||
Mode 1 is absolute milliseconds, mode 3 is next-note spacing, and other modes
|
||||
multiply the authored value by `60000 / BPM`.
|
||||
|
||||
`TuneGameData::GetWayPosition` (`0x953bc`) linearly interpolates route points
|
||||
by timestamp and clamps before/after the route. `WaySplitCheck` (`0x95c4c`)
|
||||
clips the polyline to the requested time range, emits samples at a fixed world
|
||||
distance, carries the unused distance across authored segments, and stores the
|
||||
true interpolated timestamp for each sample.
|
||||
|
||||
Android spacing is `0.20` for HOLD/SLIDE/DUAL, `0.15` for SCRATCH and `0.40`
|
||||
for BEAT. Arcade `game471.exe` uses `0.55` for BEAT; Vectorail intentionally
|
||||
keeps the arcade value when playing arcade DATs.
|
||||
|
||||
`LoadTuneMarkDataOne` (`0x967d8`) confirms the wire-field translation and
|
||||
compatibility remaps. Runtime types 7/8 become 1, 11 becomes 10, 12/14 become
|
||||
9, and 13 becomes 4. Marker effects are forced to 35 for type 10, 32 for type
|
||||
9, 37 for type 15 and 11 for raw type 13. Duration types are 3, 4, 5, 10 and
|
||||
15. MERRY type 6 expands to `count` targets separated by the authored beat
|
||||
spacing.
|
||||
|
||||
## Camera
|
||||
|
||||
`TuneGameData::GetCameraData` (`0x95588`) independently confirms the seven
|
||||
anchor modes, the two interpolation paths and the orthographic/perspective
|
||||
blend documented in `re_gc_camera.md`. Android uses right-handed OpenGL
|
||||
look-at/projection matrices. The arcade executable uses the corresponding D3D
|
||||
left-handed path; Vectorail converts the evaluated eye/target/up state to its
|
||||
OpenGL renderer and uses the arcade fixed 75 degree FOV.
|
||||
|
||||
No camera wire fields are discarded, including non-finite values in the three
|
||||
`ac_comet_*` intros.
|
||||
|
||||
## Background Runtime
|
||||
|
||||
`DrawBGColor` (`0xb3fc8`) selects the active color key, optionally cross-fades
|
||||
to the next key, and applies BPM-derived HSV brightness modulation when its
|
||||
rhythm-reactive flag is set. `DrawBGImage` (`0xb49b0`) supports tiled atlas
|
||||
images, a built-in centered image and external texture entries.
|
||||
`DrawBGVisualizer` (`0xb6118`) selects timed visualizer keys and calls the
|
||||
8572-byte procedural `DrawVisualizer` path. The conventional FFT routine at
|
||||
`0x104ec0` is not used to drive this stage visualizer.
|
||||
|
||||
The 44-byte table previously called particles is `TuneBGEffectData`.
|
||||
`GameScene::ExecFlowItem` (`0xc0610`) uses it to spawn and update authored
|
||||
flowing background elements. Shape 1 spawns a random screen point, shape 2 a
|
||||
screen-space grid and shape 3 an eight-point ring. Repeat and lifetime are
|
||||
scaled by the active beat before points are unprojected to route depth. It is
|
||||
separate from hit-effect particles.
|
||||
|
||||
## Audio
|
||||
|
||||
Stage audio is stored as encrypted `*_bgm.ogg.zip` and `*_shot.ogg.zip` files.
|
||||
`MtxSoundBuffer::LoadData` (`0x10a614`) rewrites `.m4a` resource names to
|
||||
`.ogg`. The sampled Android 10pt8tion pair is Vorbis, 44.1 kHz stereo, and both
|
||||
streams are 129.621859 seconds. The corresponding arcade BGM WAV is PCM s16le,
|
||||
44.1 kHz stereo, with the same duration.
|
||||
|
||||
`GameScene::ExecGameStage` (`0xaf8ac`) maintains a 60 Hz logical count, reads
|
||||
`MtxSoundSource::GetPlaySecTime` (`0x10af3c`) from BGM, and replaces the logical
|
||||
count when they differ by more than two frames. BGM and SHOT are prepared and
|
||||
started together. If either source is no longer playing, both are stopped,
|
||||
repositioned and restarted; SHOT is also periodically aligned to the BGM time.
|
||||
|
||||
Vectorail now derives gameplay time from consumed BGM source bytes instead of
|
||||
wall time. Its two SDL streams start together, retain their source PCM and are
|
||||
cleared/requeued at the BGM position when their source clocks differ by more
|
||||
than two frames. Corpus verification found 114 of 1674 unique arcade pairs
|
||||
with different PCM frame counts; exhausted SHOT tails remain silent when the
|
||||
BGM position is already beyond the shorter stream.
|
||||
|
||||
## Verification Status
|
||||
|
||||
`opencoaster-stage-verify` currently validates all 2970 main DATs recursively,
|
||||
including the nested `stage/sound/ac_dontfight_ex.dat` and the chart whose
|
||||
actual name ends in `_ext`. It
|
||||
compares the retained wire model to the runtime route, all camera fields, note
|
||||
type/effect/color/timing/distance translation and clip dimensions. It also
|
||||
validates resolved BGM/SHOT containers and PCM formats.
|
||||
|
||||
The final aggregate result is `2970/2970` main charts, 2927 loaded sidecars,
|
||||
18965 ext notes and 18948 linked dual-flicks. Section coverage is 16869 FlowItem
|
||||
keys, 54152 visualizer keys, 79 background image keys, 417451 background color
|
||||
keys and 1171999 stage objects. Audio resolution succeeds for 2918 charts;
|
||||
2768 have paired BGM/SHOT and 150 intentionally resolve only BGM. Gameplay
|
||||
coverage includes 193 MERRY records expanded to 676 timed targets and 6364
|
||||
SLIDE HOLD records.
|
||||
|
||||
Implemented in the player:
|
||||
|
||||
- route timing and draw windows;
|
||||
- camera modes, interpolation and projection;
|
||||
- note heads, directional overlays, long paths, BEAT samples and MERRY layout;
|
||||
- ext timing tables and dual-flick pairing, including both direction vectors;
|
||||
- stage model transforms, visibility, colors and one-level parents;
|
||||
- exact background color cross-fade and BPM brightness;
|
||||
- timed background image selection and external DDS loading;
|
||||
- timed visualizer execution for types 1 through 7;
|
||||
- FlowItem spawning, screen layouts, route-depth unprojection and motion;
|
||||
- MERRY target timing and desktop SLIDE HOLD duration judging;
|
||||
- BGM/SHOT playback, hit SE, source-position game clock and pair resync.
|
||||
|
||||
Remaining asset/input fidelity gaps:
|
||||
|
||||
- the seven visualizer types execute as GLSL procedural equivalents; the
|
||||
original Android vertex generators have not been copied constant-for-constant;
|
||||
- FlowItem timing/layout/motion executes, but original texture selectors
|
||||
34/35 are represented by colored billboards;
|
||||
- mode-3 background loading is implemented, but `stage_back10` is referenced
|
||||
by the corpus and absent from the supplied game dump; built-in modes 1/2
|
||||
still need their original atlas resources;
|
||||
- dual flick and directional SLIDE HOLD use desktop multi-input/hold semantics
|
||||
rather than Android touch lines;
|
||||
- Android touch-line gesture semantics (the desktop player maps controls to
|
||||
keyboard/gamepad inputs).
|
||||
|
||||
This distinction is intentional: a green parser/runtime translation result
|
||||
does not claim pixel-identical procedural meshes or unavailable textures.
|
||||
+69
-8
@@ -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
|
||||
|
||||
+40
-26
@@ -47,10 +47,22 @@ generated samples remain uniform over corners; it is not the normal route
|
||||
renderer.
|
||||
|
||||
The player now mirrors `DrawWay`: it uploads the original authored points plus
|
||||
the two exact timestamp intersections and draws separate behind/current and
|
||||
current/ahead line strips with endpoint color gradients. The stage values
|
||||
`backwardsDrawDist` and `forwardDrawDist` behave as seconds and are converted
|
||||
to milliseconds before comparison with track and note timestamps.
|
||||
the two exact timestamp intersections and draws separate past/current and
|
||||
current/future line strips with endpoint color gradients. Android
|
||||
`DrawGameStageCharacter` proves that the active `forwardDrawDist` value is a
|
||||
count of beats, not seconds. If `beat_ms = 60000 / bpm`, it computes:
|
||||
|
||||
```text
|
||||
beat_start = floor(current_ms / beat_ms) * beat_ms
|
||||
future_ms = forwardDrawDist * beat_ms
|
||||
first_ms = max(0, beat_start - max(beat_ms, future_ms))
|
||||
last_ms = min(song_end, beat_start + future_ms)
|
||||
```
|
||||
|
||||
The same active value drives both sides, but only the past side has a minimum
|
||||
one-beat span. Thus a dynamic value of zero removes the future route while
|
||||
retaining one past beat. The first `backwardsDrawDist` config float is retained
|
||||
while parsing but is not consumed by this normal Android rendering path.
|
||||
|
||||
Dynamic `TrackDrawDist` records are step changes, not interpolation keys. The
|
||||
StageConfig forward distance remains active until the timestamp of the first
|
||||
@@ -64,18 +76,22 @@ For `ac_10pt8tion_hard.dat`:
|
||||
```text
|
||||
first track key: 0 ms, (0, 0, 0)
|
||||
second track key: 6486 ms, (0, 0, 199.985)
|
||||
draw behind: 10 s
|
||||
draw ahead: 7 s
|
||||
first range field: 10
|
||||
active range: 7 beats
|
||||
```
|
||||
|
||||
Treating 7/10 as world units collapses the visible rail to a tiny fraction of
|
||||
the first segment; timestamp clipping produces the expected visible range.
|
||||
The rail brightness is also chart-clock driven. Over a two-beat triangle wave,
|
||||
`pulse = triangle * 0.6 + 0.4`; the past segment grades from `0.7*pulse` to
|
||||
`pulse` alpha and the future segment from `pulse` to `0.5*pulse`. Both are
|
||||
submitted with additive blending.
|
||||
|
||||
## Track colors
|
||||
|
||||
The stage config stores two RGBA colors directly after the draw-range values.
|
||||
They are used for the forward and already-travelled portions of the rail. For
|
||||
`10pt8tion_hard` they are `(255,0,128)` ahead and `(255,255,255)` behind.
|
||||
Android uses the first for the past/current call and the second for the
|
||||
current/future call. For `10pt8tion_hard` they are `(255,0,128)` and
|
||||
`(255,255,255)` respectively; the older ahead/behind field names were inferred
|
||||
backwards.
|
||||
|
||||
## Base background
|
||||
|
||||
@@ -88,15 +104,14 @@ rgba top_left
|
||||
rgba bottom_right
|
||||
rgba bottom_left
|
||||
u8 interpolate_to_next
|
||||
u8 audio_reactive_color
|
||||
u8 rhythm_reactive_color
|
||||
```
|
||||
|
||||
`FUN_00642390` holds the active colors unless `interpolate_to_next` is set; in
|
||||
that case it linearly interpolates all four RGBA values to the following key.
|
||||
`audio_reactive_color` applies `FUN_005d9650` to each active color using the
|
||||
runtime analyser value. The Linux player now implements the exact hold versus
|
||||
interpolate selection and keeps the second mode at its neutral color factor
|
||||
until the analyser feeding `stage renderer +0x24` is ported.
|
||||
`FUN_00642390` holds the active colors unless `interpolate_to_next` is set.
|
||||
The transition uses `CalcCrossFadeColor`'s 25% overlap rather than a plain
|
||||
linear mix. `rhythm_reactive_color` multiplies HSV brightness by a 0.5..0.75
|
||||
triangle wave derived from chart time and the active BPM in `SetCommonParam`;
|
||||
it is not sourced from an audio analyser. The Linux player implements both.
|
||||
|
||||
`data/stage/2d/<song>_menu.dds` is not the gameplay background. It is a
|
||||
512x256 UI atlas whose top-left 197x197 cell is the song jacket. The player has
|
||||
@@ -105,7 +120,7 @@ only an explicit debug comparison layer (`B`). The inherited procedural
|
||||
blue/black square grid has also been removed: the base layer is now only the
|
||||
stage-authored color table before particles, visualizers and objects are drawn.
|
||||
|
||||
The remaining original scene is produced by the stage `particles`,
|
||||
The remaining original scene is produced by the stage FlowItem,
|
||||
`visualizer`, and `objects` sections (plus `.tumo` models), not by a single
|
||||
background bitmap. These sections are now decoded completely by
|
||||
`StagePattern`: particle records are 44 bytes, visualizer records are 12 bytes,
|
||||
@@ -115,14 +130,13 @@ keys, 19 visualizer keys, 46 model names and 316 object instances; 10pt8tion
|
||||
contains 2, 28, 37 and 352 respectively. The Vectorail level data retains this
|
||||
decoded scene for the model-rendering pass.
|
||||
|
||||
The PSP package now retains all three timelines. The particle constructor at
|
||||
`FUN_005f0940` creates a 64-instance pool for every configured particle key.
|
||||
`FUN_005f0130` scales both `repeatMeasure` and `lifespanMeasure` by the active
|
||||
beat duration. Recovered spawn layouts are: type 1, a deterministic/random
|
||||
point; type 2, a screen/grid group using `groupShapeSize`; type 3, six points
|
||||
at 60-degree intervals around a circle. The PSP implementation follows these
|
||||
timing and layout rules, but substitutes geometry for the original particle
|
||||
texture resource until that resource binding is mapped.
|
||||
Android `GameScene::ExecFlowItem` scales both `repeatMeasure` and
|
||||
`lifespanMeasure` by the active beat duration. Recovered spawn layouts are:
|
||||
type 1, a random screen point; type 2, a screen/grid group using
|
||||
`groupShapeSize`; type 3, eight points at 45-degree intervals around a circle.
|
||||
The player follows those timing/layout rules, unprojects the points to route
|
||||
depth and applies the authored velocity, but substitutes colored billboards
|
||||
for original texture selectors 34/35.
|
||||
|
||||
The common `.tumo` container is also big-endian. Its outer count is followed,
|
||||
for each mesh, by resource names, an XYZ vertex table, eight bound floats,
|
||||
|
||||
+18
-1
@@ -186,6 +186,21 @@ struct VisualizerArray {
|
||||
Visualizer entries[sz];
|
||||
};
|
||||
|
||||
// Background Images
|
||||
struct BackgroundImage {
|
||||
u32 timeMs;
|
||||
u32 mode;
|
||||
u32 atlasIndex;
|
||||
s32 textureIndex;
|
||||
color color;
|
||||
} [[same_color]];
|
||||
struct BackgroundImageArray {
|
||||
u32 textureCount;
|
||||
string8 textureNames[textureCount];
|
||||
u32 sz;
|
||||
BackgroundImage entries[sz];
|
||||
};
|
||||
|
||||
// Color Table 1
|
||||
struct ColorTable {
|
||||
u32 timeMs;
|
||||
@@ -327,6 +342,8 @@ NoteArray notes @ hdr.notes;
|
||||
CameraArray camera @ hdr.camera;
|
||||
ParticleArray particles @ hdr.particles;
|
||||
VisualizerArray visualizer @ hdr.visualizer;
|
||||
BackgroundImageArray backgroundImages @ hdr.unk1;
|
||||
ColorTableArray colorTable @ hdr.colors;
|
||||
ObjectArray objects @ hdr.objects;
|
||||
ColorTable2Array colorTable2 @ hdr.colors2;
|
||||
// Header word 11 points to offset slot 2, one word into this table.
|
||||
ColorTable2Array colorTable2 @ hdr.colors2 - 4;
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
// Groove Coaster Android/arcade *_ext.dat
|
||||
// LoadExtData starts reading at byte 6. Bytes after notes are not consumed by
|
||||
// the recovered Android routine and are intentionally left untyped.
|
||||
|
||||
struct NoteTimingEntry {
|
||||
u32 timeMs;
|
||||
u32 mode;
|
||||
float value;
|
||||
} [[single_color]];
|
||||
struct NoteTimingList {
|
||||
u16 size;
|
||||
NoteTimingEntry entries[size];
|
||||
} [[single_color]];
|
||||
|
||||
enum NoteType : u8 {
|
||||
NONE = 0,
|
||||
NORMAL = 1,
|
||||
FLICK = 2,
|
||||
HOLD = 3,
|
||||
SCRATCH = 4,
|
||||
BEAT = 5,
|
||||
MERRY_GO_ROUND = 6,
|
||||
HIDDEN = 7,
|
||||
HIDDEN2 = 8,
|
||||
CRITICAL = 9,
|
||||
SLIDE_HOLD = 10,
|
||||
SLIDE_COUNTER = 11,
|
||||
TURN = 12,
|
||||
SPIN = 13,
|
||||
FINISH = 14,
|
||||
DUAL_HOLD = 15,
|
||||
};
|
||||
struct Note {
|
||||
u32 timeMs;
|
||||
NoteType type;
|
||||
u8 typeOverride;
|
||||
s16 params16[9];
|
||||
u8 flag24;
|
||||
float params25[3];
|
||||
u8 flag37;
|
||||
u8 flag38;
|
||||
float params39[4];
|
||||
u32 params55[3];
|
||||
float param67;
|
||||
u32 param71;
|
||||
float params75[5];
|
||||
u32 param95;
|
||||
} [[single_color]];
|
||||
|
||||
struct StageExtension {
|
||||
NoteTimingList noteTimings[4];
|
||||
u32 unknown;
|
||||
u32 noteCount;
|
||||
Note notes[noteCount];
|
||||
};
|
||||
|
||||
u8 prefix[6] @ 0x00;
|
||||
StageExtension extension @ 0x06;
|
||||
Reference in New Issue
Block a user