FE: the full authentic BattleTech mission-option catalog (TeslaSuite console)

The mission-control menu was missing player experience + many options.  Added
the complete set recovered from the Mac 4.10 operator console's BT:: adventure
tree (TeslaSuite/410console/.../Console.ini) -- the tag= values are the egg
fields; cross-checked vs the port spec + reference/cavern.egg:

  NEW groups: SCENARIO (Free For All / No Return respawn role), EXPERIENCE
  (novice/standard/veteran/expert), BADGE (7 team emblems), PATCH (8 badge
  colours), DROP ZONE (one..five), ADV. DAMAGE (on/off).
  CORRECTED: TIME (evening, not 'dusk'); COLOR (full 7: +Brown/Green/Grey/Tan,
  Red->Crimson, Gray->Grey); MECH (full 18 with console friendly names ->
  tags, was 8).

btl4fe.hpp: BTFePilot gains experience/badge/patch/dropzone/advancedDamage;
BTFeMission gains roleKey/roleModel.  The egg writer emits the selected values
(were hardcoded expert/VGL/Yellow/one/advancedDamage=1); No-Return role model
= the game's 8.3 resource 'noretun'.  5-column layout (mission | mech | pilot |
emblem/patch | session+controls); loadout persists across the relaunch
(fe_last.ini, all 14 groups).

Verified: menu drives (click Expert+Davion -> egg experience=expert
badge=Davion, persisted), and the generated egg parses + runs a full mission
(no egg errors, drive 61.501).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Cyd
2026-07-20 00:03:21 -05:00
co-authored by Claude Fable 5
parent 2a8f655a13
commit 2cbfaef177
3 changed files with 270 additions and 80 deletions
+231 -78
View File
@@ -291,20 +291,26 @@ int
fprintf(f,
"[%s]\n"
"hostType=0\n"
"advancedDamage=1\n"
"advancedDamage=%d\n"
"loadzones=1\n"
"name=%s\n"
"bitmapindex=%d\n"
"experience=expert\n"
"badge=VGL\n"
"patch=Yellow\n"
"role=Role::Default\n"
"dropzone=one\n"
"experience=%s\n"
"badge=%s\n"
"patch=%s\n"
"role=Role::%s\n"
"dropzone=%s\n"
"vehicle=%s\n"
"vehicleValue=1000\n"
"color=%s\n",
pilot.address, pilot.name, p + 1,
pilot.vehicle, pilot.color);
pilot.address, pilot.advancedDamage ? 1 : 0, pilot.name, p + 1,
pilot.experience[0] ? pilot.experience : "veteran",
pilot.badge[0] ? pilot.badge : "VGL",
pilot.patch[0] ? pilot.patch : "Red",
mission->roleKey[0] ? mission->roleKey : "Default",
pilot.dropzone[0] ? pilot.dropzone : "one",
pilot.vehicle,
pilot.color[0] ? pilot.color : "Grey");
}
//
@@ -362,41 +368,127 @@ struct CatalogEntry
const char *name;
};
// ALL option catalogs are the AUTHENTIC BattleTech mission options recovered
// from the Mac 4.10 operator console's BT:: adventure tree
// (C:\VWE\TeslaSuite\410console\4_10-console-extracted\Console.ini); the `key`
// is the egg field value (the console's `tag=`), the `name` is the display
// label. Full catalog + provenance: docs/GLASS_COCKPIT.md 2026-07-19.
static const CatalogEntry kMaps[] =
{
{ "grass", "Grasslands" },
{ "cavern", "The Cavern" },
{ "rav", "The Ravine" },
{ "polar3", "Polar Station 3" },
{ "polar4", "Polar Station 4" },
{ "arena1", "Arena One" },
{ "arena2", "Arena Two" },
{ "dbase", "Desert Base" },
{ "cavern", "Cavern" },
{ "grass", "Savannah" },
{ "rav", "Ravines" },
{ "polar3", "Polar Missile Base" },
{ "polar4", "Helicopter Base" },
{ "arena1", "Abandoned Arena" },
{ "arena2", "Destroyed Arena" },
{ "dbase", "Badlands" },
};
// The canonical 8 (ALL 18 ModelList names certified 2026-07-18; the menu
// offers the full mechs -- variants remain reachable via hand-built eggs).
// SCENARIO / role: the tag is always freeforall; the choice is the RESPAWN
// role -- Default (respawn) vs NoReturn (one life). Stored as key=role model.
struct RoleEntry { const char *roleKey; const char *model; const char *name; };
static const RoleEntry kScenarios[] =
{
{ "Default", "dfltrole", "Free For All" },
{ "NoReturn", "noretun", "No Return (one life)" }, // model = the game's 8.3 resource
};
// The 18 mechs (console friendly name -> egg tag), matching the 2026-07-18
// ModelList certification.
static const CatalogEntry kVehicles[] =
{
{ "avatar", "Avatar" },
{ "blkhawk", "Black Hawk" },
{ "loki", "Loki" },
{ "bhk1", "Nova" },
{ "madcat", "Mad Cat" },
{ "owens", "Owens" },
{ "sunder", "Sunder" },
{ "thor", "Thor" },
{ "owens", "Owens" },
{ "own1", "Kotori" },
{ "thr1", "Summoner" },
{ "lok1", "Hellbringer" },
{ "mad1", "Timber Wolf" },
{ "avatar", "Avatar" },
{ "ava1", "Satyr" },
{ "sunder", "Sunder" },
{ "snd1", "Sunder V1" },
{ "vulture", "Vulture" },
{ "vul1", "Mad Dog" },
{ "lok2", "Mischief" },
{ "mad2", "Zanin Neko" },
};
// experience = the pod's per-pilot simulation-fidelity tier (see
// context/experience-levels.md); 4 game levels.
static const CatalogEntry kExperience[] =
{
{ "novice", "Novice" },
{ "standard", "Standard" },
{ "veteran", "Veteran" },
{ "expert", "Expert" },
};
// color/camo (console display -> egg tag: Red=Crimson, Gray=Grey)
static const CatalogEntry kColors[] =
{
{ "White", "White" }, { "Black", "Black" }, { "Crimson", "Crimson" },
{ "White", "White" },
{ "Black", "Black" },
{ "Brown", "Brown" },
{ "Crimson","Crimson" },
{ "Green", "Green" },
{ "Grey", "Grey" },
{ "Tan", "Tan" },
};
// badge / team emblem (the egg `badge=` field)
static const CatalogEntry kBadges[] =
{
{ "VGL", "VGL" },
{ "Davion", "Davion" },
{ "Kurita", "Kurita" },
{ "Liao", "Liao" },
{ "Marik", "Marik" },
{ "Steiner", "Steiner" },
{ "None", "None" },
};
// patch / badge colour (the egg `patch=` field)
static const CatalogEntry kPatches[] =
{
{ "Red", "Red" },
{ "Yellow", "Yellow" },
{ "Blue", "Blue" },
{ "Green", "Green" },
{ "Violet", "Violet" },
{ "Grey", "Grey" },
{ "White", "White" },
{ "Black", "Black" },
};
// drop zone (the egg `dropzone=` field)
static const CatalogEntry kDropZones[] =
{
{ "one", "One" },
{ "two", "Two" },
{ "three", "Three" },
{ "four", "Four" },
{ "five", "Five" },
};
// advanced damage model (the egg `advancedDamage=` field)
static const CatalogEntry kAdvDamage[] =
{
{ "1", "On" },
{ "0", "Off" },
};
static const CatalogEntry kTimes[] =
{
{ "day", "Day" }, { "night", "Night" },
{ "morning", "Morning" }, { "dusk", "Dusk" },
{ "morning","Morning" },
{ "day", "Day" },
{ "evening","Evening" },
{ "night", "Night" },
};
static const CatalogEntry kWeathers[] =
@@ -429,13 +521,20 @@ static const int kPorts[] = { 1501, 1601, 1701, 1801 };
enum MenuGroup
{
GroupMap = 0,
GroupScenario,
GroupTime,
GroupWeather,
GroupVehicle,
GroupLength,
GroupMode,
GroupVehicle,
GroupExperience,
GroupColor,
GroupBadge,
GroupPatch,
GroupDropZone,
GroupAdvDamage,
GroupMode,
GroupPort,
GroupLastSelectable = GroupPort, // the last catalog group (persist + headers)
GroupLaunch,
GroupSteamHost,
GroupSteamJoin,
@@ -452,7 +551,7 @@ struct MenuItem
struct MenuState
{
int selection[GroupCount];
MenuItem items[96];
MenuItem items[128];
int itemCount;
HWND window;
HWND nameEdit;
@@ -474,15 +573,21 @@ static int
{
switch (group)
{
case GroupMap: return COUNT(kMaps);
case GroupTime: return COUNT(kTimes);
case GroupWeather: return COUNT(kWeathers);
case GroupVehicle: return COUNT(kVehicles);
case GroupLength: return COUNT(kLengths);
case GroupMode: return COUNT(kModes);
case GroupColor: return COUNT(kColors);
case GroupPort: return COUNT(kPorts);
default: return 1;
case GroupMap: return COUNT(kMaps);
case GroupScenario: return COUNT(kScenarios);
case GroupTime: return COUNT(kTimes);
case GroupWeather: return COUNT(kWeathers);
case GroupLength: return COUNT(kLengths);
case GroupVehicle: return COUNT(kVehicles);
case GroupExperience: return COUNT(kExperience);
case GroupColor: return COUNT(kColors);
case GroupBadge: return COUNT(kBadges);
case GroupPatch: return COUNT(kPatches);
case GroupDropZone: return COUNT(kDropZones);
case GroupAdvDamage: return COUNT(kAdvDamage);
case GroupMode: return COUNT(kModes);
case GroupPort: return COUNT(kPorts);
default: return 1;
}
}
@@ -491,15 +596,21 @@ static void
{
switch (group)
{
case GroupMap: strcpy(out, kMaps[index].name); break;
case GroupTime: strcpy(out, kTimes[index].name); break;
case GroupWeather: strcpy(out, kWeathers[index].name); break;
case GroupVehicle: strcpy(out, kVehicles[index].name); break;
case GroupLength: strcpy(out, kLengths[index].name); break;
case GroupMode: strcpy(out, kModes[index].name); break;
case GroupColor: strcpy(out, kColors[index].name); break;
case GroupPort: sprintf(out, "%d", kPorts[index]); break;
default: out[0] = 0; break;
case GroupMap: strcpy(out, kMaps[index].name); break;
case GroupScenario: strcpy(out, kScenarios[index].name); break;
case GroupTime: strcpy(out, kTimes[index].name); break;
case GroupWeather: strcpy(out, kWeathers[index].name); break;
case GroupLength: strcpy(out, kLengths[index].name); break;
case GroupVehicle: strcpy(out, kVehicles[index].name); break;
case GroupExperience: strcpy(out, kExperience[index].name); break;
case GroupColor: strcpy(out, kColors[index].name); break;
case GroupBadge: strcpy(out, kBadges[index].name); break;
case GroupPatch: strcpy(out, kPatches[index].name); break;
case GroupDropZone: strcpy(out, kDropZones[index].name); break;
case GroupAdvDamage: strcpy(out, kAdvDamage[index].name); break;
case GroupMode: strcpy(out, kModes[index].name); break;
case GroupPort: sprintf(out, "%d", kPorts[index]); break;
default: out[0] = 0; break;
}
}
@@ -508,34 +619,46 @@ static const char *
{
switch (group)
{
case GroupMap: return "MAP";
case GroupTime: return "TIME";
case GroupWeather: return "WEATHER";
case GroupVehicle: return "MECH";
case GroupLength: return "LENGTH";
case GroupMode: return "MODE";
case GroupColor: return "COLOR";
case GroupPort: return "NET PORT";
default: return "";
case GroupMap: return "MAP";
case GroupScenario: return "SCENARIO";
case GroupTime: return "TIME";
case GroupWeather: return "WEATHER";
case GroupLength: return "LENGTH";
case GroupVehicle: return "MECH";
case GroupExperience: return "EXPERIENCE";
case GroupColor: return "COLOR";
case GroupBadge: return "BADGE";
case GroupPatch: return "PATCH";
case GroupDropZone: return "DROP ZONE";
case GroupAdvDamage: return "ADV. DAMAGE";
case GroupMode: return "MODE";
case GroupPort: return "NET PORT";
default: return "";
}
}
//
// Layout: three catalog columns + the button column.
// col1: MAP, TIME, WEATHER col2: MECH, LENGTH
// col3: MODE, COLOR, NET PORT col4: name/peers edits + buttons
// Layout: five catalog columns + the button column.
// col1 MISSION: MAP, SCENARIO, TIME, WEATHER, LENGTH
// col2 MECH (18) col3 EXPERIENCE, COLOR, DROP ZONE, ADV. DAMAGE
// col4 BADGE, PATCH col5 MODE, NET PORT + name/peers edits + buttons
//
enum
{
MenuClientW = 960,
MenuClientH = 560,
MenuRowH = 22,
MenuColW = 190,
MenuCol1X = 28,
MenuCol2X = 254,
MenuCol3X = 480,
MenuCol4X = 706,
MenuTopY = 64
MenuClientW = 1180,
MenuClientH = 620,
MenuRowH = 20,
MenuColW = 180,
MenuCol1X = 24,
MenuCol2X = 224,
MenuCol3X = 424,
MenuCol4X = 624,
MenuCol5X = 824,
MenuTopY = 60,
MenuNameLabelY = 316, // col5: PILOT NAME label + edit
MenuNameEditY = 336,
MenuPeersLabelY = 368, // col5: LAN PEERS label + edit
MenuPeersEditY = 388
};
static void
@@ -574,18 +697,29 @@ static void
{
menu.itemCount = 0;
int y;
// col1: the mission
y = MenuTopY; AddGroup(GroupMap, MenuCol1X, &y);
AddGroup(GroupScenario, MenuCol1X, &y);
AddGroup(GroupTime, MenuCol1X, &y);
AddGroup(GroupWeather, MenuCol1X, &y);
AddGroup(GroupLength, MenuCol1X, &y);
// col2: the mech (tall)
y = MenuTopY; AddGroup(GroupVehicle, MenuCol2X, &y);
AddGroup(GroupLength, MenuCol2X, &y);
y = MenuTopY; AddGroup(GroupMode, MenuCol3X, &y);
// col3: the pilot loadout
y = MenuTopY; AddGroup(GroupExperience, MenuCol3X, &y);
AddGroup(GroupColor, MenuCol3X, &y);
AddGroup(GroupPort, MenuCol3X, &y);
AddButton(GroupLaunch, MenuCol4X, MenuClientH - 190, 220, 52);
AddGroup(GroupDropZone, MenuCol3X, &y);
AddGroup(GroupAdvDamage, MenuCol3X, &y);
// col4: emblem + patch
y = MenuTopY; AddGroup(GroupBadge, MenuCol4X, &y);
AddGroup(GroupPatch, MenuCol4X, &y);
// col5: the session + controls
y = MenuTopY; AddGroup(GroupMode, MenuCol5X, &y);
AddGroup(GroupPort, MenuCol5X, &y);
AddButton(GroupLaunch, MenuCol5X, MenuClientH - 190, 220, 52);
#ifdef BT_STEAM
AddButton(GroupSteamHost, MenuCol4X, MenuClientH - 120, 220, 34);
AddButton(GroupSteamJoin, MenuCol4X, MenuClientH - 76, 220, 34);
AddButton(GroupSteamHost, MenuCol5X, MenuClientH - 120, 220, 34);
AddButton(GroupSteamJoin, MenuCol5X, MenuClientH - 76, 220, 34);
#endif
}
@@ -731,12 +865,12 @@ static void
}
//
// Edit labels (the controls themselves are child windows).
// Edit labels (the controls themselves are child windows in col5).
//
RECT label = { MenuCol4X, MenuTopY, MenuCol4X + 220, MenuTopY + MenuRowH };
RECT label = { MenuCol5X, MenuNameLabelY, MenuCol5X + 240, MenuNameLabelY + MenuRowH };
DrawMenuText(dc, "PILOT NAME", &label, kGreenDim,
DT_LEFT | DT_VCENTER | DT_SINGLELINE);
label.top = MenuTopY + 3 * MenuRowH;
label.top = MenuPeersLabelY;
label.bottom = label.top + MenuRowH;
DrawMenuText(dc, "LAN PEERS (host: ip:port,...)", &label, kGreenDim,
DT_LEFT | DT_VCENTER | DT_SINGLELINE);
@@ -852,7 +986,11 @@ int
{
memset(spec, 0, sizeof(*spec));
memset(&menu, 0, sizeof(menu));
menu.selection[GroupLength] = 2; // 10:00, the console default
menu.selection[GroupLength] = 2; // 10:00, the console default
menu.selection[GroupExperience] = 2; // veteran
menu.selection[GroupColor] = 5; // Grey (the console default)
menu.selection[GroupBadge] = 0; // VGL
menu.selection[GroupDropZone] = 0; // one
char pilot_name[32] = "Dev";
char peers[128] = "";
@@ -891,11 +1029,11 @@ int
//
menu.nameEdit = CreateWindowW(L"EDIT", L"",
WS_CHILD | WS_VISIBLE | WS_BORDER | ES_AUTOHSCROLL,
MenuCol4X, MenuTopY + MenuRowH, 220, MenuRowH + 4,
MenuCol5X, MenuNameEditY, 240, MenuRowH + 4,
menu.window, NULL, GetModuleHandleW(NULL), NULL);
menu.peersEdit = CreateWindowW(L"EDIT", L"",
WS_CHILD | WS_VISIBLE | WS_BORDER | ES_AUTOHSCROLL,
MenuCol4X, MenuTopY + 4 * MenuRowH, 220, MenuRowH + 4,
MenuCol5X, MenuPeersEditY, 240, MenuRowH + 4,
menu.window, NULL, GetModuleHandleW(NULL), NULL);
SendMessageW(menu.nameEdit, WM_SETFONT, (WPARAM)menu.textFont, TRUE);
SendMessageW(menu.peersEdit, WM_SETFONT, (WPARAM)menu.textFont, TRUE);
@@ -939,11 +1077,14 @@ int
// Resolve the launch spec + write the egg.
//
int console_port = kPorts[menu.selection[GroupPort]];
const RoleEntry &scenario = kScenarios[menu.selection[GroupScenario]];
BTFeMission mission;
memset(&mission, 0, sizeof(mission));
strcpy(mission.map, kMaps[menu.selection[GroupMap]].key);
strcpy(mission.time, kTimes[menu.selection[GroupTime]].key);
strcpy(mission.weather, kWeathers[menu.selection[GroupWeather]].key);
strcpy(mission.roleKey, scenario.roleKey);
strcpy(mission.roleModel, scenario.model);
mission.temperature = 27;
mission.lengthSeconds = kLengths[menu.selection[GroupLength]].seconds;
mission.pilotCount = 1;
@@ -951,6 +1092,11 @@ int
strcpy(self.name, pilot_name[0] ? pilot_name : "Dev");
strcpy(self.vehicle, kVehicles[menu.selection[GroupVehicle]].key);
strcpy(self.color, kColors[menu.selection[GroupColor]].key);
strcpy(self.experience, kExperience[menu.selection[GroupExperience]].key);
strcpy(self.badge, kBadges[menu.selection[GroupBadge]].key);
strcpy(self.patch, kPatches[menu.selection[GroupPatch]].key);
strcpy(self.dropzone, kDropZones[menu.selection[GroupDropZone]].key);
self.advancedDamage = (kAdvDamage[menu.selection[GroupAdvDamage]].key[0] == '1');
spec->consolePort = console_port;
spec->missionSeconds = mission.lengthSeconds;
@@ -1049,6 +1195,13 @@ int
(menu.selection[GroupVehicle] + mission.pilotCount) % COUNT(kVehicles)].key);
strcpy(peer.color, kColors[
(menu.selection[GroupColor] + mission.pilotCount) % COUNT(kColors)].key);
// peers inherit the host's loadout defaults (the operator
// sets per-pilot detail on the real console)
strcpy(peer.experience, self.experience);
strcpy(peer.badge, self.badge);
strcpy(peer.patch, self.patch);
strcpy(peer.dropzone, self.dropzone);
peer.advancedDamage = self.advancedDamage;
sprintf(peer.address, "%s:%d", host, port + 1);
++mission.pilotCount;
char entry[64];
+9 -2
View File
@@ -22,8 +22,13 @@
struct BTFePilot
{
char name[32];
char vehicle[16];
char color[16];
char vehicle[16]; // mech tag (madcat, ...)
char color[16]; // camo tag (Grey, Crimson, ...)
char experience[16]; // novice / standard / veteran / expert
char badge[16]; // team emblem (VGL, Davion, ...)
char patch[16]; // badge colour (Red, Yellow, ...)
char dropzone[8]; // one .. five
int advancedDamage; // 0/1
char address[64]; // ip:gamePort for the roster
};
@@ -32,6 +37,8 @@ struct BTFeMission
char map[16];
char time[16];
char weather[16];
char roleKey[16]; // egg role key: Default / NoReturn
char roleModel[16]; // role model: dfltrole / noreturn
int temperature;
int lengthSeconds;
int pilotCount;