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
22 changes: 22 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,10 @@ if(RETRO_RUNTIME_TOOLS)
# runner/net_module.hpp), so it updates without a new runner. The checkout
# then supplies headers only.
option(RETRO_RUNTIME_NETPLAY_MODULE "retro-core-runner: netplay through a loadable module" OFF)
if(RETRO_RUNTIME_RECOMP_NET_DIR AND NOT EXISTS
"${RETRO_RUNTIME_RECOMP_NET_DIR}/include/recomp_net/startup_profiles.h")
message(FATAL_ERROR "Netplay requires recomp-net's startup_profiles.h API (module ABI minor 1); update RETRO_RUNTIME_RECOMP_NET_DIR")
endif()
if(RETRO_RUNTIME_NETPLAY_MODULE)
if(NOT RETRO_RUNTIME_RECOMP_NET_DIR OR NOT EXISTS "${RETRO_RUNTIME_RECOMP_NET_DIR}/include/recomp_net/module.h")
message(FATAL_ERROR "RETRO_RUNTIME_NETPLAY_MODULE needs RETRO_RUNTIME_RECOMP_NET_DIR with include/recomp_net/module.h")
Expand All @@ -144,6 +148,24 @@ if(RETRO_RUNTIME_TOOLS)
target_compile_definitions(retro-core-runner PRIVATE RETRO_RUNNER_NETPLAY=1)
message(STATUS "retro-core-runner: netplay from ${RETRO_RUNTIME_RECOMP_NET_DIR}")
endif()
set(RETRO_RUNTIME_TEST_NETPLAY_MODULE "" CACHE FILEPATH
"Optional built recomp-net module for the startup negotiation integration test")
if(RETRO_RUNTIME_RECOMP_NET_DIR AND
(NOT RETRO_RUNTIME_NETPLAY_MODULE OR RETRO_RUNTIME_TEST_NETPLAY_MODULE))
add_executable(retro-net-profiles-test tests/net_profiles_test.cpp
runner/net_session.cpp runner/net_module.cpp)
target_include_directories(retro-net-profiles-test PRIVATE ${RETRO_RUNTIME_RECOMP_NET_DIR}/include)
target_compile_definitions(retro-net-profiles-test PRIVATE RETRO_RUNNER_NETPLAY=1)
target_link_libraries(retro-net-profiles-test PRIVATE retro_core_support Threads::Threads)
if(RETRO_RUNTIME_NETPLAY_MODULE)
target_compile_definitions(retro-net-profiles-test PRIVATE RETRO_RUNNER_NETPLAY_MODULE=1)
target_link_libraries(retro-net-profiles-test PRIVATE ${CMAKE_DL_LIBS})
add_test(NAME runner_startup_profiles COMMAND retro-net-profiles-test ${RETRO_RUNTIME_TEST_NETPLAY_MODULE})
else()
target_link_libraries(retro-net-profiles-test PRIVATE recomp_net)
add_test(NAME runner_startup_profiles COMMAND retro-net-profiles-test)
endif()
endif()
# Threads: the savestate keeper hashes content off the core thread.
find_package(Threads REQUIRED)
target_link_libraries(retro-core-runner PRIVATE retro_core_support retro_corelink retro_state
Expand Down
1 change: 1 addition & 0 deletions docs/CORE_ABI.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ only the shape of the contract and why.
| Rev | Date | Change |
|---|---|---|
| 1 | 2026-09-23 | First draft. |
| 10 | 2026-10-14 | Optional pre-init `execution_profiles()` capability/qualification query; appended init `execution_profile` index, valid with `RCORE_INIT_PROFILE_AGREED`. The runner exchanges offers/ACKs before init/guest boot, folds the common profile and canonical capability transcript into existing netplay identity, and keeps selection fixed through replay/reset/rollback. Old runners retain the core's conservative startup default. |
| 9 | 2026-10-14 | Optional host `presentation_size(ctx, width, height)`: the available drawable physical pixel area before letterboxing, stable for a granted frame. Returns 0 and zeros when unavailable. Core checks host `struct_size` before reading the appended callback. Presentation policy only; never guest input or serialized simulation state. |
| 8 | 2026-10-08 | `rcore_frame::game_frame`: the pictures the GAME has finished, as distinct from the frames the machine ran (an N64 game drawing at 30 fps runs 60 VI fields a second). 0 = the core cannot tell them apart. For a host's readout only (link 2.2 carries it to the hub; `OVERLAY.md`), never pacing or netplay. n64lle fills it from `n64_vi_swap_count()`, the game's VI_ORIGIN changes. |
| 7 | 2026-10-01 | `accessory_poll` / `accessory_notify` on the host table and `RCORE_CAP_ACCESSORY_DATA`: bytes between the host and a plugged accessory whose input is not a pad, host->core drained at the frame boundary and stable for the frame, core->host for the host's UI. The first such accessory is `n64.vru`, the Voice Recognition Unit (n64lle PR #65 gave it a debug-port seam; Alex's ruling 2026-10-01: a seat's pak dropdown selects "VRU Microphone" and the hub owns the recognizer). |
Expand Down
14 changes: 14 additions & 0 deletions docs/CORE_RUNNER.md
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,20 @@ values and 16 inputs, every record with its field count.

## Netplay (headless, 2026-09-29)

**Startup execution profiles (2026-10-14, rcore draft 10).** A core with
`execution_profiles()` advertises actual CPU/backend capabilities and
qualified profiles before init. The runner completes recomp-net's immutable
startup offer/ACK barrier before init/load, supplies the agreed bit index
with `RCORE_INIT_PROFILE_AGREED`, and appends the common profile and canonical
capability transcript to existing identity. Every occupied seat must agree;
missing legacy support times out, never silently downgrades. No per-frame
capability probing, HLE/LLE handoff, live renegotiation or UI is added. Old
cores retain their startup behavior; old runners leave the new core's safe
default in force. A core/package/build check is never removed by this feature.
Requires recomp-net's startup_profiles.h API (module ABI minor 1 for module
builds). The generic fixture tests the real UDP barrier and clean one-peer
stop; native CPU qualification remains the engine's responsibility.

`runner/net_session.*` binds recomp-net's `rb_driver` once, for every core
(rev 4; the core's slots map onto `RNetRbHost` as CORE_ABI.md §Netplay
tables). Built when CMake is given `-DRETRO_RUNTIME_RECOMP_NET_DIR=<recomp-net
Expand Down
27 changes: 26 additions & 1 deletion include/rcore/rcore.h
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ extern "C" {

#define RCORE_ABI_MAJOR 0u /* 0 = draft; the first implemented contract is 1 */
#define RCORE_ABI_MINOR 0u
#define RCORE_DRAFT_REVISION 9u /* draft-only counter; see docs/CORE_ABI.md */
#define RCORE_DRAFT_REVISION 10u /* draft-only counter; see docs/CORE_ABI.md */

#if defined(_WIN32)
# define RCORE_EXPORT __declspec(dllexport)
Expand Down Expand Up @@ -432,12 +432,33 @@ typedef struct rcore_host_api {
#define RCORE_INIT_NETPLAY (1u << 1) /* requires CAP_DETERMINISTIC. Rollback sessions
also require CAP_ROLLBACK; otherwise the
session is delay-based lockstep. */
#define RCORE_INIT_PROFILE_AGREED (1u << 2) /* rev 10: startup profile barrier completed */

/* Rev 10. Query before init, no guest/worker creation. The versioned namespace
* defines validated semantics and ranking of profile bits; bit 0 is portable,
* highest common bit wins. CPU/backends are actual available capabilities,
* not OS-version guesses. A profile advertisement is NOT cross-build proof:
* the runner keeps its existing core/package/content identity checks. */
typedef struct rcore_execution_profiles {
uint32_t struct_size;
uint32_t architecture; /* diagnostic, 0..255; namespace defines values */
uint32_t cpu_features;
uint32_t backends;
uint32_t qualified_profiles;
uint32_t _pad0;
const char* qualification; /* nonempty, core-owned, immutable until deinit */
} rcore_execution_profiles;

typedef struct rcore_init_params {
uint32_t struct_size;
uint32_t flags; /* RCORE_INIT_* */
const char* system_dir; /* firmware / BIOS the host verified; read-only */
const char* cache_dir; /* core-private, writable, may be wiped */
/* Rev 10: valid only with INIT_PROFILE_AGREED. A bit INDEX from the core's
* advertised qualified_profiles. Validate and apply BEFORE guest boot;
* never reselect on reset, save load or rollback. No HLE/LLE live switch. */
uint32_t execution_profile;
uint32_t _pad0;
} rcore_init_params;

typedef struct rcore_load_params {
Expand Down Expand Up @@ -564,6 +585,10 @@ typedef struct rcore_core_api {
* row. Both are pure. NULL = the runner's generic packing (see rev 4). */
void (*net_row_from_pad)(const rcore_pad* pad, rcore_net_row* row);
void (*net_row_to_pad)(const rcore_net_row* row, rcore_pad* pad);
/* --- appended in draft revision 10: fixed execution startup profiles ---
* Optional. NULL = legacy startup policy, no profile negotiation. An old
* runner omits PROFILE_AGREED and the core keeps its safe default. */
const rcore_execution_profiles* (*execution_profiles)(void);
} rcore_core_api;

/* The single exported symbol. Returns NULL when the core cannot serve
Expand Down
4 changes: 4 additions & 0 deletions runner/net_module_api.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
extern "C" {
#include "recomp_net/rb_driver.h"
#include "recomp_net/session.h"
#include "recomp_net/startup_profiles.h"
}

#define RETRO_NET_MODULE_SYMBOLS(X) \
Expand All @@ -28,6 +29,9 @@ extern "C" {
X(rnet_session_is_running) \
X(rnet_session_peer_disconnected) \
X(rnet_session_pump) \
X(rnet_session_profiles_begin) \
X(rnet_session_profiles_status) \
X(rnet_session_profiles_peer) \
X(rnet_session_send_bye) \
X(rnet_session_start_lan) \
X(rnet_session_start_lan_hub) \
Expand Down
5 changes: 5 additions & 0 deletions runner/net_module_redirect.hpp
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
// Included by net_session.cpp ONLY, after the recomp-net headers: turns each
// call it makes into a call through the loaded module (net_module_api.hpp).
#pragma once

#include "net_module_api.hpp"

#define rnet_session_profiles_begin (::retro::runner::g_nm.rnet_session_profiles_begin)
#define rnet_session_profiles_status (::retro::runner::g_nm.rnet_session_profiles_status)
#define rnet_session_profiles_peer (::retro::runner::g_nm.rnet_session_profiles_peer)

#define rnet_rb_driver_confirmed_through (::retro::runner::g_nm.rnet_rb_driver_confirmed_through)
#define rnet_rb_driver_create (::retro::runner::g_nm.rnet_rb_driver_create)
#define rnet_rb_driver_destroy (::retro::runner::g_nm.rnet_rb_driver_destroy)
Expand Down
Loading
Loading