diff --git a/BUILD.md b/BUILD.md
index 28be5dc..4dddb47 100644
--- a/BUILD.md
+++ b/BUILD.md
@@ -112,7 +112,19 @@ Two new environment options remove the hardware dependency entirely:
Hot-plugging works; with no controller it falls back to keyboard only.
- **`L4PLASMA=SCREEN`** — renders the 128×32 plasma display as its own
desktop window ("Plasma Display", plasma orange, `L4PLASMASCALE` sets the
- pixel size, default 4). No COM port. Closing the window hides it.
+ pixel size, default 4). It opens directly below the main view;
+ `L4PLASMAPOS=x,y` overrides. No COM port. Closing the window hides it.
+- **`L4MFDSPLIT=1`** — un-packs the 7-display cockpit. The pod hardware
+ drove five monochrome MFDs from the color channels of two video outputs
+ (window 3 = upper MFDs in R/G/B, window 4 = lower MFDs in R/G) and
+ mounted the map display portrait. With split mode the game itself opens
+ seven proper windows — main view, five green-screen MFDs ("MFD upper
+ left/center/right", "MFD lower left/right") and the 90°-rotated "Map" —
+ rendered CPU-side from the shared gauge canvas; the packed windows stay
+ hidden. `L4MFDSCALE` sets the view size in percent of the gauge canvas
+ (default 50). Default layout is the pod grid to the right of the main
+ view; all windows are draggable. This replaces the external
+ BitBlt-mirror launcher wrapper.
Bindings (vRIO's default profile, condensed):
@@ -136,6 +148,7 @@ DPLARG=1
L4DPLCFG=RPDPL.INI
L4GAUGE=640x480x16
L4PLASMA=SCREEN
+L4MFDSPLIT=1
TARGETFPS=60
```
diff --git a/MUNGA_L4/L4MFDVIEW.cpp b/MUNGA_L4/L4MFDVIEW.cpp
new file mode 100644
index 0000000..39a0058
--- /dev/null
+++ b/MUNGA_L4/L4MFDVIEW.cpp
@@ -0,0 +1,168 @@
+#include "mungal4.h"
+#pragma hdrstop
+
+#include "l4mfdview.h"
+
+namespace
+{
+ const char mfdViewClass[] = "RPMFDView";
+ const char mfdViewProp[] = "RPMFDViewBlit";
+
+ struct MFDViewBlit
+ {
+ BITMAPINFOHEADER header;
+ void *pixels;
+ int sourceWidth;
+ int sourceHeight;
+ };
+
+ LRESULT CALLBACK
+ MFDViewWndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
+ {
+ switch (message)
+ {
+ case WM_PAINT:
+ {
+ PAINTSTRUCT ps;
+ HDC hdc = BeginPaint(hwnd, &ps);
+ const MFDViewBlit *blit =
+ (const MFDViewBlit *) GetPropA(hwnd, mfdViewProp);
+ if (blit != NULL && blit->pixels != NULL)
+ {
+ RECT client;
+ GetClientRect(hwnd, &client);
+ SetStretchBltMode(hdc, COLORONCOLOR);
+ StretchDIBits(
+ hdc,
+ 0, 0, client.right, client.bottom,
+ 0, 0, blit->sourceWidth, blit->sourceHeight,
+ blit->pixels, (const BITMAPINFO *) &blit->header,
+ DIB_RGB_COLORS, SRCCOPY
+ );
+ }
+ EndPaint(hwnd, &ps);
+ }
+ return 0;
+
+ case WM_CLOSE:
+ // Part of the cockpit; just hide it.
+ ShowWindow(hwnd, SW_HIDE);
+ return 0;
+
+ case WM_ERASEBKGND:
+ return 1;
+ }
+ return DefWindowProcA(hwnd, message, wParam, lParam);
+ }
+}
+
+MFDSplitView::MFDSplitView(
+ const char *title,
+ int source_width,
+ int source_height,
+ int client_width,
+ int client_height,
+ int x,
+ int y
+)
+{
+ Check_Pointer(this);
+
+ sourceWidth = source_width;
+ sourceHeight = source_height;
+
+ pixels = new unsigned long[source_width * source_height];
+ memset(pixels, 0, source_width * source_height * sizeof(unsigned long));
+
+ MFDViewBlit *blit = new MFDViewBlit;
+ memset(blit, 0, sizeof(MFDViewBlit));
+ blit->header.biSize = sizeof(BITMAPINFOHEADER);
+ blit->header.biWidth = source_width;
+ blit->header.biHeight = -source_height; // top-down
+ blit->header.biPlanes = 1;
+ blit->header.biBitCount = 32;
+ blit->header.biCompression = BI_RGB;
+ blit->pixels = pixels;
+ blit->sourceWidth = source_width;
+ blit->sourceHeight = source_height;
+ blitInfo = blit;
+
+ HINSTANCE instance = GetModuleHandleA(NULL);
+
+ static Logical class_registered = False;
+ if (!class_registered)
+ {
+ class_registered = True;
+
+ WNDCLASSA window_class;
+ memset(&window_class, 0, sizeof(window_class));
+ window_class.style = CS_HREDRAW | CS_VREDRAW;
+ window_class.lpfnWndProc = MFDViewWndProc;
+ window_class.hInstance = instance;
+ window_class.hCursor = LoadCursor(NULL, IDC_ARROW);
+ window_class.hbrBackground = (HBRUSH) GetStockObject(BLACK_BRUSH);
+ window_class.lpszClassName = mfdViewClass;
+ RegisterClassA(&window_class);
+ }
+
+ DWORD style = WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX;
+ RECT bounds;
+ bounds.left = 0;
+ bounds.top = 0;
+ bounds.right = client_width;
+ bounds.bottom = client_height;
+ AdjustWindowRect(&bounds, style, FALSE);
+
+ window = CreateWindowExA(
+ 0,
+ mfdViewClass,
+ title,
+ style,
+ x, y,
+ bounds.right - bounds.left,
+ bounds.bottom - bounds.top,
+ NULL, NULL, instance, NULL
+ );
+
+ if (window != NULL)
+ {
+ SetPropA((HWND) window, mfdViewProp, blitInfo);
+ ShowWindow((HWND) window, SW_SHOWNOACTIVATE);
+ }
+ else
+ {
+ DEBUG_STREAM << "MFDSplitView: window creation failed (" << title << ")\n" << std::flush;
+ }
+}
+
+MFDSplitView::~MFDSplitView()
+{
+ Check_Pointer(this);
+
+ if (window != NULL)
+ {
+ RemovePropA((HWND) window, mfdViewProp);
+ DestroyWindow((HWND) window);
+ window = NULL;
+ }
+ delete (MFDViewBlit *) blitInfo;
+ blitInfo = NULL;
+ delete [] pixels;
+ pixels = NULL;
+}
+
+Logical
+ MFDSplitView::TestInstance() const
+{
+ return pixels != NULL;
+}
+
+void
+ MFDSplitView::Repaint()
+{
+ Check_Pointer(this);
+ if (window != NULL)
+ {
+ InvalidateRect((HWND) window, NULL, FALSE);
+ }
+}
diff --git a/MUNGA_L4/L4MFDVIEW.h b/MUNGA_L4/L4MFDVIEW.h
new file mode 100644
index 0000000..45b3983
--- /dev/null
+++ b/MUNGA_L4/L4MFDVIEW.h
@@ -0,0 +1,53 @@
+#pragma once
+
+#include "..\munga\style.h"
+
+//########################################################################
+//############################ MFDSplitView ##############################
+//########################################################################
+//
+// One desktop window showing a single cockpit display, used by SVGA16's
+// split mode (L4MFDSPLIT=1). The pod hardware packed the five monochrome
+// MFDs into the color channels of two video outputs and mounted the map
+// display rotated; on a desktop each display gets its own window instead.
+//
+// SVGA16 fills Pixels() (32bpp top-down, sourceWidth x sourceHeight as
+// passed - already swapped by the caller for the rotated map) and calls
+// Repaint(). Closing a view window just hides it.
+//
+class MFDSplitView
+{
+public:
+ MFDSplitView(
+ const char *title,
+ int source_width,
+ int source_height,
+ int client_width,
+ int client_height,
+ int x,
+ int y
+ );
+
+ ~MFDSplitView();
+
+ Logical
+ TestInstance() const;
+
+ unsigned long *
+ Pixels()
+ { return pixels; }
+
+ void
+ Repaint();
+
+protected:
+ void
+ *window; // HWND
+ void
+ *blitInfo; // MFDViewBlit (header + source dims), see .cpp
+ unsigned long
+ *pixels;
+ int
+ sourceWidth,
+ sourceHeight;
+};
diff --git a/MUNGA_L4/L4PLASMASCREEN.cpp b/MUNGA_L4/L4PLASMASCREEN.cpp
index 5aca79e..66388b5 100644
--- a/MUNGA_L4/L4PLASMASCREEN.cpp
+++ b/MUNGA_L4/L4PLASMASCREEN.cpp
@@ -2,6 +2,7 @@
#pragma hdrstop
#include "l4plasmascreen.h"
+#include "l4app.h"
namespace
{
@@ -163,14 +164,52 @@ void
bounds.bottom = plasmaHeight * scale;
AdjustWindowRect(&bounds, WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX, FALSE);
+ int window_w = bounds.right - bounds.left;
+ int window_h = bounds.bottom - bounds.top;
+
+ //---------------------------------------------------------------
+ // Default position: directly below the main view (the pod mounts
+ // the glass adjacent to the displays). The main window sits at
+ // (0,0) with the -res size; clamp to the work area in case the
+ // main view fills the screen. L4PLASMAPOS=x,y overrides.
+ //---------------------------------------------------------------
+ int x = 0;
+ int y = (int) L4Application::GetScreenHeight();
+
+ const char *pos_string = getenv("L4PLASMAPOS");
+ if (pos_string != NULL)
+ {
+ int px = 0, py = 0;
+ if (sscanf(pos_string, "%d,%d", &px, &py) == 2)
+ {
+ x = px;
+ y = py;
+ }
+ }
+ else
+ {
+ RECT work;
+ if (SystemParametersInfoA(SPI_GETWORKAREA, 0, &work, 0))
+ {
+ if (y + window_h > work.bottom)
+ {
+ y = work.bottom - window_h;
+ }
+ if (y < 0)
+ {
+ y = 0;
+ }
+ }
+ }
+
window = CreateWindowExA(
0,
plasmaWindowClass,
"Plasma Display",
WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX,
- CW_USEDEFAULT, CW_USEDEFAULT,
- bounds.right - bounds.left,
- bounds.bottom - bounds.top,
+ x, y,
+ window_w,
+ window_h,
NULL, NULL, instance, NULL
);
diff --git a/MUNGA_L4/L4VB16.cpp b/MUNGA_L4/L4VB16.cpp
index 942fad7..bdc3e35 100644
--- a/MUNGA_L4/L4VB16.cpp
+++ b/MUNGA_L4/L4VB16.cpp
@@ -2,6 +2,7 @@
#pragma hdrstop
#include "l4vb16.h"
+#include "l4mfdview.h"
#include "../munga/gaugrend.h"
#include "L4VIDEO.h"
#include "DXUtils.h"
@@ -176,7 +177,12 @@ void SVGA16::BuildWindows(unsigned int width, unsigned int height, bool windowed
gaugeWindows[j] = CreateWindowEx(0, L"MainWndClass", L"RPL4", WS_BORDER, surfaceWindowRect.left, surfaceWindowRect.top, surfaceWindowRect.right-surfaceWindowRect.left, surfaceWindowRect.bottom-surfaceWindowRect.top, (HWND)NULL, (HMENU)NULL, L4Application::GetAppInstance(), (LPVOID)NULL);
if (gaugeWindows[j])
{
- ShowWindow(gaugeWindows[j], SW_SHOW);
+ // In split-view mode the packed windows are only kept as D3D
+ // device targets; the split windows are what the player sees.
+ if (!splitViews)
+ {
+ ShowWindow(gaugeWindows[j], SW_SHOW);
+ }
memset(&mPresentParams[j], 0, sizeof(D3DPRESENT_PARAMETERS));
@@ -3809,7 +3815,76 @@ SVGA16::SVGA16(
int *aux2Index
):Video16BitBuffered(init_width, init_height)
{
+ //------------------------------------------------------------------
+ // Split-view mode: decide before BuildWindows so the packed gauge
+ // windows can stay hidden.
+ //------------------------------------------------------------------
+ splitViews = False;
+ {
+ const char *split_string = getenv("L4MFDSPLIT");
+ if (split_string != NULL && atoi(split_string) != 0)
+ {
+ splitViews = True;
+ }
+ }
+ for (int view = 0; view < SplitViewCount; ++view)
+ {
+ splitView[view] = NULL;
+ }
+
BuildWindows(init_width,init_height,windowed, secondaryIndex, aux1Index, aux2Index);
+
+ if (splitViews)
+ {
+ //---------------------------------------------------------------
+ // One window per cockpit display. L4MFDSCALE is the view size in
+ // percent of the gauge canvas (default 50). Default layout is the
+ // pod grid to the right of the main view:
+ // [ main ] [ UL ] [ C ] [ UR ]
+ // [ LL ] [ map ] [ LR ]
+ //---------------------------------------------------------------
+ int scale_percent = 50;
+ const char *scale_string = getenv("L4MFDSCALE");
+ if (scale_string != NULL)
+ {
+ int requested = atoi(scale_string);
+ if (requested >= 10 && requested <= 200)
+ {
+ scale_percent = requested;
+ }
+ }
+
+ int cell_w = (init_width * scale_percent) / 100;
+ int cell_h = (init_height * scale_percent) / 100;
+ int frame_h = GetSystemMetrics(SM_CYCAPTION) + 2 * GetSystemMetrics(SM_CYFIXEDFRAME);
+ int frame_w = 2 * GetSystemMetrics(SM_CXFIXEDFRAME);
+ int grid_x = init_width + frame_w;
+ int row2_y = cell_h + frame_h;
+
+ splitView[SplitMFDUpperLeft] = new MFDSplitView(
+ "MFD upper left", init_width, init_height,
+ cell_w, cell_h, grid_x, 0);
+ splitView[SplitMFDUpperCenter] = new MFDSplitView(
+ "MFD upper center", init_width, init_height,
+ cell_w, cell_h, grid_x + (cell_w + frame_w), 0);
+ splitView[SplitMFDUpperRight] = new MFDSplitView(
+ "MFD upper right", init_width, init_height,
+ cell_w, cell_h, grid_x + 2 * (cell_w + frame_w), 0);
+ splitView[SplitMFDLowerLeft] = new MFDSplitView(
+ "MFD lower left", init_width, init_height,
+ cell_w, cell_h, grid_x, row2_y);
+ splitView[SplitMFDLowerRight] = new MFDSplitView(
+ "MFD lower right", init_width, init_height,
+ cell_w, cell_h, grid_x + 2 * (cell_w + frame_w), row2_y);
+ // map is portrait: source rotated 90 degrees clockwise
+ splitView[SplitMap] = new MFDSplitView(
+ "Map", init_height, init_width,
+ cell_h, cell_w,
+ grid_x + (cell_w + frame_w) + (cell_w - cell_h) / 2, row2_y);
+
+ DEBUG_STREAM << "SVGA16: split views active - 5 MFD windows + rotated map (scale "
+ << scale_percent << "%)\n" << std::flush;
+ }
//STUBBED: VIDEO RB 1/15/07
# if defined(DEBUG)
Tell("SVGA16::SVGA16()\n");
@@ -3993,6 +4068,13 @@ SVGA16::~SVGA16()
// Set VWE video splitter clock divider
//---------------------------------------------------------
//SVGASetSplitterClock(False);
+
+ for (int view = 0; view < SplitViewCount; ++view)
+ {
+ delete splitView[view];
+ splitView[view] = NULL;
+ }
+
Check_Fpu();
}
@@ -4029,6 +4111,43 @@ int GetShiftAmount(DWORD mask)
return amount;
}
+//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+// Render one MFD's bit-slice of the shared canvas into its split-view
+// window as a green screen. The mask arrives in the duplicated
+// (mask | mask<<16) form the packed path uses.
+//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+void SVGA16::FillSplitMFD(
+ SplitViewID view,
+ DWORD mask,
+ int shift
+)
+{
+ if (splitView[view] == NULL)
+ {
+ return;
+ }
+
+ Word mask16 = (Word)(mask & 0xFFFF);
+ Word max_value = (Word)(mask16 >> shift);
+ if (max_value == 0)
+ {
+ return;
+ }
+
+ Word *source = pixelBuffer.Data.MapPointer;
+ unsigned long *dest = splitView[view]->Pixels();
+ int count = pixelBuffer.Data.Size.x * pixelBuffer.Data.Size.y;
+
+ for (int i = 0; i < count; ++i)
+ {
+ unsigned long value = (unsigned long)((*source++ & mask16) >> shift);
+ unsigned long green = (value * 255ul) / max_value;
+ *dest++ = green << 8;
+ }
+
+ splitView[view]->Repaint();
+}
+
Logical SVGA16::Update(Logical forceAll)
{
HRESULT hr;
@@ -4101,6 +4220,57 @@ Logical SVGA16::Update(Logical forceAll)
}
}
+ //------------------------------------------------------------------
+ // Split views: render this window's displays into their own desktop
+ // windows, straight from the shared canvas (CPU-side, no D3D).
+ //------------------------------------------------------------------
+ if (splitViews)
+ {
+ if (mDisplayToUpdate == 0)
+ {
+ 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)
+ {
+ 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();
+ }
+ }
+ else if (mDisplayToUpdate == 1)
+ {
+ FillSplitMFD(SplitMFDUpperLeft, ulMask, ulMask_sh);
+ FillSplitMFD(SplitMFDUpperCenter, ucMask, ucMask_sh);
+ FillSplitMFD(SplitMFDUpperRight, urMask, urMask_sh);
+ if (NUMGAUGEWINDOWS < 3)
+ {
+ // spanning mode: the lower MFDs ride this window too
+ FillSplitMFD(SplitMFDLowerLeft, llMask, llMask_sh);
+ FillSplitMFD(SplitMFDLowerRight, lrMask, lrMask_sh);
+ }
+ }
+ else
+ {
+ FillSplitMFD(SplitMFDLowerLeft, llMask, llMask_sh);
+ FillSplitMFD(SplitMFDLowerRight, lrMask, lrMask_sh);
+ }
+ }
+
Word *data = pixelBuffer.Data.MapPointer;
D3DLOCKED_RECT rect;
diff --git a/MUNGA_L4/L4VB16.h b/MUNGA_L4/L4VB16.h
index bc1b987..8fc93ae 100644
--- a/MUNGA_L4/L4VB16.h
+++ b/MUNGA_L4/L4VB16.h
@@ -7,6 +7,7 @@
class L4graphicsPort;
+class MFDSplitView;
//########################################################################
//######################### Video16BitBuffered ###########################
@@ -291,6 +292,33 @@ private:
RECT *mSurfaceRects;
int mDisplayToUpdate;
+
+ //------------------------------------------------------------------
+ // Split-view mode (L4MFDSPLIT=1): the five channel-packed MFDs and
+ // the rotated map render as their own desktop windows; the packed
+ // D3D gauge windows stay hidden. See L4MFDVIEW.h.
+ //------------------------------------------------------------------
+ enum SplitViewID {
+ SplitMFDUpperLeft = 0,
+ SplitMFDUpperCenter,
+ SplitMFDUpperRight,
+ SplitMFDLowerLeft,
+ SplitMFDLowerRight,
+ SplitMap,
+ SplitViewCount
+ };
+
+ void
+ FillSplitMFD(
+ SplitViewID view,
+ DWORD mask16,
+ int shift
+ );
+
+ Logical
+ splitViews;
+ MFDSplitView
+ *splitView[SplitViewCount];
};
//########################################################################
diff --git a/MUNGA_L4/Munga_L4.vcxproj b/MUNGA_L4/Munga_L4.vcxproj
index 184deb5..e721fc9 100644
--- a/MUNGA_L4/Munga_L4.vcxproj
+++ b/MUNGA_L4/Munga_L4.vcxproj
@@ -249,6 +249,7 @@
+
@@ -438,6 +439,7 @@
+
diff --git a/MUNGA_L4/Munga_L4.vcxproj.filters b/MUNGA_L4/Munga_L4.vcxproj.filters
index 18a32c0..b3070e4 100644
--- a/MUNGA_L4/Munga_L4.vcxproj.filters
+++ b/MUNGA_L4/Munga_L4.vcxproj.filters
@@ -564,6 +564,9 @@
Source Files\MUNGA_L4
+
+ Source Files\MUNGA_L4
+
Source Files\MUNGA_L4
@@ -1127,6 +1130,9 @@
Header Files\MUNGA_L4
+
+ Header Files\MUNGA_L4
+
Header Files\MUNGA_L4
diff --git a/docs/RP412-ROADMAP.md b/docs/RP412-ROADMAP.md
index d2cf4b7..4cd8c8c 100644
--- a/docs/RP412-ROADMAP.md
+++ b/docs/RP412-ROADMAP.md
@@ -50,6 +50,12 @@ so press-feedback works like the real button field.
`Video8BitBuffered` surface into a desktop "Plasma Display" window in
plasma orange — verified drawing live game content (score readout).
See BUILD.md §4 for bindings and the desktop `environ.ini`.
+- ✅ **7-display cockpit in-engine** (`L4MFDSPLIT=1`): the five MFDs the pod
+ packed into the color channels of two video outputs, plus the
+ portrait-mounted map, now open as their own windows (green-screen MFDs,
+ 90°-rotated map), rendered CPU-side from the shared gauge canvas — the
+ external BitBlt-mirror wrapper is obsolete. Verified visually (MFD score
+ readout, full tactical map).
- Next in A: on-screen RIO panel fed by `PadRIO::lampState[]` (the
cockpit-feel layout — vRIO button clusters around the displays); port the
full vRIO bindings-file model (deflect/rate/deadzone per axis, rebinding);
diff --git a/pack-dist.ps1 b/pack-dist.ps1
index 753accc..d00cbda 100644
--- a/pack-dist.ps1
+++ b/pack-dist.ps1
@@ -67,6 +67,7 @@ DPLARG=1
L4DPLCFG=RPDPL.INI
L4GAUGE=640x480x16
L4PLASMA=SCREEN
+L4MFDSPLIT=1
TARGETFPS=60
"@
@@ -94,9 +95,12 @@ Controls (XInput controller and/or keyboard):
DPad / arrow keys joystick hat (look)
Start,Back / F1,F2 config buttons
-The "Plasma Display" window is the pod's plasma glass. Closing it just
-hides it. environ.ini options: L4PADFLIP=XY inverts the stick axes,
-L4PLASMASCALE=n sets the plasma pixel size (default 4).
+The full pod cockpit comes up as separate windows: the main view, five
+green MFD screens, the portrait map, and the orange plasma glass below
+the main view. Drag them wherever you like; closing one just hides it.
+environ.ini options: L4PADFLIP=XY inverts the stick axes, L4MFDSCALE=n
+sizes the MFD/map windows (percent, default 50), L4PLASMASCALE=n sets
+the plasma pixel size (default 4), L4PLASMAPOS=x,y places the plasma.
Known prototype notes: pods race untextured (the player1-8 skins come
from the presets system, not shipped data), and text drawn on the plasma