The console speaks to the room it is standing in

Cyd's point: a real pod bay console is its own machine, and every station
- pod, Live Cam, mission review - hears it over the wire. That is why the
spooler expects LoadMission and RunMission to arrive as packets. Ours is
colocated, and the network stack is quite right not to push bytes through
a socket to reach a client in the same process.

But the console had gone further than that. It called
application->Dispatch, stepping past the client's receive entry
altogether, so nothing watching packets ever saw the console speak. The
first Live Cam recording is the evidence: 14,342 packets, every one of
them from the racer via the interest manager, and not one LoadMission,
RunMission or StopMission - because those came from inside the house.

The tee was not in the wrong layer for the wire; it was in the right place
and the console was walking around it.

So build the packet SendWire would have built and hand it to the client's
own front door. NetworkClient::ReceiveNetworkPacket is Dispatch plus
whatever is watching, and Application IS the client for
ApplicationClientID, so this is the same delivery arriving where deliveries
arrive - no socket, no loopback, no second copy of the protocol.

Correcting myself twice over. RunMission does NOT have to be the first
packet in the spool: playback reads spool->GetPointer(), the current
cursor, by which time the task has consumed what came before, so it checks
that the cursor has REACHED RunMission rather than that the file starts
with it. And the ordering is therefore not "the whole job" as I claimed -
the recorded timings already show a normal shape, fifteen entity creations
inside the first twenty milliseconds and updates running from 0.110s to
308.940s of a five minute race.

Still open: the egg feed is local too (FeedLocalEgg), so a spool carries
no egg. Whether that matters depends on how the playback application loads
its mission, which is the next thing to find out rather than guess at.

WATCH ON NEXT LAUNCH: this changes how the local station is told to start
and stop a race. It should be identical - ReceiveNetworkPacket dispatches
the same message to the same object - but it is the launch path, so a race
that does not start is this commit.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Cyd
2026-08-11 12:26:04 -05:00
co-authored by Claude Opus 5
parent 1596feb636
commit 46418ebb5c
+50 -2
View File
@@ -164,6 +164,46 @@ namespace
pod->connection, packet, (int) sizeof(NetworkPacketHeader) + size); pod->connection, packet, (int) sizeof(NetworkPacketHeader) + size);
} }
//---------------------------------------------------------------
// The same packet, delivered to the station on this machine.
//
// A real pod bay console sits on its own machine and every station
// hears it over the wire. Here it is colocated, and the network stack
// is quite right not to push bytes through a socket to reach a client
// in the same process - but the console had gone further than that and
// called application->Dispatch, stepping past the client's receive
// entry altogether. Anything watching packets therefore never saw the
// console speak: the first Live Cam recording held all 14,342 of the
// racer's packets and not one LoadMission, RunMission or StopMission,
// because those were the messages that came from inside the house.
//
// So build the packet SendWire would have built and hand it to the
// client's own front door. No socket, no wire, no copy of the protocol
// - just the delivery arriving where a delivery arrives.
//---------------------------------------------------------------
void DeliverLocal(int client_ID, const void *message, int size)
{
char packet[sizeof(NetworkPacketHeader) + 1400];
if (size > (int) sizeof(packet) - (int) sizeof(NetworkPacketHeader))
{
return;
}
memset(packet, 0, sizeof(NetworkPacketHeader));
NetworkPacketHeader *header = (NetworkPacketHeader *) packet;
header->clientID = (NetworkClient::ClientID) client_ID;
header->gameID = 0;
header->fromHost = 1; // the console's reserved host ID
memcpy(packet + sizeof(NetworkPacketHeader), message, size);
NetworkPacket *received = (NetworkPacket *) packet;
Check(application);
application->ReceiveNetworkPacket(received, &received->messageData);
}
void SendEggTo(RemotePod *pod) void SendEggTo(RemotePod *pod)
{ {
int chunk_count = (gEggWireSize + 999) / 1000; int chunk_count = (gEggWireSize + 999) / 1000;
@@ -348,7 +388,11 @@ namespace
DEBUG_STREAM << "LocalConsole: stopping local pod\n" << std::flush; DEBUG_STREAM << "LocalConsole: stopping local pod\n" << std::flush;
InterlockedExchange(&gMissionRunning, 0); InterlockedExchange(&gMissionRunning, 0);
Application::StopMissionMessage message(0); Application::StopMissionMessage message(0);
application->Dispatch(&message); DeliverLocal(
NetworkClient::ApplicationClientID,
&message,
(int) message.messageLength
);
gPhase = PhaseStopped; gPhase = PhaseStopped;
} }
@@ -455,7 +499,11 @@ namespace
&run, (int) run.messageLength); &run, (int) run.messageLength);
} }
Application::RunMissionMessage local_run; Application::RunMissionMessage local_run;
application->Dispatch(&local_run); DeliverLocal(
NetworkClient::ApplicationClientID,
&local_run,
(int) local_run.messageLength
);
gRunSent = True; gRunSent = True;
} }
} }