Merge origin/master: the D1 relay/operator line + input remap meet the glass layer
33 master commits in (relay TCP/UDP + PySide6 operator console, CONTROLS.MAP +XInput binding engine, camera seats, torso pitch aim, sign fixes, the 1995 manual, version stamping, 18-mech certification). Conflicts: .gitignore + CLAUDE.md router rows (combined). SEMANTIC RECONCILIATION (the one real overlap): masters btinput binding engine (ungated, CONTROLS.MAP) and the glass PadRIO (gated, bindings.txt) would both read the keyboard/pad in a glass+PAD session. btinput now joins the stand-down convention: BTInputPoll yields (and BTInputSuppressKey claims NOTHING, so authentic hotkeys flow) when an operational cockpit device owns the input path -- BTRIODevicePresent, BT_KEY_BRIDGE force-override honored, forced harness exempt. One input system per mode: btinput on pod/dev desktops, PadRIO on glass. The mechmppr/mech4 bridge merges composed clean (masters negate-once sign fix inside our device-gated bridge). The D1 relay keeps its own raw sockets by design (an alternative LAN wire; Steam and relay are separate modes). Verified post-merge: all 3 configs build; glass boots with [input] binding engine standing down + PadRIO owning input (30 ticks); pod forced-walk speedDemand=61.501 with btinput ACTIVE; 2-node loopback MP full 31/31 mission, 76/76 ticks. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -580,6 +580,16 @@ void
|
||||
camera_stream.AdvancePointer(model->instanceSize);
|
||||
}
|
||||
res->Unlock();
|
||||
// BT port diag (BT_CAM_LOG): the camera-seat bring-up needs to know the
|
||||
// network actually loaded (type-27 res, all 8 maps ship in BTL4.RES)
|
||||
if (getenv("BT_CAM_LOG"))
|
||||
{
|
||||
int n = 0;
|
||||
SChainIteratorOf<CameraInstance*> it(cameraList);
|
||||
while (it.ReadAndNext() != NULL) ++n;
|
||||
DEBUG_STREAM << "[cam] streamed camera network '" << map_name
|
||||
<< "': " << n << " camera(s) loaded\n" << std::flush;
|
||||
}
|
||||
}
|
||||
|
||||
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
@@ -314,11 +314,30 @@ void
|
||||
//
|
||||
if (!goalEntity)
|
||||
{
|
||||
// BT port diag (BT_CAM_LOG, 1 Hz): parked with no goal
|
||||
if (getenv("BT_CAM_LOG"))
|
||||
{
|
||||
static float s_ng = 0.0f; s_ng += time_slice;
|
||||
if (s_ng >= 1.0f) { s_ng = 0.0f;
|
||||
DEBUG_STREAM << "[cam] ship: NO goal entity (parked)\n"
|
||||
<< std::flush; }
|
||||
}
|
||||
return;
|
||||
}
|
||||
Check(goalEntity);
|
||||
Point3D target;
|
||||
target.Multiply(focusOffset, goalEntity->localToWorld);
|
||||
// BT port diag (BT_CAM_LOG, 1 Hz): live follow state
|
||||
if (getenv("BT_CAM_LOG"))
|
||||
{
|
||||
static float s_fg = 0.0f; s_fg += time_slice;
|
||||
if (s_fg >= 1.0f) { s_fg = 0.0f;
|
||||
DEBUG_STREAM << "[cam] ship: goal at (" << target.x << ","
|
||||
<< target.z << ") cam=" << (void*)currentCamera
|
||||
<< " sees=" << (currentCamera
|
||||
? (int)currentCamera->CanCameraSee(target) : -1)
|
||||
<< "\n" << std::flush; }
|
||||
}
|
||||
|
||||
//
|
||||
//------------------------------------------------------------------------
|
||||
|
||||
@@ -11,6 +11,15 @@
|
||||
#include "l4padrio.h"
|
||||
#endif
|
||||
|
||||
//
|
||||
// BT port (input remap): keys claimed by a CONTROLS.MAP binding are consumed
|
||||
// by the binding engine (game/reconstructed/btinput.cpp) and must NOT also
|
||||
// reach the key-command channel -- that was the historic double-dispatch
|
||||
// ('w' drove AND selected pilot 0; F5's key-up value 0x74 aliased to the
|
||||
// 't' hotkey). The symbol resolves from the game lib at the btl4 exe link.
|
||||
//
|
||||
extern "C" int BTInputSuppressKey(unsigned int key_value, int is_char);
|
||||
|
||||
// #define LOCAL_TEST
|
||||
|
||||
#if defined(LOCAL_TEST)
|
||||
@@ -1572,6 +1581,11 @@ void
|
||||
{
|
||||
// swallowed: navigation key, not a character command
|
||||
}
|
||||
else if (BTInputSuppressKey((unsigned int)keyValue,
|
||||
msg.message == WM_CHAR))
|
||||
{
|
||||
// swallowed: remapped by CONTROLS.MAP
|
||||
}
|
||||
else
|
||||
{
|
||||
keyboardGroup[KeyboardPC].ForceUpdate(&keyValue, mode_mask);
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -327,6 +327,50 @@ private:
|
||||
DroppedMessageHostSocket *dropped_message_host_socket
|
||||
);
|
||||
|
||||
//
|
||||
// D1 RELAY MODE (BT_RELAY=host:port + BT_SELF=<[pilots] entry>): outbound-
|
||||
// only topology -- the pod dials the relay (extended btconsole.py) for BOTH
|
||||
// the console protocol (egg/launch; legacy raw frames, direction flipped)
|
||||
// and the game traffic (envelope-framed, one TCP connection multiplexing
|
||||
// every peer; UDP for the unreliable channel in a later phase). BT_RELAY
|
||||
// unset => relayMode False => every relay branch is dead and the classic
|
||||
// mesh is untouched.
|
||||
//
|
||||
Logical
|
||||
RelayDiscover(SOCKADDR_IN *console_endpoint);
|
||||
Logical
|
||||
RelayRequestSeat();
|
||||
SOCKET
|
||||
RelayDialTcp(const SOCKADDR_IN &endpoint, int timeout_seconds);
|
||||
Logical
|
||||
RelaySendAll(SOCKET sock, const char *buffer, int length);
|
||||
NetworkPacket *
|
||||
BuildRelayPacket(Message *message, ClientID client, int *packet_size);
|
||||
Logical
|
||||
RelaySendFrame(int route, const NetworkPacket *packet, int packet_size);
|
||||
void
|
||||
ConnectRelayGame(HostID local_host_ID);
|
||||
void
|
||||
RelayGameDown(const char *why);
|
||||
Logical
|
||||
CheckRelay(NetworkPacket *network_packet);
|
||||
//
|
||||
// UDP unreliable channel (restores the 1995 NETNUB reliable/unreliable
|
||||
// split): the ~60Hz update records ride UDP so a lost/late datagram is
|
||||
// dropped (dead reckoning absorbs the gap) instead of head-of-line-
|
||||
// blocking the reliable stream. Reliable traffic + control stay on TCP.
|
||||
//
|
||||
void
|
||||
ConnectRelayUdp(HostID local_host_ID);
|
||||
Logical
|
||||
RelayUdpSendFrame(int route, const NetworkPacket *packet, int packet_size);
|
||||
void
|
||||
RelayUdpKeepalive(HostID local_host_ID);
|
||||
Logical
|
||||
RelayShouldUseUdp(const Message *message) const;
|
||||
Logical
|
||||
CheckRelayUdp(NetworkPacket *network_packet);
|
||||
|
||||
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
// Private Data
|
||||
//
|
||||
@@ -353,6 +397,25 @@ private:
|
||||
addrinfo* thisHost;
|
||||
SOCKET gameListenerSocket;
|
||||
SOCKET consoleListenerSocket;
|
||||
|
||||
// D1 relay mode state (see the private-methods block above). All inert
|
||||
// unless BT_RELAY is set.
|
||||
Logical relayMode;
|
||||
char relaySelf[64]; // BT_SELF -- our [pilots] entry string
|
||||
SOCKADDR_IN relayConsoleAddress; // relay console endpoint (BT_RELAY port)
|
||||
SOCKADDR_IN relayGameAddress; // relay game endpoint (port + 1, tcp+udp)
|
||||
SOCKET relayGameSocket; // the ONE multiplexed game connection
|
||||
SOCKET relayUdpSocket; // unreliable channel (later phase)
|
||||
char relayPad[16384]; // relay-connection reassembly buffer
|
||||
int relayPadTail;
|
||||
Logical relayConsoleDialedOnce; // first dial gets the long timeout
|
||||
Logical udpUp; // UDP HELLO acked by the relay
|
||||
HostID relayLocalHostID; // our hostID (for UDP HELLO/keepalive/tx)
|
||||
unsigned long lastUdpKeepaliveTick;
|
||||
unsigned long lastUdpHelloTick; // HELLO retry until acked
|
||||
unsigned long udpTxSeq;
|
||||
unsigned long udpRxSeq[64]; // per-fromHost last-seen sequence
|
||||
NetworkMode currentNetworkMode; // Reliable/Unreliable (Mode() stores)
|
||||
};
|
||||
|
||||
//~~~~~~~~~~~~~~~~~~~~~~ L4NetworkManager inlines ~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
+31
-28
@@ -7431,6 +7431,9 @@ void
|
||||
Check_Pointer(display_rank_window);
|
||||
|
||||
mCamShipHUD = new CameraShipHUDRenderable(entity, CameraShipHUDRenderable::Dynamic, player_index, display_rank_window);
|
||||
if (getenv("BT_CAM_LOG"))
|
||||
DEBUG_STREAM << "[cam] CameraShipHUDRenderable created\n"
|
||||
<< std::flush;
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -8518,34 +8521,6 @@ void DPLRenderer::ExecuteImplementation(RendererComplexity, RendererOrigin::Inte
|
||||
BTDrawTranslocationSpheres(mDevice, &viewTransform, (float)dT, mTargetRenderTime);
|
||||
}
|
||||
|
||||
// DIAG (off by default): BT_SHOT=<path> dumps this instance's backbuffer (the rendered
|
||||
// WORLD, before the 2D HUD overlay) to a PNG once ~180 frames in, for non-disruptive
|
||||
// per-instance frame capture without foregrounding the window. Each instance sets its
|
||||
// own path. Built for the -net render glitch (task #53) but general.
|
||||
{
|
||||
const char *shotPath = getenv("BT_SHOT");
|
||||
if (shotPath)
|
||||
{
|
||||
static int s_btShotN = 0;
|
||||
++s_btShotN;
|
||||
// dump (overwriting) every 90 frames once past 90, so the file always holds a
|
||||
// recent frame regardless of load timing / debug fps.
|
||||
if (s_btShotN >= 90 && (s_btShotN % 90) == 0)
|
||||
{
|
||||
IDirect3DSurface9 *bb = 0;
|
||||
if (SUCCEEDED(mDevice->GetBackBuffer(0, 0, D3DBACKBUFFER_TYPE_MONO, &bb)) && bb)
|
||||
{
|
||||
HRESULT hr = D3DXSaveSurfaceToFileA(shotPath, D3DXIFF_PNG, bb, 0, 0);
|
||||
bb->Release();
|
||||
DEBUG_STREAM << "[btshot] frame " << s_btShotN << " -> " << shotPath
|
||||
<< " hr=0x" << std::hex << (unsigned)hr << std::dec << "\n" << std::flush;
|
||||
}
|
||||
else
|
||||
DEBUG_STREAM << "[btshot] GetBackBuffer FAILED frame " << s_btShotN << "\n" << std::flush;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// The BT targeting reticle / weapon pips (2D screen space, cockpit view
|
||||
// only -- the dpl2d layer; see game/reconstructed/dpl2d.cpp).
|
||||
{
|
||||
@@ -8588,6 +8563,34 @@ void DPLRenderer::ExecuteImplementation(RendererComplexity, RendererOrigin::Inte
|
||||
if (mCamShipHUD)
|
||||
mCamShipHUD->Render(0, &viewTransform);
|
||||
|
||||
// DIAG (off by default): BT_SHOT=<path> dumps this instance's backbuffer to a
|
||||
// PNG every 90 frames -- non-disruptive per-instance frame capture without
|
||||
// foregrounding the window. Built for the -net render glitch (task #53);
|
||||
// moved AFTER the 2D pass (2026-07-18, camera-seat bring-up) so captures
|
||||
// include the HUD overlays (reticle, camera-ship ranking window) the screen
|
||||
// actually shows.
|
||||
{
|
||||
const char *shotPath = getenv("BT_SHOT");
|
||||
if (shotPath)
|
||||
{
|
||||
static int s_btShotN = 0;
|
||||
++s_btShotN;
|
||||
if (s_btShotN >= 90 && (s_btShotN % 90) == 0)
|
||||
{
|
||||
IDirect3DSurface9 *bb = 0;
|
||||
if (SUCCEEDED(mDevice->GetBackBuffer(0, 0, D3DBACKBUFFER_TYPE_MONO, &bb)) && bb)
|
||||
{
|
||||
HRESULT hr = D3DXSaveSurfaceToFileA(shotPath, D3DXIFF_PNG, bb, 0, 0);
|
||||
bb->Release();
|
||||
DEBUG_STREAM << "[btshot] frame " << s_btShotN << " -> " << shotPath
|
||||
<< " hr=0x" << std::hex << (unsigned)hr << std::dec << "\n" << std::flush;
|
||||
}
|
||||
else
|
||||
DEBUG_STREAM << "[btshot] GetBackBuffer FAILED frame " << s_btShotN << "\n" << std::flush;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// DEV-COMPOSITE: in DOCKED mode (BT_DEV_GAUGES_DOCK) blit the 6-surface gauge panel
|
||||
// into this window as the LAST draw before EndScene (no-op otherwise / off pod).
|
||||
extern void BTDrawGaugeInset(LPDIRECT3DDEVICE9 device);
|
||||
|
||||
@@ -379,6 +379,7 @@ public:
|
||||
void SetCurrentFogLimits(float fogNear, float fogFar);
|
||||
|
||||
LPDIRECT3DTEXTURE9 GetNameTexture(int playerIndex) { return mNameTextures[playerIndex]; }
|
||||
LPDIRECT3DTEXTURE9 GetOrdinalTexture(int rank) { return mOrdinalTextures[rank]; }
|
||||
|
||||
inline LPDIRECT3DDEVICE9 GetDevice() { return mDevice; }
|
||||
inline LPD3DXMATRIXSTACK GetMatrixStack() { return m_MatrixStack; }
|
||||
|
||||
@@ -3045,17 +3045,124 @@ void CameraShipHUDRenderable::Execute()
|
||||
VideoRenderable::Execute();
|
||||
}
|
||||
|
||||
//
|
||||
// BT port (camera-seat bring-up, 2026-07-18): draw one screen-space textured
|
||||
// quad (u/v 0..1) -- shared by the followed-callsign banner and the ranking
|
||||
// window rows. The name/ordinal bitmaps load black-on-transparent
|
||||
// (LoadBitSliceTexture, A4R4G4B4), so alpha blending keys them over the scene.
|
||||
//
|
||||
static void CameraHUDDrawQuad(
|
||||
LPDIRECT3DDEVICE9 device,
|
||||
LPDIRECT3DTEXTURE9 texture,
|
||||
float x, float y, float w, float h,
|
||||
float u0 = 0.0f, float u1 = 1.0f)
|
||||
{
|
||||
if (texture == NULL)
|
||||
{
|
||||
return;
|
||||
}
|
||||
L4VERTEX_2D_TEX quad[4];
|
||||
memset(quad, 0, sizeof(quad));
|
||||
const DWORD color = D3DCOLOR_XRGB(0, 128, 0);
|
||||
for (int i = 0; i < 4; ++i)
|
||||
{
|
||||
quad[i].z = 0.0f;
|
||||
quad[i].rhw = 1.0f;
|
||||
quad[i].color = color;
|
||||
}
|
||||
quad[0].x = x + w; quad[0].y = y; quad[0].u = u1; quad[0].v = 0.0f;
|
||||
quad[1].x = x; quad[1].y = y; quad[1].u = u0; quad[1].v = 0.0f;
|
||||
quad[2].x = x + w; quad[2].y = y + h; quad[2].u = u1; quad[2].v = 1.0f;
|
||||
quad[3].x = x; quad[3].y = y + h; quad[3].u = u0; quad[3].v = 1.0f;
|
||||
device->SetTexture(0, texture);
|
||||
device->DrawPrimitiveUP(D3DPT_TRIANGLESTRIP, 2, quad,
|
||||
sizeof(L4VERTEX_2D_TEX));
|
||||
}
|
||||
|
||||
void CameraShipHUDRenderable::Render(int pass, const D3DXMATRIX *viewTransform)
|
||||
{
|
||||
if (application->GetApplicationState() == Application::RunningMission && oldFollowedPlayerIndex >= 0 && oldFollowedPlayerIndex < MAX_PLAYER_NAMES)
|
||||
if (application->GetApplicationState() != Application::RunningMission)
|
||||
{
|
||||
LPDIRECT3DDEVICE9 device = myRenderer->GetDevice();
|
||||
return;
|
||||
}
|
||||
LPDIRECT3DDEVICE9 device = myRenderer->GetDevice();
|
||||
device->SetFVF(L4VERTEX_2D_TEX_FVF);
|
||||
device->SetRenderState(D3DRS_ALPHABLENDENABLE, TRUE);
|
||||
device->SetRenderState(D3DRS_SRCBLEND, D3DBLEND_SRCALPHA);
|
||||
device->SetRenderState(D3DRS_DESTBLEND, D3DBLEND_INVSRCALPHA);
|
||||
|
||||
device->SetFVF(L4VERTEX_2D_TEX_FVF);
|
||||
//
|
||||
// The followed player's callsign banner (bottom center). Execute already
|
||||
// converts the 1-based goalPlayerIndex to the 0-based texture slot.
|
||||
//
|
||||
if (oldFollowedPlayerIndex >= 0 && oldFollowedPlayerIndex < MAX_PLAYER_NAMES)
|
||||
{
|
||||
device->SetStreamSource(0, mVB, 0, sizeof(L4VERTEX_2D_TEX));
|
||||
device->SetTexture(0, myRenderer->GetNameTexture(oldFollowedPlayerIndex));
|
||||
device->SetTexture(0,
|
||||
myRenderer->GetNameTexture(oldFollowedPlayerIndex));
|
||||
device->DrawPrimitive(D3DPT_TRIANGLESTRIP, 0, 2);
|
||||
}
|
||||
|
||||
//
|
||||
// THE RANKING WINDOW (the un-stubbed 1995 display): one row per scoring
|
||||
// player -- [1st/2nd/... ordinal][callsign] -- in RANK order, right of
|
||||
// center (the old DCS placement: window at x .22 y .16, ordinal left of
|
||||
// the name). Visibility is the Director's flash logic (10s on / 15s off,
|
||||
// solid for the final 30s). playerRank[bmp-1] -> &playerRanking (LIVE
|
||||
// pointers captured at ctor), so the rows re-sort as the score changes.
|
||||
//
|
||||
if (getenv("BT_CAM_LOG"))
|
||||
{
|
||||
static int s_rlog = 0;
|
||||
if ((s_rlog++ % 300) == 0)
|
||||
DEBUG_STREAM << "[cam] HUD render: followed="
|
||||
<< oldFollowedPlayerIndex
|
||||
<< " rankwin=" << (displayRankingWindow
|
||||
? (int)*displayRankingWindow : -1)
|
||||
<< " count=" << playerCount << "\n" << std::flush;
|
||||
}
|
||||
if (displayRankingWindow != NULL && *displayRankingWindow
|
||||
&& playerRank != NULL && playerCount > 0)
|
||||
{
|
||||
const float width = myRenderer->GetWidth();
|
||||
const float height = myRenderer->GetHeight();
|
||||
const float nameW = width * 0.20f;
|
||||
const float ordW = width * 0.10f;
|
||||
const float rowH = nameW * (32.0f / 128.0f); // bitmap aspect
|
||||
const float pad = width * 0.01f;
|
||||
const float x0 = width * 0.55f;
|
||||
const float y0 = height * 0.16f;
|
||||
|
||||
for (int bmp = 0; bmp < playerCount && bmp < MAX_PLAYER_NAMES; ++bmp)
|
||||
{
|
||||
if (playerRank[bmp] == NULL)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
int rank = *playerRank[bmp];
|
||||
if (getenv("BT_CAM_LOG"))
|
||||
{
|
||||
static int s_rowlog = 0;
|
||||
if ((s_rowlog++ % 300) == 0)
|
||||
DEBUG_STREAM << "[cam] rank row: bmp=" << bmp
|
||||
<< " rank=" << rank << "\n" << std::flush;
|
||||
}
|
||||
if (rank < 0 || rank >= MAX_PLAYER_NAMES)
|
||||
{
|
||||
continue; // non-scoring (director/camera)
|
||||
}
|
||||
const float y = y0 + rank * (rowH * 1.15f);
|
||||
// each 128x32 ordinal bitmap packs TWO ordinals side by side
|
||||
// ("1st|2nd", "3rd|4th", ...) -- texture rank/2, u-half by parity
|
||||
CameraHUDDrawQuad(device, myRenderer->GetOrdinalTexture(rank / 2),
|
||||
x0, y, ordW, rowH,
|
||||
(rank & 1) ? 0.5f : 0.0f, (rank & 1) ? 1.0f : 0.5f);
|
||||
CameraHUDDrawQuad(device, myRenderer->GetNameTexture(bmp),
|
||||
x0 + ordW + pad, y, nameW, rowH);
|
||||
}
|
||||
}
|
||||
|
||||
device->SetRenderState(D3DRS_ALPHABLENDENABLE, FALSE);
|
||||
}
|
||||
|
||||
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Reference in New Issue
Block a user