diff --git a/profiles/vcs/README.md b/profiles/vcs/README.md index 5a6dfe2..c1fed0f 100644 --- a/profiles/vcs/README.md +++ b/profiles/vcs/README.md @@ -99,3 +99,7 @@ Its source lives in `profiles/vcs/tools/vcs_codegen_main.cpp`. Profile-only lowe ## Development history Old stage reports, validation notes and handoffs are stored under `profiles/vcs/progress`. That directory is intentionally ignored by the repository so development history does not pollute the public source tree. + +### Internal SAVE_REPRO diagnostics + +`VCSNative.ini` ships with `[Testing] SaveRepro=false`. This keeps the F8 checkpoint and F10 trace harness completely unavailable during normal play. Developers may temporarily set it to `true` for controlled debugging. `RESTORE_SAVE_REPRO.bat` explicitly opts the harness in for its own process and does not require changing the normal INI. These checkpoints are diagnostic snapshots, not supported gameplay save states. diff --git a/profiles/vcs/RESTORE_SAVE_REPRO.bat b/profiles/vcs/RESTORE_SAVE_REPRO.bat index 893d80a..50e60a9 100644 --- a/profiles/vcs/RESTORE_SAVE_REPRO.bat +++ b/profiles/vcs/RESTORE_SAVE_REPRO.bat @@ -20,6 +20,7 @@ if not exist "%GAME%\PSP_GAME\SYSDIR\EBOOT_DECRYPTED.ELF" ( exit /b 3 ) if exist "%TRACE%" del /q "%TRACE%" >nul 2>&1 +set "PSPRECOMP_SAVE_REPRO_TESTING=1" set "PSPRECOMP_SAVE_REPRO_AUTO_RESTORE=1" echo ================================================================ echo VCS SAVE REPRO - AUTO RESTORE diff --git a/profiles/vcs/config/VCSNative.ini b/profiles/vcs/config/VCSNative.ini index 91b18df..576e91b 100644 --- a/profiles/vcs/config/VCSNative.ini +++ b/profiles/vcs/config/VCSNative.ini @@ -113,6 +113,13 @@ InvertCameraY=false PedCameraUpLimitDegrees=10 ModernControlScheme=false +[Testing] +; INTERNAL DEVELOPER TOOL ONLY. This is not a general gameplay save-state. +; false = F8/F10 checkpoint/trace hotkeys are completely disabled in normal play. +; true = F8 captures SAVE_REPRO checkpoint and F10 dumps its diagnostic trace. +; RESTORE_SAVE_REPRO.bat explicitly enables this harness only for its own run. +SaveRepro=false + [Diagnostics] LogToFile=true LogFile=VCSNative.log diff --git a/profiles/vcs/host/display_window.cpp b/profiles/vcs/host/display_window.cpp index 70f734f..088edfd 100644 --- a/profiles/vcs/host/display_window.cpp +++ b/profiles/vcs/host/display_window.cpp @@ -452,7 +452,8 @@ LRESULT CALLBACK window_procedure(HWND window, UINT message, WPARAM wparam, LPAR // Gameplay keys keep using the existing sampled-input path. Diagnostic // F8/F10 edges are queued here on the UI thread, with auto-repeat // ignored, so no GetAsyncKeyState call is added to guest timing. - if ((static_cast(lparam) & (1u << 30u)) == 0u) { + if (save_repro_testing_enabled() && + (static_cast(lparam) & (1u << 30u)) == 0u) { if (wparam == VK_F8) state.save_repro_commands.fetch_or(0x1u, std::memory_order_release); else if (wparam == VK_F10) diff --git a/profiles/vcs/host/vcs_config.cpp b/profiles/vcs/host/vcs_config.cpp index 4208cf4..1f249b0 100644 --- a/profiles/vcs/host/vcs_config.cpp +++ b/profiles/vcs/host/vcs_config.cpp @@ -561,6 +561,16 @@ void apply_timing_key(VcsConfiguration &config, const std::string &key, warning(config, line, "unknown [Timing] key '" + key + "'"); } +void apply_testing_key(VcsConfiguration &config, const std::string &key, + const std::string &value, std::size_t line) { + if (key == "saverepro" || key == "savereprodiagnostics") { + if (!parse_bool(value, config.testing.save_repro)) + warning(config, line, "Testing.SaveRepro expects true/false"); + return; + } + warning(config, line, "unknown [Testing] key '" + key + "'"); +} + void apply_diagnostics_key(VcsConfiguration &config, const std::string &key, const std::string &value, std::size_t line) { if (key == "logtofile" || key == "enablelog" || key == "enabled") { @@ -760,6 +770,8 @@ VcsConfiguration load_vcs_configuration(const std::filesystem::path &path) { apply_timing_key(config, key, value, line_number); else if (section == "diagnostics" || section == "logging") apply_diagnostics_key(config, key, value, line_number); + else if (section == "testing") + apply_testing_key(config, key, value, line_number); else if (section == "widescreen") apply_widescreen_key(config, key, value, line_number); else if (section == "controls") @@ -801,6 +813,17 @@ void initialize_vcs_configuration(const std::filesystem::path &executable_direct VcsConfiguration loaded = load_vcs_configuration(path); load_proper_shaders_configuration(loaded, executable_directory / "ProperShaders.ini"); + // SAVE_REPRO is intentionally off in the shipped INI. The internal restore + // script/environment is an explicit developer opt-in and may enable it for + // that process without requiring the user's normal configuration to change. + const auto env_enabled = [](const char *name) noexcept { + const char *value = std::getenv(name); + return value != nullptr && *value != '\0' && std::string_view(value) != "0"; + }; + if (env_enabled("PSPRECOMP_SAVE_REPRO_TESTING") || + env_enabled("PSPRECOMP_SAVE_REPRO_AUTO_RESTORE")) { + loaded.testing.save_repro = true; + } loaded.initialized = true; loaded.executable_directory = executable_directory; { @@ -879,6 +902,11 @@ const VcsConfiguration &vcs_configuration() { return global_configuration(); } +bool save_repro_testing_enabled() noexcept { + const VcsConfiguration &config = global_configuration(); + return config.initialized && config.testing.save_repro; +} + const char *display_resolution_mode_name(DisplayResolutionMode mode) noexcept { switch (mode) { case DisplayResolutionMode::PspNative: return "PSP"; diff --git a/profiles/vcs/host/vcs_config.hpp b/profiles/vcs/host/vcs_config.hpp index db19438..c095aff 100644 --- a/profiles/vcs/host/vcs_config.hpp +++ b/profiles/vcs/host/vcs_config.hpp @@ -151,6 +151,13 @@ struct TimingConfiguration { std::uint64_t realtime_speed_interval_vblanks{120u}; }; +struct TestingConfiguration { + // Internal developer-only SAVE_REPRO checkpoint/trace harness. This is not + // a general user save-state system: checkpoints can contain host-side state + // that is only safe for controlled diagnostics. Keep disabled for normal play. + bool save_repro{false}; +}; + struct DiagnosticsConfiguration { // Optional text log beside the executable. Used for startup failures, // DirectX 12 device/present errors, missing-asset reports and other host @@ -272,6 +279,7 @@ struct VcsConfiguration { AudioConfiguration audio{}; TimingConfiguration timing{}; DiagnosticsConfiguration diagnostics{}; + TestingConfiguration testing{}; WidescreenConfiguration widescreen{}; VolumetricCloudsConfiguration volumetric_clouds{}; std::filesystem::path source_path{}; @@ -294,6 +302,9 @@ struct VcsConfiguration { void initialize_vcs_configuration(const std::filesystem::path &executable_directory); [[nodiscard]] const VcsConfiguration &vcs_configuration(); +// Resolved once at startup. Explicit SAVE_REPRO restore/test environment flags +// can opt the internal harness in even when the INI keeps it disabled. +[[nodiscard]] bool save_repro_testing_enabled() noexcept; [[nodiscard]] const char *display_resolution_mode_name(DisplayResolutionMode mode) noexcept; [[nodiscard]] const char *display_aspect_mode_name(DisplayAspectMode mode) noexcept; [[nodiscard]] const char *display_upscale_filter_name(DisplayUpscaleFilter filter) noexcept; diff --git a/profiles/vcs/host/vcs_profile.cpp b/profiles/vcs/host/vcs_profile.cpp index de0c2c1..76c459a 100644 --- a/profiles/vcs/host/vcs_profile.cpp +++ b/profiles/vcs/host/vcs_profile.cpp @@ -3135,6 +3135,9 @@ void save_repro_dump_trace(psprecomp::Runtime &runtime, std::string_view reason) } void save_repro_vblank_hotkeys(psprecomp::Runtime &runtime, const psprecomp::AllegrexContext &ctx) { + // Shipped builds keep SAVE_REPRO disabled. Return before even touching the + // UI command atomic so normal gameplay follows the V8.2.7 path exactly. + if (!save_repro_testing_enabled()) return; // The UI thread records F8/F10 edges in WindowState. Before F8/restore this // stays one atomic exchange per vblank, preserving the V8.2.5 mission and // cutscene path. Once tracing is armed we additionally poll F10 on Windows: @@ -11568,6 +11571,7 @@ void vcs_starvation_tick(psprecomp::Runtime &, psprecomp::AllegrexContext &ctx) } // namespace bool restore_save_repro_checkpoint_if_requested(psprecomp::Runtime &runtime, std::string &error) { + if (!save_repro_testing_enabled()) return false; const char *value = std::getenv("PSPRECOMP_SAVE_REPRO_AUTO_RESTORE"); if (value == nullptr || *value == '\0' || std::string_view(value) == "0") return false; return save_repro_restore_checkpoint_impl(runtime, error); diff --git a/profiles/vcs/host/vcs_runtime_log.cpp b/profiles/vcs/host/vcs_runtime_log.cpp index fbe994a..e4d3e33 100644 --- a/profiles/vcs/host/vcs_runtime_log.cpp +++ b/profiles/vcs/host/vcs_runtime_log.cpp @@ -65,12 +65,14 @@ void runtime_log_initialize(const VcsConfiguration &configuration) { return; } s.file << "VCSNative runtime log\n"; - s.file << "stage=correctness-v8.2.7-save-thread-lifecycle-fix-2026-08-18\n"; + s.file << "stage=correctness-v8.2.7a-internal-save-repro-gate-2026-08-18\n"; s.file << "config=" << configuration.source_path.string() << '\n'; s.file << "started=" << timestamp_now() << '\n'; s.file << "perf_telemetry=" << (configuration.diagnostics.perf_telemetry ? 1 : 0) << " interval_vblanks=" << configuration.diagnostics.perf_telemetry_interval_vblanks << "\n"; + s.file << "save_repro_testing=" << (configuration.testing.save_repro ? 1 : 0) + << " default=0 internal_only=1\n"; s.file << "guest_hotspot=" << (configuration.diagnostics.guest_hotspot_profile ? 1 : 0) << " sample_stride=256 interval_vblanks=300" #if defined(PSPRECOMP_RUNTIME_CHAIN_TELEMETRY) @@ -85,9 +87,9 @@ void runtime_log_initialize(const VcsConfiguration &configuration) { << " unwind_fix=1 reentry_guard=1 dataflow=1 vfpu_block32=133 mem_runs=35 mem_words=287" << " append32=51 advance32=89 simd_mat4=4 simd_matvec=19" << " gpr_shadow_clusters=4 gpr_shadow_regs=24 gpr_shadow_occurrences=3461 geometry_shadow=0" - << " perf_layer=8 cpu_lean_revision=2 correctness_revision=827 recovery_from_821=1" + << " perf_layer=8 cpu_lean_revision=2 correctness_revision=8271 recovery_from_821=1" << " save_transaction=1 save_lifecycle_v82=1 sas_endflag_latched=1 sas_loop_history_restore=1" - << " atrac_virtual_source=1 atrac_stall_diag=1 atrac_stream_resident_status=1 atrac_nonloop_resident=-2 atrac_loop_resident=-3 output2_success_zero=1 output2_master_watermark=1 output2_late_catchup=0 save_repro_checkpoint=1 save_repro_trace=1 save_repro_hotkey_f8=1 save_repro_trace_hotkey_f10=1 save_repro_auto_restore=1 save_repro_dispatch_sample_stride=64 save_repro_passive_until_f8=1 save_repro_hle_hotpath=0 save_repro_ui_hotkeys=1 save_repro_f10_async_fallback=1 save_repro_collector_partial_bundle=1 save_exitdelete_semantics=1 save_repro_legacy_exitdelete_repair=1 save_partition_reuse=1 news_atrac_v825_guard=1 runtime_chain_telemetry_default=0 arch_fastmem=1 aot_direct_fastmem_default=1" + << " atrac_virtual_source=1 atrac_stall_diag=1 atrac_stream_resident_status=1 atrac_nonloop_resident=-2 atrac_loop_resident=-3 output2_success_zero=1 output2_master_watermark=1 output2_late_catchup=0 save_repro_checkpoint=1 save_repro_trace=1 save_repro_hotkey_f8=1 save_repro_trace_hotkey_f10=1 save_repro_auto_restore=1 save_repro_dispatch_sample_stride=64 save_repro_passive_until_f8=1 save_repro_hle_hotpath=0 save_repro_ui_hotkeys=1 save_repro_f10_async_fallback=1 save_repro_collector_partial_bundle=1 save_repro_internal_ini_gate=1 save_repro_default_enabled=0 save_exitdelete_semantics=1 save_repro_legacy_exitdelete_repair=1 save_partition_reuse=1 news_atrac_v825_guard=1 runtime_chain_telemetry_default=0 arch_fastmem=1 aot_direct_fastmem_default=1" << " tier2_direct_fastmem=1 tier2_direct_mem_sites=1969 tier2_deep_telemetry_default=0 geometry_fusion_rollback=1" << " entity_leaf_inline=1 entity_leaf_scheduler_accounting=1 entity_leaf_resume_pc_fix=1" << " ge_async_default=0 parallel_vertex_decode_default=0" diff --git a/profiles/vcs/scripts/build_release_ninja.bat b/profiles/vcs/scripts/build_release_ninja.bat index fd1fa3d..29c1242 100644 --- a/profiles/vcs/scripts/build_release_ninja.bat +++ b/profiles/vcs/scripts/build_release_ninja.bat @@ -113,6 +113,7 @@ set "CORRECTNESS_V826_SAVE_REPRO_STAMP=%BUILD%\.vcs_correctness_v826_save_repro_ set "CORRECTNESS_V826A_SAVE_REPRO_STAMP=%BUILD%\.vcs_correctness_v826a_save_repro_passive_20260818" set "CORRECTNESS_V826B_SAVE_REPRO_STAMP=%BUILD%\.vcs_correctness_v826b_save_repro_trace_20260818" set "CORRECTNESS_V827_SAVE_THREAD_STAMP=%BUILD%\.vcs_correctness_v827_save_thread_lifecycle_20260818" +set "CORRECTNESS_V827A_SAVE_REPRO_GATE_STAMP=%BUILD%\.vcs_correctness_v827a_save_repro_gate_20260818" if not defined PSPRECOMP_TIER2_DEEP_TELEMETRY set "PSPRECOMP_TIER2_DEEP_TELEMETRY=OFF" if not defined PSPRECOMP_RUNTIME_CHAIN_TELEMETRY set "PSPRECOMP_RUNTIME_CHAIN_TELEMETRY=OFF" if /I "%PSPRECOMP_TIER2_DEEP_TELEMETRY%"=="1" set "PSPRECOMP_TIER2_DEEP_TELEMETRY=ON" @@ -131,7 +132,7 @@ echo Ninja: %NINJA_EXE% echo Ninja workers: %JOBS% echo cl.exe /MP: OFF ^(Ninja owns compile parallelism^) echo Generated AOT: O3, cold /Ob0, measured hot /Ob3; V8.2 runtime-chain lean + V8.1 Geometry/direct-fastmem baseline -echo Correctness: V8.2.6B SAVE REPRO TRACE HOTFIX over protected V8.2.5 NEWS audio +echo Correctness: V8.2.7A SAVE lifecycle + internal SAVE_REPRO gate; protected V8.2.5 NEWS audio echo Host/core LTCG: ON echo AVX2/fast paths: ON echo Tier2 deep diag: %PSPRECOMP_TIER2_DEEP_TELEMETRY% @@ -159,6 +160,8 @@ rem V8.2.6 passive checkpoint contract and the corrected PSP ExitDelete lifecycl rem Older revision checkers pin exact stage strings and must not gate this stage. %PYTHON3_CMD% "%PROFILE%\tests\check_v827_save_thread_lifecycle.py" if errorlevel 1 goto :FAIL +%PYTHON3_CMD% "%PROFILE%\tests\check_v827a_internal_save_repro_gate.py" +if errorlevel 1 goto :FAIL if exist "%BUILD%" if not exist "%SUPERBLOCK_STAMP%" ( echo. @@ -383,6 +386,17 @@ if exist "%BUILD%" if not exist "%CORRECTNESS_V827_SAVE_THREAD_STAMP%" ( del /s /q "%BUILD%\*vcs_runtime_log*.obj" >nul 2>&1 ) +if exist "%BUILD%" if not exist "%CORRECTNESS_V827A_SAVE_REPRO_GATE_STAMP%" ( + echo. + echo [0c-v827a/7] V8.2.7A INTERNAL SAVE_REPRO GATE - rebuilding host config/diagnostic objects only... + rem Normal gameplay has SAVE_REPRO disabled in VCSNative.ini. The internal + rem restore script opts in explicitly; generated AOT/Tier2/DX12 are unchanged. + del /s /q "%BUILD%\*vcs_config*.obj" >nul 2>&1 + del /s /q "%BUILD%\*display_window*.obj" >nul 2>&1 + del /s /q "%BUILD%\*vcs_profile*.obj" >nul 2>&1 + del /s /q "%BUILD%\*vcs_runtime_log*.obj" >nul 2>&1 +) + if exist "%BUILD%" if not exist "%BOOTFIX_STAMP%" ( echo. echo [0c/7] BOOTFIX revision changed - invalidating stale .obj/.pch once... @@ -440,6 +454,7 @@ if errorlevel 1 goto :FAIL >"%CORRECTNESS_V826A_SAVE_REPRO_STAMP%" echo VCS V8.2.6A SAVE REPRO PASSIVE RECOVERY 2026-08-18 >"%CORRECTNESS_V826B_SAVE_REPRO_STAMP%" echo VCS V8.2.6B SAVE REPRO TRACE HOTFIX 2026-08-18 >"%CORRECTNESS_V827_SAVE_THREAD_STAMP%" echo VCS V8.2.7 SAVE THREAD LIFECYCLE FIX 2026-08-18 +>"%CORRECTNESS_V827A_SAVE_REPRO_GATE_STAMP%" echo VCS V8.2.7A INTERNAL SAVE_REPRO GATE 2026-08-18 echo. echo [2b/7] Building tests and DX12 probes... diff --git a/profiles/vcs/tests/check_v827_save_thread_lifecycle.py b/profiles/vcs/tests/check_v827_save_thread_lifecycle.py index 3fd3057..bd346d0 100644 --- a/profiles/vcs/tests/check_v827_save_thread_lifecycle.py +++ b/profiles/vcs/tests/check_v827_save_thread_lifecycle.py @@ -18,8 +18,8 @@ def need(cond, msg): raise SystemExit(1) print('PASS:', msg) -need('stage=correctness-v8.2.7-save-thread-lifecycle-fix-2026-08-18' in log, 'V8.2.7 runtime stage') -need('correctness_revision=827' in log, 'V8.2.7 correctness revision') +need(('stage=correctness-v8.2.7-save-thread-lifecycle-fix-2026-08-18' in log) or ('stage=correctness-v8.2.7a-internal-save-repro-gate-2026-08-18' in log), 'V8.2.7/V8.2.7A runtime stage') +need(('correctness_revision=827 ' in log) or ('correctness_revision=8271 ' in log), 'V8.2.7/V8.2.7A correctness revision') need('save_exitdelete_semantics=1' in log and 'save_repro_legacy_exitdelete_repair=1' in log and 'save_partition_reuse=1' in log, 'ExitDelete/partition fix and migration metadata') need('atrac_stream_resident_status=1' in log and 'atrac_nonloop_resident=-2' in log and diff --git a/profiles/vcs/tests/check_v827a_internal_save_repro_gate.py b/profiles/vcs/tests/check_v827a_internal_save_repro_gate.py new file mode 100644 index 0000000..4c73879 --- /dev/null +++ b/profiles/vcs/tests/check_v827a_internal_save_repro_gate.py @@ -0,0 +1,63 @@ +#!/usr/bin/env python3 +from pathlib import Path + +root = Path(__file__).resolve().parents[3] +profile = root / 'profiles' / 'vcs' +host = profile / 'host' +config_h = (host / 'vcs_config.hpp').read_text(encoding='utf-8') +config_cpp = (host / 'vcs_config.cpp').read_text(encoding='utf-8') +profile_cpp = (host / 'vcs_profile.cpp').read_text(encoding='utf-8') +display_cpp = (host / 'display_window.cpp').read_text(encoding='utf-8') +log = (host / 'vcs_runtime_log.cpp').read_text(encoding='utf-8') +ini = (profile / 'config' / 'VCSNative.ini').read_text(encoding='utf-8') +restore = (profile / 'RESTORE_SAVE_REPRO.bat').read_text(encoding='utf-8') +config_tests = (profile / 'tests' / 'vcs_config_tests.cpp').read_text(encoding='utf-8') + +def need(cond, msg): + if not cond: + print('FAIL:', msg) + raise SystemExit(1) + print('PASS:', msg) + +need('stage=correctness-v8.2.7a-internal-save-repro-gate-2026-08-18' in log, + 'V8.2.7A runtime stage') +need('correctness_revision=8271' in log, 'V8.2.7A correctness revision') +need('save_repro_internal_ini_gate=1' in log and 'save_repro_default_enabled=0' in log, + 'internal-only gate metadata') +need('struct TestingConfiguration' in config_h and 'bool save_repro{false};' in config_h, + 'SAVE_REPRO configuration defaults off in code') +need('[Testing]' in ini and 'SaveRepro=false' in ini, + 'shipped INI disables SAVE_REPRO') +need('INTERNAL DEVELOPER TOOL ONLY' in ini and 'not a general gameplay save-state' in ini, + 'INI labels SAVE_REPRO as internal diagnostics') +need('apply_testing_key' in config_cpp and 'Testing.SaveRepro expects true/false' in config_cpp, + 'Testing.SaveRepro parser exists') +need('else if (section == "testing")' in config_cpp and 'apply_testing_key(config, key, value, line_number);' in config_cpp, + '[Testing] section is accepted') +need('bool save_repro_testing_enabled() noexcept' in config_cpp and + 'return config.initialized && config.testing.save_repro;' in config_cpp, + 'resolved runtime gate is a cheap config boolean') +need('PSPRECOMP_SAVE_REPRO_TESTING' in config_cpp and 'PSPRECOMP_SAVE_REPRO_AUTO_RESTORE' in config_cpp, + 'explicit internal environment paths can opt in before runtime') +need('if (save_repro_testing_enabled() &&' in display_cpp, + 'WndProc ignores F8/F10 while diagnostics are disabled') +need('if (!save_repro_testing_enabled()) return;' in profile_cpp, + 'vblank checkpoint path exits before touching command queue when disabled') +need('if (!save_repro_testing_enabled()) return false;' in profile_cpp, + 'auto-restore refuses non-testing runs') +need('set "PSPRECOMP_SAVE_REPRO_TESTING=1"' in restore and + 'set "PSPRECOMP_SAVE_REPRO_AUTO_RESTORE=1"' in restore, + 'internal restore script explicitly opts the harness in') +need('SaveRepro=true' in config_tests and 'SAVE_REPRO must be disabled by default' in config_tests, + 'configuration tests cover opt-in and safe default') +# Protected correctness/performance guards. +for token, label in [ + ('save_exitdelete_semantics=1', 'Save memory lifecycle fix preserved'), + ('atrac_nonloop_resident=-2', 'NEWS non-loop ATRAC sentinel preserved'), + ('atrac_loop_resident=-3', 'NEWS loop ATRAC sentinel preserved'), + ('output2_late_catchup=0', 'rejected Output2 pacing remains off'), + ('hot_blocks=1060', 'V8.2 CPU shape preserved'), + ('geometry_fusion_rollback=1', 'Geometry rollback preserved'), +]: + need(token in log, label) +print('V8.2.7A INTERNAL SAVE_REPRO GATE audit PASS') diff --git a/profiles/vcs/tests/vcs_config_tests.cpp b/profiles/vcs/tests/vcs_config_tests.cpp index 9faa896..d874804 100644 --- a/profiles/vcs/tests/vcs_config_tests.cpp +++ b/profiles/vcs/tests/vcs_config_tests.cpp @@ -62,6 +62,8 @@ int main() { << "MouseSensitivity=17\n" << "InvertCameraY=true\n" << "PedCameraUpLimitDegrees=40\n" + << "[Testing]\n" + << "SaveRepro=true\n" << "[Widescreen]\n" << "Enabled=true\n" << "AspectRatio=21:9\n" @@ -148,6 +150,8 @@ int main() { require(config.controls.invert_camera_y, "camera inversion was not parsed"); require(config.controls.ped_camera_up_limit_degrees == 40u, "on-foot camera upper limit was not parsed"); + require(config.testing.save_repro, + "Testing.SaveRepro was not parsed"); const vcs::PresentationRectangle fit = vcs::calculate_presentation_rectangle( 1920u, 1080u, 480u, 272u, vcs::DisplayAspectMode::Preserve, false); @@ -164,6 +168,8 @@ int main() { const vcs::VcsConfiguration missing = vcs::load_vcs_configuration(root / "missing.ini"); + require(!missing.testing.save_repro, + "SAVE_REPRO must be disabled by default"); { std::ofstream proper(root / "ProperShaders.ini", std::ios::trunc); proper << "[VolumetricClouds]\n"