mirror of
https://github.com/jessicanataliagta/PSPRecomp
synced 2026-09-26 16:49:34 -04:00
fixed save games
fixed save games
This commit is contained in:
@@ -0,0 +1,42 @@
|
||||
@echo off
|
||||
setlocal EnableExtensions
|
||||
for %%I in ("%~dp0..\..") do set "REPO=%%~fI"
|
||||
set "BIN=%REPO%\out\vcs-release-ninja\bin\Release"
|
||||
set "CHECKPOINT=%BIN%\VCS_SAVE_REPRO_CHECKPOINT.bin"
|
||||
set "TRACE=%BIN%\VCS_SAVE_REPRO_TRACE.txt"
|
||||
set "LOG=%BIN%\VCSNative.log"
|
||||
set "MISSING=%BIN%\VCS_SAVE_REPRO_TRACE_MISSING.txt"
|
||||
set "BUNDLE=%BIN%\VCS_SAVE_REPRO_BUNDLE.zip"
|
||||
if not exist "%CHECKPOINT%" (
|
||||
echo ERROR: missing %CHECKPOINT%
|
||||
echo The expensive post-mission checkpoint is not present.
|
||||
pause
|
||||
exit /b 2
|
||||
)
|
||||
if exist "%MISSING%" del /q "%MISSING%" >nul 2>&1
|
||||
set "FILES='%CHECKPOINT%'"
|
||||
if exist "%TRACE%" (
|
||||
set "FILES=%FILES%,'%TRACE%'"
|
||||
) else (
|
||||
>"%MISSING%" echo VCS SAVE REPRO TRACE WAS NOT WRITTEN.
|
||||
>>"%MISSING%" echo The checkpoint is valid and is included in this bundle.
|
||||
>>"%MISSING%" echo V8.2.6B adds a post-F8/GetAsyncKeyState F10 fallback; restore the checkpoint and retry F10 if a full trace is still needed.
|
||||
set "FILES=%FILES%,'%MISSING%'"
|
||||
echo WARNING: %TRACE% is missing.
|
||||
echo The bundle will still be created with the checkpoint and runtime log.
|
||||
)
|
||||
if exist "%LOG%" set "FILES=%FILES%,'%LOG%'"
|
||||
powershell -NoProfile -ExecutionPolicy Bypass -Command "Compress-Archive -LiteralPath @(%FILES%) -DestinationPath '%BUNDLE%' -Force"
|
||||
if errorlevel 1 (
|
||||
echo ERROR: could not create bundle.
|
||||
pause
|
||||
exit /b 4
|
||||
)
|
||||
echo.
|
||||
echo SAVE REPRO bundle created:
|
||||
echo %BUNDLE%
|
||||
echo.
|
||||
if not exist "%TRACE%" echo NOTE: trace missing, but checkpoint was preserved in the ZIP.
|
||||
echo Send this ZIP back in the chat.
|
||||
pause
|
||||
exit /b 0
|
||||
@@ -0,0 +1,30 @@
|
||||
@echo off
|
||||
setlocal EnableExtensions
|
||||
for %%I in ("%~dp0..\..") do set "REPO=%%~fI"
|
||||
set "BIN=%REPO%\out\vcs-release-ninja\bin\Release"
|
||||
set "GAME=%BIN%\PSP_DATA"
|
||||
set "CHECKPOINT=%BIN%\VCS_SAVE_REPRO_CHECKPOINT.bin"
|
||||
set "TRACE=%BIN%\VCS_SAVE_REPRO_TRACE.txt"
|
||||
if not exist "%CHECKPOINT%" (
|
||||
echo ERROR: checkpoint not found:
|
||||
echo %CHECKPOINT%
|
||||
echo.
|
||||
echo Finish the problematic mission once with V8.2.6 and press F8 after gameplay returns.
|
||||
pause
|
||||
exit /b 2
|
||||
)
|
||||
if not exist "%GAME%\PSP_GAME\SYSDIR\EBOOT_DECRYPTED.ELF" (
|
||||
echo ERROR: PSP_DATA not found beside the Release executable:
|
||||
echo %GAME%
|
||||
pause
|
||||
exit /b 3
|
||||
)
|
||||
if exist "%TRACE%" del /q "%TRACE%" >nul 2>&1
|
||||
set "PSPRECOMP_SAVE_REPRO_AUTO_RESTORE=1"
|
||||
echo ================================================================
|
||||
echo VCS SAVE REPRO - AUTO RESTORE
|
||||
echo Checkpoint:
|
||||
echo %CHECKPOINT%
|
||||
echo ================================================================
|
||||
call "%~dp0scripts\play.bat" "%GAME%"
|
||||
exit /b %ERRORLEVEL%
|
||||
@@ -133,6 +133,7 @@ struct WindowState {
|
||||
std::atomic<bool> ready{false};
|
||||
std::atomic<bool> focused{false};
|
||||
std::atomic<bool> close_requested{false};
|
||||
std::atomic<std::uint32_t> save_repro_commands{0u};
|
||||
// Raw mouse motion accumulated by the window thread and drained by the
|
||||
// guest's controller poll. Raw input rather than cursor position: the
|
||||
// cursor stops at the screen edge, and a camera that stops turning when
|
||||
@@ -448,10 +449,17 @@ LRESULT CALLBACK window_procedure(HWND window, UINT message, WPARAM wparam, LPAR
|
||||
state.focused.store(false, std::memory_order_relaxed);
|
||||
return 0;
|
||||
case WM_KEYDOWN:
|
||||
// Keyboard state is sampled with GetAsyncKeyState. Do not infer pause
|
||||
// menu ownership from Escape here: the guest may consume the press for
|
||||
// an intro/transition. Cursor mode is driven only by VCS' real native
|
||||
// frontend-active byte through display_window_set_guest_frontend_active.
|
||||
// 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<std::uint32_t>(lparam) & (1u << 30u)) == 0u) {
|
||||
if (wparam == VK_F8)
|
||||
state.save_repro_commands.fetch_or(0x1u, std::memory_order_release);
|
||||
else if (wparam == VK_F10)
|
||||
state.save_repro_commands.fetch_or(0x2u, std::memory_order_release);
|
||||
}
|
||||
// Do not infer pause menu ownership from Escape here: the guest may
|
||||
// consume the press for an intro/transition.
|
||||
return 0;
|
||||
case WM_INPUT: {
|
||||
// Raw mouse deltas. Sized from the message rather than assumed: the
|
||||
@@ -1310,6 +1318,11 @@ bool display_window_close_requested() {
|
||||
return window_state().close_requested.load(std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
std::uint32_t display_window_take_save_repro_commands() noexcept {
|
||||
if (!display_window_enabled()) return 0u;
|
||||
return window_state().save_repro_commands.exchange(0u, std::memory_order_acq_rel);
|
||||
}
|
||||
|
||||
// Keeps the last rendered frame on screen after the guest stops so the run can
|
||||
// be inspected. PSPRECOMP_WINDOW_HOLD=0 closes immediately instead.
|
||||
void display_window_shutdown() {
|
||||
@@ -1357,6 +1370,7 @@ void display_window_set_guest_frontend_active(bool) noexcept {}
|
||||
bool display_window_native_boot_user_committed() noexcept { return false; }
|
||||
void display_window_set_system_utility_mode(bool) noexcept {}
|
||||
bool display_window_close_requested() { return false; }
|
||||
std::uint32_t display_window_take_save_repro_commands() noexcept { return 0u; }
|
||||
void display_window_shutdown() {}
|
||||
|
||||
} // namespace vcs
|
||||
|
||||
@@ -99,6 +99,10 @@ void display_window_set_system_utility_mode(bool active) noexcept;
|
||||
// True once the user closed the window or pressed Escape.
|
||||
[[nodiscard]] bool display_window_close_requested();
|
||||
|
||||
// Diagnostic-only F8/F10 edges captured by the UI thread. The guest runtime
|
||||
// consumes and clears these bits at vblank.
|
||||
[[nodiscard]] std::uint32_t display_window_take_save_repro_commands() noexcept;
|
||||
|
||||
void display_window_shutdown();
|
||||
|
||||
// Native window handle (HWND on Windows, nullptr elsewhere or before the window
|
||||
|
||||
@@ -308,13 +308,19 @@ int main(int argc, char **argv) {
|
||||
runtime.cpu().set_gpr(31, 0u);
|
||||
runtime.cpu().set_gpr(4, 0u);
|
||||
runtime.cpu().set_gpr(5, 0u);
|
||||
std::string save_repro_restore_error;
|
||||
const bool save_repro_restored =
|
||||
vcs::restore_save_repro_checkpoint_if_requested(runtime, save_repro_restore_error);
|
||||
if (!save_repro_restore_error.empty())
|
||||
throw psprecomp::Error("SAVE REPRO restore failed: " + save_repro_restore_error);
|
||||
const std::uint32_t runtime_entry = save_repro_restored ? runtime.cpu().pc : elf.runtime_entry();
|
||||
const std::uint64_t max_dispatches = configured_max_dispatches();
|
||||
std::cout << "Dispatch cap: " << max_dispatches << "\n";
|
||||
vcs::display_window_start();
|
||||
|
||||
vcs::install_display_heartbeat();
|
||||
vcs::install_starvation_preemption();
|
||||
runtime.run(elf.runtime_entry(), max_dispatches);
|
||||
runtime.run(runtime_entry, max_dispatches);
|
||||
const bool shutdown_diag = std::getenv("PSPRECOMP_SHUTDOWN_DIAG") != nullptr;
|
||||
if (shutdown_diag) std::cerr << "[shutdown] runtime-run-returned\n";
|
||||
std::cout << "Runtime stopped: " << runtime.stop_reason() << "\n";
|
||||
|
||||
+1288
-23
File diff suppressed because it is too large
Load Diff
@@ -6,6 +6,10 @@
|
||||
|
||||
namespace vcs {
|
||||
void install_profile(psprecomp::Runtime &runtime, std::uint32_t user_arena_start);
|
||||
// Restores the persistent V8.2.6 post-mission save-repro checkpoint when
|
||||
// PSPRECOMP_SAVE_REPRO_AUTO_RESTORE=1. Returns true only when a checkpoint
|
||||
// was requested and restored successfully; `error` is populated on failure.
|
||||
bool restore_save_repro_checkpoint_if_requested(psprecomp::Runtime &runtime, std::string &error);
|
||||
|
||||
// Feeds the display window a dispatch/vblank heartbeat so long synchronous
|
||||
// guest phases still show progress instead of looking frozen.
|
||||
|
||||
@@ -65,7 +65,7 @@ void runtime_log_initialize(const VcsConfiguration &configuration) {
|
||||
return;
|
||||
}
|
||||
s.file << "VCSNative runtime log\n";
|
||||
s.file << "stage=correctness-v8.2.5-news-atrac-stream-fix-2026-08-18\n";
|
||||
s.file << "stage=correctness-v8.2.7-save-thread-lifecycle-fix-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)
|
||||
@@ -85,9 +85,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=825 recovery_from_821=1"
|
||||
<< " perf_layer=8 cpu_lean_revision=2 correctness_revision=827 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 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_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"
|
||||
|
||||
@@ -109,6 +109,10 @@ set "CORRECTNESS_V822_STAMP=%BUILD%\.vcs_correctness_v822_recovery_20260817"
|
||||
set "CORRECTNESS_V823_NEWS_STAMP=%BUILD%\.vcs_correctness_v823_news_audio_20260817"
|
||||
set "CORRECTNESS_V824_NEWS_PACING_STAMP=%BUILD%\.vcs_correctness_v824_news_pacing_20260818"
|
||||
set "CORRECTNESS_V825_NEWS_ATRAC_STAMP=%BUILD%\.vcs_correctness_v825_news_atrac_stream_20260818"
|
||||
set "CORRECTNESS_V826_SAVE_REPRO_STAMP=%BUILD%\.vcs_correctness_v826_save_repro_20260818"
|
||||
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"
|
||||
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"
|
||||
@@ -127,7 +131,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.5 NEWS ATRAC stream state - resident sentinels + V8.2.3 Output2 base
|
||||
echo Correctness: V8.2.6B SAVE REPRO TRACE HOTFIX over protected V8.2.5 NEWS audio
|
||||
echo Host/core LTCG: ON
|
||||
echo AVX2/fast paths: ON
|
||||
echo Tier2 deep diag: %PSPRECOMP_TIER2_DEEP_TELEMETRY%
|
||||
@@ -150,10 +154,10 @@ if not defined PYTHON3_CMD goto :NO_PYTHON3
|
||||
echo Python 3: %PYTHON3_CMD%
|
||||
%PYTHON3_CMD% "%PROFILE%\tools\build_tier2_superblocks.py" "%PROFILE%"
|
||||
if errorlevel 1 goto :FAIL
|
||||
rem V8.2.5 checker includes the protected V8.2 CPU/Geometry invariants and the
|
||||
rem V8.2.3 Output2 ABI/watermark invariants. Older revision checkers pin their
|
||||
rem exact stage strings and therefore must not gate a newer correctness stage.
|
||||
%PYTHON3_CMD% "%PROFILE%\tests\check_v825_news_atrac_stream.py"
|
||||
rem V8.2.7 checker includes the protected V8.2 CPU/Geometry, V8.2.5 NEWS,
|
||||
rem V8.2.6 passive checkpoint contract and the corrected PSP ExitDelete lifecycle.
|
||||
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
|
||||
|
||||
if exist "%BUILD%" if not exist "%SUPERBLOCK_STAMP%" (
|
||||
@@ -337,6 +341,48 @@ if exist "%BUILD%" if not exist "%CORRECTNESS_V825_NEWS_ATRAC_STAMP%" (
|
||||
del /s /q "%BUILD%\*vcs_runtime_log*.obj" >nul 2>&1
|
||||
)
|
||||
|
||||
if exist "%BUILD%" if not exist "%CORRECTNESS_V826_SAVE_REPRO_STAMP%" (
|
||||
echo.
|
||||
echo [0c-v826save/7] V8.2.6 SAVE REPRO CAPTURE - rebuilding only checkpoint/trace host objects...
|
||||
rem No runtime.hpp, generated AOT, Tier2, Geometry or DX12 changes. runtime.cpp
|
||||
rem only exposes current HLE identity to the diagnostic trace.
|
||||
del /s /q "%BUILD%\*vcs_profile*.obj" >nul 2>&1
|
||||
del /s /q "%BUILD%\*vcs_runtime_log*.obj" >nul 2>&1
|
||||
del /s /q "%BUILD%\*runtime.cpp.obj" >nul 2>&1
|
||||
del /s /q "%BUILD%\*main.cpp.obj" >nul 2>&1
|
||||
)
|
||||
|
||||
|
||||
if exist "%BUILD%" if not exist "%CORRECTNESS_V826A_SAVE_REPRO_STAMP%" (
|
||||
echo.
|
||||
echo [0c-v826a/7] V8.2.6A SAVE REPRO PASSIVE RECOVERY - removing passive HLE instrumentation...
|
||||
rem Revert core runtime.cpp to the exact V8.2.5 hot path. Diagnostic F8/F10
|
||||
rem edges are queued by the Win32 window thread and consumed once per vblank.
|
||||
rem Rebuild only files touched by this recovery; keep generated AOT/Tier2/DX12.
|
||||
del /s /q "%BUILD%\*vcs_profile*.obj" >nul 2>&1
|
||||
del /s /q "%BUILD%\*vcs_runtime_log*.obj" >nul 2>&1
|
||||
del /s /q "%BUILD%\*runtime.cpp.obj" >nul 2>&1
|
||||
del /s /q "%BUILD%\*display_window*.obj" >nul 2>&1
|
||||
)
|
||||
|
||||
if exist "%BUILD%" if not exist "%CORRECTNESS_V826B_SAVE_REPRO_STAMP%" (
|
||||
echo.
|
||||
echo [0c-v826b/7] V8.2.6B SAVE REPRO TRACE HOTFIX - rebuilding trace host objects only...
|
||||
rem Before F8/restore the runtime path remains V8.2.5/V8.2.6A. The F10
|
||||
rem async-key fallback is active only after trace capture has been armed.
|
||||
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 "%CORRECTNESS_V827_SAVE_THREAD_STAMP%" (
|
||||
echo.
|
||||
echo [0c-v827save/7] V8.2.7 SAVE THREAD LIFECYCLE - rebuilding profile/runtime-log objects only...
|
||||
rem Fix sceKernelExitDeleteThread stack/object reclamation and migrate the
|
||||
rem already captured V8.2.6 checkpoint. Generated AOT/Tier2/DX12 are unchanged.
|
||||
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...
|
||||
@@ -390,6 +436,10 @@ if errorlevel 1 goto :FAIL
|
||||
>"%CORRECTNESS_V823_NEWS_STAMP%" echo VCS V8.2.3 NEWS AUDIO FIX 2026-08-17
|
||||
>"%CORRECTNESS_V824_NEWS_PACING_STAMP%" echo VCS V8.2.4 NEWS PACING FIX 2026-08-18
|
||||
>"%CORRECTNESS_V825_NEWS_ATRAC_STAMP%" echo VCS V8.2.5 NEWS ATRAC STREAM FIX 2026-08-18
|
||||
>"%CORRECTNESS_V826_SAVE_REPRO_STAMP%" echo VCS V8.2.6 SAVE REPRO CAPTURE 2026-08-18
|
||||
>"%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
|
||||
|
||||
echo.
|
||||
echo [2b/7] Building tests and DX12 probes...
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
#!/usr/bin/env python3
|
||||
from pathlib import Path
|
||||
|
||||
root = Path(__file__).resolve().parents[3]
|
||||
profile = root / 'profiles' / 'vcs'
|
||||
host = profile / 'host'
|
||||
source = (host / 'vcs_profile.cpp').read_text(encoding='utf-8')
|
||||
runtime = (root / 'src' / 'runtime.cpp').read_text(encoding='utf-8')
|
||||
main = (host / 'main.cpp').read_text(encoding='utf-8')
|
||||
header = (host / 'vcs_profile.hpp').read_text(encoding='utf-8')
|
||||
log = (host / 'vcs_runtime_log.cpp').read_text(encoding='utf-8')
|
||||
guest = (profile / 'generated' / 'generated_unit_0170.cpp').read_text(encoding='utf-8')
|
||||
|
||||
def need(cond, msg):
|
||||
if not cond:
|
||||
print('FAIL:', msg)
|
||||
raise SystemExit(1)
|
||||
print('PASS:', msg)
|
||||
|
||||
# Runtime identity and protected V8.2/V8.2.5 guards.
|
||||
need('stage=correctness-v8.2.6-save-repro-capture-2026-08-18' in log, 'V8.2.6 runtime stage')
|
||||
need('correctness_revision=826' in log, 'V8.2.6 correctness revision')
|
||||
need('hot_blocks=1060' in log and 'static_fused_calls=36' in log and 'geometry_fusion_rollback=1' in log,
|
||||
'V8.2 CPU/Tier2 shape preserved')
|
||||
need('ge_async_quarantined=1' in log and 'parallel_vertex_decode_quarantined=1' in log,
|
||||
'unstable GE paths remain quarantined')
|
||||
need('atrac_stream_resident_status=1' in log and 'atrac_nonloop_resident=-2' in log and
|
||||
'atrac_loop_resident=-3' in log and 'news_atrac_v825_guard=1' in log,
|
||||
'working V8.2.5 NEWS semantics are a regression guard')
|
||||
need('output2_late_catchup=0' in log, 'rejected Output2 catch-up remains disabled')
|
||||
need('kAtracRemainNonLoopOnMemory = 0xFFFFFFFEu' in source and
|
||||
'kAtracRemainLoopOnMemory = 0xFFFFFFFDu' in source and
|
||||
source.count('atrac_remain_frame_status(*state)') >= 3,
|
||||
'V8.2.5 ATRAC resident implementation preserved')
|
||||
need('static_cast<std::uint32_t>(-1)' in guest and 'static_cast<std::uint32_t>(-2)' in guest,
|
||||
'VCS guest resident-state branches remain covered')
|
||||
|
||||
# Checkpoint/trace surface.
|
||||
for token, label in [
|
||||
('kSaveReproVersion = 826u', 'checkpoint format version'),
|
||||
('VCS_SAVE_REPRO_CHECKPOINT.bin', 'persistent checkpoint filename'),
|
||||
('VCS_SAVE_REPRO_TRACE.txt', 'circular trace filename'),
|
||||
('VK_F8', 'F8 checkpoint hotkey'),
|
||||
('VK_F10', 'F10 trace hotkey'),
|
||||
('PSPRECOMP_SAVE_REPRO_AUTO_RESTORE', 'auto-restore switch'),
|
||||
('kSaveReproTraceCapacity = 131072u', 'deep trace ring'),
|
||||
('kSaveReproDispatchSampleStride = 64u', 'sampled dispatch trace cadence'),
|
||||
('save_repro_fnv1a', 'checkpoint integrity checksum'),
|
||||
('file_open_flags', 'live file descriptor reopen flags'),
|
||||
('recent_atrac_reads', 'ATRAC producer association state'),
|
||||
('save_repro_write_kernel_state', 'kernel/thread state serialization'),
|
||||
('save_repro_write_media_audio_state', 'audio/media state serialization'),
|
||||
('save_repro_write_ge_state', 'GE/callback state serialization'),
|
||||
]:
|
||||
need(token in source, label)
|
||||
need('save_repro_checkpoint=1' in log and 'save_repro_trace=1' in log and
|
||||
'save_repro_auto_restore=1' in log and 'save_repro_dispatch_sample_stride=64' in log,
|
||||
'checkpoint runtime capabilities advertised')
|
||||
need('save-repro full checkpoint did not restore RAM/time/controller/CPU exactly' in source,
|
||||
'full RAM/EDRAM persistent checkpoint roundtrip regression test present')
|
||||
need('save_repro_dump_trace(rt, "window-close")' in source,
|
||||
'trace is preserved automatically when the repro window closes')
|
||||
|
||||
# Restore must enter at the captured guest continuation, not the ELF entry.
|
||||
need('restore_save_repro_checkpoint_if_requested' in header and
|
||||
'restore_save_repro_checkpoint_if_requested(runtime, save_repro_restore_error)' in main,
|
||||
'main invokes persistent checkpoint restore')
|
||||
need('save_repro_restored ? runtime.cpu().pc : elf.runtime_entry()' in main,
|
||||
'restored CPU PC is used as Runtime::run entry')
|
||||
|
||||
# HLE trace gets exact import identity without changing generated-call ABI.
|
||||
need('runtime_current_import_library() noexcept' in runtime and 'runtime_current_import_nid() noexcept' in runtime,
|
||||
'runtime exposes current import identity to diagnostic trace')
|
||||
need('g_runtime_current_import_library = previous_import_library' in runtime and
|
||||
'g_runtime_current_import_nid = previous_import_nid' in runtime,
|
||||
'nested import metadata is restored')
|
||||
need('runtime_current_import_library() noexcept;' in source and 'runtime_current_import_nid() noexcept;' in source,
|
||||
'profile uses local diagnostic declarations without runtime.hpp churn')
|
||||
|
||||
# Diagnostic only: do not alter protected time/scheduler configuration.
|
||||
capture_block = source[source.index('// V8.2.6 SAVE REPRO CAPTURE'):source.index('constexpr std::uint32_t kPspUtilityStart')]
|
||||
need('PSPRECOMP_TIME_TICK_DISPATCHES' not in capture_block and 'g_runtime_starvation_interval_fast' not in capture_block,
|
||||
'checkpoint block does not override scheduler cadence')
|
||||
need('save_repro_capture_requested = true' in source and
|
||||
'if (save_repro_capture_requested)' in source and
|
||||
'save_repro_write_checkpoint(rt, ctx, error)' in source,
|
||||
'F8 arms at vblank and checkpoint captures exact post-dispatch CPU context')
|
||||
print('V8.2.6 SAVE REPRO CAPTURE audit PASS')
|
||||
@@ -0,0 +1,79 @@
|
||||
#!/usr/bin/env python3
|
||||
from pathlib import Path
|
||||
|
||||
root = Path(__file__).resolve().parents[3]
|
||||
profile = root / 'profiles' / 'vcs'
|
||||
host = profile / 'host'
|
||||
source = (host / 'vcs_profile.cpp').read_text(encoding='utf-8')
|
||||
runtime = (root / 'src' / 'runtime.cpp').read_text(encoding='utf-8')
|
||||
display_cpp = (host / 'display_window.cpp').read_text(encoding='utf-8')
|
||||
display_hpp = (host / 'display_window.hpp').read_text(encoding='utf-8')
|
||||
main = (host / 'main.cpp').read_text(encoding='utf-8')
|
||||
header = (host / 'vcs_profile.hpp').read_text(encoding='utf-8')
|
||||
log = (host / 'vcs_runtime_log.cpp').read_text(encoding='utf-8')
|
||||
guest = (profile / 'generated' / 'generated_unit_0170.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.6a-save-repro-passive-recovery-2026-08-18' in log, 'V8.2.6A runtime stage')
|
||||
need('correctness_revision=8261' in log, 'V8.2.6A correctness revision')
|
||||
need('hot_blocks=1060' in log and 'static_fused_calls=36' in log and 'geometry_fusion_rollback=1' in log,
|
||||
'V8.2 CPU/Tier2 shape preserved')
|
||||
need('atrac_stream_resident_status=1' in log and 'atrac_nonloop_resident=-2' in log and
|
||||
'atrac_loop_resident=-3' in log and 'news_atrac_v825_guard=1' in log,
|
||||
'working V8.2.5 NEWS semantics preserved')
|
||||
need('output2_late_catchup=0' in log, 'rejected Output2 catch-up remains disabled')
|
||||
need('save_repro_passive_until_f8=1' in log and 'save_repro_hle_hotpath=0' in log and
|
||||
'save_repro_ui_hotkeys=1' in log, 'passive-capture recovery advertised')
|
||||
need('kAtracRemainNonLoopOnMemory = 0xFFFFFFFEu' in source and
|
||||
'kAtracRemainLoopOnMemory = 0xFFFFFFFDu' in source, 'V8.2.5 ATRAC resident code preserved')
|
||||
need('static_cast<std::uint32_t>(-1)' in guest and 'static_cast<std::uint32_t>(-2)' in guest,
|
||||
'VCS guest ATRAC resident branches preserved')
|
||||
|
||||
# The regression source: V8.2.6 put library/NID bookkeeping around every import.
|
||||
# V8.2.6A must have none of that in core runtime or profile hot paths.
|
||||
for forbidden in ['g_runtime_current_import_library', 'g_runtime_current_import_nid',
|
||||
'runtime_current_import_library()', 'runtime_current_import_nid()']:
|
||||
need(forbidden not in runtime, f'core runtime has no passive import identity: {forbidden}')
|
||||
need('save_repro_trace_hle' not in source, 'profile has no per-HLE SAVE_REPRO tracing')
|
||||
need('GetAsyncKeyState(' not in source, 'SAVE_REPRO does not poll Win32 keyboard from guest/vblank code')
|
||||
need('display_window_take_save_repro_commands()' in source, 'guest consumes queued UI-thread F8/F10 commands')
|
||||
need('save_repro_commands.fetch_or(0x1u' in display_cpp and 'save_repro_commands.fetch_or(0x2u' in display_cpp,
|
||||
'WndProc queues F8/F10 edges')
|
||||
need('save_repro_commands.exchange(0u' in display_cpp and
|
||||
'display_window_take_save_repro_commands() noexcept' in display_hpp,
|
||||
'queued diagnostic commands are atomically consumed')
|
||||
need('(1u << 30u)' in display_cpp, 'Win32 key autorepeat is ignored')
|
||||
|
||||
# Checkpoint remains useful once explicitly armed.
|
||||
for token, label in [
|
||||
('kSaveReproVersion = 826u', 'checkpoint format compatibility'),
|
||||
('VCS_SAVE_REPRO_CHECKPOINT.bin', 'checkpoint filename'),
|
||||
('VCS_SAVE_REPRO_TRACE.txt', 'trace filename'),
|
||||
('PSPRECOMP_SAVE_REPRO_AUTO_RESTORE', 'auto-restore switch'),
|
||||
('kSaveReproTraceCapacity = 131072u', 'trace ring capacity'),
|
||||
('kSaveReproDispatchSampleStride = 64u', 'dispatch trace sampling'),
|
||||
('save_repro_fnv1a', 'checkpoint checksum'),
|
||||
('save_repro_write_kernel_state', 'kernel state serialization'),
|
||||
('save_repro_write_media_audio_state', 'media/audio state serialization'),
|
||||
('save_repro_write_ge_state', 'GE state serialization'),
|
||||
]: need(token in source, label)
|
||||
need('save_repro_capture_requested = true' in source and
|
||||
'if (save_repro_capture_requested)' in source and
|
||||
'save_repro_write_checkpoint(rt, ctx, error)' in source,
|
||||
'F8 still captures at exact post-dispatch boundary')
|
||||
need('restore_save_repro_checkpoint_if_requested' in header and
|
||||
'restore_save_repro_checkpoint_if_requested(runtime, save_repro_restore_error)' in main and
|
||||
'save_repro_restored ? runtime.cpu().pc : elf.runtime_entry()' in main,
|
||||
'persistent checkpoint restore path preserved')
|
||||
need('save_repro_dump_trace(rt, "window-close")' in source,
|
||||
'trace auto-dump on close preserved after capture')
|
||||
|
||||
capture = source[source.index('// V8.2.6 SAVE REPRO CAPTURE'):source.index('constexpr std::uint32_t kPspUtilityStart')]
|
||||
need('PSPRECOMP_TIME_TICK_DISPATCHES' not in capture and 'g_runtime_starvation_interval_fast' not in capture,
|
||||
'capture block does not change scheduler cadence')
|
||||
print('V8.2.6A SAVE REPRO PASSIVE RECOVERY audit PASS')
|
||||
@@ -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'
|
||||
source = (host / 'vcs_profile.cpp').read_text(encoding='utf-8')
|
||||
runtime = (root / 'src' / 'runtime.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')
|
||||
collector = (profile / 'COLLECT_SAVE_REPRO.bat').read_text(encoding='utf-8')
|
||||
restore = (profile / 'RESTORE_SAVE_REPRO.bat').read_text(encoding='utf-8')
|
||||
guest = (profile / 'generated' / 'generated_unit_0170.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.6b-save-repro-trace-hotfix-2026-08-18' in log, 'V8.2.6B runtime stage')
|
||||
need('correctness_revision=8262' in log, 'V8.2.6B correctness revision')
|
||||
need('hot_blocks=1060' in log and 'static_fused_calls=36' in log and 'geometry_fusion_rollback=1' in log,
|
||||
'V8.2 CPU/Tier2 shape preserved')
|
||||
need('atrac_stream_resident_status=1' in log and 'atrac_nonloop_resident=-2' in log and
|
||||
'atrac_loop_resident=-3' in log and 'news_atrac_v825_guard=1' in log,
|
||||
'working V8.2.5 NEWS semantics preserved')
|
||||
need('output2_late_catchup=0' in log, 'rejected Output2 catch-up remains disabled')
|
||||
need('save_repro_passive_until_f8=1' in log and 'save_repro_hle_hotpath=0' in log and
|
||||
'save_repro_f10_async_fallback=1' in log and 'save_repro_collector_partial_bundle=1' in log,
|
||||
'V8.2.6B trace recovery advertised')
|
||||
need('kAtracRemainNonLoopOnMemory = 0xFFFFFFFEu' in source and
|
||||
'kAtracRemainLoopOnMemory = 0xFFFFFFFDu' in source, 'V8.2.5 ATRAC resident code preserved')
|
||||
need('static_cast<std::uint32_t>(-1)' in guest and 'static_cast<std::uint32_t>(-2)' in guest,
|
||||
'VCS guest ATRAC resident branches preserved')
|
||||
for forbidden in ['g_runtime_current_import_library', 'g_runtime_current_import_nid',
|
||||
'runtime_current_import_library()', 'runtime_current_import_nid()']:
|
||||
need(forbidden not in runtime, f'core runtime has no passive import identity: {forbidden}')
|
||||
need('save_repro_trace_hle' not in source, 'profile still has no per-HLE SAVE_REPRO tracing')
|
||||
need('save_repro_commands.fetch_or(0x1u' in display_cpp and 'save_repro_commands.fetch_or(0x2u' in display_cpp,
|
||||
'WndProc F8/F10 edge queue preserved')
|
||||
need('if (save_repro_trace_enabled)' in source and 'GetAsyncKeyState(VK_F10)' in source and
|
||||
'f10_async_was_down' in source, 'F10 async fallback is gated behind armed trace')
|
||||
# Ensure GetAsyncKeyState is not used anywhere before the save-repro function block.
|
||||
prefix = source[:source.index('void save_repro_vblank_hotkeys')]
|
||||
need('GetAsyncKeyState(VK_F10)' not in prefix, 'no F10 polling exists on pre-F8 gameplay/cutscene path')
|
||||
need('VCS_SAVE_REPRO_TRACE_MISSING.txt' in collector and
|
||||
'The bundle will still be created with the checkpoint and runtime log.' in collector and
|
||||
'exit /b 3' not in collector, 'collector preserves checkpoint even when trace is absent')
|
||||
need('if exist "%TRACE%" del /q "%TRACE%"' in restore, 'restore deletes stale trace before a new repro')
|
||||
for token, label in [
|
||||
('kSaveReproVersion = 826u', 'checkpoint format compatibility'),
|
||||
('VCS_SAVE_REPRO_CHECKPOINT.bin', 'checkpoint filename'),
|
||||
('VCS_SAVE_REPRO_TRACE.txt', 'trace filename'),
|
||||
('PSPRECOMP_SAVE_REPRO_AUTO_RESTORE', 'auto-restore switch'),
|
||||
('kSaveReproTraceCapacity = 131072u', 'trace ring capacity'),
|
||||
('kSaveReproDispatchSampleStride = 64u', 'dispatch trace sampling'),
|
||||
('save_repro_dump_trace(runtime, "manual-F10")', 'manual F10 dump path'),
|
||||
('save_repro_dump_trace(rt, "window-close")', 'window-close dump fallback'),
|
||||
]: need(token in source, label)
|
||||
need('PSPRECOMP_TIME_TICK_DISPATCHES' not in source[source.index('// V8.2.6 SAVE REPRO CAPTURE'):source.index('constexpr std::uint32_t kPspUtilityStart')],
|
||||
'capture block does not change scheduler cadence')
|
||||
print('V8.2.6B SAVE REPRO TRACE HOTFIX audit PASS')
|
||||
@@ -0,0 +1,94 @@
|
||||
#!/usr/bin/env python3
|
||||
from pathlib import Path
|
||||
|
||||
root = Path(__file__).resolve().parents[3]
|
||||
profile = root / 'profiles' / 'vcs'
|
||||
host = profile / 'host'
|
||||
source = (host / 'vcs_profile.cpp').read_text(encoding='utf-8')
|
||||
log = (host / 'vcs_runtime_log.cpp').read_text(encoding='utf-8')
|
||||
runtime = (root / 'src' / 'runtime.cpp').read_text(encoding='utf-8')
|
||||
guest_registry = (profile / 'generated' / 'generated_registry.cpp').read_text(encoding='utf-8')
|
||||
sfx = (profile / 'generated' / 'generated_unit_0096.cpp').read_text(encoding='utf-8')
|
||||
memstick = (profile / 'generated' / 'generated_unit_0172.cpp').read_text(encoding='utf-8')
|
||||
stupid = (profile / 'generated' / 'generated_unit_0076.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.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('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
|
||||
'atrac_loop_resident=-3' in log and 'news_atrac_v825_guard=1' in log,
|
||||
'working V8.2.5 NEWS semantics preserved')
|
||||
need('output2_late_catchup=0' in log, 'rejected Output2 pacing experiment remains disabled')
|
||||
need('hot_blocks=1060' in log and 'static_fused_calls=36' in log and 'geometry_fusion_rollback=1' in log,
|
||||
'V8.2 CPU/Tier2 shape preserved')
|
||||
|
||||
# Core correctness: ExitThread remains dormant/Completed, ExitDelete actually
|
||||
# destroys the object and releases its stack.
|
||||
need('void exit_delete_current_thread' in source, 'dedicated ExitDelete lifecycle helper')
|
||||
need('release_thread_stack(deleted);' in source and 'thread_table.threads.erase(deleted_uid);' in source,
|
||||
'ExitDelete releases stack and erases thread object')
|
||||
need('wake_thread_end_waiters(deleted_uid, 0u);' in source,
|
||||
'ExitDelete still wakes thread-end waiters')
|
||||
need('activate_next_thread(ctx, "thread-exit-delete")' in source,
|
||||
'ExitDelete schedules the next ready PSP thread')
|
||||
exitdelete_reg = source[source.index('runtime.register_hle("ThreadManForUser", 0x809CE29Bu'):]
|
||||
exitdelete_reg = exitdelete_reg[:exitdelete_reg.index('runtime.register_hle("ThreadManForUser", 0x383F7BCCu')]
|
||||
need('exit_delete_current_thread(rt, ctx);' in exitdelete_reg and 'complete_current_thread(rt, ctx);' not in exitdelete_reg,
|
||||
'sceKernelExitDeleteThread no longer aliases sceKernelExitThread')
|
||||
exit_reg = source[source.index('runtime.register_hle("ThreadManForUser", 0xAA73C935u'):]
|
||||
exit_reg = exit_reg[:exit_reg.index('runtime.register_hle("ThreadManForUser", 0x278C0DF5u')]
|
||||
need('complete_current_thread(rt, ctx);' in exit_reg,
|
||||
'sceKernelExitThread keeps dormant Completed semantics')
|
||||
|
||||
# Evidence baked into the generated guest: all three historically leaked VCS
|
||||
# worker entries reach the ExitDelete import.
|
||||
need('runtime.register_function(0x08B734F4u, &import_74, "ThreadManForUser::0x809CE29B")' in guest_registry,
|
||||
'guest import 0x08B734F4 maps to sceKernelExitDeleteThread')
|
||||
need('L_08986B50:' in sfx and 'ctx.pc = 0x08B734F4u;' in sfx,
|
||||
'sfx bank worker reaches ExitDelete import')
|
||||
need('L_08AB5AA0:' in memstick and 'ctx.pc = 0x08B734F4u;' in memstick,
|
||||
'memstick worker reaches ExitDelete import')
|
||||
need('L_08934734:' in stupid and 'ctx.pc = 0x08B734F4u;' in stupid,
|
||||
'stupidthread worker reaches ExitDelete import')
|
||||
|
||||
# Existing V8.2.6 checkpoint migration is intentionally scoped to proven VCS
|
||||
# worker entries and must leave normal ExitThread records alone.
|
||||
need('bool is_legacy_vcs_exit_delete_worker' in source, 'legacy checkpoint worker classifier')
|
||||
for token, label in [
|
||||
('thread.entry == 0x08934734u && thread.name == "stupidthread"', 'stupidthread migration guard'),
|
||||
('thread.entry == 0x08AB5AA0u && thread.name == "memstick"', 'memstick migration guard'),
|
||||
('thread.entry == 0x08986B50u && thread.name == "sfx bank load thread"', 'sfx worker migration guard'),
|
||||
('SAVE_REPRO legacy_memory_repair exitdelete_threads=', 'restore migration diagnostic'),
|
||||
]:
|
||||
need(token in source, label)
|
||||
need('const LegacyExitDeleteRepairStats exitdelete_repair = repair_legacy_vcs_exit_delete_threads();' in source,
|
||||
'checkpoint restore invokes legacy ExitDelete repair')
|
||||
need('std::uint32_t partition_arena_base{};' in source and 'allocate_user_arena_range' in source and
|
||||
'recompute_partition_frontier' in source, 'partition allocator tracks reusable arena holes')
|
||||
need(source.count('recompute_partition_frontier();') >= 5,
|
||||
'partition/FPL free and restore paths recompute the active frontier')
|
||||
need('PARTITION_ALLOC failed name=' in source and 'FPL_CREATE failed name=' in source,
|
||||
'partition exhaustion diagnostics are present only on failure paths')
|
||||
need('sceKernelExitDeleteThread left a Completed thread record behind' in source,
|
||||
'live ExitDelete regression self-test')
|
||||
need('legacy ExitDelete migration did not recover the top-down stack frontier' in source,
|
||||
'legacy checkpoint migration regression self-test')
|
||||
need('thread_table.threads.contains(1) && thread_table.threads.contains(13)' in source,
|
||||
'migration self-test protects ordinary/current threads')
|
||||
|
||||
# V8.2.6A passive rule remains: no reintroduction of import tracking into core.
|
||||
for forbidden in ['g_runtime_current_import_library', 'g_runtime_current_import_nid',
|
||||
'runtime_current_import_library()', 'runtime_current_import_nid()']:
|
||||
need(forbidden not in runtime, f'core runtime remains free of passive save tracing: {forbidden}')
|
||||
need('kSaveReproVersion = 826u' in source, 'existing user checkpoint remains format-compatible')
|
||||
need('PSPRECOMP_TIME_TICK_DISPATCHES' not in source[source.index('// V8.2.6 SAVE REPRO CAPTURE'):source.index('constexpr std::uint32_t kPspUtilityStart')],
|
||||
'save repair does not change scheduler cadence')
|
||||
|
||||
print('V8.2.7 SAVE THREAD LIFECYCLE audit PASS')
|
||||
Reference in New Issue
Block a user