diff --git a/docs/GLASS_COCKPIT.md b/docs/GLASS_COCKPIT.md index 39d18f2..d583949 100644 --- a/docs/GLASS_COCKPIT.md +++ b/docs/GLASS_COCKPIT.md @@ -174,3 +174,33 @@ desktops, `bindings.txt`/PadRIO on glass. The D1 relay keeps its own raw sockets (relay and Steam are alternative wires; the BTNet seam covers the arcade mesh both need). Verified: 3-config build, glass stand-down live, pod forced-walk with btinput active, 2-node loopback MP 31/31. + +## 2026-07-19 — Mission-control options: the AUTHORITATIVE catalog (TeslaSuite console.ini) + +Source: `C:\VWE\TeslaSuite\410console\4_10-console-extracted\Console.ini`, the Mac 4.10 operator +console's `BT::` adventure tree (the `tag=` values are the egg field values; display names are the +console's). Cross-checked vs `410console/battletech-port/BATTLETECH-PORT-SPEC.md` + `reference/ +cavern.egg`. This is every option the original console exposed for a BattleTech mission. + +**Mission-level** — `[mission]`: `adventure=BattleTech` (fixed), `temperature=27` (fixed), +`scenario=freeforall` (the tag is always freeforall; the RESPAWN choice is the ROLE): +- map (8): cavern, grass, rav, polar3, polar4, arena1, arena2, dbase +- time (4): morning, day, evening, night ← was day/night/morning/**dusk** (wrong: evening) +- weather (3): clear, fog (light), soup (heavy) +- role (2): Default (`model=dfltrole`, respawn) / NoReturn (`model=noreturn`, one life) +- length: seconds + +**Per-pilot** — participant block: +- experience (4 [T1 game decode]): novice, standard, veteran, expert ← was hardcoded `expert` +- vehicle (18, friendly→tag): Black Hawk=blkhawk, Loki=loki, Nova=bhk1, Mad Cat=madcat, Thor=thor, + Owens=owens, Kotori=own1, Summoner=thr1, Hellbringer=lok1, Timber Wolf=mad1, Avatar=avatar, + Satyr=ava1, Sunder=sunder, Sunder V1=snd1, Vulture=vulture, Mad Dog=vul1, Mischief=lok2, + Zanin Neko=mad2 (the 18 ModelList names; matches the 2026-07-18 vehicle sweep) +- color/camo (7, display→tag): White=White, Black=Black, Brown=Brown, Red=**Crimson**, Green=Green, + Gray=**Grey**, Tan=Tan ← was White/Black/Crimson only +- badge/emblem (7, the `badge=` field): VGL, Davion, Kurita, Liao, Marik, None, Steiner ← was `VGL` +- patch/badge-color (8, the `patch=` field): Yellow, Blue, Grey, Green, Violet, Red, White, Black + ← was `Yellow` +- dropzone (5, the `dropzone=` field): one, two, three, four, five ← was `one` +- advancedDamage (0/1): the advanced damage model toggle ← was hardcoded `1` +- vehicleValue: 1000 (const), loadzones=1 (const), bitmapindex (per-pilot ordinal) diff --git a/game/glass/btl4fe.cpp b/game/glass/btl4fe.cpp index 5947cfd..d91f53f 100644 --- a/game/glass/btl4fe.cpp +++ b/game/glass/btl4fe.cpp @@ -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]; diff --git a/game/glass/btl4fe.hpp b/game/glass/btl4fe.hpp index 3715ee7..e037b42 100644 --- a/game/glass/btl4fe.hpp +++ b/game/glass/btl4fe.hpp @@ -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;