vr: cockpit trim does not depend on float contraction

The hue bins moved when a fused multiply-add changed a sample's last bit: the
same data gave different trim in the harness and in the game build. The colour
on screen is 8 bits, so it is rounded before the hue is binned, and the trim
rule's file is built with -ffp-contract=off (lesson 15), for that file only: the
vr_view test compares vr_math with the game's own, fused, types.c. Docs: the
trims the real data gives, the 88 row of the mockup checks, and the device
status.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
MadisonandClaude Opus 5.5 committed 2026-10-04 11:09:56 +09:00
1 parent 67b8f2207c
commit 739a8d7270
4 files changed
+16 -3

No files matched your search

+6
View File
@@ -89,6 +89,12 @@ target_compile_definitions(wipeout_vr_core PUBLIC WIPEOUT_VR=1)
target_compile_options(wipeout_vr_core PRIVATE
$<$<NOT:$<C_COMPILER_ID:MSVC>>:-Wall -Wextra>
)
# -ffp-contract=off (lesson 15) for the cockpit's trim rule only: it bins hues, and a fused multiply-add
# moved samples across a bin edge, so the same data gave different trim in two builds. Not for the
# rest of the core: test_vr_view compares vr_math with the game's own types.c, which is compiled fused.
if(NOT MSVC)
set_source_files_properties(src/vr/vr_cockpit_core.c PROPERTIES COMPILE_OPTIONS "-ffp-contract=off")
endif()
target_link_libraries(wipeout_vr_core PUBLIC sfvr OpenXR::headers m)
+5 -2
View File
@@ -314,7 +314,7 @@ The head sits at the fitted eye through the ship's full matrix, `S * e'`, with `
3. Samples the trim from the ship's own textures. The pixels come from decoding `allsh.cmp` again, copied out of the game's temp memory (eight objects at most) and freed at the end of the load, because the atlas is not read back (lesson 10).
4. Pre-shades the authored parts: a flat colour per triangle from its slot and the mockup's light in ship space (ambient 0.55, the sun (0.35, 1, 0.45) in the page's axes, the trim slots +0.25, the normal turned toward the fitted eye). The parts are untextured and `RENDER_NO_TEXTURE` is a 128 grey that the shader's x2 makes 1.0, so the vertex colour is the colour on screen.
The trim rule in `vr_cockpit_core.c` is the page's, with one tie-break the page leaves to float rounding: a sample exactly on a texel boundary takes the lower texel, and a sample outside the texture has no texel and is skipped. Without it anasta's orange secondary flipped to violet on one sample (it is 1.35% saturated). Run on the real data it picks, against the page: sophia b39900 / 0000b3 (page the same), solaar 01b275 / 607fb2 (01b374 / 5f7fb3), jacko 0707b2 / b20532 (0707b2 / b20533), chang b38f02 / 321fb3 (b39001 / 3120b3), arian b22c02 / 621801 fallback (b32e04 / 621902), arial b20d0d / 620707 fallback (b30d0d / 620707), anasta 5d7db3 / b27405 (5d7eb3 / b27505), Dekka b38f01 / 00b23f (b38f01 / 00b23f). Every channel is within 3 levels, with the same hues and the same fallbacks.
The trim rule in `vr_cockpit_core.c` is the page's, with one tie-break the page leaves to float rounding: a sample exactly on a texel boundary takes the lower texel, and a sample outside the texture has no texel and is skipped. Without it anasta's orange secondary flipped to violet on one sample (it is 1.35% saturated). Colours are rounded to 8 bits before the hue is binned, and the core is built with `-ffp-contract=off` (lesson 15), because a fused multiply-add moved samples across hue bin edges and gave different trim in the harness and in the game build. Run on the real data it picks, against the page: sophia b39900 / 0000b2 (page b39900 / 0000b3), solaar 01b274 / 607fb3 (01b374 / 5f7fb3), jacko 0707b3 / b20532 (0707b2 / b20533), chang b38f02 / 321fb3 (b39001 / 3120b3), arian b22c02 / 621801 fallback (b32e04 / 621902 fallback), arial b20d0d / 620707 fallback (b30d0d / 620707 fallback), anasta 5d7cb2 / b27405 (5d7eb3 / b27505), Dekka b38f01 / 00b23f (b38f01 / 00b23f). Every channel is within 3 levels, with the same hues and the same fallbacks.
### Draw (`ships_draw`, one fenced branch)
@@ -374,6 +374,8 @@ All five have defaults, clamping, `WIPEOUT_VR_<KEY>` overrides and a live re-rea
Verified against the approved mockup (`compare_cockpit_render.py`, ships 0 and 1, the cockpit alone on black at the mockup's field of view): 98.7% and 97.7% of the pixels either draws are drawn by both, the mean colour difference is 10 levels and 95% of the pixels are within 32 levels. The faces differ in what they show.
On the Frame (2026-10-04, `deploy.sh --no-register`, the package from the Docker build, the user's data already there): the cockpit module loaded all 8 ships' cockpits at game start (8 of 8, hull shapes and trim logged) and the game ran at 72 fps with 0 to 4 missed frames in the first seconds. No cockpit frame has been dumped: the unattended run sat in the intro and title for the 4 minutes the lock allowed (the title only goes to the attract mode after the intro video, which `force_render` cannot skip), another session was waiting, so the lock was released. The attract diagnostic `diag_attract_cockpit` is in place for the next run (`WIPEOUT_VR_FORCE_RENDER=1 WIPEOUT_VR_DIAG_ATTRACT_STEREO=1 WIPEOUT_VR_DIAG_ATTRACT_COCKPIT=1`, plus `dump_frame`).
Then a wear session (lesson 1): readability, the keystone, the bank modes, and comfort with the hull this close.
## Open questions
@@ -443,8 +445,9 @@ All the variations pass on all four hull shapes (ships 0, 1, 3 and 5), looking f
| C2 | 0.82° | 0 of 120 | 0 | 100% | 0.34 m (spine) | 0.54–0.59 m |
| C3 | 0.78° | 0 of 120 | 0 | 100% | 0.34 m (bulkhead) | 0.54–0.59 m |
| H (100) | 0.78° | 0 of 160 | 0 | 100% | 0.34 m (header) | 0.42–0.47 m |
| H (88, decided) | 0.78° | 0 of 160 | 0 | 100% | 0.34 m (struts, headrest) | 0.38–0.42 m |
At the Frame's roughly 16 px per degree, 0.74° is about 12 px. Every overhead and rear part is at least 0.34 m from the eye. The hull is at least 0.54 m away, which beats the 0.35 m the Star Fox rig test asks for.
The H (88) row is the final decision, re-run on all four hulls on 2026-10-04 (`checks.json`); it passes. At the Frame's roughly 16 px per degree, 0.74° is about 12 px. Every overhead and rear part is at least 0.34 m from the eye. The hull is at least 0.54 m away, which beats the 0.35 m the Star Fox rig test asks for.
## The mockup page
+1 -1
View File
@@ -219,7 +219,7 @@ Levels: **worn** (the user played it in the headset), **device** (unattended run
| Haptics (`system`, `impact`, `ui_click`) | 2 | host | Same tests. The buzz has never been felt. `ui_hover` is not hooked |
| Recentre itself | 2 | host | View hold 1 s calls `vr_request_recentre()`; the renderer takes it at the next frame boundary (`vr_stage` tests). Never worn |
| Eye dumps at 0, 90 and 180° yaw | 2 | host + device | Host: `build/vr-harness/yaw-{000,090-right,180}/`, synthetic scene. Device (2026-10-02, `force_render` + `diag_attract_stereo`): the title on the mono panel, and stereo eyes of the real attract race at yaw 0 with correct disparity; 7538 tris, about 1 ms per eye, 72 fps with 0 to 1 missed frames per 10 s. Yaw 90 and 180 on the device are not dumped yet |
| Cockpit H (hull cut, trim, parts, faces, bank modes, settings keys) | 3 | host | `src/vr/vr_cockpit*.{c,h}`. Unit tests, the harness on synthetic hulls, and the data-gated harness on the 8 real ships with two rendered against the approved mockup (98% overlap, mean difference 10 levels). Never drawn in the game itself on the Frame: see the device row below |
| Cockpit H (hull cut, trim, parts, faces, bank modes, settings keys) | 3 | host | `src/vr/vr_cockpit*.{c,h}`. Unit tests, the harness on synthetic hulls, and the data-gated harness on the 8 real ships with two rendered against the approved mockup (98% overlap, mean difference 10 levels). On the Frame (2026-10-04): the cockpits of all 8 ships build at game start from the real data (8 of 8) and the title runs at 72 fps; no cockpit frame has been dumped there yet (the unattended run did not get past the intro and title in the time the lock allowed). Never worn |
| Settings page (in the headset) | 3 | not started | Designed separately. The cockpit keys are registered and live so it can drive them |
| First wear | 3 | not started | Ready: build and data are on the Frame |
+4
View File
@@ -394,6 +394,10 @@ void vr_cockpit_sample_trim(const vr_cockpit_tri_t *tris, int count, const vr_co
rgb[c] = fmin(255.0, TRIM_NO_TEXTURE_TEXEL * vc[c] / 127.5);
}
}
// the colour on screen is 8 bits: round it, so the hue bins do not depend on float noise
for (int c = 0; c < 3; c++) {
rgb[c] = floor(rgb[c] + 0.5);
}
total += w;
double h, sat, val;
rgb_to_hsv(rgb[0], rgb[1], rgb[2], &h, &sat, &val);