From af82cac5d205befa946ab4fb9cc616136d3bf9d2 Mon Sep 17 00:00:00 2001 From: Cyd Date: Mon, 10 Aug 2026 15:56:58 -0500 Subject: [PATCH] A Live Cam keeps the map and drops the panes A Live Cam host raced with the full glass cockpit over its view: five instrument MFD panes with no pod behind them, compositing as black rectangles across the viewscreen, and the map stood on end. Both are cabinet artefacts. The panes belong to a pod; the map is portrait because that is how the glass was bolted into the cabinet. A camera has no cabinet, so it keeps the map - landscape - and nothing else. The arcade got the same result by running its camera cabinet with -lc. The role is picked in the lobby, so there is no -lc on the command line to carry it to the renderers. Application::IsCameraStation joins suppressGauges, set by the front end where the egg settles owner_is_camera - before the renderers exist, since the single-binary race loop builds a fresh application per race after the menu. Set on every launch either way: a stale True would strip the cockpit off the next pod. In the compositor: the five MFD panes are not built, which LayoutCockpit and FillSplitMFD already tolerate, so no layout surgery; the map's glass swaps to 432x324 while still honouring L4RADARSCALE and L4RADARPOS; and the per-frame copy runs straight through rather than rotating 90 degrees clockwise. The pane's source dimensions swap to match, so the buffer the copy fills is the size the pane allocated - checked both ways. Untested on screen, unlike the rest of this work: it only engages with a lobby member present and that needs the second machine. What is checked is that it builds, that the buffer arithmetic agrees, and that every pane access on the glass-cockpit path is NULL-guarded. L4MFDSPLIT=2 and =0 are left alone. The exploded view still opens five empty MFD windows for a camera; it is a diagnostic view where that is arguably the point, and its layout derefs the panes unguarded. Co-Authored-By: Claude Opus 5 (1M context) --- MUNGA/APP.cpp | 1 + MUNGA/APP.h | 16 ++++++ MUNGA_L4/L4VB16.cpp | 128 ++++++++++++++++++++++++++++++++------------ RP_L4/RPL4FE.cpp | 13 +++++ 4 files changed, 123 insertions(+), 35 deletions(-) diff --git a/MUNGA/APP.cpp b/MUNGA/APP.cpp index d8ed7e8..765b92e 100644 --- a/MUNGA/APP.cpp +++ b/MUNGA/APP.cpp @@ -33,6 +33,7 @@ Application *application = NULL; int Exit_Code = 0; Logical Application::suppressGauges = False; +Logical Application::cameraStation = False; // // RP412CAMLOG - see app.h. Cached: the waiting trace asks once a second diff --git a/MUNGA/APP.h b/MUNGA/APP.h index 0114086..f2f7b74 100644 --- a/MUNGA/APP.h +++ b/MUNGA/APP.h @@ -454,6 +454,21 @@ public: static Logical DoSuppressGauges() { return suppressGauges; } + // + // A Live Cam station. It has no pod, so none of the five instrument + // MFDs have anything to put on them - the panes would just composite + // as black holes over the view. It DOES keep the map, but landscape: + // the pod's is portrait because that is how the glass was physically + // mounted in the cabinet, and a camera has no cabinet. + // + // Settable because the role is picked in the lobby rather than passed + // as -lc on the command line, so the front end has to hand it over. + // That happens before the renderers are built - the single-binary race + // loop makes a fresh application per race, after the menu. + // + static Logical IsCameraStation() { return cameraStation; } + static void SetCameraStation(Logical state) { cameraStation = state; } + //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Modules // @@ -489,6 +504,7 @@ protected: *modeManager; static Logical suppressGauges; + static Logical cameraStation; //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Module Creation diff --git a/MUNGA_L4/L4VB16.cpp b/MUNGA_L4/L4VB16.cpp index cf3ea1b..5801e48 100644 --- a/MUNGA_L4/L4VB16.cpp +++ b/MUNGA_L4/L4VB16.cpp @@ -4054,11 +4054,24 @@ void << percent[SplitMap] << "%\n" << std::flush; } + // + // A Live Cam's map lies down: the pod's is 324x432 because the glass + // was mounted portrait in the cabinet, and a camera station has no + // cabinet, so the same pixels are shown the way they are drawn. + // + const Logical camera_station = Application::IsCameraStation(); + GlassSize glass[SplitViewCount]; for (int view = 0; view < SplitViewCount; ++view) { int base_w = (view == SplitMap) ? cockpitRadarBaseW : cockpitMfdBaseW; int base_h = (view == SplitMap) ? cockpitRadarBaseH : cockpitMfdBaseH; + if (view == SplitMap && camera_station) + { + int swap = base_w; + base_w = base_h; + base_h = swap; + } glass[view].w = (base_w * percent[view]) / 100; glass[view].h = (base_h * percent[view]) / 100; } @@ -4656,31 +4669,53 @@ SVGA16::SVGA16( GlassSize glass[SplitViewCount]; CockpitGlassSizes(scale_num, glass); - splitView[SplitMFDUpperLeft] = new MFDSplitView( - "MFD upper left", init_width, init_height, - glass[SplitMFDUpperLeft].w, glass[SplitMFDUpperLeft].h, 0, 0, - MFDSplitView::MFDStrips, 0x2F, 0, cockpit); - splitView[SplitMFDUpperCenter] = new MFDSplitView( - "MFD upper center", init_width, init_height, - glass[SplitMFDUpperCenter].w, glass[SplitMFDUpperCenter].h, 0, 0, - MFDSplitView::MFDStrips, 0x27, 0, cockpit); - splitView[SplitMFDUpperRight] = new MFDSplitView( - "MFD upper right", init_width, init_height, - glass[SplitMFDUpperRight].w, glass[SplitMFDUpperRight].h, 0, 0, - MFDSplitView::MFDStrips, 0x37, 0, cockpit); - splitView[SplitMFDLowerLeft] = new MFDSplitView( - "MFD lower left", init_width, init_height, - glass[SplitMFDLowerLeft].w, glass[SplitMFDLowerLeft].h, 0, 0, - MFDSplitView::MFDStrips, 0x0F, 0, cockpit); - splitView[SplitMFDLowerRight] = new MFDSplitView( - "MFD lower right", init_width, init_height, - glass[SplitMFDLowerRight].w, glass[SplitMFDLowerRight].h, 0, 0, - MFDSplitView::MFDStrips, 0x07, 0, cockpit); - // map is portrait: source rotated 90 degrees clockwise - splitView[SplitMap] = new MFDSplitView( - "Map", init_height, init_width, - glass[SplitMap].w, glass[SplitMap].h, 0, 0, - MFDSplitView::SideColumns, 0x10, 0x18, cockpit); + //--------------------------------------------------------------- + // A Live Cam gets the map and nothing else. The five instrument + // MFDs belong to a pod: with no pod behind them their panes carry + // no image at all and composite as black rectangles over the + // viewscreen, which is what the arcade's camera cabinet avoided by + // running with -lc. LayoutCockpit and FillSplitMFD both already + // skip a NULL pane, so leaving them unbuilt is all it takes. + //--------------------------------------------------------------- + if (!Application::IsCameraStation()) + { + splitView[SplitMFDUpperLeft] = new MFDSplitView( + "MFD upper left", init_width, init_height, + glass[SplitMFDUpperLeft].w, glass[SplitMFDUpperLeft].h, 0, 0, + MFDSplitView::MFDStrips, 0x2F, 0, cockpit); + splitView[SplitMFDUpperCenter] = new MFDSplitView( + "MFD upper center", init_width, init_height, + glass[SplitMFDUpperCenter].w, glass[SplitMFDUpperCenter].h, 0, 0, + MFDSplitView::MFDStrips, 0x27, 0, cockpit); + splitView[SplitMFDUpperRight] = new MFDSplitView( + "MFD upper right", init_width, init_height, + glass[SplitMFDUpperRight].w, glass[SplitMFDUpperRight].h, 0, 0, + MFDSplitView::MFDStrips, 0x37, 0, cockpit); + splitView[SplitMFDLowerLeft] = new MFDSplitView( + "MFD lower left", init_width, init_height, + glass[SplitMFDLowerLeft].w, glass[SplitMFDLowerLeft].h, 0, 0, + MFDSplitView::MFDStrips, 0x0F, 0, cockpit); + splitView[SplitMFDLowerRight] = new MFDSplitView( + "MFD lower right", init_width, init_height, + glass[SplitMFDLowerRight].w, glass[SplitMFDLowerRight].h, 0, 0, + MFDSplitView::MFDStrips, 0x07, 0, cockpit); + } + // + // The map's source canvas is landscape and the pod rotates it 90 + // degrees clockwise to match its portrait mounting, which is why + // the pane is built with the source dimensions swapped. A camera + // shows it as drawn, so it is not swapped and not rotated (see the + // copy in ExecuteBackground). + // + splitView[SplitMap] = Application::IsCameraStation() + ? new MFDSplitView( + "Map", init_width, init_height, + glass[SplitMap].w, glass[SplitMap].h, 0, 0, + MFDSplitView::SideColumns, 0x10, 0x18, cockpit) + : new MFDSplitView( + "Map", init_height, init_width, + glass[SplitMap].w, glass[SplitMap].h, 0, 0, + MFDSplitView::SideColumns, 0x10, 0x18, cockpit); //--------------------------------------------------------------- // Everything above built the panes at a starting size; the @@ -5136,23 +5171,46 @@ Logical SVGA16::Update(Logical forceAll) { if (splitView[SplitMap] != NULL) { - // Map is portrait-mounted: rotate 90 degrees clockwise. - // dest(x,y) = source(row = srcH-1-x, col = y) Word *source_base = pixelBuffer.Data.MapPointer; unsigned long *dest = splitView[SplitMap]->Pixels(); int src_w = pixelBuffer.Data.Size.x; int src_h = pixelBuffer.Data.Size.y; - for (int dy = 0; dy < src_w; ++dy) + if (Application::IsCameraStation()) { - for (int dx = 0; dx < src_h; ++dx) + // + // Live Cam: straight through, row by row. The pod's + // rotation below is about how the glass was bolted + // into the cabinet, and there is no cabinet here. + // + for (int dy = 0; dy < src_h; ++dy) { - Word pixel = source_base[(src_h - 1 - dx) * src_w + dy]; - PaletteTriplet *entry = - &secPalette->paletteData.Color[pixel & secMask]; - *dest++ = ((unsigned long) entry->Red << 16) | - ((unsigned long) entry->Green << 8) | - ((unsigned long) entry->Blue); + for (int dx = 0; dx < src_w; ++dx) + { + Word pixel = source_base[dy * src_w + dx]; + PaletteTriplet *entry = + &secPalette->paletteData.Color[pixel & secMask]; + *dest++ = ((unsigned long) entry->Red << 16) | + ((unsigned long) entry->Green << 8) | + ((unsigned long) entry->Blue); + } + } + } + else + { + // Map is portrait-mounted: rotate 90 degrees clockwise. + // dest(x,y) = source(row = srcH-1-x, col = y) + for (int dy = 0; dy < src_w; ++dy) + { + for (int dx = 0; dx < src_h; ++dx) + { + Word pixel = source_base[(src_h - 1 - dx) * src_w + dy]; + PaletteTriplet *entry = + &secPalette->paletteData.Color[pixel & secMask]; + *dest++ = ((unsigned long) entry->Red << 16) | + ((unsigned long) entry->Green << 8) | + ((unsigned long) entry->Blue); + } } } splitView[SplitMap]->Repaint(); diff --git a/RP_L4/RPL4FE.cpp b/RP_L4/RPL4FE.cpp index 7603933..8628349 100644 --- a/RP_L4/RPL4FE.cpp +++ b/RP_L4/RPL4FE.cpp @@ -1621,6 +1621,19 @@ namespace << std::flush; } + // + // Tell the renderers, which are built after this menu and need to + // know not to hang five empty instrument panes over the view. Set + // either way: the same process races again after this one, and a + // stale True would strip the cockpit off a pod. + // + Application::SetCameraStation(owner_is_camera); + if (owner_is_camera) + { + DEBUG_STREAM << "FE: Live Cam host - no instrument panes, map landscape\n" + << std::flush; + } + for (int p = 0; p < pilot_count; ++p) { Logical camera_entry = (p == 0 && owner_is_camera);