Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions ENHANCEMENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -209,8 +209,8 @@ Findings (tracked for the next work cycle):
has no sprite-tag ws hooks (ws work parked at 4:3).

**Policy gates confirmed already in-tree (no code change needed for the merge):**
- Vulkan is hidden by default: launcher offers only Software<->OpenGL
(launcher.cpp:347/776); PSX_ENABLE_VULKAN defaults OFF (runtime.cmake:484);
- Vulkan is hidden by default: the shared launcher offers only
Software<->OpenGL; PSX_ENABLE_VULKAN defaults OFF (runtime.cmake);
runtime downgrades renderer=vulkan -> opengl when not compiled (main.cpp:2463).
Vulkan stays a dev/CLI-only backend (--renderer vulkan on a VK-enabled build).
- Widescreen carries an EXPERIMENTAL tag in the launcher.
Expand Down
39 changes: 0 additions & 39 deletions ISSUES.md
Original file line number Diff line number Diff line change
Expand Up @@ -513,45 +513,6 @@ sites aren't, it's an emit gap (fix in code_generator.cpp). If
neighbors are absent too, it's a discovery gap (fix in function
discovery seeds). Either way, then close Issue #3 alongside.

## Issue #6 — Launcher art has rough cutout edges (memory cards + controllers)

**Status:** open, cosmetic — deferred
**Date opened:** 2026-06-12
**Phase:** Launcher initiative (UI polish)

### Symptom

This issue tracked cosmetic problems in the removed in-tree launcher asset
pipeline. It is retained only as historical context.

### Cause

`FloodTransparent` uses a binary alpha decision (`max(R,G,B) < thresh`
→ alpha 0, else keep). There is no feathering of the boundary band, so
the object silhouette inherits the threshold's hard step. The memory
card and (grey) controller bodies are closer in luminance to the dark
mockup background than the disc is, so the same threshold leaves more
fringe on them.

### Fix options (later)

- Soft alpha ramp across a luminance band (`t_lo..t_hi`) instead of a
hard cutoff, applied to the flooded boundary pixels.
- Or supersample: crop at 2–4× from the mockup, knock out, then
downscale with high-quality bicubic so the edge anti-aliases.
- Or hand-mask the four assets once in an image editor (cleanest, but
manual).
- Best long-term: replace the mockup-derived crops with proper source
renders (transparent PNGs) — the `decorator: image(...)` pipeline is
already in place, so it's a drop-in.

### Notes

Tooling: this historical note referred to the removed in-tree launcher asset
pipeline. The active launcher path is downstream `recomp-ui`.

---

## Issue #7 — sljit live execution is unvalidated (pure-live save-load wedge)

**Status:** open, root-caused — fix in progress (branch `feat/sljit-backend`)
Expand Down
2 changes: 1 addition & 1 deletion docs/BUILDING.md
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ On Windows with MSVC or plain MinGW makefiles, swap `-G Ninja` for your generato
|---|---|---|
| `PSX_DEBUG_TOOLS` | ON for Debug/RelWithDebInfo, OFF for Release | TCP debug server + heartbeat + per-block recording |
| `PSX_STATIC_RUNTIME` | ON for MinGW Release | Self-contained exe (statically links SDL2 + libgcc/libstdc++) |
| `PSX_LAUNCHER` | ON | Allow downstream game projects to wire recomp-ui launcher support |
| `PSX_RECOMP_UI` | ON | Wire a downstream game's pinned recomp-ui launcher; set OFF for headless/generated builds |
| `PSX_ENABLE_VULKAN` | OFF | Build the experimental Vulkan renderer |
| `PSX_BUILD_COSIM` | OFF | Build the first-divergence co-sim oracle target |

Expand Down
2 changes: 1 addition & 1 deletion docs/RECURSION_BUG.md
Original file line number Diff line number Diff line change
Expand Up @@ -203,7 +203,7 @@ cmake --build ../psxrecomp/recompiler/build --target psxrecomp-game psxrecomp-bi
(cd ../psxrecomp && ./recompiler/build/psxrecomp-bios.exe --config bios/SCPH1001.toml)
../psxrecomp/recompiler/build/psxrecomp-game.exe --config tomba_soak_e.toml
# build:
cmake -S . -B <build-dir> -G Ninja -DCMAKE_BUILD_TYPE=Release -DPSX_DEBUG_TOOLS=ON -DPSX_LAUNCHER=OFF \
cmake -S . -B <build-dir> -G Ninja -DCMAKE_BUILD_TYPE=Release -DPSX_DEBUG_TOOLS=ON -DPSX_RECOMP_UI=OFF \
-DCMAKE_C_COMPILER=C:/msys64/mingw64/bin/gcc.exe -DCMAKE_CXX_COMPILER=C:/msys64/mingw64/bin/g++.exe
cmake --build <build-dir> --target psx-runtime -j8
# run (per-instance card; taskkill first):
Expand Down
2 changes: 1 addition & 1 deletion docs/beetle-linux.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ make platform=unix STATIC_LINKING=1 HAVE_LIGHTREC=0 -j"$(nproc)"
cp mednafen_psx_libretro.so libmednafen_psx.a

cd ../runtime
cmake -B build -G Ninja -DCMAKE_BUILD_TYPE=Release -DPSX_LAUNCHER=OFF -DPSX_DEBUG_TOOLS=ON
cmake -B build -G Ninja -DCMAKE_BUILD_TYPE=Release -DPSX_RECOMP_UI=OFF -DPSX_DEBUG_TOOLS=ON
ninja -C build psx-beetle

# Headless run (picks the next free port from 4380 if taken; watch stderr)
Expand Down
8 changes: 4 additions & 4 deletions docs/internal/FAITHFUL_TIMING_PLAN.md
Original file line number Diff line number Diff line change
Expand Up @@ -304,9 +304,9 @@ on a fixed region -> next.
re-enumerates gamecontrollers, keeps selection by GUID. Offline + netplay.

- **2026-07-20 (launcher: lobby lock emoji via symbol fallback font):**
Password lobbies showed □ for 🔒 — LatoLatin has no emoji. Load bundled
`NotoSansSymbols2-Regular.ttf` (system Segoe UI Symbol on Windows) with
RmlUi `fallback_face=true` so missing glyphs resolve.
Password lobbies showed □ for 🔒 because the primary face has no emoji.
Load a symbol fallback face through the shared Dear ImGui font atlas so
missing glyphs resolve.

- **2026-07-20 (launcher lobbies: button order + dblclick join):**
Lobbies actions: Return to Launcher → Change Player Name → Host Game →
Expand Down Expand Up @@ -559,7 +559,7 @@ on a fixed region -> next.
- **2026-07-19 (netplay lobby server + launcher menus):**
Lobby WS+JSON owned by proprietary `recomp-net-server` (was C
`servers/lobby/` under open recomp-net);
`psx_lobby_client` + RmlUi home → Offline / Netplay → lobbies table
`psx_lobby_client` + shared launcher home → Offline / Netplay → lobbies table
(host/join/password). Launch hands `PsxNetplayConfig` to
`psx_netplay_start` (LAN endpoints from lobby). ICE relay stubbed.

Expand Down
6 changes: 6 additions & 0 deletions runtime/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,12 @@ psxrecomp_add_runtime_target(psx-runtime
)

if(BUILD_TESTING)
add_executable(launcher_device_roundtrip_test
tests/test_launcher_device_roundtrip.cpp)
target_include_directories(launcher_device_roundtrip_test PRIVATE include)
add_test(NAME launcher_device_roundtrip_test
COMMAND launcher_device_roundtrip_test)

add_executable(psx_cyc_batch_test tests/test_psx_cyc_batch.c)
target_include_directories(psx_cyc_batch_test PRIVATE include)
target_compile_definitions(psx_cyc_batch_test PRIVATE
Expand Down
2 changes: 1 addition & 1 deletion runtime/include/disc_identity.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
// serial prefix, and optionally compares against an expected serial / CRC32.
//
// Used both by the runtime's launch-time disc check (runtime/src/main.cpp)
// and by the integrated launcher's "Disc verified" badge (launcher.cpp), so
// and by the shared launcher's "Disc verified" badge, so
// the two never drift apart.

#pragma once
Expand Down
47 changes: 47 additions & 0 deletions runtime/include/launcher_device.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
// launcher_device.h - controller-source conversion at the recomp-ui C ABI.

#pragma once

#include <algorithm>
#include <cctype>
#include <string>

namespace PSXRecompV4 {

inline std::string trim_launcher_device(const std::string& device) {
const auto first = std::find_if_not(device.begin(), device.end(),
[](unsigned char c) { return std::isspace(c) != 0; });
const auto last = std::find_if_not(device.rbegin(), device.rend(),
[](unsigned char c) { return std::isspace(c) != 0; }).base();
return first < last ? std::string(first, last) : std::string();
}

inline std::string normalize_launcher_device(const std::string& device) {
std::string normalized = trim_launcher_device(device);
std::transform(normalized.begin(), normalized.end(), normalized.begin(),
[](unsigned char c) { return static_cast<char>(std::tolower(c)); });
return normalized;
}

inline int launcher_source_from_device(const std::string& device) {
const std::string normalized = normalize_launcher_device(device);
if (normalized.empty() || normalized == "none") return 0;
if (normalized == "keyboard") return 1;
return 2;
}

inline std::string launcher_device_from_source(
int source, const std::string& previous_device) {
if (source <= 0) return "none";
if (source == 1) return "keyboard";

// recomp-ui's C ABI currently returns a source category, not the selected
// controller GUID. Preserve an existing gamepad/GUID assignment; when the
// user switched from None/Keyboard, persist the runtime's first-pad alias.
if (launcher_source_from_device(previous_device) == 2) {
return trim_launcher_device(previous_device);
}
return "gamepad";
}

} // namespace PSXRecompV4
62 changes: 46 additions & 16 deletions runtime/src/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ extern "C" void psx_event_step_conservative_env_init(void);
#include "crc32.h"
#include "disc_identity.h"
#include "iso_reader.h" /* text-image guard: extract the boot EXE from the disc */
#include "launcher_device.h" /* recomp-ui controller-source round-trip */
#include "psx_keybinds.h" /* configurable keyboard->DualShock keybinds (keybinds.ini) */
#if defined(RECOMP_LAUNCHER)
#include "recomp_launcher.h" /* shared recomp-ui Dear ImGui launcher */
Expand Down Expand Up @@ -3865,6 +3866,7 @@ namespace {
bool g_lnch_hosting_lan = false;
bool g_lnch_joined_lan = false;
std::string g_lnch_lan_endpoint;
std::string g_lnch_lan_guest_bind;

struct AeLanLobbyState {
std::string name;
Expand Down Expand Up @@ -3926,6 +3928,7 @@ namespace {
g_lnch_hosting_lan = true;
g_lnch_joined_lan = false;
g_lnch_lan_endpoint = state.endpoint;
g_lnch_lan_guest_bind.clear();
}

int ae_np_read_lan_lobby(RecompLauncherCNetplayLobby* out) {
Expand Down Expand Up @@ -4203,6 +4206,7 @@ namespace {
}
g_lnch_joined_lan = false;
g_lnch_lan_endpoint.clear();
g_lnch_lan_guest_bind.clear();
if (host_endpoint)
std::snprintf(host_endpoint, 96, "%s", endpoint);
return psx_lobby_create(lobby_name && lobby_name[0] ? lobby_name : "Netplay Lobby",
Expand All @@ -4214,6 +4218,17 @@ namespace {
* (prefer 7778); never advertise :0 to the lobby. */
int ae_np_join(void*, const char* lobby_id, const char* password,
char* guest_bind) {
char bind_buf[64];
const char* bind = guest_bind;
const char* colon = (bind && bind[0]) ? std::strrchr(bind, ':') : nullptr;
const unsigned port = (colon && colon[1])
? static_cast<unsigned>(std::strtoul(colon + 1, nullptr, 10)) : 0u;
if (!bind || !bind[0] || port == 0u) {
std::snprintf(bind_buf, sizeof(bind_buf), "0.0.0.0:7778");
bind = bind_buf;
if (guest_bind)
std::snprintf(guest_bind, 64, "%s", bind_buf);
}
if (lobby_id && strncmp(lobby_id, "lan:", 4) == 0) {
AeLanLobbyState state;
if (!ae_np_read_lan_state(&state) || !state.joiner_name.empty()) return -1;
Expand All @@ -4225,19 +4240,9 @@ namespace {
g_lnch_hosting_lan = false;
g_lnch_joined_lan = true;
g_lnch_lan_endpoint = state.endpoint;
g_lnch_lan_guest_bind = bind;
return 0;
}
char bind_buf[64];
const char* bind = guest_bind;
const char* colon = (bind && bind[0]) ? std::strrchr(bind, ':') : nullptr;
const unsigned port = (colon && colon[1])
? static_cast<unsigned>(std::strtoul(colon + 1, nullptr, 10)) : 0u;
if (!bind || !bind[0] || port == 0u) {
std::snprintf(bind_buf, sizeof(bind_buf), "0.0.0.0:7778");
bind = bind_buf;
if (guest_bind)
std::snprintf(guest_bind, 64, "%s", bind_buf);
}
return psx_lobby_join(lobby_id, password ? password : "", bind);
}

Expand All @@ -4256,6 +4261,7 @@ namespace {
}
g_lnch_joined_lan = false;
g_lnch_lan_endpoint.clear();
g_lnch_lan_guest_bind.clear();
g_lnch_pending_direct_launch = {};
return psx_lobby_leave();
}
Expand Down Expand Up @@ -4340,7 +4346,9 @@ namespace {
"0.0.0.0:%s", port);
} else {
std::snprintf(g_lnch_pending_direct_launch.bind_hostport,
sizeof(g_lnch_pending_direct_launch.bind_hostport), "0.0.0.0:0");
sizeof(g_lnch_pending_direct_launch.bind_hostport), "%s",
g_lnch_lan_guest_bind.empty()
? "0.0.0.0:0" : g_lnch_lan_guest_bind.c_str());
std::snprintf(g_lnch_pending_direct_launch.peer_hostport,
sizeof(g_lnch_pending_direct_launch.peer_hostport), "%s",
state.endpoint.c_str());
Expand Down Expand Up @@ -5296,8 +5304,20 @@ int main(int argc, char** argv) {
ls.enable_audio = 1;
ls.audio_freq = 44100;
ls.volume = 100;
ls.player_src[0] = (p1_device == "keyboard") ? 1 : (p1_device == "none") ? 0 : 2;
ls.player_src[1] = (p2_device == "keyboard") ? 1 : (p2_device == "none") ? 0 : 2;
ls.player_src[0] = PSXRecompV4::launcher_source_from_device(p1_device);
ls.player_src[1] = PSXRecompV4::launcher_source_from_device(p2_device);
#if defined(RECOMP_LAUNCHER_HAS_PLAYER_GAMEPAD_GUID)
if (ls.player_src[0] == 2) {
std::snprintf(ls.player_gamepad_guid[0],
sizeof(ls.player_gamepad_guid[0]), "%s",
p1_device.c_str());
}
if (ls.player_src[1] == 2) {
std::snprintf(ls.player_gamepad_guid[1],
sizeof(ls.player_gamepad_guid[1]), "%s",
p2_device.c_str());
}
#endif
{
int rui_deadzone_pct = seed.deadzone * 100 / 32767;
ls.deadzone[0] = rui_deadzone_pct;
Expand Down Expand Up @@ -5484,8 +5504,18 @@ int main(int argc, char** argv) {
* is the legacy fallback field for consoles without the cap and is
* left unused here. */
seed.texture_filter = ls.texture_filter ? 1 : 0; seed.has_texture_filter = true;
p1_device = (ls.player_src[0] == 1) ? "keyboard" : (ls.player_src[0] == 0) ? "none" : p1_device;
p2_device = (ls.player_src[1] == 1) ? "keyboard" : (ls.player_src[1] == 0) ? "none" : p2_device;
std::string p1_launcher_device = p1_device;
std::string p2_launcher_device = p2_device;
#if defined(RECOMP_LAUNCHER_HAS_PLAYER_GAMEPAD_GUID)
if (ls.player_gamepad_guid[0][0])
p1_launcher_device = ls.player_gamepad_guid[0];
if (ls.player_gamepad_guid[1][0])
p2_launcher_device = ls.player_gamepad_guid[1];
#endif
p1_device = PSXRecompV4::launcher_device_from_source(
ls.player_src[0], p1_launcher_device);
p2_device = PSXRecompV4::launcher_device_from_source(
ls.player_src[1], p2_launcher_device);
seed.p1_device = p1_device; seed.has_p1_device = true;
seed.p2_device = p2_device; seed.has_p2_device = true;
seed.deadzone = ls.deadzone[0] * 32767 / 100; seed.has_deadzone = true;
Expand Down
24 changes: 24 additions & 0 deletions runtime/tests/test_launcher_device_roundtrip.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
#include "launcher_device.h"

#include <cassert>

int main() {
using PSXRecompV4::launcher_device_from_source;
using PSXRecompV4::launcher_source_from_device;

assert(launcher_source_from_device("") == 0);
assert(launcher_source_from_device(" none ") == 0);
assert(launcher_source_from_device("Keyboard") == 1);
assert(launcher_source_from_device("gamepad") == 2);
assert(launcher_source_from_device("030000005e0400008e02000000000000") == 2);

assert(launcher_device_from_source(0, "keyboard") == "none");
assert(launcher_device_from_source(1, "none") == "keyboard");
assert(launcher_device_from_source(2, "keyboard") == "gamepad");
assert(launcher_device_from_source(2, "none") == "gamepad");
assert(launcher_device_from_source(2, " auto ") == "auto");
assert(launcher_device_from_source(
2, " 030000005e0400008e02000000000000 ") ==
"030000005e0400008e02000000000000");
return 0;
}
Loading