//===========================================================================// // File: btl4galm.cpp // // Project: BattleTech Brick: Gauge Renderer Manager // // Contents: BTL4GaugeAlarmManager -- the BattleTech override of the MUNGA // // GaugeAlarmManager. It supplies the two per-item stream virtuals // // that the base class declares pure ("not overridden!"): it knows how // // to compile a named cockpit-gauge alarm definition into a memory // // stream (CreateGaugeAlarmStreamItem) and how to instantiate a live // // GaugeAlarm for a given subsystem/condition from that stream // // (ReadGaugeAlarmStreamItem). // //---------------------------------------------------------------------------// // Date Who Modification // // -------- --- ---------------------------------------------------------- // // 02/22/96 CPB Initial coding. // //---------------------------------------------------------------------------// // Copyright (C) 1996, Virtual World Entertainment, Inc. All rights reserved // // PROPRIETARY AND CONFIDENTIAL // //===========================================================================// // // RECONSTRUCTED -- IMPORTANT PROVENANCE NOTE // ------------------------------------------ // ⚠ 2026-07-25 (#47): THE CENTRAL CLAIM BELOW IS WRONG. The BTL4 override // bodies DO exist in the shipped image, at @004cc148..@004cc2fc -- immediately // BELOW the @004cc40c boundary this note drew: // @004cc2fc ReadGaugeAlarmStreamItem: stream-reads {trigger level, lamp // code}; when the subsystem's alarm level matches, maps the lamp // (<0x80 fixed via @004cc148; >=0x80 SUBSYSTEM-RELATIVE eng-page // button via @004cc1a0; Condenser @004cc264 / power-family // @004cc27c specials), FindLamp @00444c80 on the renderer's lamp // map (+0x1c0c8), Lamp::SetAlertState(@00444e64, the FLASH // counter), and records the lamp on the GaugeAlarm's lampList. // @004cc294 the Create-side sibling. // This is the #47 ENG-BUTTON ATTENTION FLASH (jam / bay fire); the authored // data survives as the type-31 GaugeAlarmStream in BTL4.RES (exactly 1: // ModelList 'mechalrm' 83 -> stream 331). The bodies below ARE now the real // re-transcriptions from those addresses (2026-07-25, same day). The // original (wrong) note is preserved for the record: // // btl4galm.obj links after btl4grnd.obj and before btl4vid.obj (BTL4.MAK // BTL4_OBJS order). In the shipped optimised image (BTL4OPT.EXE) there is **no // distinct, separately-emitted BTL4GaugeAlarmManager override body**: // * the only GaugeAlarmManager code in the binary is the MUNGA base class in // gaugalrm.cpp (recovered/all/part_006.c, @00448928..@00448f48), including // the two "not overridden!" trap virtuals // CreateGaugeAlarmStreamItem @00448ab8 (asserts GAUGALRM.CPP:0x71) // ReadGaugeAlarmStreamItem @00448ad4 (asserts GAUGALRM.CPP:0x7e) // and the base driver helpers that CALL those virtuals // GaugeAlarmManager::CreateGaugeAlarmStream @00448af0 // GaugeAlarmManager::BuildAlarmsForModel @00448d00 // * no subclass vtable (one whose Create/Read slots point into BattleTech // code) appears in vtables.tsv, and no caller constructs a subclass // (the base ctors @00448ebd/@00448edc/@00448efb have no external callers). // The code region the linker reserved for btl4galm.obj (@004cc40c..@004cdac0) // actually holds a 3-D HUD-model builder (PNAME1..8.bgf), i.e. the compiler // folded BTL4GaugeAlarmManager's tiny overrides away / it was never instantiated // in this build configuration. // // This file is therefore reconstructed to MATCH THE SURVIVING BTL4GALM.HPP and // the base driver's calling contract (the argument order, the GaugeAlarm object // it expects, and the "undefined alarm type ... resource not built" diagnostic // the base prints when the virtual returns False). Bodies below are BEST-EFFORT // (low confidence): they describe what the override must do for the base driver // to function, not bytes lifted from this image. // // Base contract (recovered, high confidence): // GaugeAlarmManager::CreateGaugeAlarmStream @00448af0 // - opens the "gaugeAlarm" sub-resource of a model record (FUN_00404720, // "gaugeAlarm" @004eecc9), counts its entries, makes a MemoryStream // (0x18), and for each entry calls the virtual // this->CreateGaugeAlarmStreamItem(stream, entry_name, entry_data) // (entry_name @entry+8, entry_data @entry[1]); if it returns 0 it prints // "Model file '' has undefined alarm type '' - // resource not built." (strings @004eecd4/@004eece1/@004eecfe) // and aborts the build. On success it bakes the stream (FUN_00406f3c). // GaugeAlarmManager::BuildAlarmsForModel @00448d00 // - for each baked alarm record it makes a GaugeAlarm (FUN_004489e8, // 0x28 bytes; vtable PTR_FUN_004eed7c; ctor stores entity@+0xc, // subsystem@+0x10, condition@+0x14), adds it to the manager's alarm list // (this+4, list-insert vtbl+4), then calls the virtual // this->ReadGaugeAlarmStreamItem(alarm, entity, subsystem, condition, // stream) // once per record to configure it. // // Engine-internal helper map (from the base + the BattleTech alarm bricks): // FUN_004489e8 GaugeAlarm ctor FUN_00448a28 GaugeAlarm dtor // FUN_00448ebd GaugeAlarmManager base ctor (chains a ChainOf) // FUN_0040385c Verify(msg,file,line) // FUN_004dbb24 DebugStream << (char*) FUN_004db78c DebugStream << (int) // DAT_00524e20 DebugStream (warning channel) // MemoryStream write/read = (*stream + 0x20)(stream,&v,4) / (*stream+0x1c)(...) // #include #pragma hdrstop #if !defined(BTL4GALM_HPP) # include #endif #if !defined(APP_HPP) # include #endif // // THE REAL OVERRIDE BODIES (Gitea #47, 2026-07-25) -- re-transcribed from the // binary at @004cc294 (Create) / @004cc2fc (Read) + the mapping helpers // @004cc108/@004cc148/@004cc1a0/@004cc264/@004cc27c and their data tables // @0051cf1c..@0051d084. The previous bodies here were admitted fabrications // ("threshold/range/state/flash" types that exist nowhere in the image); the // provenance note at the top of this file records why. // // The STREAM FORMAT (verified against the shipped resource -- BTL4.RES carries // ModelList 'mechalrm' id=83 -> GaugeAlarmStream id=331, 11 items): // { int32 condition; int32 lampCode; } per item // where condition = the MechSubsystem TechStatusType (the @0x50de74 name table: // Destroyed 0, Damaged 1, CoolantLeaking 2, Overheating 3, AmmoBurning 4, // Jammed 5, BadPower 6) and lampCode = the @0051cf1c vocabulary below. // // The shipped 'mechalrm' authoring (decoded 2026-07-25): // Destroyed -> gotoEngineering + engCooling + engBusMode // CoolantLeaking -> gotoEngineering + engCooling // AmmoBurning -> gotoEngineering + engEject // Jammed -> gotoEngineering + engEject // BadPower -> gotoEngineering + engBusMode // ("on a jam or bay fire the display eng button for the system flashes" -- // Cyd's pod testimony, verbatim in the data.) // // // @0051cf1c -- the lamp-code name vocabulary ({char*, code} pairs, 0-terminated). // struct BTLampNameEntry { const char *name; int code; }; static const BTLampNameEntry kBTLampNames[] = { { "coolingLoop1", 0x00 }, { "coolingLoop2", 0x01 }, { "coolingLoop3", 0x02 }, { "coolingLoop4", 0x03 }, { "coolingLoop5", 0x04 }, { "coolingLoop6", 0x05 }, { "gotoEngineering", 0x80 }, { "engGeneratorA", 0x81 }, { "engGeneratorB", 0x82 }, { "engGeneratorC", 0x83 }, { "engGeneratorD", 0x84 }, { "engEject", 0x85 }, { "engBusMode", 0x86 }, { "engCooling", 0x87 }, { "engBackToQuad", 0x88 }, { 0, 0 } }; // // The lamp-resolution tables, byte-verified from the image: // @0051cf9c quad-page mode masks [12] (aux screens 1..12) // @0051cfcc eng-page mode masks [12] // @0051cffc quad-select button lamp ids [12] (code 0x80 gotoEngineering) // @0051d02c eng-page bank-top lamp ids [12] (codes 0x81.. descend from it) // @0051d058 per-condenser lamps (index = condenserNumber @0x1D4, 1-based) // @0051d070 per-placement lamps (index = auxScreenPlacement @0x1E0) // static const int kBTQuadModeMask[12] = { 0x1,0x1,0x1,0x1, 0x20,0x20,0x20,0x20, 0x400,0x400,0x400,0x400 }; static const int kBTEngModeMask[12] = { 0x2,0x4,0x8,0x10, 0x40,0x80,0x100,0x200, 0x800,0x1000,0x2000,0x4000 }; static const int kBTQuadLamp[12] = { 0xF,0xD,0xB,0x9, 0x27,0x25,0x23,0x21, 0x7,0x5,0x3,0x1 }; static const int kBTEngBankTop[12] = { 0xF,0xF,0xF,0xF, 0x27,0x27,0x27,0x27, 0x7,0x7,0x7,0x7 }; static const int kBTCondenserLamp[6] = { 0x7, 0x2F,0x2E,0x2D,0x2B,0x2A }; // [condenserNumber] static const int kBTPlacementLamp[5] = { 0x29, 0x1A,0x1B,0x1C,0x1D }; // [auxScreenPlacement] // // @004cc148 -- the fixed (<0x80) map: coolingLoop1..6 -> the heat-display bank. // static int BTFixedLampOf(int code) { static const int kFixed[6] = { 0x2F, 0x2E, 0x2D, 0x2C, 0x2B, 0x2A }; return (code >= 0 && code < 6) ? kFixed[code] : -1; } // // @004cc108 -- map an authored lamp NAME to its code. // static Logical BTLampCodeOf(const char *data, int *out_code) { for (const BTLampNameEntry *e = kBTLampNames; e->name != 0; ++e) { if (strcmp(data, e->name) == 0) // FUN_004d4b58 { *out_code = e->code; return True; } } return False; } static Logical BTLampLog() { static int s_on = -1; if (s_on < 0) s_on = (getenv("BT_LAMP_LOG") != 0) ? 1 : 0; return s_on ? True : False; } // // BTL4GaugeAlarmManager::CreateGaugeAlarmStreamItem -- @004cc294 [T1] // // iVar1 = FUN_004ac194(name, &condition); // MechSubsystem::LookupStatusType // if (!iVar1) return 0; // iVar1 = FUN_004cc108(data, &code); // the @0051cf1c vocabulary // if (!iVar1) return 0; // stream->WriteBytes(&condition, 4); // (*stream+0x20) // stream->WriteBytes(&code, 4); // return 1; // // (Bake-side only -- the shipped BTL4.RES already carries the baked stream; this // matters when re-baking resources from notation.) // Logical BTL4GaugeAlarmManager::CreateGaugeAlarmStreamItem( MemoryStream *mem_stream, const char *alarm_name, const char *alarm_data ) { int condition, code; if (!MechSubsystem::LookupStatusType(alarm_name, &condition)) // FUN_004ac194 { return False; // -> "has undefined alarm type ... resource not built" } if (!BTLampCodeOf(alarm_data, &code)) // FUN_004cc108 { return False; } mem_stream->WriteBytes(&condition, sizeof(condition)); mem_stream->WriteBytes(&code, sizeof(code)); return True; } // // BTL4GaugeAlarmManager::ReadGaugeAlarmStreamItem -- @004cc2fc [T1] // // Read one {condition, lampCode} item; when the item's condition equals the // condition being ACTIVATED, resolve the lamp: // * Condenser-derived (0x50e4fc) + condition CoolantLeaking(2): // lamp = @0051d058[condenserNumber@0x1D4] (its own loop button) // * Generator-derived (0x50fb60) + condition CoolantLeaking(2): // lamp = @0051d070[auxScreenPlacement@0x1E0] (its radar-rail button) // * else code < 0x80: the fixed heat-bank map (@004cc148) // * else code >= 0x80: PoweredSubsystem-derived only (0x50f4bc -- the same // gate BTGetSubsystemAuxScreen applies) + auxScreen@0x1DC > 0: // 0x80 gotoEngineering: lamp = @0051cffc[aux], mode = @0051cf9c[aux] // 0x81..: lamp = @0051d02c[aux] - (code - 0x81), mode = @0051cfcc[aux] // then FindLamp(lamp, mode) on the renderer lamp manager (@00444c80), start it // flashing (Lamp::SetAlertState(True) @00444e64 -- a COUNTER, alarms stack), and // record it on the GaugeAlarm lampList so ~GaugeAlarm un-flashes on Deactivate. // A lamp code that resolves to no lamp is silently skipped (iVar2 = -1 path). // // (0x50f4bc = PoweredSubsystem::ClassDerivations, NOT Generator -- pinned via // @004b1208 = PoweredSubsystem::TestInstance in the powersub method cluster; // 0x50fb60 = the Generator one, adjacent to its message table @0x50fb90.) // void BTL4GaugeAlarmManager::ReadGaugeAlarmStreamItem( GaugeAlarm *alarm, Entity * /*the_entity*/, Subsystem *the_subsystem, Enumeration the_condition, MemoryStream *mem_stream ) { int item_condition = -1, lamp_code = -1; mem_stream->ReadBytes(&item_condition, sizeof(item_condition)); // (*stream+0x1c) mem_stream->ReadBytes(&lamp_code, sizeof(lamp_code)); if ((int)the_condition != item_condition) { return; } extern int BTSubsystemIsCondenser(::Subsystem *sub); // heat.cpp (0x50e4fc) extern int BTCondenserNumber(::Subsystem *sub); // heat.cpp (+0x1D4) extern int BTSubsystemIsGenerator(::Subsystem *sub); // powersub.cpp (0x50fb60) extern bool BTGetSubsystemAuxScreen(::Subsystem *sub, int *screen, int *placement, char *label64); // powersub.cpp (0x50f4bc gate + 0x1DC/0x1E0) int lamp_id = -1; int mode = -1; // local_10: stays -1 for the fixed/family paths if (BTSubsystemIsCondenser(the_subsystem)) { if (the_condition == 2) // CoolantLeaking { int n = BTCondenserNumber(the_subsystem); if (n >= 0 && n < 6) lamp_id = kBTCondenserLamp[n]; } } else if (BTSubsystemIsGenerator(the_subsystem)) { if (the_condition == 2) // CoolantLeaking { int screen = 0, placement = -1; BTGetSubsystemAuxScreen(the_subsystem, &screen, &placement, 0); if (placement >= 0 && placement < 5) lamp_id = kBTPlacementLamp[placement]; } } else if (lamp_code < 0x80) { lamp_id = BTFixedLampOf(lamp_code); } else { int screen = 0, placement = -1; if (BTGetSubsystemAuxScreen(the_subsystem, &screen, &placement, 0) // PoweredSubsystem gate && screen > 0) { int aux = screen - 1; // +0x1DC - 1 if (aux < 12) { if (lamp_code == 0x80) // gotoEngineering { lamp_id = kBTQuadLamp[aux]; mode = kBTQuadModeMask[aux]; } else // engGeneratorA..engBackToQuad { lamp_id = kBTEngBankTop[aux] - (lamp_code - 0x81); mode = kBTEngModeMask[aux]; } } } } if (lamp_id < 0) { return; } // // @004cc2fc tail: FindLamp on the gauge renderer lamp manager, flash it, // and record it on the alarm. (DAT_004efc94+0x4c = the gauge renderer.) // GaugeRenderer *renderer = (GaugeRenderer *)application->GetGaugeRenderer(); if (renderer == 0) { return; } Lamp *lamp = (renderer->GetLampManager() != 0) ? renderer->GetLampManager()->FindLamp((LampID)lamp_id, (ModeMask)mode) : 0; if (BTLampLog()) DEBUG_STREAM << "[galarm] condition " << (int)the_condition << " code 0x" << std::hex << lamp_code << " -> lamp 0x" << lamp_id << " mode 0x" << mode << std::dec << (lamp ? " FLASH" : " (no lamp registered -- skipped)") << "\n" << std::flush; if (lamp != 0) { lamp->SetAlertState(True); // FUN_00444e64 -- the flash counter alarm->lampList.Add(lamp); // un-flashed by ~GaugeAlarm on Deactivate } }