From 72786e6e4ec8e4d614514cd392befd4a11ce2678 Mon Sep 17 00:00:00 2001 From: Codex Date: Tue, 27 Oct 2026 22:48:29 +0100 Subject: [PATCH 1/5] Add optional English EU1 low input latency mod --- docs/regional/low-input-latency.md | 24 ++++++++ eu/CMakeLists.txt | 20 +++++++ eu/low_input_latency.c | 41 ++++++++++++++ .../0.1.0/manifest.toml | 22 ++++++++ eu/pc.toml | 1 + eu/release/game.toml | 1 + eu/seamless/game.toml | 1 + eu/tests/test_low_input_latency.c | 56 +++++++++++++++++++ 8 files changed, 166 insertions(+) create mode 100644 docs/regional/low-input-latency.md create mode 100644 eu/low_input_latency.c create mode 100644 eu/mods/preloaded/packages/tombi.enhancement.low-input-latency/0.1.0/manifest.toml create mode 100644 eu/tests/test_low_input_latency.c diff --git a/docs/regional/low-input-latency.md b/docs/regional/low-input-latency.md new file mode 100644 index 0000000..d3ed297 --- /dev/null +++ b/docs/regional/low-input-latency.md @@ -0,0 +1,24 @@ +# Low Input Latency + +Optional, default-off input resampling for SCES-01330 Rev 1 English MAIN. +The original digital pad reader and its outer 50 Hz update still execute in +full. A checked return-site hook substitutes a newer resolved digital sample; +the original game alone derives button edges, applies input locks and updates +rumble. There is no render pass, interpolation, extra simulation or frame queue. + +Requires the companion psxrecomp digital-pad snapshot API. Rebuild the runtime +and native overlays with the updated `mod_instruction_sites`; do not rename +existing native DLLs into the new configuration namespace. + +Initial coverage is offline single-player digital P1. Other EU1 languages, +analog/Hybrid controllers, controller transforms/sources, multitap, netplay, +replay, rewind and state-load input guards retain original input. Previously +captured taps are not replayed across a discontinuity. Host observations cannot +recover taps that the operating system never delivered. + +The engine regression is `digital_pad_snapshot_test`. The optional title +fixture `eu/tests/test_low_input_latency.c` accepts extracted MAIN0..4 paths +and checks the original-byte contracts, all 65536 return values, preserved +registers and unsupported-language/device fallback. Retail inputs are local +only and must not be distributed. Internal timings are not controller-to-TV +measurements. diff --git a/eu/CMakeLists.txt b/eu/CMakeLists.txt index 1db7517..316145d 100644 --- a/eu/CMakeLists.txt +++ b/eu/CMakeLists.txt @@ -96,6 +96,7 @@ if(TOMBI_PC_FEATURES) endif() # Address-free host policy; guest widescreen hooks use checked EU adapters. target_sources(${tombi_target} PRIVATE + "${CMAKE_CURRENT_SOURCE_DIR}/low_input_latency.c" "${TOMBA_ROOT}/src/mods/tomba_hybrid_controller_plugin.c" "${TOMBA_ROOT}/src/mods/tomba_skip_fmv_plugin.c") if(TARGET ${tombi_target}-pgxp) @@ -105,6 +106,25 @@ if(TOMBI_PC_FEATURES) endif() include(CTest) if(BUILD_TESTING) + add_executable(tombi-low-input-tests tests/test_low_input_latency.c) + target_include_directories(tombi-low-input-tests PRIVATE + "${PSXRECOMP_ROOT}/runtime/include") + target_compile_options(tombi-low-input-tests PRIVATE + $<$:/UNDEBUG> + $<$>:-UNDEBUG>) + set(TOMBI_INPUT_TEST_MAIN_DIR "" CACHE PATH + "Optional owner-extracted EU1 MAIN0..4 directory") + if(TOMBI_INPUT_TEST_MAIN_DIR) + add_test(NAME tombi_low_input COMMAND tombi-low-input-tests + "${TOMBI_INPUT_TEST_MAIN_DIR}/MAIN0.EXE" + "${TOMBI_INPUT_TEST_MAIN_DIR}/MAIN1.EXE" + "${TOMBI_INPUT_TEST_MAIN_DIR}/MAIN2.EXE" + "${TOMBI_INPUT_TEST_MAIN_DIR}/MAIN3.EXE" + "${TOMBI_INPUT_TEST_MAIN_DIR}/MAIN4.EXE") + else() + add_test(NAME tombi_low_input COMMAND tombi-low-input-tests) + endif() + set_tests_properties(tombi_low_input PROPERTIES SKIP_RETURN_CODE 77) add_executable(tombi-widescreen-identity-tests tests/test_widescreen_identity.c "${tombi_ws}/eu_widescreen.c") target_include_directories(tombi-widescreen-identity-tests PRIVATE diff --git a/eu/low_input_latency.c b/eu/low_input_latency.c new file mode 100644 index 0000000..cfe9ad2 --- /dev/null +++ b/eu/low_input_latency.c @@ -0,0 +1,41 @@ +/* SCES-01330 Rev 1, English MAIN. Execute the complete original pad reader, + * then replace only its digital return value for the original P1 caller. + * The original outer routine still owns edges, locks, actions and rumble. */ +#include "mod_plugins.h" +#include "cpu_state.h" +#include + +#define PLUGIN "tombi.low-input-latency" +static PSXModDigitalPadReader reader = { sizeof reader }; + +static int contract(uint32_t address, unsigned length, uint32_t expected) { + uint32_t hash=2166136261u; + for(unsigned i=0;igpr[31]!=0x8001f88cu || + psx_mod_read_byte(0x8009f7f0u)!=0 || + psx_mod_read_half(0x8009d610u)!=4 || + !contract(0x8002623cu,0x180,0xafd11216u) || + !contract(0x8001f850u,0x94,0x4ed36982u)) { + reset();return; + } + uint32_t held; + if(psx_mod_read_digital_pad(0,&reader,cpu->gpr[2],&held)) + cpu->gpr[2]=(uint32_t)(int32_t)(int16_t)(uint16_t)held; +} + +PSX_MOD_CONSTRUCTOR(tombi_register_low_input_latency) { + (void)psx_mod_register_session_reset_plugin(PLUGIN,reset); + (void)psx_mod_register_activation_plugin(PLUGIN,reset); + (void)psx_mod_register_savestate_plugin(PLUGIN,reset); + (void)psx_mod_register_instruction_plugin(PLUGIN,0x800263b4u, + 0x03e00008u,read_return); +} diff --git a/eu/mods/preloaded/packages/tombi.enhancement.low-input-latency/0.1.0/manifest.toml b/eu/mods/preloaded/packages/tombi.enhancement.low-input-latency/0.1.0/manifest.toml new file mode 100644 index 0000000..7bf3620 --- /dev/null +++ b/eu/mods/preloaded/packages/tombi.enhancement.low-input-latency/0.1.0/manifest.toml @@ -0,0 +1,22 @@ +format_version = 5 +id = "tombi.enhancement.low-input-latency" +version = "0.1.0" +name = "Low Input Latency" +author = "TombiRecomp contributors" +description = "Uses fresher digital input without changing the original simulation rate." +resolver = "declarative" +save_compatibility = "shared" + +[[target]] +game_id = "SCES-01330" + +[[feature]] +id = "low-input-latency" +name = "Low Input Latency" +description = "Reduces the original digital controller packet delay. Currently verified for English EU1 Rev 1, offline single-player D-pad. Other languages and controller modes retain original input. Applies at the next game start." +group = "Gameplay" +default_enabled = false + +[[plugin]] +feature = "low-input-latency" +id = "tombi.low-input-latency" diff --git a/eu/pc.toml b/eu/pc.toml index 90df356..e1d218d 100644 --- a/eu/pc.toml +++ b/eu/pc.toml @@ -20,6 +20,7 @@ known_md5 = ["5029ec8fee56ec6bb6316bd16ee77d54"] known_sha1 = ["2505d958b4439d04844cadd3f968fc7432a48e00"] [recompiler] +mod_instruction_sites = ["0x800263B4"] bios_config = "../psxrecomp/bios/SCPH1001.toml" # ABI/address metadata only; runtime uses bundled OpenBIOS. seeds = "seeds/boot.txt" out_dir = "../build-eu-prototype/generated" diff --git a/eu/release/game.toml b/eu/release/game.toml index 55c2c05..0ac28fd 100644 --- a/eu/release/game.toml +++ b/eu/release/game.toml @@ -16,6 +16,7 @@ known_md5 = ["5029ec8fee56ec6bb6316bd16ee77d54"] known_sha1 = ["2505d958b4439d04844cadd3f968fc7432a48e00"] [recompiler] +mod_instruction_sites = ["0x800263B4"] seeds = "seeds/boot.txt" out_dir = "cache/generated" strict = true diff --git a/eu/seamless/game.toml b/eu/seamless/game.toml index c6b2451..cf654b8 100644 --- a/eu/seamless/game.toml +++ b/eu/seamless/game.toml @@ -20,6 +20,7 @@ known_md5 = ["5029ec8fee56ec6bb6316bd16ee77d54"] known_sha1 = ["2505d958b4439d04844cadd3f968fc7432a48e00"] [recompiler] +mod_instruction_sites = ["0x800263B4"] bios_config = "../../psxrecomp/bios/SCPH1001.toml" # ABI/address metadata only; runtime uses bundled OpenBIOS. seeds = "../seeds/boot.txt" out_dir = "../../build-eu-seamless/generated/boot" diff --git a/eu/tests/test_low_input_latency.c b/eu/tests/test_low_input_latency.c new file mode 100644 index 0000000..354a332 --- /dev/null +++ b/eu/tests/test_low_input_latency.c @@ -0,0 +1,56 @@ +/* Optional original-disc contract test: pass the owner's extracted MAIN0..4. + * No retail bytes, cards, rendering or network are needed in this fixture. */ +#include +#include +#include +#include +#include "../low_input_latency.c" + +static unsigned char image[0x90000]; +static uint32_t sample; +static int allowed, reads; +uint8_t psx_mod_read_byte(uint32_t pc) { + return pc>=0x80010000u && pc<0x800a0000u?image[pc-0x80010000u]:0; +} +uint16_t psx_mod_read_half(uint32_t pc) { + return psx_mod_read_byte(pc)|(uint16_t)psx_mod_read_byte(pc+1)<<8; +} +int psx_mod_read_digital_pad(uint32_t player,PSXModDigitalPadReader *r, + uint32_t legacy,uint32_t *buttons) { + (void)r;(void)legacy;assert(player==0);++reads; + *buttons=sample;return allowed; +} +int psx_mod_register_session_reset_plugin(const char *id,PSXModActivationCallback cb) {(void)id;(void)cb;return 1;} +int psx_mod_register_activation_plugin(const char *id,PSXModActivationCallback cb) {(void)id;(void)cb;return 1;} +int psx_mod_register_savestate_plugin(const char *id,PSXModVBlankCallback cb) {(void)id;(void)cb;return 1;} +int psx_mod_register_instruction_plugin(const char *id,uint32_t pc,uint32_t word,PSXModFunctionEntryCallback cb) { + (void)id;(void)cb;assert(pc==0x800263b4u && word==0x03e00008u);return 1; +} +int main(int argc,char **argv) { + if(argc<2)return 77; + for(int language=0;language0x163bc);fclose(f); + image[0x8f7f0]=0;image[0x8d610]=4;image[0x8d611]=0; + CPUState cpu;memset(&cpu,0x5a,sizeof cpu);cpu.gpr[31]=0x8001f88cu; + CPUState before=cpu;allowed=1;reads=0; + if(language==0) { + for(unsigned bits=0;bits<65536;++bits) { + cpu=before;sample=bits;read_return(&cpu,0x800263b4u); + CPUState expected=before;expected.gpr[2]=(uint32_t)(int32_t)(int16_t)bits; + assert(!memcmp(&cpu,&expected,sizeof cpu)); + } + assert(reads==65536); + allowed=0;cpu=before;read_return(&cpu,0x800263b4u);assert(!memcmp(&cpu,&before,sizeof cpu)); + allowed=1;cpu=before;cpu.gpr[31]=0;before=cpu;reads=0; + read_return(&cpu,0x800263b4u);assert(!reads && !memcmp(&cpu,&before,sizeof cpu)); + cpu.gpr[31]=0x8001f88cu;before=cpu;image[0x8d610]=7;reads=0; + read_return(&cpu,0x800263b4u);assert(!reads && !memcmp(&cpu,&before,sizeof cpu)); + image[0x8d610]=4;image[0x8f7f0]=0xff;reads=0; + read_return(&cpu,0x800263b4u);assert(!reads && !memcmp(&cpu,&before,sizeof cpu)); + } else { + read_return(&cpu,0x800263b4u);assert(!reads && !memcmp(&cpu,&before,sizeof cpu)); + } + } + puts("PASS EU1 reader: all 65536 returns, register preservation, unsupported languages, wrong caller, disconnect and analog fallback"); +} From 8ac4a1903185fdc3522d32c845641473e50bc040 Mon Sep 17 00:00:00 2001 From: Codex Date: Wed, 28 Oct 2026 00:09:02 +0100 Subject: [PATCH 2/5] Extend guarded digital input profiles across regions and languages --- docs/regional/low-input-latency.md | 38 ++++----- eu/CMakeLists.txt | 2 +- eu/low_input_latency.c | 43 +--------- .../0.1.1/manifest.toml | 30 +++++++ eu/tests/test_low_input_latency.c | 80 ++++++++++--------- eu2/CMakeLists.txt | 7 +- eu2/game.toml | 1 + .../0.1.1/manifest.toml | 30 +++++++ eu2/pc.toml | 1 + eu2/release/game.toml | 1 + eu2/rev1/game.toml | 1 + eu2/rev1/pc.toml | 1 + eu2/rev1/release/game.toml | 1 + eu2/seamless.toml | 1 + game.toml | 1 + jp/CMakeLists.txt | 7 +- jp/game.toml | 1 + .../0.1.1/manifest.toml | 30 +++++++ jp/pc.toml | 1 + .../0.1.1/manifest.toml | 30 +++++++ src/mods/tomba_input_profiles.inc | 39 +++++++++ src/mods/tomba_low_input_latency_plugin.c | 63 +++++++++++++++ 22 files changed, 309 insertions(+), 100 deletions(-) create mode 100644 eu/mods/preloaded/packages/tombi.enhancement.low-input-latency/0.1.1/manifest.toml create mode 100644 eu2/mods/packages/tombi.enhancement.low-input-latency/0.1.1/manifest.toml create mode 100644 jp/mods/packages/tombi.enhancement.low-input-latency/0.1.1/manifest.toml create mode 100644 mods/preloaded/packages/tombi.enhancement.low-input-latency/0.1.1/manifest.toml create mode 100644 src/mods/tomba_input_profiles.inc create mode 100644 src/mods/tomba_low_input_latency_plugin.c diff --git a/docs/regional/low-input-latency.md b/docs/regional/low-input-latency.md index d3ed297..87ce848 100644 --- a/docs/regional/low-input-latency.md +++ b/docs/regional/low-input-latency.md @@ -1,24 +1,26 @@ # Low Input Latency -Optional, default-off input resampling for SCES-01330 Rev 1 English MAIN. -The original digital pad reader and its outer 50 Hz update still execute in -full. A checked return-site hook substitutes a newer resolved digital sample; -the original game alone derives button edges, applies input locks and updates -rumble. There is no render pass, interpolation, extra simulation or frame queue. +Optional, default-off fresher resolved digital P1 input. The original pad reader, +button-edge logic, dialogue locks, actions and rumble remain in control; no extra +simulation or rendering is introduced. US/Japan retain 60 Hz and Europe 50 Hz. + +Verified original-byte profiles cover USA, all five EU1 languages, both EU2 +revisions and their five languages, and Japan. European routines differ only in +verified data relocations. Japan retains its original packed P1/P2 BIOS producer; +the checked cached-P1 store is observed in both original loop callers. Requires the companion psxrecomp digital-pad snapshot API. Rebuild the runtime -and native overlays with the updated `mod_instruction_sites`; do not rename -existing native DLLs into the new configuration namespace. +and overlays with the added mod_instruction_sites. Never rename old native DLLs +into a new configuration namespace. Whole-function contracts and device checks +fall back to original input when a profile or controller mode is unsupported. -Initial coverage is offline single-player digital P1. Other EU1 languages, -analog/Hybrid controllers, controller transforms/sources, multitap, netplay, -replay, rewind and state-load input guards retain original input. Previously -captured taps are not replayed across a discontinuity. Host observations cannot -recover taps that the operating system never delivered. +Coverage is offline single-player digital P1. Analog/Hybrid, controller transforms +or sources, multitap, netplay, replay and input guards retain original input. +Short observed taps are latched; discontinuities re-prime without replaying old +edges. No host API can recover input that the operating system never delivered. -The engine regression is `digital_pad_snapshot_test`. The optional title -fixture `eu/tests/test_low_input_latency.c` accepts extracted MAIN0..4 paths -and checks the original-byte contracts, all 65536 return values, preserved -registers and unsupported-language/device fallback. Retail inputs are local -only and must not be distributed. Internal timings are not controller-to-TV -measurements. +The engine regression is digital_pad_snapshot_test. The optional title fixture +eu/tests/test_low_input_latency.c accepts owner-extracted regional executables +and checks original-byte contracts, preserved registers and fallback paths. +Retail bytes are never distributed. Internal timings are not controller-to-TV +measurements. Frame interpolation is independent and is not part of this change. diff --git a/eu/CMakeLists.txt b/eu/CMakeLists.txt index 316145d..1a5d0b5 100644 --- a/eu/CMakeLists.txt +++ b/eu/CMakeLists.txt @@ -96,7 +96,7 @@ if(TOMBI_PC_FEATURES) endif() # Address-free host policy; guest widescreen hooks use checked EU adapters. target_sources(${tombi_target} PRIVATE - "${CMAKE_CURRENT_SOURCE_DIR}/low_input_latency.c" + "${TOMBA_ROOT}/src/mods/tomba_low_input_latency_plugin.c" "${TOMBA_ROOT}/src/mods/tomba_hybrid_controller_plugin.c" "${TOMBA_ROOT}/src/mods/tomba_skip_fmv_plugin.c") if(TARGET ${tombi_target}-pgxp) diff --git a/eu/low_input_latency.c b/eu/low_input_latency.c index cfe9ad2..9b75d18 100644 --- a/eu/low_input_latency.c +++ b/eu/low_input_latency.c @@ -1,41 +1,2 @@ -/* SCES-01330 Rev 1, English MAIN. Execute the complete original pad reader, - * then replace only its digital return value for the original P1 caller. - * The original outer routine still owns edges, locks, actions and rumble. */ -#include "mod_plugins.h" -#include "cpu_state.h" -#include - -#define PLUGIN "tombi.low-input-latency" -static PSXModDigitalPadReader reader = { sizeof reader }; - -static int contract(uint32_t address, unsigned length, uint32_t expected) { - uint32_t hash=2166136261u; - for(unsigned i=0;igpr[31]!=0x8001f88cu || - psx_mod_read_byte(0x8009f7f0u)!=0 || - psx_mod_read_half(0x8009d610u)!=4 || - !contract(0x8002623cu,0x180,0xafd11216u) || - !contract(0x8001f850u,0x94,0x4ed36982u)) { - reset();return; - } - uint32_t held; - if(psx_mod_read_digital_pad(0,&reader,cpu->gpr[2],&held)) - cpu->gpr[2]=(uint32_t)(int32_t)(int16_t)(uint16_t)held; -} - -PSX_MOD_CONSTRUCTOR(tombi_register_low_input_latency) { - (void)psx_mod_register_session_reset_plugin(PLUGIN,reset); - (void)psx_mod_register_activation_plugin(PLUGIN,reset); - (void)psx_mod_register_savestate_plugin(PLUGIN,reset); - (void)psx_mod_register_instruction_plugin(PLUGIN,0x800263b4u, - 0x03e00008u,read_return); -} +/* Regional input implementation is shared; original readers still execute. */ +#include "../src/mods/tomba_low_input_latency_plugin.c" diff --git a/eu/mods/preloaded/packages/tombi.enhancement.low-input-latency/0.1.1/manifest.toml b/eu/mods/preloaded/packages/tombi.enhancement.low-input-latency/0.1.1/manifest.toml new file mode 100644 index 0000000..dcaaade --- /dev/null +++ b/eu/mods/preloaded/packages/tombi.enhancement.low-input-latency/0.1.1/manifest.toml @@ -0,0 +1,30 @@ +format_version = 5 +id = "tombi.enhancement.low-input-latency" +version = "0.1.1" +name = "Low Input Latency" +author = "TombiRecomp contributors" +description = "Uses fresher digital input without changing the original simulation rate." +resolver = "declarative" +save_compatibility = "shared" + +[[target]] +game_id = "SCUS-94236" + +[[target]] +game_id = "SCES-01330" + +[[target]] +game_id = "SCES-01331" + +[[target]] +game_id = "SLPS-01144" + +[[feature]] +id = "low-input-latency" +name = "Low Input Latency" +description = "Reduces digital input packet delay. Verified bindings for USA, both European language sets and Japan. Offline single-player D-pad; other controller modes keep original input. Applies at the next game start." +group = "Gameplay" +default_enabled = false +[[plugin]] +feature = "low-input-latency" +id = "tombi.low-input-latency" diff --git a/eu/tests/test_low_input_latency.c b/eu/tests/test_low_input_latency.c index 354a332..ed7f179 100644 --- a/eu/tests/test_low_input_latency.c +++ b/eu/tests/test_low_input_latency.c @@ -1,56 +1,60 @@ -/* Optional original-disc contract test: pass the owner's extracted MAIN0..4. +/* Optional original-disc contract test: pass owner-extracted executables. * No retail bytes, cards, rendering or network are needed in this fixture. */ #include #include #include #include #include "../low_input_latency.c" - -static unsigned char image[0x90000]; +static unsigned char image[0x200000]; static uint32_t sample; -static int allowed, reads; -uint8_t psx_mod_read_byte(uint32_t pc) { - return pc>=0x80010000u && pc<0x800a0000u?image[pc-0x80010000u]:0; -} -uint16_t psx_mod_read_half(uint32_t pc) { - return psx_mod_read_byte(pc)|(uint16_t)psx_mod_read_byte(pc+1)<<8; -} -int psx_mod_read_digital_pad(uint32_t player,PSXModDigitalPadReader *r, - uint32_t legacy,uint32_t *buttons) { - (void)r;(void)legacy;assert(player==0);++reads; - *buttons=sample;return allowed; +static int allowed,reads; +uint8_t psx_mod_read_byte(uint32_t pc) {return (pc&0x1fffffffu)>(8*i));} +int psx_mod_read_digital_pad(uint32_t player,PSXModDigitalPadReader *r,uint32_t old,uint32_t *out) { + (void)r;(void)old;assert(player==0);++reads;*out=sample;return allowed; } int psx_mod_register_session_reset_plugin(const char *id,PSXModActivationCallback cb) {(void)id;(void)cb;return 1;} int psx_mod_register_activation_plugin(const char *id,PSXModActivationCallback cb) {(void)id;(void)cb;return 1;} int psx_mod_register_savestate_plugin(const char *id,PSXModVBlankCallback cb) {(void)id;(void)cb;return 1;} int psx_mod_register_instruction_plugin(const char *id,uint32_t pc,uint32_t word,PSXModFunctionEntryCallback cb) { - (void)id;(void)cb;assert(pc==0x800263b4u && word==0x03e00008u);return 1; + (void)id;(void)cb; + for(unsigned i=0;i0x163bc);fclose(f); - image[0x8f7f0]=0;image[0x8d610]=4;image[0x8d611]=0; - CPUState cpu;memset(&cpu,0x5a,sizeof cpu);cpu.gpr[31]=0x8001f88cu; - CPUState before=cpu;allowed=1;reads=0; - if(language==0) { - for(unsigned bits=0;bits<65536;++bits) { - cpu=before;sample=bits;read_return(&cpu,0x800263b4u); - CPUState expected=before;expected.gpr[2]=(uint32_t)(int32_t)(int16_t)bits; - assert(!memcmp(&cpu,&expected,sizeof cpu)); - } - assert(reads==65536); - allowed=0;cpu=before;read_return(&cpu,0x800263b4u);assert(!memcmp(&cpu,&before,sizeof cpu)); - allowed=1;cpu=before;cpu.gpr[31]=0;before=cpu;reads=0; - read_return(&cpu,0x800263b4u);assert(!reads && !memcmp(&cpu,&before,sizeof cpu)); - cpu.gpr[31]=0x8001f88cu;before=cpu;image[0x8d610]=7;reads=0; - read_return(&cpu,0x800263b4u);assert(!reads && !memcmp(&cpu,&before,sizeof cpu)); - image[0x8d610]=4;image[0x8f7f0]=0xff;reads=0; - read_return(&cpu,0x800263b4u);assert(!reads && !memcmp(&cpu,&before,sizeof cpu)); - } else { - read_return(&cpu,0x800263b4u);assert(!reads && !memcmp(&cpu,&before,sizeof cpu)); + for(int input=1;input0x20000);fclose(f); + const TombaInputProfile *p=0; + for(unsigned i=0;istart,q->size)==q->hash && body_hash(q->extra,q->extra_size)==q->extra_hash) {p=q;break;} + } + assert(p);assert(psx_mod_read_word(p->hook)==p->instruction); + if(p->kind)put(p->field1,1); + else {image[p->field0&0x1fffffu]=0;put(p->field1,4);} + CPUState before;memset(&before,0x5a,sizeof before);before.gpr[31]=p->caller; + if(p->kind)before.gpr[4]=p->field0; + allowed=1;reads=0; + for(unsigned bit=0;bit<18;++bit) { + sample=bit<16?1u<kind?(uint16_t)sample:(uint32_t)(int32_t)(int16_t)(uint16_t)sample; + read_return(&cpu,p->hook);assert(!memcmp(&cpu,&expected,sizeof cpu)); } + assert(reads==18); + CPUState cpu=before;allowed=0;read_return(&cpu,p->hook);assert(!memcmp(&cpu,&before,sizeof cpu)); + allowed=1;cpu=before;cpu.gpr[31]=0;CPUState bad=cpu;reads=0; + read_return(&cpu,p->hook);assert(!reads && !memcmp(&cpu,&bad,sizeof cpu)); + if(p->kind)put(p->field1,0);else put(p->field1,7); + cpu=before;reads=0;read_return(&cpu,p->hook);assert(!reads && !memcmp(&cpu,&before,sizeof cpu)); + if(p->kind)put(p->field1,1);else put(p->field1,4); + image[p->start&0x1fffffu]^=1;cpu=before;reads=0; + read_return(&cpu,p->hook);assert(!reads && !memcmp(&cpu,&before,sizeof cpu)); + printf("PASS original input ABI: %s\n",argv[input]); } - puts("PASS EU1 reader: all 65536 returns, register preservation, unsupported languages, wrong caller, disconnect and analog fallback"); } diff --git a/eu2/CMakeLists.txt b/eu2/CMakeLists.txt index 55a9bcf..dec2eb6 100644 --- a/eu2/CMakeLists.txt +++ b/eu2/CMakeLists.txt @@ -12,7 +12,10 @@ option(TOMBI_EU2_GEOMETRY_VALIDATE "Private original-vs-HLE differential checks" if(TOMBI_EU2_PLAYER_RELEASE AND PSX_DEBUG_TOOLS) message(FATAL_ERROR "Player releases must use PSX_DEBUG_TOOLS=OFF") endif() -set(eu2_mods NONE) +set(input_mods "${CMAKE_CURRENT_BINARY_DIR}/input-preloaded") +file(COPY "${TOMBA_ROOT}/mods/preloaded/packages/tombi.enhancement.low-input-latency" + DESTINATION "${input_mods}/packages") +set(eu2_mods "${input_mods}") if(TOMBI_EU2_SEAMLESS) find_package(Python3 REQUIRED COMPONENTS Interpreter) execute_process(COMMAND "${Python3_EXECUTABLE}" -I -B @@ -112,3 +115,5 @@ if(TOMBI_EU2_SEAMLESS) endif() endif() endif() + +target_sources(tombi-eu2 PRIVATE "${TOMBA_ROOT}/src/mods/tomba_low_input_latency_plugin.c") diff --git a/eu2/game.toml b/eu2/game.toml index e2296ee..a5424ef 100644 --- a/eu2/game.toml +++ b/eu2/game.toml @@ -20,6 +20,7 @@ known_md5 = ["5cbbd8800409d992ce4b6d0df4bd7fab"] known_sha1 = ["684dbf0ad03f872511046d40d3cc85b17d839dbf"] [recompiler] +mod_instruction_sites = ["0x80026368"] bios_config = "../psxrecomp/bios/SCPH1001.toml" seeds = "seeds/boot.txt" out_dir = "../build-eu2/generated" diff --git a/eu2/mods/packages/tombi.enhancement.low-input-latency/0.1.1/manifest.toml b/eu2/mods/packages/tombi.enhancement.low-input-latency/0.1.1/manifest.toml new file mode 100644 index 0000000..dcaaade --- /dev/null +++ b/eu2/mods/packages/tombi.enhancement.low-input-latency/0.1.1/manifest.toml @@ -0,0 +1,30 @@ +format_version = 5 +id = "tombi.enhancement.low-input-latency" +version = "0.1.1" +name = "Low Input Latency" +author = "TombiRecomp contributors" +description = "Uses fresher digital input without changing the original simulation rate." +resolver = "declarative" +save_compatibility = "shared" + +[[target]] +game_id = "SCUS-94236" + +[[target]] +game_id = "SCES-01330" + +[[target]] +game_id = "SCES-01331" + +[[target]] +game_id = "SLPS-01144" + +[[feature]] +id = "low-input-latency" +name = "Low Input Latency" +description = "Reduces digital input packet delay. Verified bindings for USA, both European language sets and Japan. Offline single-player D-pad; other controller modes keep original input. Applies at the next game start." +group = "Gameplay" +default_enabled = false +[[plugin]] +feature = "low-input-latency" +id = "tombi.low-input-latency" diff --git a/eu2/pc.toml b/eu2/pc.toml index 607e6af..ac88004 100644 --- a/eu2/pc.toml +++ b/eu2/pc.toml @@ -20,6 +20,7 @@ known_md5 = ["5cbbd8800409d992ce4b6d0df4bd7fab"] known_sha1 = ["684dbf0ad03f872511046d40d3cc85b17d839dbf"] [recompiler] +mod_instruction_sites = ["0x80026368"] bios_config = "../psxrecomp/bios/SCPH1001.toml" seeds = "seeds/boot.txt" out_dir = "../build-eu2-seamless/generated/boot" diff --git a/eu2/release/game.toml b/eu2/release/game.toml index ac5d116..7da2068 100644 --- a/eu2/release/game.toml +++ b/eu2/release/game.toml @@ -16,6 +16,7 @@ known_md5 = ["5cbbd8800409d992ce4b6d0df4bd7fab"] known_sha1 = ["684dbf0ad03f872511046d40d3cc85b17d839dbf"] [recompiler] +mod_instruction_sites = ["0x80026368"] seeds = "seeds/boot.txt" out_dir = "cache/generated" strict = true diff --git a/eu2/rev1/game.toml b/eu2/rev1/game.toml index 23b2525..131c8eb 100644 --- a/eu2/rev1/game.toml +++ b/eu2/rev1/game.toml @@ -20,6 +20,7 @@ known_md5 = ["2456298c5fc4c7686e739f02ee165363"] known_sha1 = ["ec50074b6fd98f611bae11e3de68d6e76ffdd6d4"] [recompiler] +mod_instruction_sites = ["0x800263B4"] bios_config = "../psxrecomp/bios/SCPH1001.toml" seeds = "seeds/boot.txt" out_dir = "../build-eu2/generated" diff --git a/eu2/rev1/pc.toml b/eu2/rev1/pc.toml index ca06825..42c7f66 100644 --- a/eu2/rev1/pc.toml +++ b/eu2/rev1/pc.toml @@ -20,6 +20,7 @@ known_md5 = ["2456298c5fc4c7686e739f02ee165363"] known_sha1 = ["ec50074b6fd98f611bae11e3de68d6e76ffdd6d4"] [recompiler] +mod_instruction_sites = ["0x800263B4"] bios_config = "../psxrecomp/bios/SCPH1001.toml" seeds = "seeds/boot.txt" out_dir = "../build-eu2-seamless/generated/boot" diff --git a/eu2/rev1/release/game.toml b/eu2/rev1/release/game.toml index 592dbf0..e71fe7b 100644 --- a/eu2/rev1/release/game.toml +++ b/eu2/rev1/release/game.toml @@ -16,6 +16,7 @@ known_md5 = ["2456298c5fc4c7686e739f02ee165363"] known_sha1 = ["ec50074b6fd98f611bae11e3de68d6e76ffdd6d4"] [recompiler] +mod_instruction_sites = ["0x800263B4"] seeds = "seeds/boot.txt" out_dir = "cache/generated" strict = true diff --git a/eu2/seamless.toml b/eu2/seamless.toml index 5ab20d3..2eaf70e 100644 --- a/eu2/seamless.toml +++ b/eu2/seamless.toml @@ -20,6 +20,7 @@ known_md5 = ["5cbbd8800409d992ce4b6d0df4bd7fab"] known_sha1 = ["684dbf0ad03f872511046d40d3cc85b17d839dbf"] [recompiler] +mod_instruction_sites = ["0x80026368"] bios_config = "../psxrecomp/bios/SCPH1001.toml" seeds = "seeds/boot.txt" out_dir = "../build-eu2-seamless/generated/boot" diff --git a/game.toml b/game.toml index 0912e58..119d757 100644 --- a/game.toml +++ b/game.toml @@ -27,6 +27,7 @@ known_sha1 = [ ] [recompiler] +mod_instruction_sites = ["0x80028EEC"] seeds = "seeds/ghidra_funcs.txt" out_dir = "generated" strict = true diff --git a/jp/CMakeLists.txt b/jp/CMakeLists.txt index 71899ea..47acc5f 100644 --- a/jp/CMakeLists.txt +++ b/jp/CMakeLists.txt @@ -11,7 +11,10 @@ include("${PSXRECOMP_ROOT}/runtime/runtime.cmake") # adapter, jp/widescreen_profile.py). option(TOMBA_JP_ENHANCEMENTS "Seamless Loading, FMV Skip and Widescreen" OFF) set(TOMBA_JP_GENERATED_DIR "${TOMBA_ROOT}/build-jp/generated" CACHE PATH "Japanese boot code directory") -set(jp_mods NONE) +set(input_mods "${CMAKE_CURRENT_BINARY_DIR}/input-preloaded") +file(COPY "${TOMBA_ROOT}/mods/preloaded/packages/tombi.enhancement.low-input-latency" + DESTINATION "${input_mods}/packages") +set(jp_mods "${input_mods}") if(TOMBA_JP_ENHANCEMENTS) find_package(Python3 REQUIRED COMPONENTS Interpreter) get_filename_component(jp_generated "${TOMBA_JP_GENERATED_DIR}" DIRECTORY) @@ -57,3 +60,5 @@ endif() # The shared launcher already embeds the UTF-8 application manifest # (recomp-ui windows/recomp_utf8.manifest); a second manifest resource would # collide with it at link time. + +target_sources(tomba-jp PRIVATE "${TOMBA_ROOT}/src/mods/tomba_low_input_latency_plugin.c") diff --git a/jp/game.toml b/jp/game.toml index 4fc1bf1..cb1c24b 100644 --- a/jp/game.toml +++ b/jp/game.toml @@ -17,6 +17,7 @@ known_md5 = ["788922f281da3337e03c7113f9bd9bf5"] known_sha1 = ["c86baf702303e7e0963b99aff307b1c40e40478a"] [recompiler] +mod_instruction_sites = ["0x800207C8"] seeds = "seeds/boot.txt" out_dir = "../build-jp/generated" strict = true diff --git a/jp/mods/packages/tombi.enhancement.low-input-latency/0.1.1/manifest.toml b/jp/mods/packages/tombi.enhancement.low-input-latency/0.1.1/manifest.toml new file mode 100644 index 0000000..dcaaade --- /dev/null +++ b/jp/mods/packages/tombi.enhancement.low-input-latency/0.1.1/manifest.toml @@ -0,0 +1,30 @@ +format_version = 5 +id = "tombi.enhancement.low-input-latency" +version = "0.1.1" +name = "Low Input Latency" +author = "TombiRecomp contributors" +description = "Uses fresher digital input without changing the original simulation rate." +resolver = "declarative" +save_compatibility = "shared" + +[[target]] +game_id = "SCUS-94236" + +[[target]] +game_id = "SCES-01330" + +[[target]] +game_id = "SCES-01331" + +[[target]] +game_id = "SLPS-01144" + +[[feature]] +id = "low-input-latency" +name = "Low Input Latency" +description = "Reduces digital input packet delay. Verified bindings for USA, both European language sets and Japan. Offline single-player D-pad; other controller modes keep original input. Applies at the next game start." +group = "Gameplay" +default_enabled = false +[[plugin]] +feature = "low-input-latency" +id = "tombi.low-input-latency" diff --git a/jp/pc.toml b/jp/pc.toml index c43edd3..72c3932 100644 --- a/jp/pc.toml +++ b/jp/pc.toml @@ -17,6 +17,7 @@ known_md5 = ["788922f281da3337e03c7113f9bd9bf5"] known_sha1 = ["c86baf702303e7e0963b99aff307b1c40e40478a"] [recompiler] +mod_instruction_sites = ["0x800207C8"] seeds = "seeds/boot.txt" out_dir = "../build-jp/rc2/generated/boot" strict = true diff --git a/mods/preloaded/packages/tombi.enhancement.low-input-latency/0.1.1/manifest.toml b/mods/preloaded/packages/tombi.enhancement.low-input-latency/0.1.1/manifest.toml new file mode 100644 index 0000000..dcaaade --- /dev/null +++ b/mods/preloaded/packages/tombi.enhancement.low-input-latency/0.1.1/manifest.toml @@ -0,0 +1,30 @@ +format_version = 5 +id = "tombi.enhancement.low-input-latency" +version = "0.1.1" +name = "Low Input Latency" +author = "TombiRecomp contributors" +description = "Uses fresher digital input without changing the original simulation rate." +resolver = "declarative" +save_compatibility = "shared" + +[[target]] +game_id = "SCUS-94236" + +[[target]] +game_id = "SCES-01330" + +[[target]] +game_id = "SCES-01331" + +[[target]] +game_id = "SLPS-01144" + +[[feature]] +id = "low-input-latency" +name = "Low Input Latency" +description = "Reduces digital input packet delay. Verified bindings for USA, both European language sets and Japan. Offline single-player D-pad; other controller modes keep original input. Applies at the next game start." +group = "Gameplay" +default_enabled = false +[[plugin]] +feature = "low-input-latency" +id = "tombi.low-input-latency" diff --git a/src/mods/tomba_input_profiles.inc b/src/mods/tomba_input_profiles.inc new file mode 100644 index 0000000..acfa9d8 --- /dev/null +++ b/src/mods/tomba_input_profiles.inc @@ -0,0 +1,39 @@ +/* Original-disc instruction/body contracts; no retail code. */ +static const TombaInputProfile input_profiles[] = { + /* us/SCUS_942.36 */ + {0x80028eecu,0x3e00008u,0x8002241cu,0x80028d70u,0x184u,0xa81e6469u,0x800223e0u,0x94u,0xfd11d463u,0x8009eb58u,0x8009c978u,0x0u}, + /* eu1/EXE/MAIN0.EXE */ + {0x800263b4u,0x3e00008u,0x8001f88cu,0x8002623cu,0x180u,0xafd11216u,0x8001f850u,0x94u,0x4ed36982u,0x8009f7f0u,0x8009d610u,0x0u}, + /* eu1/EXE/MAIN1.EXE */ + {0x800263b4u,0x3e00008u,0x8001f88cu,0x8002623cu,0x180u,0xad1d2e07u,0x8001f850u,0x94u,0xb8d62702u,0x8009f810u,0x8009d630u,0x0u}, + /* eu1/EXE/MAIN2.EXE */ + {0x800263b4u,0x3e00008u,0x8001f88cu,0x8002623cu,0x180u,0xc09ccae7u,0x8001f850u,0x94u,0xbee40a82u,0x8009f830u,0x8009d650u,0x0u}, + /* eu1/EXE/MAIN3.EXE */ + {0x800263b4u,0x3e00008u,0x8001f88cu,0x8002623cu,0x180u,0x7f0a61cfu,0x8001f850u,0x94u,0xc4712b12u,0x8009f828u,0x8009d648u,0x0u}, + /* eu1/EXE/MAIN4.EXE */ + {0x800263b4u,0x3e00008u,0x8001f88cu,0x8002623cu,0x180u,0xc09ccae7u,0x8001f850u,0x94u,0xbee40a82u,0x8009f830u,0x8009d650u,0x0u}, + /* eu2/EXE/MAIN0.EXE */ + {0x80026368u,0x3e00008u,0x8001f88cu,0x800261f0u,0x180u,0xccf4bcd5u,0x8001f850u,0x94u,0xbfb96c65u,0x8009f2e0u,0x8009d100u,0x0u}, + /* eu2/EXE/MAIN5.EXE */ + {0x80026368u,0x3e00008u,0x8001f88cu,0x800261f0u,0x180u,0x8155e9eau,0x8001f850u,0x94u,0xe320cdc5u,0x8009f308u,0x8009d128u,0x0u}, + /* eu2/EXE/MAIN6.EXE */ + {0x80026368u,0x3e00008u,0x8001f88cu,0x800261f0u,0x180u,0x9bc8bb6u,0x8001f850u,0x94u,0xe320cdc5u,0x8009f308u,0x8009d128u,0x0u}, + /* eu2/EXE/MAIN7.EXE */ + {0x80026368u,0x3e00008u,0x8001f88cu,0x800261f0u,0x180u,0x1bb6f19du,0x8001f850u,0x94u,0x712c5305u,0x8009f2e8u,0x8009d108u,0x0u}, + /* eu2/EXE/MAIN8.EXE */ + {0x80026368u,0x3e00008u,0x8001f88cu,0x800261f0u,0x180u,0x9bc8bb6u,0x8001f850u,0x94u,0xe320cdc5u,0x8009f308u,0x8009d128u,0x0u}, + /* eu2r1/EXE/MAIN0.EXE */ + {0x800263b4u,0x3e00008u,0x8001f88cu,0x8002623cu,0x180u,0xafd11216u,0x8001f850u,0x94u,0x4ed36982u,0x8009f7f0u,0x8009d610u,0x0u}, + /* eu2r1/EXE/MAIN5.EXE */ + {0x800263b4u,0x3e00008u,0x8001f88cu,0x8002623cu,0x180u,0x51e0290fu,0x8001f850u,0x94u,0x6f08c132u,0x8009f818u,0x8009d638u,0x0u}, + /* eu2r1/EXE/MAIN6.EXE */ + {0x800263b4u,0x3e00008u,0x8001f88cu,0x8002623cu,0x180u,0x8fc5b777u,0x8001f850u,0x94u,0x17f62302u,0x8009f820u,0x8009d640u,0x0u}, + /* eu2r1/EXE/MAIN7.EXE */ + {0x800263b4u,0x3e00008u,0x8001f88cu,0x8002623cu,0x180u,0x75eb7997u,0x8001f850u,0x94u,0xdd502482u,0x8009f800u,0x8009d620u,0x0u}, + /* eu2r1/EXE/MAIN8.EXE */ + {0x800263b4u,0x3e00008u,0x8001f88cu,0x8002623cu,0x180u,0x8fc5b777u,0x8001f850u,0x94u,0x17f62302u,0x8009f820u,0x8009d640u,0x0u}, + /* jp/SLPS_011.44 */ + {0x800207c8u,0xa4820000u,0x80018ac8u,0x80020794u,0xc8u,0xde4a36eau,0x80016960u,0xc0u,0xaf653575u,0x8009f200u,0x8009bfe4u,0x1u}, + /* jp/SLPS_011.44/alternate-game-loop */ + {0x800207c8u,0xa4820000u,0x800196b4u,0x80020794u,0xc8u,0xde4a36eau,0x80016960u,0xc0u,0xaf653575u,0x8009f200u,0x8009bfe4u,0x1u}, +}; diff --git a/src/mods/tomba_low_input_latency_plugin.c b/src/mods/tomba_low_input_latency_plugin.c new file mode 100644 index 0000000..3bb4550 --- /dev/null +++ b/src/mods/tomba_low_input_latency_plugin.c @@ -0,0 +1,63 @@ +/* Execute the original input path in full. US/EU substitute only the + * digital reader return for their audited P1 caller. Japan substitutes the + * cached P1 halfword at the game's original store, after the load delay; + * its BIOS poll, packed P2 word and initialization remain original. + * The original outer routine still owns edges, locks, actions and rumble. */ +#include "mod_plugins.h" +#include "cpu_state.h" +#include +#define PLUGIN "tombi.low-input-latency" +typedef struct TombaInputProfile { + uint32_t hook,instruction,caller,start,size,hash; + uint32_t extra,extra_size,extra_hash,field0,field1,kind; +} TombaInputProfile; +#include "tomba_input_profiles.inc" +static PSXModDigitalPadReader reader={sizeof reader}; +static const TombaInputProfile *current; +static uint32_t body_hash(uint32_t address,unsigned bytes) { + uint32_t h=2166136261u; + for(unsigned i=0;ihook!=pc || p->caller!=cpu->gpr[31])continue; + if(base!=p->start || size!=p->size) { + base=p->start;size=p->size;hash=body_hash(base,size); + } + if(hash!=p->hash)continue; + if(extra!=p->extra || extra_size!=p->extra_size) { + extra=p->extra;extra_size=p->extra_size;extra_hash=body_hash(extra,extra_size); + } + if(extra_hash!=p->extra_hash)continue; + if(p->kind) { + if(cpu->gpr[4]!=p->field0 || psx_mod_read_word(p->field1)!=1 || + psx_mod_read_word(p->caller-8)!=0x0c0081e5u || psx_mod_read_word(p->caller-4)!=0)continue; + } else if(psx_mod_read_byte(p->field0)!=0 || psx_mod_read_half(p->field1)!=4)continue; + match=p;break; + } + if(!match) {reset();return;} + if(current!=match) { + if(!current || current->start!=match->start || current->hash!=match->hash)reset(); + current=match; + } + uint32_t held; + if(psx_mod_read_digital_pad(0,&reader,cpu->gpr[2],&held)) + cpu->gpr[2]=match->kind?(uint16_t)held:(uint32_t)(int32_t)(int16_t)(uint16_t)held; +} +PSX_MOD_CONSTRUCTOR(tombi_register_low_input_latency) { + (void)psx_mod_register_session_reset_plugin(PLUGIN,reset); + (void)psx_mod_register_activation_plugin(PLUGIN,reset); + (void)psx_mod_register_savestate_plugin(PLUGIN,reset); + for(unsigned i=0;ihook)duplicate=1; + if(!duplicate)(void)psx_mod_register_instruction_plugin(PLUGIN,p->hook,p->instruction,read_return); + } +} From de4105f592f365fed2ab2a5d69801533459d7b47 Mon Sep 17 00:00:00 2001 From: Codex Date: Wed, 28 Oct 2026 00:12:49 +0100 Subject: [PATCH 3/5] Clarify pending regional gameplay acceptance --- docs/regional/low-input-latency.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/regional/low-input-latency.md b/docs/regional/low-input-latency.md index 87ce848..2a7f86a 100644 --- a/docs/regional/low-input-latency.md +++ b/docs/regional/low-input-latency.md @@ -14,6 +14,9 @@ and overlays with the added mod_instruction_sites. Never rename old native DLLs into a new configuration namespace. Whole-function contracts and device checks fall back to original input when a profile or controller mode is unsupported. +Runtime acceptance beyond EU1 is still pending; static contracts alone are not +a gameplay acceptance test. This regional extension remains a review draft. + Coverage is offline single-player digital P1. Analog/Hybrid, controller transforms or sources, multitap, netplay, replay and input guards retain original input. Short observed taps are latched; discontinuities re-prime without replaying old From 28f45ebed3e0d8a38b983e4ec9c5883070a87773 Mon Sep 17 00:00:00 2001 From: Codex Date: Wed, 28 Oct 2026 08:45:38 +0100 Subject: [PATCH 4/5] Finalize regional input validation notes and Japanese caller fixture --- docs/regional/low-input-latency.md | 18 ++++++++++++++---- eu/tests/test_low_input_latency.c | 13 +++++++++++++ 2 files changed, 27 insertions(+), 4 deletions(-) diff --git a/docs/regional/low-input-latency.md b/docs/regional/low-input-latency.md index 2a7f86a..660cda6 100644 --- a/docs/regional/low-input-latency.md +++ b/docs/regional/low-input-latency.md @@ -14,11 +14,17 @@ and overlays with the added mod_instruction_sites. Never rename old native DLLs into a new configuration namespace. Whole-function contracts and device checks fall back to original input when a profile or controller mode is unsupported. -Runtime acceptance beyond EU1 is still pending; static contracts alone are not -a gameplay acceptance test. This regional extension remains a review draft. +Short controller-only LOAD checks pass for USA and English EU2 original revision: +walking, direction changes, jump/landing, and observed input edges. English EU1 +also has an owner-confirmed responsiveness improvement. The other European +languages and EU2 Rev 1 have original-byte contract coverage, not new gameplay +acceptance. Japan's original menu lists the save, but loading into controllable +gameplay remains unconfirmed. These checks are not campaign certification. -Coverage is offline single-player digital P1. Analog/Hybrid, controller transforms -or sources, multitap, netplay, replay and input guards retain original input. +Coverage is offline single-player digital P1. Analog/Hybrid, registered controller +transforms or sources, configured multiple pads, multitap, netplay, replay and +input guards retain original input. Unknown guest-only multiplayer patches are +not validated by this API. Short observed taps are latched; discontinuities re-prime without replaying old edges. No host API can recover input that the operating system never delivered. @@ -27,3 +33,7 @@ eu/tests/test_low_input_latency.c accepts owner-extracted regional executables and checks original-byte contracts, preserved registers and fallback paths. Retail bytes are never distributed. Internal timings are not controller-to-TV measurements. Frame interpolation is independent and is not part of this change. + +The validated production path is unchanged. Re-polling SDL inside the guest hook +or changing presentation timing needs separate correctness work; neither is an +additional proven safe latency reduction in this change. diff --git a/eu/tests/test_low_input_latency.c b/eu/tests/test_low_input_latency.c index ed7f179..10cf40c 100644 --- a/eu/tests/test_low_input_latency.c +++ b/eu/tests/test_low_input_latency.c @@ -47,6 +47,19 @@ int main(int argc,char **argv) { read_return(&cpu,p->hook);assert(!memcmp(&cpu,&expected,sizeof cpu)); } assert(reads==18); + /* Both verified Japanese callers reach the same original P1 store. + * Exercise each, not just the first matching regional profile. */ + if(p->kind) { + for(unsigned i=0;ikind!=p->kind || q->hash!=p->hash || q->start!=p->start)continue; + CPUState call=before,expected=before; + call.gpr[31]=expected.gpr[31]=q->caller; + sample=0xa55au;expected.gpr[2]=sample; + int count=reads;read_return(&call,q->hook); + assert(reads==count+1 && !memcmp(&call,&expected,sizeof call)); + } + } CPUState cpu=before;allowed=0;read_return(&cpu,p->hook);assert(!memcmp(&cpu,&before,sizeof cpu)); allowed=1;cpu=before;cpu.gpr[31]=0;CPUState bad=cpu;reads=0; read_return(&cpu,p->hook);assert(!reads && !memcmp(&cpu,&bad,sizeof cpu)); From c853a062ca9cbeda733418ccfd018aa73bce8272 Mon Sep 17 00:00:00 2001 From: Codex Date: Wed, 28 Oct 2026 09:22:48 +0100 Subject: [PATCH 5/5] Normalize legacy Japanese pad byte order at the input boundary --- docs/regional/low-input-latency.md | 10 +++++++--- eu/tests/test_low_input_latency.c | 14 ++++++++++---- src/mods/tomba_low_input_latency_plugin.c | 11 ++++++++--- 3 files changed, 25 insertions(+), 10 deletions(-) diff --git a/docs/regional/low-input-latency.md b/docs/regional/low-input-latency.md index 660cda6..1436770 100644 --- a/docs/regional/low-input-latency.md +++ b/docs/regional/low-input-latency.md @@ -7,7 +7,9 @@ simulation or rendering is introduced. US/Japan retain 60 Hz and Europe 50 Hz. Verified original-byte profiles cover USA, all five EU1 languages, both EU2 revisions and their five languages, and Japan. European routines differ only in verified data relocations. Japan retains its original packed P1/P2 BIOS producer; -the checked cached-P1 store is observed in both original loop callers. +the checked cached-P1 store is observed in both original loop callers. Its +legacy ReadPad button bytes are converted to the canonical host layout before +priming and back to the original layout before the guest store. P2 stays original. Requires the companion psxrecomp digital-pad snapshot API. Rebuild the runtime and overlays with the added mod_instruction_sites. Never rename old native DLLs @@ -19,7 +21,8 @@ walking, direction changes, jump/landing, and observed input edges. English EU1 also has an owner-confirmed responsiveness improvement. The other European languages and EU2 Rev 1 have original-byte contract coverage, not new gameplay acceptance. Japan's original menu lists the save, but loading into controllable -gameplay remains unconfirmed. These checks are not campaign certification. +gameplay remains unconfirmed after the bounded follow-up, including the byte- +layout correction. These checks are not campaign certification. Coverage is offline single-player digital P1. Analog/Hybrid, registered controller transforms or sources, configured multiple pads, multitap, netplay, replay and @@ -34,6 +37,7 @@ and checks original-byte contracts, preserved registers and fallback paths. Retail bytes are never distributed. Internal timings are not controller-to-TV measurements. Frame interpolation is independent and is not part of this change. -The validated production path is unchanged. Re-polling SDL inside the guest hook +The validated US/EU production paths are unchanged; Japan only received the +necessary legacy-button byte-layout correction. Re-polling SDL inside the guest hook or changing presentation timing needs separate correctness work; neither is an additional proven safe latency reduction in this change. diff --git a/eu/tests/test_low_input_latency.c b/eu/tests/test_low_input_latency.c index 10cf40c..d48a592 100644 --- a/eu/tests/test_low_input_latency.c +++ b/eu/tests/test_low_input_latency.c @@ -6,14 +6,14 @@ #include #include "../low_input_latency.c" static unsigned char image[0x200000]; -static uint32_t sample; +static uint32_t sample,last_legacy; static int allowed,reads; uint8_t psx_mod_read_byte(uint32_t pc) {return (pc&0x1fffffffu)>(8*i));} int psx_mod_read_digital_pad(uint32_t player,PSXModDigitalPadReader *r,uint32_t old,uint32_t *out) { - (void)r;(void)old;assert(player==0);++reads;*out=sample;return allowed; + (void)r;last_legacy=old;assert(player==0);++reads;*out=sample;return allowed; } int psx_mod_register_session_reset_plugin(const char *id,PSXModActivationCallback cb) {(void)id;(void)cb;return 1;} int psx_mod_register_activation_plugin(const char *id,PSXModActivationCallback cb) {(void)id;(void)cb;return 1;} @@ -43,7 +43,7 @@ int main(int argc,char **argv) { for(unsigned bit=0;bit<18;++bit) { sample=bit<16?1u<kind?(uint16_t)sample:(uint32_t)(int32_t)(int16_t)(uint16_t)sample; + expected.gpr[2]=p->kind?((sample>>8)&0xffu)|((sample&0xffu)<<8):(uint32_t)(int32_t)(int16_t)(uint16_t)sample; read_return(&cpu,p->hook);assert(!memcmp(&cpu,&expected,sizeof cpu)); } assert(reads==18); @@ -55,11 +55,17 @@ int main(int argc,char **argv) { if(q->kind!=p->kind || q->hash!=p->hash || q->start!=p->start)continue; CPUState call=before,expected=before; call.gpr[31]=expected.gpr[31]=q->caller; - sample=0xa55au;expected.gpr[2]=sample; + sample=0xa55au;expected.gpr[2]=0x5aa5u; int count=reads;read_return(&call,q->hook); assert(reads==count+1 && !memcmp(&call,&expected,sizeof call)); } } + /* Priming/fallback receives canonical buttons while preserving the + * original guest result when the host reader declines. */ + CPUState raw=before;raw.gpr[2]=0x1234u;CPUState original=raw; + allowed=0;read_return(&raw,p->hook); + assert(last_legacy==(p->kind?0x3412u:0x1234u)); + assert(!memcmp(&raw,&original,sizeof raw)); CPUState cpu=before;allowed=0;read_return(&cpu,p->hook);assert(!memcmp(&cpu,&before,sizeof cpu)); allowed=1;cpu=before;cpu.gpr[31]=0;CPUState bad=cpu;reads=0; read_return(&cpu,p->hook);assert(!reads && !memcmp(&cpu,&bad,sizeof cpu)); diff --git a/src/mods/tomba_low_input_latency_plugin.c b/src/mods/tomba_low_input_latency_plugin.c index 3bb4550..f42b2fa 100644 --- a/src/mods/tomba_low_input_latency_plugin.c +++ b/src/mods/tomba_low_input_latency_plugin.c @@ -47,9 +47,14 @@ static void read_return(struct CPUState *cpu,uint32_t pc) { if(!current || current->start!=match->start || current->hash!=match->hash)reset(); current=match; } - uint32_t held; - if(psx_mod_read_digital_pad(0,&reader,cpu->gpr[2],&held)) - cpu->gpr[2]=match->kind?(uint16_t)held:(uint32_t)(int32_t)(int16_t)(uint16_t)held; + /* Japan's legacy BIOS ReadPad layout swaps the two button bytes. + * Keep the shared host reader canonical in both directions; only convert + * the checked guest boundary. Original P2 and SIO data stay untouched. */ + uint32_t legacy=cpu->gpr[2],held; + if(match->kind)legacy=((legacy>>8)&0xffu)|((legacy&0xffu)<<8); + if(psx_mod_read_digital_pad(0,&reader,legacy,&held)) + cpu->gpr[2]=match->kind?((held>>8)&0xffu)|((held&0xffu)<<8): + (uint32_t)(int32_t)(int16_t)(uint16_t)held; } PSX_MOD_CONSTRUCTOR(tombi_register_low_input_latency) { (void)psx_mod_register_session_reset_plugin(PLUGIN,reset);