From b033335f4b885c09b1b708b655decf054188873f Mon Sep 17 00:00:00 2001 From: YuutaTsubasa Date: Thu, 22 Oct 2026 17:45:44 +0800 Subject: [PATCH] Kinect defaults after #231: steadying per camera, v2 view for old settings - The menu cursor steadying is kept per choice: the webcam and the Xbox 360 Kinect start at 8 mm again (menu_steady_radius), a Kinect v2 starts off (menu_steady_radius_v2). The slider edits the chosen camera's. - A Kinect v2 standing in for the Xbox 360 choice (an old camera=kinect) gets the launcher's v2 view and tuning, Recompiled by default, instead of the game's own fallback (Simulated). - A saved camera=picture, no longer offered, reads as off at once instead of only after the camera page was drawn. Co-Authored-By: Claude Opus 5.5 --- src/launcher_main.cpp | 4 ++-- src/launcher_settings.cpp | 28 +++++++++++++++++++------ src/launcher_settings.h | 17 +++++++++++---- tests/launcher_settings_test.cpp | 36 ++++++++++++++++++++++++-------- 4 files changed, 64 insertions(+), 21 deletions(-) diff --git a/src/launcher_main.cpp b/src/launcher_main.cpp index 6e14da16..c3ed6fe6 100644 --- a/src/launcher_main.cpp +++ b/src/launcher_main.cpp @@ -2971,14 +2971,14 @@ struct Launcher { if (kinect_v2_chosen && settings.kinect_v2_view == "ideal") kinect_v2_tuning_rows(row_width, scale); const float slider_width = kinect_chosen ? row_width : 220 * scale; setting_row(tr(MenuSteady), tr(MenuSteadyHint), slider_width, scale, [&] { - int radius = int((std::min)(settings.menu_steady_radius, 30u)); + int radius = int((std::min)(menu_steady_for(settings), 30u)); char shown[32]; if (radius) std::snprintf(shown, sizeof shown, "%d mm", radius); else std::snprintf(shown, sizeof shown, "%s", tr(MenuSteadyOff)); ImGui::SetNextItemWidth(slider_width); bool moved = ImGui::SliderInt("##menu_steady", &radius, 0, 30, shown); moved |= slider_steps(&radius, 0, 30, 1); - if (moved) settings.menu_steady_radius = uint32_t(std::clamp(radius, 0, 30)); + if (moved) menu_steady_for(settings) = uint32_t(std::clamp(radius, 0, 30)); }); // For a bug report: numbers in game.log (motion_trace.h). setting_row(tr(MotionTrace), tr(MotionTraceHint), ImGui::GetFrameHeight() * 1.9f, scale, diff --git a/src/launcher_settings.cpp b/src/launcher_settings.cpp index dfc70d03..e6c47c15 100644 --- a/src/launcher_settings.cpp +++ b/src/launcher_settings.cpp @@ -138,13 +138,15 @@ LauncherSettings parse_launcher_settings(const std::string& text) { else if (key == "touch_controls") read_flag(value, settings.touch_controls); else if (key == "touch_labels") read_flag(value, settings.touch_labels); else if (key == "tilt") read_flag(value, settings.tilt); - else if (key == "camera" && (value == "off" || value == "picture" || value == "motion" || value == "kinect" || value == "kinect_v2")) settings.camera = value; + // "picture" is no longer offered: it reads as the default, off. + else if (key == "camera" && (value == "off" || value == "motion" || value == "kinect" || value == "kinect_v2")) settings.camera = value; else if (key == "camera_device" && value.size() <= 128) settings.camera_device = value; else if (key == "camera_mirror") read_flag(value, settings.camera_mirror); // kinect_smooth (the v2's smoothing switch) is gone: the game smooths // a real Kinect's skeletons once, as the title asks. An old file's // key is ignored like any unknown one and not written again. else if (key == "menu_steady_radius") read_number(value, 0, 30, settings.menu_steady_radius); + else if (key == "menu_steady_radius_v2") read_number(value, 0, 30, settings.menu_steady_radius_v2); else if (key == "kinect_v2_view" && (value == "fit" || value == "sensor" || value == "ideal")) settings.kinect_v2_view = value; else if (key == "kinect_v2_distance") read_number(value, 100, 400, settings.kinect_v2_distance); @@ -234,6 +236,7 @@ std::string format_launcher_settings(const LauncherSettings& s) { << "camera_device=" << s.camera_device << '\n' << "camera_mirror=" << s.camera_mirror << '\n' << "menu_steady_radius=" << (std::min)(s.menu_steady_radius, 30u) << '\n' + << "menu_steady_radius_v2=" << (std::min)(s.menu_steady_radius_v2, 30u) << '\n' << "kinect_v2_view=" << s.kinect_v2_view << '\n' << "kinect_v2_distance=" << s.kinect_v2_distance << '\n' << "kinect_v2_height=" << s.kinect_v2_height << '\n' @@ -318,8 +321,20 @@ bool has_game_save(const std::filesystem::path& saves) { return false; } +uint32_t& menu_steady_for(LauncherSettings& settings) { + return settings.camera == "kinect_v2" ? settings.menu_steady_radius_v2 : settings.menu_steady_radius; +} +uint32_t menu_steady_for(const LauncherSettings& settings) { + return settings.camera == "kinect_v2" ? settings.menu_steady_radius_v2 : settings.menu_steady_radius; +} + namespace { -bool v2_recompiled(const LauncherSettings& s) { return s.camera == "kinect_v2" && s.kinect_v2_view == "ideal"; } +// A Kinect v2 may be what the game opens: chosen, or standing in when the +// Xbox 360 choice finds no v1 (kinect_sensor.cpp, open_selected). Its view +// is sent for both, so an old camera=kinect with a v2 gets the default view +// (Recompiled) too, not the game's own fallback (Simulated). +bool v2_possible(const LauncherSettings& s) { return s.camera == "kinect_v2" || s.camera == "kinect"; } +bool v2_recompiled(const LauncherSettings& s) { return v2_possible(s) && s.kinect_v2_view == "ideal"; } // A hundredths value as the decimal text the game reads (250 -> "2.50"). std::string centi(uint32_t value) { char text[32]; @@ -407,8 +422,9 @@ std::vector> game_environment(const Launcher {"SFR_CAMERA", s.camera == "off" ? "" : s.camera == "kinect_v2" ? "kinect" : s.camera}, {"SFR_KINECT_MODEL", s.camera == "kinect_v2" ? "2" : s.camera == "kinect" ? "1" : ""}, // A Kinect v2's depth view and Recompiled's tuning (kinect_v2_depth.h); - // only for the v2 choice, so an inherited value is cleared otherwise. - {"SFR_KINECT_V2_DEPTH_VIEW", s.camera == "kinect_v2" ? s.kinect_v2_view : ""}, + // only when a v2 may open (v2_possible), so an inherited value is + // cleared otherwise. + {"SFR_KINECT_V2_DEPTH_VIEW", v2_possible(s) ? s.kinect_v2_view : ""}, {"SFR_KINECT_V2_DEPTH_DISTANCE", v2_recompiled(s) ? centi(s.kinect_v2_distance) : ""}, {"SFR_KINECT_V2_DEPTH_HEIGHT", v2_recompiled(s) ? centi(s.kinect_v2_height) : ""}, {"SFR_KINECT_V2_DEPTH_FOLLOW", v2_recompiled(s) ? centi(s.kinect_v2_follow * 10) : ""}, @@ -417,8 +433,8 @@ std::vector> game_environment(const Launcher {"SFR_KINECT_V2_SKELETON_DISTANCE", ""}, {"SFR_CAMERA_DEVICE", s.camera_device}, {"SFR_CAMERA_MIRROR", s.camera_mirror ? "1" : "0"}, - {"SFR_MENU_HAND_STEADY", s.menu_steady_radius ? "1" : "0"}, - {"SFR_MENU_STEADY_RADIUS", s.menu_steady_radius ? std::to_string((std::min)(s.menu_steady_radius, 30u)) : ""}, + {"SFR_MENU_HAND_STEADY", menu_steady_for(s) ? "1" : "0"}, + {"SFR_MENU_STEADY_RADIUS", menu_steady_for(s) ? std::to_string((std::min)(menu_steady_for(s), 30u)) : ""}, {"SFR_VOICE", s.voice ? "1" : ""}, {"SFR_CAMERA_RACE_TRACE", s.motion_trace && (s.camera == "kinect" || s.camera == "motion") ? "1" : ""}, {"SFR_AVATAR", s.avatar_model.empty() ? "0" : "1"}, diff --git a/src/launcher_settings.h b/src/launcher_settings.h index e544f0ba..6247435b 100644 --- a/src/launcher_settings.h +++ b/src/launcher_settings.h @@ -12,6 +12,12 @@ namespace sfr { // What the launcher asks of the player before the game starts, kept in // settings.ini next to the launcher. The game reads all of it at startup // (environment variables and arguments), so it is fixed for a run. +struct LauncherSettings; +// The menu cursor steadying the chosen camera uses (menu_steady_radius or +// menu_steady_radius_v2). +uint32_t& menu_steady_for(LauncherSettings& settings); +uint32_t menu_steady_for(const LauncherSettings& settings); + struct LauncherSettings { // 0 = not chosen yet: the launcher fills in the desktop size // (apply_default_window_size); the game falls back to 1280x720 without it. @@ -71,11 +77,12 @@ struct LauncherSettings { // picture the title shows is a real one. "pad" is the emulated player the // gamepad drives; "camera" tracks a real body with a webcam, which cannot // share the pad's sticks, so the two are one choice. "picture" keeps the - // pad in charge and only gives the title a camera image. + // pad in charge and only gives the title a camera image; the launcher no + // longer offers it, and a saved "picture" reads as "off". // "kinect" is a real Xbox 360 Kinect tracking the players, as on the // console (a Kinect v2 stands in when no v1 opens); "kinect_v2" is a // Kinect v2 for Xbox One only. - std::string camera = "off"; // "off", "picture", "motion", "kinect" or "kinect_v2" + std::string camera = "off"; // "off", "motion", "kinect" or "kinect_v2" // Which of the host's cameras, by the name it lists (SFR_CAMERA_DEVICE). // A name and not a number, because the list changes: a phone camera or a // capture card comes and goes, and the number then means another camera. @@ -91,8 +98,10 @@ struct LauncherSettings { // The menu cursor's steadying (pose_stability.h), webcam and Kinect: the // circle a hand held still may wander in without moving the cursor, in // millimetres; 0 turns it off. SFR_MENU_STEADY_RADIUS and - // SFR_MENU_HAND_STEADY. - uint32_t menu_steady_radius = 0; + // SFR_MENU_HAND_STEADY. Each choice keeps its own: the webcam and the + // Xbox 360 Kinect start at 8 mm, a Kinect v2 (steadier) starts off. + uint32_t menu_steady_radius = 8; // webcam, Kinect (Xbox 360) + uint32_t menu_steady_radius_v2 = 0; // Kinect v2 // How a Kinect v2's depth becomes the v1's (SFR_KINECT_V2_DEPTH_VIEW): // "ideal" (Recompiled, the launcher's default: a virtual level v1 that // follows the players), "fit" (Simulated, the v2's whole view fitted into diff --git a/tests/launcher_settings_test.cpp b/tests/launcher_settings_test.cpp index 13fdbc58..45b9cef3 100644 --- a/tests/launcher_settings_test.cpp +++ b/tests/launcher_settings_test.cpp @@ -564,19 +564,30 @@ void save_directory_settings() { void menu_steady_settings() { const sfr::LauncherSettings defaults; - require(sfr::format_launcher_settings(defaults).find("menu_steady_radius=0\n") != std::string::npos, - "the menu cursor steadying defaults to off"); - require(value_of(defaults, "SFR_MENU_HAND_STEADY") == "0" && value_of(defaults, "SFR_MENU_STEADY_RADIUS").empty(), - "the default (off) reaches the game"); - const auto wide = sfr::parse_launcher_settings("menu_steady_radius=15\n"); + require(sfr::format_launcher_settings(defaults).find("menu_steady_radius=8\n") != std::string::npos && + sfr::format_launcher_settings(defaults).find("menu_steady_radius_v2=0\n") != std::string::npos, + "the steadying starts at 8 mm for the webcam and Xbox 360 Kinect, off for a Kinect v2"); + for (const char* camera : {"motion", "kinect"}) { + const auto chosen = sfr::parse_launcher_settings(std::string("camera=") + camera + "\n"); + require(value_of(chosen, "SFR_MENU_HAND_STEADY") == "1" && value_of(chosen, "SFR_MENU_STEADY_RADIUS") == "8", + "8 mm reaches the game with the webcam and the Xbox 360 Kinect"); + } + const auto v2 = sfr::parse_launcher_settings("camera=kinect_v2\nmenu_steady_radius=8\n"); + require(value_of(v2, "SFR_MENU_HAND_STEADY") == "0" && value_of(v2, "SFR_MENU_STEADY_RADIUS").empty(), + "a Kinect v2 starts off, whatever the webcam's circle is"); + const auto v2_set = sfr::parse_launcher_settings("camera=kinect_v2\nmenu_steady_radius_v2=5\n"); + require(value_of(v2_set, "SFR_MENU_STEADY_RADIUS") == "5" && sfr::menu_steady_for(v2_set) == 5 && + v2_set.menu_steady_radius == 8, + "a Kinect v2's own circle reaches the game and leaves the webcam's alone"); + const auto wide = sfr::parse_launcher_settings("camera=motion\nmenu_steady_radius=15\n"); require(wide.menu_steady_radius == 15 && value_of(wide, "SFR_MENU_STEADY_RADIUS") == "15" && sfr::format_launcher_settings(wide).find("menu_steady_radius=15\n") != std::string::npos, "a chosen circle survives a save and reaches the game"); - const auto off = sfr::parse_launcher_settings("menu_steady_radius=0\n"); + const auto off = sfr::parse_launcher_settings("camera=motion\nmenu_steady_radius=0\n"); require(value_of(off, "SFR_MENU_HAND_STEADY") == "0" && value_of(off, "SFR_MENU_STEADY_RADIUS").empty(), "0 starts the steadying off"); for (const char* bad : {"31", "-1", "big"}) - require(sfr::parse_launcher_settings(std::string("menu_steady_radius=") + bad + "\n").menu_steady_radius == 0, + require(sfr::parse_launcher_settings(std::string("menu_steady_radius=") + bad + "\n").menu_steady_radius == 8, "an out-of-range circle keeps the default"); } @@ -612,8 +623,15 @@ void kinect_v2_view_settings() { require(saved.find("kinect_v2_view=ideal\n") != std::string::npos && saved.find("kinect_v2_skeleton_distance") == std::string::npos, "the view and its tuning survive a save"); - require(value_of(sfr::parse_launcher_settings("camera=kinect\nkinect_v2_view=ideal\n"), "SFR_KINECT_V2_DEPTH_VIEW").empty(), - "the Xbox 360 choice sends no v2 view"); + const auto old_kinect = sfr::parse_launcher_settings("camera=kinect\n"); + require(value_of(old_kinect, "SFR_KINECT_V2_DEPTH_VIEW") == "ideal" && + value_of(old_kinect, "SFR_KINECT_V2_DEPTH_DISTANCE") == "2.50", + "the Xbox 360 choice sends the v2 view too, for a v2 standing in (an old camera=kinect)"); + require(value_of(sfr::parse_launcher_settings("camera=motion\n"), "SFR_KINECT_V2_DEPTH_VIEW").empty(), + "the webcam sends no v2 view"); + const auto picture = sfr::parse_launcher_settings("camera=picture\n"); + require(picture.camera == "off" && value_of(picture, "SFR_CAMERA").empty(), + "a saved Picture mode, no longer offered, reads as off"); require(sfr::parse_launcher_settings("kinect_v2_view=player\n").kinect_v2_view == "ideal", "an unknown view keeps the default"); require(sfr::parse_launcher_settings("kinect_v2_distance=9000\n").kinect_v2_distance == 250,