I have fully traced the widget. Here is the complete implementation spec.

---

# MessageBoard — Cockpit Gauge Widget Decode & Reconstruction Spec

**Verdict up front:** `MessageBoard` is *already fully declared* in `game/reconstructed/btl4gau3.hpp` and *documented in prose* in `btl4gau3.cpp`, but has **no method bodies, no `methodDescription`, and no registration** → the CFG keyword `messageBoard` is currently **parse-skipped**. This spec supplies the byte-exact bodies + registration, **corrects three field mislabels** in the existing header, and **confirms the data is DEFERRED (empty board)** — with a correction to the task hint about *which* subsystem is the blocker.

---

## 1. Identity

| Property | Value | Evidence |
|---|---|---|
| Class name | `MessageBoard` | id string `s_MessageBoard_0051bb61` (`part_014.c:3835`); already in `btl4gau3.hpp:260` |
| Base class | `GraphicGauge` (→ `Gauge`) | base ctor `FUN_00444818` (`part_014.c:3857`); `engine/MUNGA/GAUGE.h:400` |
| Config keyword | `"messageBoard"` | `content/GAUGE/L4GAUGE.CFG:4913` (1 use, in `Secondary1`) |
| **Make** | `@004cb678` = `FUN_004cb678` | `part_014.c:3825-3843` |
| **ctor** | `@004cb704` = `FUN_004cb704` | `part_014.c:3849-3866` |
| **dtor** | `@004cb788` = `FUN_004cb788` | `part_014.c:3872-3886` |
| **TestInstance** | `@004cb7e4` (→ `FUN_004448ac` = `GraphicGauge::TestInstance`) | `part_014.c:3892-3897` |
| ShowInstance (empty no-op vtbl slot) | `@004cb7f4` (bare `return`) | `part_014.c:3903-3907` |
| **BecameActive** | `@004cb7fc` = `FUN_004cb7fc` | `part_014.c:3913-3919` |
| **SetSource** (setter; **not** `SetEnable`) | `@004cb818` = `FUN_004cb818` | `part_014.c:3925-3930` |
| **Execute** | `@004cb82c` = `FUN_004cb82c` | `part_014.c:3936-4013` |
| vtable | `PTR_FUN_0051bddc` | ctor `part_014.c:3858`, dtor `:3876` |
| **sizeof** | **`0xA4`** (== Make alloc `FUN_00402298(0xa4)`) | `part_014.c:3832` |
| GUID | none | — |

There is **no `methodDescription` in the binary** for this widget (the binary's Make read raw `DAT_0051b43c..0051b508`); like `PlayerStatus`/`PilotList`/`VertTwoPartBar`, the reconstruction supplies a `methodDescription` and rewires Make to read `methodDescription.parameterList[]`.

---

## 2. What it DISPLAYS + the data binding (DEFERRED — blocker confirmed)

**What it draws:** the secondary MFD's **comm/status message ticker**. Two 128×32 cells at the port origin (`SetOrigin(x,y)`, `offset=(113,607)`):
- **Message cell** (view-rel `(0,0)`): blits one cell of the strip bitmap `btsmsgs.pcx`. Message id → grid cell `col = id&3` (×128 px), `row = id>>2` (×32 px): `srcRect = [cellX, cellY, cellX+0x7f, cellY+0x1f]` (`part_014.c:3969-3979`). `id == -1` → clear the cell to `color` (`:3963-3967`).
- **Name cell** (view-rel `(0x80,0)`): blits the *sender's* name bitmap from the App name/palette cache (`part_014.c:3983-4008`).

**The data source it reads (decoded from `FUN_004cb82c`):**
```
source = this->trackedMech          // this[0x24] @0x90   (set by SetSource @004cb818)
player = *(source + 0x190)          // mech -> owning BTPlayer   (:3949 == MECH_OWNING_PLAYER)
msg    = *(player + 0x1dc)          // BTPlayer's current status-message record   (:3951)
  msg == 0  -> messageId = -1, nameEntity = 0            (:3952-3955)
  else      -> nameEntity = *(msg+0x10), messageId = *(msg+0xc)   (:3957-3958)
nameEntity -> +0x1e0 = palette/name key -> App+0xc8 name-bitmap cache lookup (:3992-3994, 4002-4004)
```
`mech+0x190 = owningPlayer (BTPlayer*)` is confirmed by `btplayer.cpp:68` and the `MECH_OWNING_PLAYER` macro; `TeamStatusDisplay` uses the same `mech+0x190` (`btl4gau3.cpp:167`).

**Is the data LIVE now? NO — DEFERRED. Two independent blockers:**

1. **The source is never bound.** `SetSource` (`FUN_004cb818`, the *only* writer of `this[0x24]` besides the ctor's `=0`) has **zero recovered callers** (grep across `reference/decomp/all` returns only its own definition). The Make **ignores** its `entity` param (`FUN_004cb678` never touches `param_4`), so nothing binds a mech at construction either. → `trackedMech` stays `NULL` → the whole Execute body is gated `if (source != 0 …)` (`part_014.c:3949`) → **no-op**.

2. **The message-record feed is a NULL stub.** The record at `BTPlayer+0x1dc` is populated only by `AddStatusMessage(Player__StatusMessage*)`, and that is gated on `StatusMessagePool` — **a NULL bring-up stub** (`btplayer.cpp:135` `extern MemoryBlock *StatusMessagePool;`, guarded at `:508` `if (sender_owner && StatusMessagePool)`). No status messages are ever created, so even a bound mech would read `msg == 0` → messageId `-1` → empty.

**⚠ Correction to the task hint:** the source is **NOT** the `SubsystemMessageManager` (0xBD3). That class *is* reconstructed (`game/reconstructed/messmgr.cpp`) but it is the **per-mech DAMAGE/explosion hub** (`ConsolidateAndSendDamage`, `weaponExplosions`, `commonDamageInformation`) — its fields are damage records, not `{msgId, nameEntity}`, and the MessageBoard never references it. The real source is the **`BTPlayer` status-message queue** (`Player__StatusMessage`, `btplayer.cpp:513`) gated by `StatusMessagePool`. **That pool is the blocker to name.**

---

## 3. Class layout (byte-exact) + locks

`GraphicGauge` base ends at `0x90` (`Gauge` 0x48 + `GraphicsView localView` 0x48; confirmed by every sibling: `VertTwoPartBar` first field `tileImage@0x90`, `btl4gaug.cpp:1138`). MessageBoard adds `this[0x24..0x28]`:

```cpp
class MessageBoard : public GraphicGauge {
    ...
protected:
    Entity  *trackedMech;         // @0x90  this[0x24]  source mech (SetSource); ctor=0   ⟵ WAS "int enabled" (MISLABEL)
    char    *stripImage;          // @0x94  this[0x25]  interned "btsmsgs.pcx" (held bitmap ref)
    int      color;               // @0x98  this[0x26]  param_10 (CFG 4th arg = 0)
    int      previousNameId;      // @0x9C  this[0x27]  BecameActive=-1   ⟵ WAS "previousMessageId" (SWAPPED)
    int      previousMessageId;   // @0xA0  this[0x28]  BecameActive=-2   ⟵ WAS "previousNameId"    (SWAPPED)
    friend struct MessageBoardLayoutCheck;
};
```

**Three header corrections** vs the current `btl4gau3.hpp:273-279`:
- `int enabled` → **`Entity *trackedMech`** (it's a pointer dereferenced `*(src+0x190)` at `:3949`, not a bool; the setter is `SetSource`, not `SetEnable`).
- `previousMessageId`/`previousNameId` are **swapped**. Proof: BecameActive writes `+0x9c=-1`, `+0xa0=-2` (`:3916-3917`); Execute's *message-id* compare is against `+0xa0` (`:3960`) and the *name-entity* compare against `+0x9c` (`:3983`). So `+0x9c=previousNameId(-1)`, `+0xa0=previousMessageId(-2)`. (The prose at `btl4gau3.cpp:784` already states "previousNameId=-1; previousMessageId=-2" — the struct labels contradict the prose.)

**Locks (add to `btl4gau3.cpp`; protected members ⇒ `friend struct`):**
```cpp
struct MessageBoardLayoutCheck {
    static_assert(sizeof(GraphicGauge)               == 0x90, "GraphicGauge base size");
    static_assert(offsetof(MessageBoard, trackedMech)      == 0x90, "trackedMech");
    static_assert(offsetof(MessageBoard, stripImage)       == 0x94, "stripImage");
    static_assert(offsetof(MessageBoard, color)            == 0x98, "color");
    static_assert(offsetof(MessageBoard, previousNameId)   == 0x9C, "previousNameId");
    static_assert(offsetof(MessageBoard, previousMessageId)== 0xA0, "previousMessageId");
    static_assert(sizeof(MessageBoard)               == 0xA4, "MessageBoard sizeof == Make alloc 0xa4");
};
```

---

## 4. Full method bodies to write

### 4.0 CFG call shape → `methodDescription`
`content/GAUGE/L4GAUGE.CFG:4912-4917`:
```
offset = (113,607);
messageBoard(
    E,                # p[0] rate
    ModeAlwaysActive, # p[1] mode mask
    btsmsgs.pcx,      # p[2] strip bitmap
    0                 # p[3] draw/erase color
);
```
`offset` supplies `position` (x=113, y=607) to Make (the ctor's `SetOrigin`). 4 in-paren params:

```cpp
MethodDescription
    MessageBoard::methodDescription =
    {
        "messageBoard",
        MessageBoard::Make,
        {
            { ParameterDescription::typeRate,     NULL },   // p[0] rate (E)
            { ParameterDescription::typeModeMask, NULL },   // p[1] mode (ModeAlwaysActive)
            { ParameterDescription::typeString,   NULL },   // p[2] strip bitmap (btsmsgs.pcx)
            { ParameterDescription::typeColor,    NULL },   // p[3] draw/erase color (0)
            PARAMETER_DESCRIPTION_END
        }
    };
```

### 4.1 Deferred data bridge (declare near top of `btl4gau3.cpp`)
```cpp
// Resolve the viewpoint player's current HUD status message for `tracked_mech`.
// Returns True if the mech has a bound owning-player record; fills:
//   *messageId  -- btsmsgs.pcx strip cell index (-1 = no active message)
//   *nameBitmap -- sender's name bitmap (NULL = none / App name-cache not wired)
// DEFERRED: StatusMessagePool (btplayer.cpp) is a NULL stub + BTPlayer+0x1dc is
// never populated, so this returns False today -> the board stays empty.  Reads
// the raw BTPlayer/StatusMessage offsets in a COMPLETE-BTPlayer TU (never here),
// dodging the databinding trap.  Implement in btplayer.cpp / btl4mppr.cpp.
extern Logical BTResolveMessageBoard(Entity *tracked_mech, int *messageId, BitMap **nameBitmap);
```
(Provide a stub `Logical BTResolveMessageBoard(Entity*, int *id, BitMap **bmp){ *id=-1; *bmp=NULL; return False; }` in `btplayer.cpp` until the feed lands — mirrors `LookupPlayerNameBitmap`/`BTResolveRosterPilot`.)

### 4.2 Make — `@004cb678`
Alloc `0xa4`, construct, then presence-check the strip bitmap (`FUN_00442aec` Get / `FUN_00442c12` Release) and return whether it exists. `param_5`=renderer, `param_5+0x4c+4`=`bitMapBin`.
```cpp
Logical
    MessageBoard::Make(
        int             display_port_index,
        Vector2DOf<int> position,
        Entity          * /*entity -- ignored by the binary (FUN_004cb678)*/,
        GaugeRenderer   *gauge_renderer
    )
{
    ParameterDescription *p = methodDescription.parameterList;

    MessageBoard *gauge = (MessageBoard *)operator new(0xa4);        // FUN_00402298(0xa4)
    if (gauge != NULL)
    {
        new (gauge) MessageBoard(
            p[0].data.rate, p[1].data.modeMask,
            (L4GaugeRenderer *)gauge_renderer,
            display_port_index,             // graphics_port_number
            position.x, position.y,         // x, y  (offset=(113,607))
            p[2].data.string,               // strip image (btsmsgs.pcx)
            p[3].data.color,                // color (0)
            "MessageBoard");
    }

    // Presence-check the strip bitmap (binary returns this as the Make result).
    L4Warehouse *wh = (L4Warehouse *)gauge_renderer->warehousePointer;   // renderer+0x4c
    BitMap *strip = wh->bitMapBin.Get(p[2].data.string);                 // FUN_00442aec
    if (strip == NULL)
        return False;
    wh->bitMapBin.Release(p[2].data.string);                            // FUN_00442c12
    return True;
}
```

### 4.3 ctor — `@004cb704`
```cpp
MessageBoard::MessageBoard(
    GaugeRate rate, ModeMask mode_mask, L4GaugeRenderer *renderer_in,
    int graphics_port_number, int x, int y,
    const char *strip_image, int color_in, const char *identification_string)
  : GraphicGauge(rate, mode_mask, renderer_in, 0,     // FUN_00444818 (owner 0 folded in)
                 graphics_port_number, identification_string)
{
    // vtable PTR_FUN_0051bddc set by the base->derived chain.
    stripImage = new char[strlen(strip_image) + 1];   // FUN_004700ac (intern a private copy)
    strcpy(stripImage, strip_image);
    L4Warehouse *wh = (L4Warehouse *)renderer_in->warehousePointer;
    wh->bitMapBin.Get(stripImage);                    // FUN_00442aec (hold a ref for life)

    localView.SetOrigin(x, y);                        // this+0x48 vtbl+0x10
    color       = color_in;                           // this[0x26]
    trackedMech = NULL;                               // this[0x24]  (param_1[0x24]=0, :3864)
    // previousNameId / previousMessageId set by BecameActive (byte-faithful).
}
```

### 4.4 dtor — `@004cb788`
Release the strip ref (keyed on the name, **before** the free), free the name; **do not** write the base-dtor call (compiler emits `FUN_00444870`).
```cpp
MessageBoard::~MessageBoard()
{
    L4Warehouse *wh = (L4Warehouse *)renderer->warehousePointer;
    wh->bitMapBin.Release(stripImage);                // FUN_00442c12
    delete[] stripImage;                              // FUN_004022e8
    stripImage = NULL;
}
```

### 4.5 TestInstance — `@004cb7e4`
```cpp
Logical MessageBoard::TestInstance() const { return GraphicGauge::TestInstance(); }  // FUN_004448ac
```
(`@004cb7f4` is an empty no-op virtual slot — `ShowInstance`; omit it and inherit, as `PlayerStatus`/`PilotList` do.)

### 4.6 SetSource — `@004cb818`  (retained; no recovered caller ⇒ deferred)
```cpp
void MessageBoard::SetSource(Entity *mech) { trackedMech = mech; }   // this[0x24] = param_2
```

### 4.7 BecameActive — `@004cb7fc`
```cpp
void MessageBoard::BecameActive()
{
    previousNameId    = -1;    // this[0x27] @0x9c  (0xffffffff, :3916)
    previousMessageId = -2;    // this[0x28] @0xa0  (0xfffffffe, :3917)
}
```

### 4.8 Execute — `@004cb82c`  (safe reconstruction; faithful draw path via the bridge)
```cpp
void MessageBoard::Execute()
{
    // FAITHFUL LITERAL: the board only draws while a source mech is bound.  The
    // binder (SetSource @004cb818) has no recovered caller AND the per-player
    // status feed (StatusMessagePool, btplayer.cpp) is a NULL stub, so trackedMech
    // stays NULL and Execute is a safe no-op == the DEFERRED / empty board.
    if (trackedMech == NULL)                                  // binary gate :3949
        return;

    // Message record via the safe bridge (NOT raw *(mech+0x190)/+0x1dc offsets:
    // the databinding trap -- our compiled BTPlayer/StatusMessage layout != 1995).
    int     messageId  = -1;
    BitMap *nameBitmap = NULL;
    BTResolveMessageBoard(trackedMech, &messageId, &nameBitmap);

    // --- message strip cell (:3960-3981) -------------------------------------
    if (messageId != previousMessageId)
    {
        previousMessageId = messageId;
        localView.MoveToAbsolute(0, 0);                       // vtbl+0x24 :3962
        if (messageId == -1)                                  // no message -> clear
        {
            localView.SetColor(color);                        // vtbl+0x18 :3964
            localView.DrawFilledRectangleToRelative(0x80, 0x20);  // vtbl+0x4c :3966
        }
        else
        {
            int cellX = (messageId & 3) << 7;                 // :3969
            int cellY = (messageId >> 2) << 5;                // :3970
            localView.SetColor(7);                            // FUN_004c2f88()==7 :3971
            L4Warehouse *wh = (L4Warehouse *)renderer->warehousePointer;
            BitMap *strip = wh->bitMapBin.Get(stripImage);    // FUN_00442aec :3974
            if (strip != NULL)
                localView.DrawBitMapOpaque(color, 0, strip,   // vtbl+0x58 :3976
                    cellX, cellY, cellX + 0x7f, cellY + 0x1f);
            wh->bitMapBin.Release(stripImage);                // FUN_00442c12 :3980
        }
    }

    // --- sender name cell (:3983-4009) ---------------------------------------
    // Binary tracks the name ENTITY pointer + reads (+0x1e0)->App+0xc8 cache; the
    // safe bridge form returns the resolved bitmap directly (App name cache is not
    // wired -> NULL -> the "clear" branch, same deferral as PilotList).
    int nameState = (nameBitmap != NULL) ? 1 : 0;
    if (nameState != previousNameId)
    {
        localView.MoveToAbsolute(0x80, 0);                    // vtbl+0x24 (name cell x=128) :3984-3985
        if (nameBitmap == NULL)
        {
            localView.SetColor(color);                        // vtbl+0x18 :3987
            localView.DrawFilledRectangleToRelative(0x80, 0x20);  // vtbl+0x4c :3989
        }
        else
        {
            localView.SetColor(7);                            // :4000
            localView.DrawBitMapOpaque(color, 0, nameBitmap, 0, 0,   // vtbl+0x58 :4005
                nameBitmap->Data.Size.x - 1, nameBitmap->Data.Size.y - 1);
        }
        previousNameId = nameState;                           // :4009
    }
}
```
GraphicsView vtable slots confirmed from `engine/MUNGA/GRAPH2D.h`: `SetOrigin@0x10:557`, `SetColor@0x18:568`, `MoveToAbsolute@0x24:581`, `DrawFilledRectangleToAbsolute@0x44:621`, `DrawFilledRectangleToRelative@0x4c:624`, `DrawBitMap@0x54:636`, `DrawBitMapOpaque@0x58:656`, `DrawPixelMap8@0x5c:679`. The binary's name-*erase*-previous path uses `DrawBitMap` (vtbl+0x54, `part_014.c:3995`) — collapsed into the clear branch above since no previous bitmap is tracked in the safe bridge form.

### 4.9 Registration (`btl4grnd.cpp`, before `&BTL4ChainToPrevious` at line 150)
```cpp
    &PilotList::methodDescription,          // "pilotList"
    &GeneratorCluster::methodDescription,   // "GeneratorCluster"
    &MessageBoard::methodDescription,       // "messageBoard" -- sec-MFD comm/status ticker (DEFERRED source)
    &BTL4ChainToPrevious
```
Add `static MethodDescription methodDescription;` to the `MessageBoard` class in `btl4gau3.hpp`.

---

## 5. Systemic-check verdicts

- **Container-Execute rule:** N/A — `MessageBoard` is a **leaf** `GraphicGauge`, not a container (no child gauges, no `Execute=Fail` base risk). It **overrides `Execute`** (`FUN_004cb82c`) and **overrides `BecameActive`** (`FUN_004cb7fc`) with a **non-inactivating** body (sets sentinels only) → satisfies the rule inherently. ✅
- **Shadow fields:** none. Derived fields start at `0x90` (`GraphicGauge` ends there); `renderer`/`localView` are inherited base members, accessed by name — no re-declaration. ✅
- **Alias fields:** none. `trackedMech`/`stripImage`/`color`/`previousNameId`/`previousMessageId` are all genuine new slots (`0x90`–`0xA3`). ✅
- **Phantom fields:** none — last member `previousMessageId@0xA0` (4 bytes) ends at `0xA4 == sizeof == Make alloc`. ✅
- **Field mislabels (fix in header):** `enabled`→`trackedMech` (pointer, not bool; setter is `SetSource` not `SetEnable`); `previousMessageId`/`previousNameId` **swapped**. ✅ (§3)
- **Databinding NULL risks:** the raw `*(mech+0x190)`, `*(player+0x1dc)`, `nameEntity+0x1e0`, `App+0xc8` reads are the **databinding trap** — kept **out** of this TU via `BTResolveMessageBoard` (complete-BTPlayer TU). The `bitMapBin.Get` result is NULL-checked (`:3975`) before the strip blit. ✅
- **Every-mech crash risk:** none. With `trackedMech==NULL` (always, until wired) Execute early-returns — zero draws, zero visual change to the sec-MFD composite, byte-identical to the current parse-skipped behavior. Make/ctor allocate + hold one bitmap ref (released in dtor, per the corrected base-dtor-glue rule — no explicit base call). ✅
- **`/FORCE` stub risk:** `BTResolveMessageBoard` **must** have a real (stub) definition, or the `/FORCE` link turns the call into a runtime AV (CLAUDE.md gotcha). Provide the returns-`False` stub in `btplayer.cpp`. ⚠

---

## 6. Confidence + unknowns

**Confidence: HIGH** for Make/ctor/dtor/BecameActive/TestInstance/SetSource/layout/registration — all fully recovered, sizeof/vtable/offsets cross-checked against the `VertTwoPartBar`/`PlayerStatus`/`PilotList` templates and the GraphicsView vtable header. The widget will **build, register (stop parse-skipping), construct, and run as a safe no-op**.

**Confidence: HIGH** on the blocker: the board is **DEFERRED/empty** because (1) `SetSource` is never called (no recovered caller) and (2) `StatusMessagePool` is a NULL stub (`btplayer.cpp:135/508`). Both confirmed by grep + source.

**Unknowns / follow-ups (do not block the landing):**
1. **Who calls `SetSource`.** No caller in the recovered window — likely in the gauge renderer's viewpoint-bind step (`GAUGREND`/`btl4grnd`, not in `part_014.c`). To bring the board **alive** without it, self-resolve in Execute: `trackedMech = (Entity*)application->GetViewpointEntity();` (`APP.h:373`) — the accepted approach `PlayerStatus`/`PilotList` already use. Left as the safe no-op by default to guarantee zero regression on the dev-gauge composite.
2. **The `Player__StatusMessage` record layout** (`+0xc` messageId, `+0x10` nameEntity) and the App+0xc8 name-bitmap cache — needed inside `BTResolveMessageBoard` once `StatusMessagePool` is wired. These live in `btplayer.cpp`'s domain (`Player__StatusMessage` ctor at `:513`); decode `FUN_0042e580`=`AddStatusMessage` and the message record ctor to expose `messageId`/`nameEntity` as compiled accessors.
3. **`btsmsgs.pcx` cell grid** (4 cols × N rows of 128×32) is derived from the `(id&3)<<7,(id>>2)<<5` math (`:3969-3970`), not from the file — verify against the asset when the feed is live.
4. Cosmetic: the empty-state `color==0` clear boxes are never painted in the default landing (guarded off); if self-resolve is enabled later, confirm palette index 0 on the sec MFD is background (else the message region paints black — faithful, but verify visually).