Files
BT411/tools/mechs.tpl
T
CydandClaude Opus 4.8 150492054a docs: the mech roster page (docs/MECHS.html + generator)
An HTML roster of all 18 selectable mechs in the VTV-PRESETS layout style:
per-mech portrait, loadout chips, manual stat sheet, and the authored
weapons/ammo table, with filter (all/base/variant), search and live count.

Nothing is transcribed by hand where the game can be read directly
(tools/build_mechs.py + tools/mechs.tpl -> docs/MECHS.html):

  - ROSTER: kVehicles[] parsed from game/glass/btl4fe.cpp -- the exact
    18-entry catalog the menu offers, in menu order (8 base chassis + 10
    variants).
  - WEAPONS + AMMO BINS: each mech's cmCrit(...) lines in
    content/GAUGE/L4GAUGE.CFG (the critical-schematic's authored subsystem
    roster; independently cross-checked against the rtype-17 subsystem
    records in BTL4.RES, 2026-08-12 -- all 18 agree).
  - PORTRAITS: the game's own mission-review images (GAUGE/<stem>_MR.PCC,
    PCC=PCX) resolved through mrpal.pcc with the zone slots (32..63) held
    at adpal entry 0 -- the review screen's undamaged green.  Variants
    without their own portrait share the base's, as the RES does.
  - MECH STATS: the original player's manual stat sheets, transcribed [T1]
    from page renders (pp. 25 Loki/Thor, 26 Vulture/MadCat, 29
    Owens/Blackhawk).  The manual covers the six 4.0 hulls only; Avatar,
    Sunder and the variants show a dash, never an invented number.
  - FIXED vs TWISTING torso: derived from the shipped RES joint tables
    (jointtorso/jointtshadow presence; torso.cpp:270 gate) -- blkhawk,
    bhk1, owens, own1 are fixed, matching the manual's Torso 0/0 rows.

Rendered + verified headless (top and bottom, all 18 cards, filters wired).
Not yet shipped by mkdist (MAPS.html is not either); add a block modelled
on the CONTROLS.html one if testers should get it in the zip.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-12 21:00:11 -05:00

261 lines
11 KiB
Smarty

<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>BattleTech 4.11 &mdash; the mech roster</title>
<style>
*, *::before, *::after {{ box-sizing: border-box; }}
:root {{
--ground: #0e0f11; --panel: #16181b; --rule: #2a2e33;
--ink: #e8e6e2; --ink-mid: #a3a8ae; --ink-low: #71777e;
--hot: #f39313; /* palette 6, the display's own amber */
--nrg: #ff5340; /* energy weapons */
--bal: #f39313; /* ballistic */
--msl: #58c470; /* missiles -- the armor doll's green family */
--mono: ui-monospace, "Cascadia Mono", Consolas, Menlo, monospace;
--sans: ui-sans-serif, system-ui, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
}}
@media (prefers-color-scheme: light) {{
:root {{
--ground: #f4f2ee; --panel: #fbfaf8; --rule: #ddd8d0;
--ink: #14161a; --ink-mid: #565c63; --ink-low: #7d838a;
--hot: #a35f00; --nrg: #b3260f; --bal: #a35f00; --msl: #1c7a3f;
}}
}}
body {{
margin: 0; background: var(--ground); color: var(--ink);
font: 400 15px/1.6 var(--sans);
}}
.wrap {{ max-width: 78rem; margin: 0 auto; padding: 0 clamp(1rem, 4vw, 2.5rem); }}
.sr-only {{
position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}}
:focus-visible {{ outline: 2px solid var(--hot); outline-offset: 2px; }}
header.top {{ border-bottom: 1px solid var(--rule); padding: clamp(2.5rem,6vw,4rem) 0 1.75rem; }}
.eyebrow {{
font: 500 12px/1 var(--mono); letter-spacing: .2em; text-transform: uppercase;
color: var(--ink-low); margin: 0 0 1rem;
}}
h1 {{
font: 700 clamp(1.9rem,1.4rem+2.2vw,3rem)/1.05 var(--mono);
letter-spacing: -.02em; text-transform: uppercase; margin: 0 0 .9rem;
}}
h1 em {{ font-style: normal; color: var(--hot); }}
.lede {{ max-width: 62ch; color: var(--ink-mid); margin: 0 0 1.4rem; font-size: 1.05rem; }}
.lede strong {{ color: var(--ink); font-weight: 600; }}
.rules {{ display: flex; flex-wrap: wrap; gap: .6rem 2rem; margin: 0; padding: 0; list-style: none; }}
.rules li {{
font: 500 12px/1.5 var(--mono); color: var(--ink-mid);
padding-left: 1.1rem; position: relative;
}}
.rules li::before {{
content: ""; position: absolute; left: 0; top: .45em;
width: .45rem; height: .45rem; background: var(--hot);
}}
.rules b {{ color: var(--ink); font-weight: 700; }}
.bar {{
position: sticky; top: 0; z-index: 5;
background: var(--ground);
border-bottom: 1px solid var(--rule); padding: .75rem 0;
}}
.bar__in {{ display: flex; flex-wrap: wrap; gap: .75rem 1.25rem; align-items: center; }}
.seg {{ display: inline-flex; border: 1px solid var(--rule); }}
.seg button {{
font: 500 12px/1 var(--mono); letter-spacing: .12em; text-transform: uppercase;
background: none; border: 0; color: var(--ink-mid); padding: .55rem .9rem; cursor: pointer;
}}
.seg button[aria-pressed="true"] {{ background: var(--hot); color: var(--ground); }}
.search {{
flex: 1 1 12rem; min-width: 9rem;
font: 400 12px var(--mono);
background: var(--panel); color: var(--ink);
border: 1px solid var(--rule); padding: .55rem .7rem;
}}
.search::placeholder {{ color: var(--ink-low); }}
.count {{ font: 500 12px var(--mono); color: var(--ink-low); letter-spacing: .1em; }}
.key {{ display: flex; flex-wrap: wrap; gap: .4rem .9rem; padding: 1.25rem 0 0; }}
.key span {{ display: inline-flex; align-items: center; gap: .4rem;
font: 500 12px var(--mono); letter-spacing: .08em; color: var(--ink-low); }}
.key i {{ width: .7rem; height: .7rem; display: inline-block; }}
.roster {{ display: grid; gap: 1px; background: var(--rule); border: 1px solid var(--rule);
margin: 1.75rem 0 3rem; }}
.mech {{ background: var(--panel); display: grid; gap: 0;
grid-template-columns: minmax(15rem, 19rem) 1fr; }}
@media (max-width: 46rem) {{ .mech {{ grid-template-columns: 1fr; }} }}
.mech[hidden] {{ display: none; }}
.mech__id {{ padding: 1.1rem 1.25rem 1.35rem; border-right: 1px solid var(--rule); }}
@media (max-width: 46rem) {{ .mech__id {{ border-right: 0; border-bottom: 1px solid var(--rule); }} }}
.hull {{ margin: 0 0 .9rem; display: flex; flex-direction: column; gap: .45rem;
align-items: flex-start; }}
/* The game's own review image on the display's own black, either theme. */
.hull__img {{
display: block; width: 7rem; height: auto;
background: #000; border: 1px solid var(--rule); image-rendering: pixelated;
}}
.hull figcaption {{
font: 500 11px/1.35 var(--mono); letter-spacing: .1em;
text-transform: uppercase; color: var(--ink-low);
}}
.mech__id h3 {{
font: 700 1.5rem/1 var(--mono); letter-spacing: .01em;
margin: 0 0 .55rem; text-transform: uppercase;
}}
.mech__key {{
font: 500 12px/1 var(--mono); letter-spacing: .12em;
color: var(--ink-low); margin: -.3rem 0 .7rem;
}}
.mech__load {{ display: flex; flex-wrap: wrap; gap: .3rem; margin: 0 0 .7rem; }}
.chip {{
font: 500 11px/1 var(--mono); letter-spacing: .08em;
padding: .3rem .45rem; border: 1px solid currentColor;
}}
.chip--e {{ color: var(--nrg); }}
.chip--b {{ color: var(--bal); }}
.chip--m {{ color: var(--msl); }}
.mech__meta {{ display: flex; flex-wrap: wrap; gap: .8rem; margin: 0 0 .2rem;
font: 400 12px var(--mono); color: var(--ink-low); font-variant-numeric: tabular-nums; }}
.spec {{
margin: .75rem 0 0; display: grid; grid-template-columns: 1fr auto;
gap: .2rem .8rem; font: 500 12px var(--mono);
}}
.spec div {{ display: contents; }}
.spec dt {{ color: var(--ink-low); letter-spacing: .06em; }}
.spec dd {{ margin: 0; text-align: right; color: var(--ink); font-variant-numeric: tabular-nums; }}
.mech__sheet {{ margin: .6rem 0 0; font: 400 11px/1.5 var(--mono); color: var(--ink-low); }}
.mech__tables {{ min-width: 0; }}
.tablewrap {{ overflow-x: auto; }}
table {{ border-collapse: collapse; width: 100%; font-variant-numeric: tabular-nums; }}
thead th {{
font: 500 12px/1 var(--mono); letter-spacing: .14em;
color: var(--ink-low); text-align: left; font-weight: 500;
padding: .9rem .9rem .55rem; border-bottom: 1px solid var(--rule); white-space: nowrap;
}}
tbody th {{
font: 500 13px/1.3 var(--mono); text-align: left; color: var(--ink);
padding: .5rem .9rem; border-bottom: 1px solid var(--rule); white-space: nowrap;
}}
tbody td {{
font: 400 13px/1.3 var(--mono); color: var(--ink-mid);
padding: .5rem .9rem; border-bottom: 1px solid var(--rule); white-space: nowrap;
}}
tbody tr:last-child th, tbody tr:last-child td {{ border-bottom: 0; }}
th.num, td.num {{ text-align: right; }}
.empty-state {{
margin: 2rem 0; font: 500 13px var(--mono); color: var(--ink-low);
}}
.foot {{
border-top: 1px solid var(--rule); padding: 1.5rem 0 0; max-width: 76ch;
color: var(--ink-low); font-size: 13px;
}}
.foot:last-child {{ padding-bottom: 4rem; }}
.foot code {{ font-family: var(--mono); color: var(--ink-mid); }}
</style>
</head>
<body>
<header class="top">
<div class="wrap">
<p class="eyebrow">BattleTech 4.11 &middot; from btl4fe.cpp, L4GAUGE.CFG and the player's manual</p>
<h1>Every mech,<br><em>and what it carries</em></h1>
<p class="lede">All <strong>{n} mechs the menu offers</strong> &mdash; {nbase} base chassis
and {nvar} variants &mdash; each with its authored weapons roster, ammo bins and the
stat sheet from the original player's manual. The portraits are the game's own
mission-review images, shown in their undamaged state.</p>
<ul class="rules">
<li><b>Variants share the chassis</b>, not the loadout</li>
<li><b>Blackhawk, Nova, Owens, Kotori</b> have a fixed torso</li>
<li>The manual sheets cover the <b>six 4.0 hulls</b> only</li>
</ul>
</div>
</header>
<div class="bar">
<div class="wrap bar__in">
<div class="seg" role="group" aria-label="Filter">
<button type="button" data-filter="all" aria-pressed="true">All {n}</button>
<button type="button" data-filter="base" aria-pressed="false">Base {nbase}</button>
<button type="button" data-filter="variant" aria-pressed="false">Variants {nvar}</button>
</div>
<input id="search" class="search" type="search" placeholder="Search mech or weapon&hellip;"
aria-label="Search mech or weapon">
<span class="count" id="count">{n} / {n} mechs</span>
</div>
</div>
<main class="wrap">
<div class="key">
<span><i style="background:var(--nrg)"></i>Energy</span>
<span><i style="background:var(--bal)"></i>Ballistic</span>
<span><i style="background:var(--msl)"></i>Missile</span>
</div>
<div class="roster">{cards}</div>
<p class="empty-state" id="nomatch" hidden>No mech matches that.</p>
<p class="foot">The roster is <code>kVehicles[]</code> in <code>game/glass/btl4fe.cpp</code>
&mdash; the exact catalog the in-game menu offers, in menu order. Each mech's weapons and
ammo bins are its <code>cmCrit(...)</code> lines in <code>content/GAUGE/L4GAUGE.CFG</code>,
the critical-damage schematic's authored subsystem roster (independently confirmed against
the subsystem records in <code>BTL4.RES</code>). Nothing is transcribed by hand.</p>
<p class="foot">The portraits are each mech's mission-review image
(<code>GAUGE/&lt;stem&gt;_MR.PCC</code>) resolved through the review screen's own palette.
In play, its zone indexes are live damage slots painted green&rarr;orange&rarr;red by the
ColorMapper gauges; this page shows every zone at the undamaged ramp entry &mdash; the
same green the armor doll idles at. Variants without their own portrait share the base's,
exactly as the resource file does.</p>
<p class="foot">Mech stats are the original player's manual stat sheets (pp. 25, 26, 29).
The manual documents the six 4.0 hulls; Avatar and Sunder arrived with 4.10 and have no
sheet, so their rows show a dash rather than an invented number. FIXED vs twisting torso
is read from the shipped resource file: a mech twists only if its subsystem record carries
the <code>jointtorso</code>/<code>jointtshadow</code> joints &mdash; the four fixed mechs
match the manual's Torso&nbsp;0/0 rows exactly.</p>
<p class="foot">Weapon notes: ER lasers fire red beams, PPCs blue, standard lasers yellow.
Authored ranges where the game states them: PPCs 900&nbsp;m, missiles 800&nbsp;m, the
Blackhawk's lasers 500&nbsp;m, the AFC&nbsp;50 750&nbsp;m. The Blackhawk's SRM&nbsp;6
racks fire Streak ammunition. LRM tube counts are the salvo size; each launcher's ammo
bin is listed beside it.</p>
</main>
<script>
const cards = [...document.querySelectorAll('.mech')];
const count = document.getElementById('count');
const search = document.getElementById('search');
let filter = 'all';
function apply() {{
const q = search.value.trim().toLowerCase();
let shown = 0;
for (const c of cards) {{
const okTag = filter === 'all' || c.dataset.tags.split(' ').includes(filter);
const okQ = !q || c.dataset.search.includes(q);
const on = okTag && okQ;
c.hidden = !on;
if (on) shown++;
}}
count.textContent = shown + ' / ' + cards.length + ' mechs';
document.getElementById('nomatch').hidden = shown > 0;
}}
for (const b of document.querySelectorAll('[data-filter]')) {{
b.addEventListener('click', () => {{
filter = b.dataset.filter;
for (const o of document.querySelectorAll('[data-filter]'))
o.setAttribute('aria-pressed', String(o === b));
apply();
}});
}}
search.addEventListener('input', apply);
apply();
</script>
</body>
</html>