ropa NPC palette gate + 60Hz pose-quantization root cause (boxcar filter)

This commit is contained in:
mchughalex 2026-07-10 23:59:03 +01:00
parent e308e840b0
commit 6580ed141a
4 changed files with 959 additions and 56 deletions

View file

@ -26,8 +26,12 @@ REXCVAR_DECLARE(bool, skate3_native_render_scene_tex_mips);
REXCVAR_DECLARE(int32_t, skate3_native_render_scene_debug);
// Smoothness / pacing.
REXCVAR_DECLARE(bool, skate3_native_render_scene_smooth_camera);
REXCVAR_DECLARE(double, skate3_native_render_scene_smooth_camera_filter_ms);
REXCVAR_DECLARE(bool, skate3_native_render_scene_sort_opaque);
REXCVAR_DECLARE(double, skate3_guest_fps_cap);
REXCVAR_DECLARE(int32_t, skate3_native_render_scene_synthetic_pan);
REXCVAR_DECLARE(double, skate3_native_render_scene_synthetic_pan_rate);
REXCVAR_DECLARE(double, skate3_native_render_scene_synthetic_pan_amp);
// SDK: emulated-draw suppression while the native output is active.
REXCVAR_DECLARE(bool, native_render_suppress_emulated_draws);
@ -161,6 +165,20 @@ void NativeDebugDialog::OnDraw(ImGuiIO& io) {
"sim tick; raw poses judder at high render rates. Re-times "
"them on the host clock (1 kHz camera sampler + pose "
"interpolation, a few ms of camera latency)."));
{
float w = float(REXCVAR_GET(skate3_native_render_scene_smooth_camera_filter_ms));
if (ImGui::SliderFloat("camera filter (ms, 0 = off)", &w, 0.0f, 100.0f, "%.0f")) {
REXCVAR_SET(skate3_native_render_scene_smooth_camera_filter_ms, double(w));
}
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip(
"Boxcar average over the camera pose signal. The game's camera "
"advances in 60 Hz-quantized lumps at high fps (the measured cause "
"of stick-pan stutter); 50 ms = three 60 Hz periods cancels it "
"exactly for ~25 ms extra camera latency. 0 reverts to raw "
"interpolation.");
}
}
REXCVAR_SET(skate3_native_render_scene_sort_opaque,
CvarCheckbox("Front-to-back opaque sort",
REXCVAR_GET(skate3_native_render_scene_sort_opaque),
@ -180,6 +198,59 @@ void NativeDebugDialog::OnDraw(ImGuiIO& io) {
"directly).");
}
}
{
int mode = REXCVAR_GET(skate3_native_render_scene_synthetic_pan);
const char* kPanModes[] = {"0: off", "1: time-based (build clock)",
"2: fixed step per frame", "3: through smoother"};
if (ImGui::Combo("Synthetic pan (P)", &mode, kPanModes, 4)) {
REXCVAR_SET(skate3_native_render_scene_synthetic_pan, mode);
}
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip(
"Judder isolation probe: replaces the camera with a host-generated "
"constant-rate 360-degree pan. AFTER ENGAGING, SWEEP THE REAL CAMERA "
"AROUND ONCE WITH THE STICK; the game only submits what its own "
"frustum sees; the sweep fills a static-world union so the full "
"circle is populated.\n1 smooth = camera path is fine; 1 judders = "
"frame pacing / present / display, not the camera.\n2 = constant "
"angle per FRAME (smooth only if displayed frames are evenly spaced "
", the complement of 1).\n3 = feeds synthetic ~200 Hz samples "
"through the camera smoother and logs reconstruction error "
"(synthetic-pan: lines).");
}
float rate = float(REXCVAR_GET(skate3_native_render_scene_synthetic_pan_rate));
if (ImGui::SliderFloat("pan rate (deg/s)", &rate, 5.0f, 720.0f, "%.0f")) {
REXCVAR_SET(skate3_native_render_scene_synthetic_pan_rate, double(rate));
}
float amp = float(REXCVAR_GET(skate3_native_render_scene_synthetic_pan_amp));
if (ImGui::SliderFloat("pan amplitude (0 = full spin)", &amp, 0.0f, 60.0f, "%.0f")) {
REXCVAR_SET(skate3_native_render_scene_synthetic_pan_amp, double(amp));
}
if (ImGui::Button("Record camera signal (8 s)")) {
skate3::native_scene::RecordCameraSignal(8.0);
}
ImGui::SameLine();
if (ImGui::Button("Record bone signal (6 s)")) {
skate3::native_scene::RecordBoneSignal(6.0);
}
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip(
"Click, then skate at a steady speed for 6 seconds. Records every "
"raw entity pose (bone palettes with timestamps) + the rendered "
"interpolation output to logs\\bone_signal_<ts>.bin, so wheel-vs-"
"deck lag/orbit can be measured and candidate fixes replayed "
"offline against real data.");
}
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip(
"Click, then IMMEDIATELY pan the camera with the right stick at a "
"steady rate for 8 seconds (synthetic pan should be OFF). Records "
"every distinct guest camera pose with 1 kHz-sampler timestamps + "
"the smoothed output, to logs\\cam_signal_<ts>.csv; offline "
"analysis shows whether the game's own camera signal is jerky at "
"the source.");
}
}
ImGui::SeparatorText("Caches (lightmap-era plumbing)");
REXCVAR_SET(

View file

@ -377,6 +377,17 @@ void OnFrameEnd(uint8_t* base) {
}
f10_was_down = f10_down;
}
// P: cycle the synthetic camera pan probe (judder isolation: a host-
// driven constant-rate pan injected at a selectable pipeline stage; see
// the skate3_native_render_scene_synthetic_pan cvar).
{
static bool p_was_down = false;
const bool p_down = (GetAsyncKeyState('P') & 0x8000) != 0;
if (p_down && !p_was_down) {
skate3::native_scene::CycleSyntheticPan();
}
p_was_down = p_down;
}
// F11: paired A/B parity capture; one keypress produces
// shot_<ts>_native.png + shot_<ts>_emulated.png (same viewpoint, ~half a
// second apart while the renderer toggles) + an immediate F10-style

File diff suppressed because it is too large Load diff

View file

@ -376,4 +376,26 @@ bool ToggleSceneEnabled();
void FlushTextureCache();
void FlushMeshCache();
// Synthetic camera pan probe (judder isolation, hotkey P): cycles
// skate3_native_render_scene_synthetic_pan 0 -> 1 -> 2 -> 3 -> 0 and returns
// the new mode. 1 = time-based constant-rate pan injected at scene build,
// 2 = fixed angle step per published frame, 3 = synthetic pose samples fed
// through the camera smoother (reconstruction error logged numerically).
int CycleSyntheticPan();
// Camera-signal recorder (judder diagnosis): for `seconds`, records every
// distinct guest camera pose (1 kHz sampler timestamps) plus the per-frame
// raw and smoothed headings, then writes logs/cam_signal_<ts>.csv. Pan the
// camera with the stick at a steady rate while it runs; offline analysis
// shows whether the game's own pose sequence is irregular at the source.
void RecordCameraSignal(double seconds);
// Bone-signal recorder (wheel-guard diagnosis): for `seconds`, records every
// raw entity pose (ring pushes: bone palettes / rigid worlds, timestamps)
// and each frame's interpolated output, then writes
// logs/bone_signal_<ts>.bin. Skate at a steady speed while it runs;
// offline analysis replays candidate guard strategies against the real
// board data.
void RecordBoneSignal(double seconds);
} // namespace skate3::native_scene