From 86d6b950e5c98d84c943ee18477c2107710e4ce7 Mon Sep 17 00:00:00 2001 From: Cyd Date: Fri, 24 Jul 2026 10:39:30 -0500 Subject: [PATCH] emulator: B2 RIO lamp/button bezel on the VDB heads + explode-view polish Draw the cockpit RIO buttons around the mono-MFD and radar heads (explode layout), lit by the host-commanded lamp state -- the display side of the in-fork glass cockpit. Live-validated 2026-07-24 (operator). - vpxlog.cpp: per-head button bezel in pal_draw, reading serialrio's new RIO_GetPanelState seam. The 5 MFD heads get red buttons (4 top / 4 bottom, 100px tall tucked under a grown 640x500 display so a 10px lip shows); the radar gets amber Secondary/Screen side columns (6x 104px each) plus a centered bottom indicator strip (the 4 spares). Lamp byte decoded to off/dim/bright (vRIO RioLampState, brighter of the two brightness fields); a press shows white-hot. No labels -- the MFD shows each button's function. Also swapped the two upper-outer MFD window NAMES to match their (already position-swapped) desktop locations. - serialrio.{cpp,h}: RIO_GetPanelState(lamps, pressed) accessor -- returns false when no rio port, so non-rio configs and other heads render normally. - pod-launch: in explode (dev) layout the Division bridge is a normal, freely movable window -- not pinned topmost (Focus.cs) and nudged to 8,40 so its title bar clears the top of the screen (its client was at 0,0, pushing the frame off-screen). Cockpit/kiosk keeps the topmost + 0,0 borderless look. Only affects the explode layout and only when a serial=rio port is present. Co-Authored-By: Claude Opus 4.8 --- emulator/pod-launch/Focus.cs | 12 +- emulator/pod-launch/Options.cs | 10 +- emulator/pod-launch/Program.cs | 12 +- emulator/vpx-device/README.md | 8 +- emulator/vpx-device/serialrio.cpp | 14 +++ emulator/vpx-device/serialrio.h | 7 ++ emulator/vpx-device/vpxlog.cpp | 188 +++++++++++++++++++++++++++++- 7 files changed, 234 insertions(+), 17 deletions(-) diff --git a/emulator/pod-launch/Focus.cs b/emulator/pod-launch/Focus.cs index af1a7c69..79205878 100644 --- a/emulator/pod-launch/Focus.cs +++ b/emulator/pod-launch/Focus.cs @@ -15,16 +15,19 @@ namespace VwePod { internal static class Focus { - public static string Apply(int x, int y) + public static string Apply(int x, int y, bool pinRenderer) { var s = new StringBuilder(); - // 1) renderer -> topmost + // 1) renderer Z-order. Kiosk/cockpit: pin it always-on-top so a head + // window never covers the out-the-window view. Explode (dev): leave + // it a normal window so the operator can freely move/restack it. IntPtr r = FindRenderer(); if (r != IntPtr.Zero) { - SetWindowPos(r, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE); - s.Append("renderer topmost; "); + SetWindowPos(r, pinRenderer ? HWND_TOPMOST : HWND_NOTOPMOST, + 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE); + s.Append(pinRenderer ? "renderer topmost; " : "renderer movable; "); } else s.Append("renderer NOT found; "); @@ -69,6 +72,7 @@ namespace VwePod // ---- native ---- private static readonly IntPtr HWND_TOPMOST = new IntPtr(-1); + private static readonly IntPtr HWND_NOTOPMOST = new IntPtr(-2); private const uint SWP_NOSIZE = 0x0001, SWP_NOMOVE = 0x0002, SWP_NOZORDER = 0x0004; private const int SW_RESTORE = 9; diff --git a/emulator/pod-launch/Options.cs b/emulator/pod-launch/Options.cs index 683c5289..cbb80faf 100644 --- a/emulator/pod-launch/Options.cs +++ b/emulator/pod-launch/Options.cs @@ -51,7 +51,7 @@ namespace VwePod var o = new Options(); string modeName = null, confArg = null, rootArg = null, dosboxArg = null, bridgeScriptArg = null, rendererArg = null, aweArg = null; - bool dosboxXySet = false; + bool dosboxXySet = false, bridgePosSet = false; for (int i = 0; i < args.Length; i++) { @@ -73,7 +73,7 @@ namespace VwePod case "--renderer": rendererArg = Next(a); break; case "--awe-rom": aweArg = Next(a); break; case "--layout": o.Layout = Next(a).Equals("explode", StringComparison.OrdinalIgnoreCase) ? LayoutMode.Explode : LayoutMode.Cockpit; break; - case "--bridge-pos": o.BridgePos = Next(a); break; + case "--bridge-pos": o.BridgePos = Next(a); bridgePosSet = true; break; case "--bridge-size": ParseWH(Next(a), out o.BridgeW, out o.BridgeH); break; case "--dosbox-xy": ParseWH(Next(a), out o.DosBoxX, out o.DosBoxY); dosboxXySet = true; break; case "--no-bridge": o.NoBridge = true; break; @@ -101,6 +101,12 @@ namespace VwePod // under the render window unless the caller placed it explicitly. if (!dosboxXySet && o.Mode.Name == "test") { o.DosBoxX = 900; o.DosBoxY = 600; } + // explode (dev) layout: the bridge is a normal framed window, but at + // 0,0 SDL puts the CLIENT at 0,0 so the title bar sits off the top of + // the screen (unmovable). Nudge it on-screen (frame needs y >= 31) + // unless the caller placed it. Cockpit keeps 0,0 (kiosk, borderless-look). + if (!bridgePosSet && o.Layout == LayoutMode.Explode) o.BridgePos = "8,40"; + o.DosBox = dosboxArg ?? FirstExisting( Path.Combine(o.Root, "dosbox-x.exe"), Path.Combine(o.Root, "src", "src", "dosbox-x.exe")) diff --git a/emulator/pod-launch/Program.cs b/emulator/pod-launch/Program.cs index b282d0c4..c201bf8f 100644 --- a/emulator/pod-launch/Program.cs +++ b/emulator/pod-launch/Program.cs @@ -142,14 +142,16 @@ namespace VwePod // TerminateProcess skips this, but the job still closes with us.) Console.CancelKeyPress += (s, e) => { e.Cancel = false; job.Dispose(); }; - // Window layout LAST so it wins over the child windows: renderer - // -> always-on-top; DOSBox -> parked + foreground (keeps the emu - // thread at foreground priority so the RIO ACK deadline isn't - // missed). Give the windows a moment to exist first. + // Window layout LAST so it wins over the child windows: DOSBox -> + // parked + foreground (keeps the emu thread at foreground priority + // so the RIO ACK deadline isn't missed). The renderer is pinned + // topmost only in cockpit (kiosk) layout; in explode (dev) it stays + // a normal, freely movable window. Give the windows a moment first. if (!opt.NoFocus) { Thread.Sleep(3000); - Console.WriteLine("focus: " + Focus.Apply(opt.DosBoxX, opt.DosBoxY)); + Console.WriteLine("focus: " + Focus.Apply( + opt.DosBoxX, opt.DosBoxY, opt.Layout == LayoutMode.Cockpit)); } // Authoritative for the whole session: block until DOSBox exits. diff --git a/emulator/vpx-device/README.md b/emulator/vpx-device/README.md index b7a09c1e..78235cf8 100644 --- a/emulator/vpx-device/README.md +++ b/emulator/vpx-device/README.md @@ -7,7 +7,13 @@ version control because the DOSBox-X source tree itself - **`vpxlog.cpp`** — the Division VPX link adapter (INMOS C012 at I/O base `0x150`), grown through Phase 3: iserver handshake responder, FIFO scene decode, GL Division renderer (gallery shading model), VDB video-head - splitter windows. + splitter windows. **RIO lamp/button bezel (B2, 2026-07-24, explode layout):** + `pal_draw` rings each mono-MFD head with its 8 RIO buttons (red, 4 top / 4 + bottom, tucked under a 640x500 display so a 10px lip shows) and the radar + with the amber Secondary/Screen side columns + a centered bottom indicator + strip, lit from `serialrio`'s `RIO_GetPanelState` (off/dim/bright + white-hot + press). No labels -- the display shows each button's function. No-op when no + `serial=rio` port is present. - **`vweawe.cpp`** — the dual-AWE32 sound device: two vendored EMU8000 cores at 0x620/0x640 (+0x400/+0x800 triplets), rear-card DSP/mixer stub at 0x240, autonomous render thread with direct winmm output. Needs the diff --git a/emulator/vpx-device/serialrio.cpp b/emulator/vpx-device/serialrio.cpp index 1c1b9b6e..53cb8547 100644 --- a/emulator/vpx-device/serialrio.cpp +++ b/emulator/vpx-device/serialrio.cpp @@ -211,6 +211,12 @@ void RIO_HostFocusLost(void) { if (rio_instance) rio_instance->hostFocusLost(); } +bool RIO_GetPanelState(uint8_t lamps[72], uint8_t pressed[72]) { + if (!rio_instance) return false; + rio_instance->getPanelState(lamps, pressed); + return true; +} + // ============================================================================ CSerialRio::CSerialRio(Bitu id, CommandLine* cmd) : CSerial(id, cmd) { @@ -570,6 +576,14 @@ void CSerialRio::hostFocusLost() { releaseAllKeys(); } +// panel snapshot for the in-fork lamp bezel (vpxlog reads this each frame) +void CSerialRio::getPanelState(uint8_t lampsOut[72], uint8_t pressedOut[72]) { + for (int i = 0; i < 72; i++) { + lampsOut[i] = lamps[i]; + pressedOut[i] = holdCounts.count(i) ? 1 : 0; // addr held right now + } +} + void CSerialRio::releaseAllKeys() { std::set keys = heldKeys; heldKeys.clear(); diff --git a/emulator/vpx-device/serialrio.h b/emulator/vpx-device/serialrio.h index aa1ce71a..590276a7 100644 --- a/emulator/vpx-device/serialrio.h +++ b/emulator/vpx-device/serialrio.h @@ -72,6 +72,12 @@ bool RIO_HostKeyEvent(int sdl_scancode, bool pressed, bool repeat, unsigned int sdl_mods); void RIO_HostFocusLost(void); +// Current panel state for the in-fork lamp/button bezel (B2): fills lamps[] +// with the host-commanded LampRequest byte and pressed[] with 1 where a button +// address is currently held. Returns false (and touches nothing) when no +// serial=rio port is installed, so the VDB heads render normally. +bool RIO_GetPanelState(uint8_t lamps[72], uint8_t pressed[72]); + // ---- binding profile (transcribed from vRIO BindingProfile) ----------------- // key-axis modes: DEFLECT holds the axis at value while the key is down and // springs back; RATE ("slew" in bindings files) walks the axis by value/second @@ -99,6 +105,7 @@ public: // host keyboard (forwarded by the RIO_Host* free functions) bool hostKeyEvent(int sdl_scancode, bool pressed, bool repeat, unsigned int sdl_mods); void hostFocusLost(); + void getPanelState(uint8_t lampsOut[72], uint8_t pressedOut[72]); private: // ---- board -> game byte delivery (UART RX pacing; mirrors directserial) -- diff --git a/emulator/vpx-device/vpxlog.cpp b/emulator/vpx-device/vpxlog.cpp index 24fb65ee..df27344b 100644 --- a/emulator/vpx-device/vpxlog.cpp +++ b/emulator/vpx-device/vpxlog.cpp @@ -1323,6 +1323,156 @@ static void write_bmp(const char *path, const unsigned char *rgb, int W, int H) * encoded stream the VDB splits): win0 = bits 0-7 via pal0 = color radar; * win3/win4 = bits 8-15 via pal1/pal2 = the two MFD heads (mono MFDs ride * the individual R/G/B channel wires). g=1/2 (exploratory) no longer used. */ +/* ---- in-fork RIO lamp/button bezel (B2) --------------------------------- + * The five mono-MFD heads (g=5..9, explode layout) render the RIO buttons that + * physically ring each MFD -- 4 across the top, 4 across the bottom -- lit by + * the host-commanded lamp state (and highlighted while pressed). Address map = + * vRIO CockpitLayout: each MFD is a 4x2 block whose top-left is 'hi', addresses + * descending row-major (top row hi..hi-3, bottom row hi-4..hi-7). The seam is + * serialrio's RIO_GetPanelState (no-op / returns false when no rio port). */ +extern bool RIO_GetPanelState(uint8_t lamps[72], uint8_t pressed[72]); + +static int mfd_hi_for_g(int g) { + switch (g) { + case 7: return 0x2F; /* upper-left MFD (AuxUpperLeft) */ + case 8: return 0x27; /* upper-mid MFD (AuxUpperCenter) */ + case 9: return 0x37; /* upper-right MFD (AuxUpperRight = target hotbox) */ + case 5: return 0x0F; /* lower-left MFD (AuxLowerLeft) */ + case 6: return 0x07; /* lower-right MFD (AuxLowerRight) */ + } + return -1; +} + +/* Panel button color at the host lamp level (vRIO RioLampState: brighter of the + * two brightness fields, bits 2-3 = 0x0C bright/0x04 dim, bits 4-5 = 0x30 bright/ + * 0x10 dim). MFD buttons are red, radar Secondary/Screen buttons are amber. A + * press shows white-hot so it's visible over any level. */ +static void rio_button_color(uint8_t ls, bool pr, bool amber, float *r, float *g, float *b) { + int f1 = ls & 0x0C, f2 = ls & 0x30; + int level = (f1 == 0x0C || f2 == 0x30) ? 2 : (f1 || f2) ? 1 : 0; + if (pr) { *r = 1.00f; *g = 0.90f; *b = 0.62f; return; } /* pressed white-hot */ + if (amber) { + if (level == 2) { *r = 1.00f; *g = 0.72f; *b = 0.05f; } /* bright amber */ + else if (level == 1) { *r = 0.50f; *g = 0.36f; *b = 0.02f; } /* dim amber */ + else { *r = 0.16f; *g = 0.12f; *b = 0.02f; } /* off */ + } else { + if (level == 2) { *r = 1.00f; *g = 0.12f; *b = 0.08f; } /* bright red */ + else if (level == 1) { *r = 0.52f; *g = 0.05f; *b = 0.04f; } /* dim red */ + else { *r = 0.16f; *g = 0.03f; *b = 0.03f; } /* off */ + } +} + +/* The buttons are drawn UNDER the display image, which is inset by RIO_BEZEL_LIP + * on the button edges -- so only the outer LIP of each button "sticks out" past + * the display edge (the lamp tip you can see), while the rest tucks behind the + * display's soft-key region (the future click zone). MFDs grow in height (480 + + * 2*LIP = 500), the radar in width (480 + 2*LIP = 500). */ +#define RIO_BEZEL_LIP 10 /* protruding lip px, each side */ +#define RIO_BEZEL_BTN 100 /* full MFD button height px (mostly hidden) */ +#define RIO_RADAR_BOT 10 /* radar bottom indicator strip height px */ + +static void draw_mfd_bezel(int hi, int cw, int ch, + const uint8_t *lamps, const uint8_t *pressed) { + glViewport(0, 0, cw, ch); + glMatrixMode(GL_PROJECTION); glPushMatrix(); glLoadIdentity(); + glOrtho(0, cw, 0, ch, -1, 1); /* pixel space, y up */ + glMatrixMode(GL_MODELVIEW); glPushMatrix(); glLoadIdentity(); + glDisable(GL_TEXTURE_2D); glDisable(GL_DEPTH_TEST); glDisable(GL_LIGHTING); + const float bw = (float)cw / 4.0f; + for (int row = 0; row < 2; row++) { + /* top row butts up under the display top; bottom row under the bottom */ + float y0 = (row == 0) ? (float)(ch - RIO_BEZEL_BTN) : 0.0f; + float y1 = (row == 0) ? (float)ch : (float)RIO_BEZEL_BTN; + for (int col = 0; col < 4; col++) { + int addr = hi - (row * 4 + col); + uint8_t ls = (addr >= 0 && addr < 72) ? lamps[addr] : 0; + bool pr = (addr >= 0 && addr < 72) && pressed[addr]; + /* No labels -- the MFD display shows each button's function. */ + float r, g_, b; rio_button_color(ls, pr, false /*red*/, &r, &g_, &b); + float x0 = col * bw + 3, x1 = (col + 1) * bw - 3; + float yy0 = y0, yy1 = y1; /* full height; lip is what shows */ + glColor3f(r, g_, b); + glBegin(GL_QUADS); + glVertex2f(x0, yy0); glVertex2f(x1, yy0); + glVertex2f(x1, yy1); glVertex2f(x0, yy1); + glEnd(); + glColor3f(0.40f, 0.10f, 0.10f); /* bezel outline */ + glBegin(GL_LINE_LOOP); + glVertex2f(x0, yy0); glVertex2f(x1, yy0); + glVertex2f(x1, yy1); glVertex2f(x0, yy1); + glEnd(); + } + } + glMatrixMode(GL_PROJECTION); glPopMatrix(); + glMatrixMode(GL_MODELVIEW); glPopMatrix(); +} + +/* Radar/secondary-screen bezel (explode only), amber buttons. The Secondary + * column (0x10-0x15) runs down the LEFT, the Screen column (0x18-0x1D) down the + * RIGHT -- 6 buttons each, 104px tall / 3px apart, 100px wide with 90px tucked + * behind the display so a 10px lip shows on the outer edge; the columns center + * in the display region above the bottom strip. The 4 spares (Secondary + * 0x16/0x17, Screen 0x1E/0x1F) are INDICATOR-only: a 10px strip along the + * bottom of the window. */ +static void draw_amber_quad(float x0, float y0, float x1, float y1, + uint8_t ls, bool pr) { + float r, g_, b; rio_button_color(ls, pr, true /*amber*/, &r, &g_, &b); + glColor3f(r, g_, b); + glBegin(GL_QUADS); + glVertex2f(x0, y0); glVertex2f(x1, y0); + glVertex2f(x1, y1); glVertex2f(x0, y1); + glEnd(); + glColor3f(0.42f, 0.30f, 0.05f); + glBegin(GL_LINE_LOOP); + glVertex2f(x0, y0); glVertex2f(x1, y0); + glVertex2f(x1, y1); glVertex2f(x0, y1); + glEnd(); +} + +static void draw_radar_bezel(int cw, int ch, const uint8_t *lamps, const uint8_t *pressed) { + static const int leftCol[6] = { 0x10, 0x11, 0x12, 0x13, 0x14, 0x15 }; /* Secondary top->bottom */ + static const int rightCol[6] = { 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D }; /* Screen top->bottom */ + static const int bottomInd[4] = { 0x16, 0x17, 0x1E, 0x1F }; /* spares (indicators) */ + const int BTN_H = 104, GAP = 3, N = 6, BTN_W = 100; + float stack = (float)(N * BTN_H + (N - 1) * GAP); + float regionH = (float)(ch - RIO_RADAR_BOT); /* display region (above the strip) */ + float margin = (regionH - stack) / 2.0f; /* center the stack in the display */ + glViewport(0, 0, cw, ch); + glMatrixMode(GL_PROJECTION); glPushMatrix(); glLoadIdentity(); + glOrtho(0, cw, 0, ch, -1, 1); + glMatrixMode(GL_MODELVIEW); glPushMatrix(); glLoadIdentity(); + glDisable(GL_TEXTURE_2D); glDisable(GL_DEPTH_TEST); glDisable(GL_LIGHTING); + /* side columns */ + for (int side = 0; side < 2; side++) { + const int *col = side == 0 ? leftCol : rightCol; + float x0 = side == 0 ? 0.0f : (float)(cw - BTN_W); /* outer edge flush to window */ + float x1 = x0 + (float)BTN_W; + for (int i = 0; i < N; i++) { + int addr = col[i]; + uint8_t ls = (addr >= 0 && addr < 72) ? lamps[addr] : 0; + bool pr = (addr >= 0 && addr < 72) && pressed[addr]; + float yy1 = (float)ch - (margin + (float)(i * (BTN_H + GAP))); /* GL top (y up) */ + float yy0 = yy1 - (float)BTN_H; + draw_amber_quad(x0, yy0, x1, yy1, ls, pr); + } + } + /* bottom indicator strip: 4 spares, ~25% narrower than full width and + * centered (indicators only -- 0x16/0x17 Secondary, 0x1E/0x1F Screen). */ + float rowW = 0.75f * (float)cw; + float rowX = ((float)cw - rowW) / 2.0f; + float igap = 8.0f; + float iw = (rowW - 3.0f * igap) / 4.0f; + for (int i = 0; i < 4; i++) { + int addr = bottomInd[i]; + uint8_t ls = (addr >= 0 && addr < 72) ? lamps[addr] : 0; + bool pr = (addr >= 0 && addr < 72) && pressed[addr]; + float x0 = rowX + (float)i * (iw + igap); + draw_amber_quad(x0, 0.0f, x0 + iw, (float)RIO_RADAR_BOT, ls, pr); + } + glMatrixMode(GL_PROJECTION); glPopMatrix(); + glMatrixMode(GL_MODELVIEW); glPopMatrix(); +} + static void pal_draw(HDC dc, int g, int cw, int ch, bool dump) { const int W = 640, H = 480; static unsigned char img[640 * 480 * 3]; @@ -1530,10 +1680,38 @@ static void pal_draw(HDC dc, int g, int cw, int ch, bool dump) { snprintf(p, sizeof p, "%s/win%d.bmp", pal_dump_dir, g); write_bmp(p, out, DW, DH); } + /* B2: the mono-MFD heads reserve a top+bottom band for the RIO button + * bezel (only when a serial=rio port is present); the display insets into + * the middle. Other heads and non-rio configs render full-window as before. */ + /* B2 (explode layout only): buttons draw FIRST (under), then the opaque + * display insets over their inner part -- only the outer LIP stays visible. + * MFD heads inset top/bottom; the radar insets left/right. */ + int hi = mfd_hi_for_g(g); + uint8_t lamps[72], pressed[72]; + bool have = RIO_GetPanelState(lamps, pressed); + bool mfd_bz = (hi >= 0) && have; /* g5..9 exist only in explode */ + bool radar_bz = (g == 0) && pal_radar_cw && have; /* radar bezel = explode only */ + glViewport(0, 0, cw, ch); glClearColor(0, 0, 0, 1); glClear(GL_COLOR_BUFFER_BIT); glDisable(GL_DEPTH_TEST); glDisable(GL_TEXTURE_2D); glDisable(GL_LIGHTING); - glPixelZoom((float)cw / DW, -(float)ch / DH); /* scale + flip y */ + if (mfd_bz) { + draw_mfd_bezel(hi, cw, ch, lamps, pressed); + int bh = ch - 2 * RIO_BEZEL_LIP; /* display band (= DH when ch=500) */ + if (bh < 1) bh = 1; + glViewport(0, RIO_BEZEL_LIP, cw, bh); /* GL origin bottom-left */ + glPixelZoom((float)cw / DW, -(float)bh / DH); + } else if (radar_bz) { + draw_radar_bezel(cw, ch, lamps, pressed); + int bw2 = cw - 2 * RIO_BEZEL_LIP; /* display band (= DW when cw=500) */ + int bh2 = ch - RIO_RADAR_BOT; /* leave the bottom indicator strip */ + if (bw2 < 1) bw2 = 1; + if (bh2 < 1) bh2 = 1; + glViewport(RIO_BEZEL_LIP, RIO_RADAR_BOT, bw2, bh2); + glPixelZoom((float)bw2 / DW, -(float)bh2 / DH); + } else { + glPixelZoom((float)cw / DW, -(float)ch / DH); /* scale + flip y */ + } glRasterPos2f(-1.0f, 1.0f); glDrawPixels(DW, DH, GL_RGB, GL_UNSIGNED_BYTE, out); SwapBuffers(dc); @@ -1644,9 +1822,9 @@ static DWORD WINAPI rt_main(LPVOID) { "MFD head B, 3 upper (bits 8-15 via pal2)", "MFD lower-left (HEAD A / red wire)", "MFD lower-right (HEAD A / green wire)", - "MFD upper-left (HEAD B / red wire)", + "MFD upper-right (HEAD B / red wire)", /* name swapped to match its screen position (sits on the right, x=1340) */ "MFD upper-center (HEAD B / green wire)", - "MFD upper-right (HEAD B / blue wire)" }; + "MFD upper-left (HEAD B / blue wire)" }; /* name swapped to match its screen position (sits on the left, x=20) */ static const int ck_x[5] = { 800, 0, 0, 1440, 2080 }; /* explode grid, all displays at native 640x480 (radar 480x640 * portrait, rotated upright): upper MFD row on top; below it the two @@ -1673,8 +1851,8 @@ static DWORD WINAPI rt_main(LPVOID) { int x, y, w, h; if (cockpit) { x = ck_x[g]; y = 0; w = 640; h = 480; } else if (explode) { x = ex_x[g]; y = ex_y[g]; - if (g == 0) { w = 480; h = 640; } /* portrait radar */ - else { w = 640; h = 480; } } + if (g == 0) { w = 500; h = 650; } /* portrait radar: +20 wide (side lips) +10 tall (bottom strip) */ + else { w = 640; h = 500; } } /* +20 tall: RIO top/bottom lips (B2) */ else { x = 700 + (slot % 3) * 500; y = 20 + (slot / 3) * 400; w = 480; h = 360; } slot++;