D1: relay-assigned seats -- players never need a player number
A pod launched with no BT_SELF asks the relay for a seat before joining: new control frames SEAT_REQUEST (-6) -> relay reserves the lowest roster seat not claimed or reserved (60s reservation so simultaneous joiners can't race onto one seat) -> SEAT_ASSIGN (-7, int32 hostID + NUL tag) becomes relaySelf; everything downstream (egg self-match, HELLO) runs exactly as if BT_SELF had been set. Roster full -> SEAT_FULL (-8) -> clean Fail(). A real HELLO pops the reservation; a pod drop frees the seat. Explicit BT_SELF still claims a specific seat (the operator's local launches use it). Client: L4NetworkManager::RelayRequestSeat (throwaway TCP dial to the relay game port, 10s reply window). Relay: SEAT_REQUEST branch in _handle_game_frame. Operator exporter collapsed from per-seat join_as_playerN.bat to ONE universal join.bat (+ join_lan.bat with BT_RELAY=auto) -- every player gets the same file, first come first served, the arcade walk-up-to-a-pod model. players/ regenerated. Verified: 8/8 seat stub tests (distinct seats in roster order, FULL on exhaustion, claimed+reserved stays FULL, HELLO claim accepted, duplicate HELLO refused); 2-node localhost e2e with NO BT_SELF on either pod -> both seated, full ladder, RunMission pair, UDP flowing post-launch; regression smoke: explicit-BT_SELF relay session clean with zero seat requests, classic mesh (no BT_RELAY) un-regressed. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
efd440c64b
commit
f04e8019c2
@@ -84,7 +84,10 @@ enum
|
||||
RELAY_ROUTE_HELLO = -2, // first frame: magic + our hostID
|
||||
RELAY_ROUTE_PEER_UP = -3, // relay->pod: hostID registered
|
||||
RELAY_ROUTE_PEER_DOWN = -4, // relay->pod: hostID gone
|
||||
RELAY_ROUTE_UDP_ACK = -5 // relay->pod: UDP HELLO acknowledged
|
||||
RELAY_ROUTE_UDP_ACK = -5, // relay->pod: UDP HELLO acknowledged
|
||||
RELAY_ROUTE_SEAT_REQUEST= -6, // pod->relay: assign me a free seat
|
||||
RELAY_ROUTE_SEAT_ASSIGN = -7, // relay->pod: int32 hostID + NUL tag
|
||||
RELAY_ROUTE_SEAT_FULL = -8 // relay->pod: roster full
|
||||
};
|
||||
#define RELAY_HELLO_MAGIC 0x31525442 // 'BTR1' little-endian
|
||||
// LAN auto-discovery (BT_RELAY=auto): probe broadcast on this UDP port; the
|
||||
@@ -388,12 +391,25 @@ L4NetworkManager::L4NetworkManager():
|
||||
relayGameAddress.sin_port =
|
||||
htons((unsigned short)(ntohs(relayConsoleAddress.sin_port) + 1));
|
||||
|
||||
//
|
||||
// Identity: an explicit BT_SELF claims a specific seat; absent
|
||||
// (or "auto"), the RELAY assigns the next free roster seat --
|
||||
// players never need to know a player number.
|
||||
//
|
||||
const char *self_env = getenv("BT_SELF");
|
||||
if (self_env == NULL || self_env[0] == '\0')
|
||||
if (self_env == NULL || self_env[0] == '\0'
|
||||
|| _stricmp(self_env, "auto") == 0)
|
||||
{
|
||||
Fail("BT_RELAY requires BT_SELF=<your [pilots] entry>\n");
|
||||
if (!RelayRequestSeat())
|
||||
{
|
||||
Fail("relay seat request failed (roster full or relay "
|
||||
"unreachable)\n");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Str_Copy(relaySelf, self_env, sizeof(relaySelf));
|
||||
}
|
||||
Str_Copy(relaySelf, self_env, sizeof(relaySelf));
|
||||
relayMode = True;
|
||||
DEBUG_STREAM << "[relay] mode ON: relay "
|
||||
<< inet_ntoa(relayConsoleAddress.sin_addr) << ":"
|
||||
@@ -1335,6 +1351,84 @@ void L4NetworkManager::HostDisconnectedMessageHandler(HostDisconnectedMessage* H
|
||||
// D1 RELAY MODE support units. Everything below is dead unless BT_RELAY set.
|
||||
//#############################################################################
|
||||
|
||||
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
// RelayRequestSeat -- SERVER-ASSIGNED SEATS: no BT_SELF means "give me any
|
||||
// free seat". A short throwaway connection to the relay game port sends
|
||||
// SEAT_REQUEST; the relay reserves the lowest unclaimed roster seat and
|
||||
// answers SEAT_ASSIGN {int32 hostID, NUL-terminated tag}. The tag becomes
|
||||
// relaySelf and everything downstream (egg self-match, HELLO) proceeds as if
|
||||
// BT_SELF had been set. SEAT_FULL / timeout => False.
|
||||
//
|
||||
Logical L4NetworkManager::RelayRequestSeat()
|
||||
{
|
||||
SOCKET seat_socket = RelayDialTcp(relayGameAddress, 60);
|
||||
if (seat_socket == INVALID_SOCKET)
|
||||
{
|
||||
return False;
|
||||
}
|
||||
Logical got_seat = False;
|
||||
RelayTcpEnvelope request;
|
||||
request.route = RELAY_ROUTE_SEAT_REQUEST;
|
||||
request.length = 0;
|
||||
if (RelaySendAll(seat_socket, (const char *)&request, sizeof(request)))
|
||||
{
|
||||
char reply[sizeof(RelayTcpEnvelope) + 80];
|
||||
int have = 0;
|
||||
unsigned long deadline = GetTickCount() + 10000UL;
|
||||
while (GetTickCount() < deadline && !got_seat)
|
||||
{
|
||||
fd_set read_set;
|
||||
FD_ZERO(&read_set);
|
||||
FD_SET(seat_socket, &read_set);
|
||||
timeval tv;
|
||||
tv.tv_sec = 0;
|
||||
tv.tv_usec = 200000;
|
||||
if (select(0, &read_set, NULL, NULL, &tv) != 1)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
int received = recv(seat_socket, reply + have,
|
||||
(int)sizeof(reply) - have, 0);
|
||||
if (received <= 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
have += received;
|
||||
if (have < (int)sizeof(RelayTcpEnvelope))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
RelayTcpEnvelope *envelope = (RelayTcpEnvelope *)reply;
|
||||
if (envelope->route == RELAY_ROUTE_SEAT_FULL)
|
||||
{
|
||||
DEBUG_STREAM << "[relay] seat request: ROSTER FULL\n"
|
||||
<< std::flush;
|
||||
break;
|
||||
}
|
||||
if (envelope->route != RELAY_ROUTE_SEAT_ASSIGN)
|
||||
{
|
||||
break;
|
||||
}
|
||||
if (have < (int)(sizeof(RelayTcpEnvelope) + envelope->length))
|
||||
{
|
||||
continue; // partial payload; keep reading
|
||||
}
|
||||
const char *tag = reply + sizeof(RelayTcpEnvelope) + 4;
|
||||
int tag_max = (int)envelope->length - 4;
|
||||
if (tag_max > 0 && tag_max < (int)sizeof(relaySelf))
|
||||
{
|
||||
memcpy(relaySelf, tag, tag_max);
|
||||
relaySelf[tag_max] = '\0';
|
||||
DEBUG_STREAM << "[relay] seat ASSIGNED: '" << relaySelf
|
||||
<< "'\n" << std::flush;
|
||||
got_seat = True;
|
||||
}
|
||||
}
|
||||
}
|
||||
closesocket(seat_socket);
|
||||
return got_seat;
|
||||
}
|
||||
|
||||
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
// RelayDialTcp -- outbound TCP dial with a BOUNDED retry window (unlike
|
||||
// OpenConnection's unbounded 10061 busy-loop), then nonblocking + NODELAY.
|
||||
|
||||
@@ -338,6 +338,8 @@ private:
|
||||
//
|
||||
Logical
|
||||
RelayDiscover(SOCKADDR_IN *console_endpoint);
|
||||
Logical
|
||||
RelayRequestSeat();
|
||||
SOCKET
|
||||
RelayDialTcp(const SOCKADDR_IN &endpoint, int timeout_seconds);
|
||||
Logical
|
||||
|
||||
Reference in New Issue
Block a user