Files
BT411/docs/CONTROLS.html
T
CydandClaude Opus 5 9d536294e4 Mouse buttons are bindable; mouse LOOK scoped (not built)
BUTTONS (shipped).  Win32 hands mouse buttons out as virtual keys, and the
binding path already stores VKs and polls GetAsyncKeyState -- so this needed no
verb, no parser change and no new machinery, just names:

    key MOUSE4 button 0x40        # side buttons
    key MOUSE5 button 0x46
    key MOUSEMIDDLE button 0x41

MOUSELEFT/MOUSERIGHT are named too, with a warning in the file header and the
docs: they are how the player presses cockpit buttons (left = press, right =
latch), and the poll is focus-guarded but not click-aware, so binding either
ALSO fires on every panel click.  The side and middle buttons are the free
ones.  Axes work as well (`key MOUSE4 axis Throttle slew + 0.7`).

One guard: the RGB lamp mirror skips mouse VKs when building its map -- a
keyboard lamp array has no mouse buttons to paint (BTPadBindingIsMouseKey).

Verified live: a bindings.txt carrying two mouse rows loads 76 keys (74 + 2),
and a real injected XBUTTON1 press produced `[emitter] FIRED #1`.  Clean A/B --
the earlier run whose SendInput failed the struct-size check logged no fire at
all.  Shipped commented-out examples in the default profile so the capability
is discoverable.

LOOK (scoped only).  docs/MOUSELOOK_PLAN.md.  Mouse MOVEMENT is a real feature,
not a table entry, and the hard part is not reading the device:

 - The cursor is already spoken for.  The glass cockpit's premise is that all
   72 buttons are mouse targets; mouse-look wants a captured, hidden, recentred
   cursor.  Both cannot be true, so this is a MODE question first -- four
   models compared, recommending hold-to-look (costs nothing when unbound,
   cannot strand a player, behaves the same in surround and exploded layouts).
 - The channels are POSITIONAL (JoystickX/Y are -1..1 deflections the mapper
   reads per control mode); a mouse gives deltas.  Accumulate-into-a-virtual-
   stick reuses every existing rule; a rate model would need new mapper
   semantics.
 - Control mode changes what it MEANS: BASIC steers with the stick, MID/ADV
   twist the torso -- so mouse-look steers in one and aims in the other.

Proposed grammar (new row type, so old builds skip it with a warning),
where the code goes, ~250 lines, and a verification plan whose load-bearing
item is "panel clicks still work when a mouselook binding exists but is not
engaged".  Also flagged: the pod had NO mouse, so nothing here is
reconstruction -- every choice is design, judged on feel, and it must stay out
of the pod build's path.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-26 09:06:58 -05:00

1063 lines
46 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<title>BattleTech 4.11 — Controls</title>
<style>
:root {
--void: #05070a;
--void-2: #080d10;
--glass: #0a1410;
--glass-edge: #16261d;
--phosphor: #40ff40;
--phosphor-2: #8dffa0;
--dim: #189418;
--hazard: #b0302a;
--hazard-lit: #ff5a48;
--amber: #c8a32e;
--amber-lit: #ffd25e;
--ink: #b9cfc0;
--ink-quiet: #6d8478;
--mono: "Cascadia Mono", Consolas, "SF Mono", ui-monospace, Menlo, monospace;
--sans: system-ui, -apple-system, "Segoe UI Variable Text", "Segoe UI", Roboto, sans-serif;
--u: 42px;
--gap: 5px;
}
* { box-sizing: border-box; }
body {
margin: 0;
background:
radial-gradient(120% 90% at 50% -10%, #0d1a14 0%, transparent 60%),
var(--void);
color: var(--ink);
font-family: var(--sans);
line-height: 1.65;
-webkit-font-smoothing: antialiased;
}
body::after {
content: "";
position: fixed;
inset: 0;
pointer-events: none;
background: radial-gradient(130% 100% at 50% 45%, transparent 55%, rgba(0,0,0,.55) 100%);
z-index: 5;
}
.wrap {
max-width: 1180px;
margin: 0 auto;
padding: clamp(28px, 5vw, 64px) clamp(16px, 4vw, 40px) 80px;
position: relative;
z-index: 1;
}
.masthead { border-bottom: 1px solid var(--glass-edge); padding-bottom: 28px; }
.eyebrow {
font-family: var(--mono);
font-size: .72rem;
letter-spacing: .28em;
text-transform: uppercase;
color: var(--dim);
margin: 0 0 14px;
}
h1 {
font-family: var(--mono);
font-size: clamp(1.9rem, 5.2vw, 3.1rem);
line-height: 1.05;
letter-spacing: .04em;
text-transform: uppercase;
color: var(--phosphor);
text-shadow: 0 0 22px rgba(64,255,64,.28);
margin: 0 0 18px;
text-wrap: balance;
}
.lede { max-width: 64ch; margin: 0; color: var(--ink); }
.lede code { font-family: var(--mono); color: var(--phosphor-2); font-size: .92em; }
.provenance {
margin: 18px 0 0;
font-family: var(--mono);
font-size: .74rem;
letter-spacing: .08em;
color: var(--ink-quiet);
}
.glance {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(190px, 1fr));
gap: 1px;
background: var(--glass-edge);
border: 1px solid var(--glass-edge);
margin: 34px 0 0;
}
.glance div { background: var(--void-2); padding: 16px 18px; }
.glance dt {
font-family: var(--mono);
font-size: .68rem;
letter-spacing: .2em;
text-transform: uppercase;
color: var(--ink-quiet);
margin: 0 0 6px;
}
.glance dd { margin: 0; font-family: var(--mono); font-size: 1.02rem; color: var(--phosphor); }
.glance dd small { display: block; font-family: var(--sans); font-size: .78rem; color: var(--ink-quiet); letter-spacing: 0; margin-top: 3px; }
section { margin-top: 64px; }
h2 {
font-family: var(--mono);
font-size: .82rem;
letter-spacing: .3em;
text-transform: uppercase;
color: var(--phosphor);
margin: 0 0 6px;
display: flex;
align-items: center;
gap: 14px;
}
h2::after { content: ""; flex: 1; height: 1px; background: linear-gradient(90deg, var(--glass-edge), transparent); }
h3 {
font-family: var(--mono);
font-size: .78rem;
font-weight: 400;
letter-spacing: .2em;
text-transform: uppercase;
color: var(--phosphor);
margin: 46px 0 8px;
}
.sub { margin: 0 0 26px; max-width: 64ch; color: var(--ink-quiet); font-size: .93rem; }
.panel {
background: linear-gradient(180deg, rgba(64,255,64,.03), transparent 40%), var(--glass);
border: 1px solid var(--glass-edge);
padding: clamp(16px, 3vw, 30px);
overflow-x: auto;
}
.readout {
display: flex;
flex-wrap: wrap;
align-items: baseline;
gap: 8px 22px;
border: 1px solid var(--glass-edge);
border-top: none;
background: var(--void-2);
padding: 13px clamp(16px, 3vw, 30px);
font-family: var(--mono);
min-height: 54px;
}
.readout .rd-name { color: var(--phosphor); font-size: 1rem; letter-spacing: .04em; }
.readout .rd-addr { color: var(--amber-lit); font-size: .82rem; border: 1px solid rgba(200,163,46,.35); padding: 1px 8px; }
.readout .rd-desc { color: var(--ink-quiet); font-size: .85rem; font-family: var(--sans); }
.readout[data-idle="true"] .rd-name { color: var(--ink-quiet); }
.kb { display: flex; flex-direction: column; gap: 14px; min-width: 940px; position: relative; padding-top: 22px; }
.kb-groups { position: absolute; inset: 0; pointer-events: none; }
.kb-groups svg { position: absolute; inset: 0; width: 100%; height: 100%; overflow: visible; }
.kb-groups path { fill: none; stroke-width: 1; opacity: .85; }
.kb-groups path.g-mfd { stroke: #ff9a8c; }
.kb-groups path.g-col { stroke: var(--amber-lit); }
.kb-groups .g-label {
position: absolute;
transform: translate(0, -100%);
font-family: var(--mono);
font-size: .56rem;
letter-spacing: .16em;
text-transform: uppercase;
white-space: nowrap;
padding-bottom: 3px;
}
.kb-groups .g-label.g-mfd { color: #ff9a8c; }
.kb-groups .g-label.g-col { color: var(--amber-lit); }
.kb-bank { display: flex; gap: 22px; }
.kb-main { display: flex; flex-direction: column; gap: var(--gap); }
.kb-row { display: flex; gap: var(--gap); }
.kb-row.zone { margin-top: 18px; }
.kb-side { display: flex; flex-direction: column; gap: var(--gap); justify-content: flex-end; }
.kb-nav { display: flex; flex-direction: column; gap: var(--gap); align-items: center; padding-top: calc(var(--u) + var(--gap)); height: 100%; }
.kb-nav .grow { flex: 1 1 auto; min-height: var(--gap); }
.kb-pad { display: grid; grid-template-columns: repeat(4, var(--u)); gap: var(--gap); }
.key {
width: var(--u);
height: var(--u);
flex: none;
border: 1px solid #1d2b24;
border-bottom-color: #0c1512;
background: linear-gradient(180deg, #101a16, #0b120f);
color: #4e6a5c;
font-family: var(--mono);
font-size: .72rem;
line-height: 1.1;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 2px;
padding: 2px;
cursor: default;
transition: transform .08s ease, box-shadow .12s ease, border-color .12s ease;
}
.key span.k-sub { font-size: .53rem; letter-spacing: .04em; text-transform: uppercase; opacity: .85; }
.key[data-role] { cursor: pointer; }
.key:focus-visible { outline: 2px solid var(--phosphor); outline-offset: 2px; }
.key[data-role="flight"] { color: var(--phosphor); border-color: #2c6b38; background: linear-gradient(180deg, #10281a, #0a1a11); }
.key[data-role="mfd"] { color: #ff9a8c; border-color: #6b2723; background: linear-gradient(180deg, #2a100e, #1a0a09); }
.key[data-role="column"] { color: var(--amber-lit); border-color: #6d5a1c; background: linear-gradient(180deg, #251d08, #171205); }
.key[data-role]:hover, .key.is-active {
transform: translateY(-1px);
border-color: currentColor;
box-shadow: 0 0 0 1px currentColor, 0 0 18px -4px currentColor;
}
.key.w125 { width: calc(var(--u) * 1.25 + var(--gap) * .25); }
.key.w15 { width: calc(var(--u) * 1.5 + var(--gap) * .5); }
.key.w175 { width: calc(var(--u) * 1.75 + var(--gap) * .75); }
.key.w2 { width: calc(var(--u) * 2 + var(--gap)); }
.key.w225 { width: calc(var(--u) * 2.25 + var(--gap) * 1.25); }
.key.w275 { width: calc(var(--u) * 2.75 + var(--gap) * 1.75); }
.key.w6 { width: calc(var(--u) * 6.25 + var(--gap) * 5.25); }
.key.tall { grid-row: span 2; height: calc(var(--u) * 2 + var(--gap)); }
.key.pad0 { grid-column: span 2; width: calc(var(--u) * 2 + var(--gap)); }
.spacer { width: calc(var(--u) * .5); flex: none; }
.legend { display: flex; flex-wrap: wrap; gap: 10px 26px; margin-top: 20px; font-family: var(--mono); font-size: .74rem; letter-spacing: .06em; }
.legend span { display: flex; align-items: center; gap: 8px; color: var(--ink-quiet); }
.legend i { width: 13px; height: 13px; border: 1px solid; display: block; }
.lg-flight { color: var(--phosphor); border-color: currentColor; background: #10281a; }
.lg-mfd { color: #ff9a8c; border-color: currentColor; background: #2a100e; }
.lg-col { color: var(--amber-lit); border-color: currentColor; background: #251d08; }
.lg-off { color: #4e6a5c; border-color: #1d2b24; background: #101a16; }
.pad-shell { display: flex; justify-content: center; }
svg.pad { width: 100%; max-width: 720px; height: auto; }
svg.pad .body { fill: #0c1512; stroke: #1f3329; stroke-width: 2; }
svg.pad .ctl { fill: #12211a; stroke: #2c6b38; stroke-width: 1.6; cursor: pointer; transition: fill .12s ease, stroke .12s ease; }
svg.pad .ctl:hover, svg.pad .ctl.is-active { fill: #1a3b26; stroke: var(--phosphor); }
svg.pad .lbl { font-family: var(--mono); font-size: 11px; fill: var(--phosphor); letter-spacing: .06em; pointer-events: none; }
svg.pad .lbl-q { font-family: var(--mono); font-size: 10px; fill: var(--ink-quiet); letter-spacing: .08em; pointer-events: none; text-transform: uppercase; }
svg.pad .lead { stroke: #244a33; stroke-width: 1; fill: none; }
svg.pad .cap { font-family: var(--mono); font-size: 11px; fill: #cfe8d8; pointer-events: none; }
.cockpit { display: grid; grid-template-columns: repeat(3, 1fr); gap: 16px; min-width: 720px; align-items: start; }
.mfd { border: 1px solid var(--glass-edge); background: #060c09; display: flex; flex-direction: column; }
.mfd-strip { display: flex; gap: 2px; padding: 3px; background: #150a09; }
.mfd-strip b {
flex: 1;
background: linear-gradient(180deg, #b0302a, #7d211d);
color: #ffd9d3;
font-family: var(--mono);
font-size: .62rem;
font-weight: 400;
text-align: center;
padding: 4px 0;
letter-spacing: .04em;
}
.mfd-face { padding: 14px 12px; text-align: center; font-family: var(--mono); }
.mfd-face em { display: block; font-style: normal; color: var(--phosphor); font-size: .8rem; letter-spacing: .12em; text-transform: uppercase; }
.mfd-face small { display: block; color: var(--ink-quiet); font-size: .68rem; margin-top: 5px; letter-spacing: .06em; }
.cockpit-note { grid-column: span 3; margin: 2px 0 0; font-size: .82rem; color: var(--ink-quiet); }
.mfd-map { min-height: 300px; }
.map-body { display: flex; align-items: stretch; flex: 1; }
.map-col { display: flex; flex-direction: column; gap: 2px; padding: 3px; background: #150a09; }
.map-col b {
flex: 1;
min-height: 26px;
display: flex;
align-items: center;
justify-content: center;
background: linear-gradient(90deg, #c8a32e, #8d711b);
color: #2a2205;
font-family: var(--mono);
font-size: .62rem;
font-weight: 400;
letter-spacing: .04em;
padding: 0 7px;
}
.map-face {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 6px;
padding: 14px 8px;
font-family: var(--mono);
text-align: center;
background: repeating-linear-gradient(0deg, rgba(64,255,64,.05) 0 1px, transparent 1px 22px), #060c09;
}
.map-face em { font-style: normal; color: var(--phosphor); font-size: .8rem; letter-spacing: .12em; text-transform: uppercase; }
.map-face small { color: var(--ink-quiet); font-size: .66rem; letter-spacing: .06em; line-height: 1.5; }
.tbl-scroll { overflow-x: auto; border: 1px solid var(--glass-edge); }
table { width: 100%; border-collapse: collapse; font-size: .87rem; min-width: 480px; }
caption { text-align: left; font-family: var(--mono); font-size: .7rem; letter-spacing: .2em; text-transform: uppercase; color: var(--ink-quiet); padding: 12px 14px; background: var(--void-2); }
th, td { text-align: left; padding: 9px 14px; border-top: 1px solid var(--glass-edge); }
th { font-family: var(--mono); font-size: .68rem; letter-spacing: .16em; text-transform: uppercase; color: var(--ink-quiet); font-weight: 400; }
td.mono { font-family: var(--mono); color: var(--phosphor); white-space: nowrap; }
td.addr { font-family: var(--mono); color: var(--amber-lit); font-variant-numeric: tabular-nums; white-space: nowrap; }
tbody tr:hover td { background: rgba(64,255,64,.035); }
pre {
margin: 0;
padding: 18px clamp(14px, 3vw, 24px);
background: var(--void-2);
border: 1px solid var(--glass-edge);
overflow-x: auto;
font-family: var(--mono);
font-size: .82rem;
line-height: 1.7;
color: var(--ink);
}
pre b { color: var(--phosphor); font-weight: 400; }
pre i { color: var(--ink-quiet); font-style: normal; }
.two-col { display: grid; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); gap: 22px; }
.callout {
border-left: 2px solid var(--hazard);
background: linear-gradient(90deg, rgba(176,48,42,.12), transparent 70%);
padding: 14px 18px;
margin-top: 26px;
}
.callout p { margin: 0; font-size: .9rem; }
.callout strong { font-family: var(--mono); color: var(--hazard-lit); letter-spacing: .04em; }
.reach { display: flex; justify-content: center; }
.reach-fig { margin: 0; width: min(430px, 100%); }
.reach-glass { position: relative; aspect-ratio: 4 / 3; background: var(--glass); border: 1px solid var(--glass-edge); overflow: hidden; }
.reach-band {
position: absolute;
left: 0; right: 0;
height: 50%;
background: repeating-linear-gradient(-45deg, rgba(176,48,42,.16) 0 5px, transparent 5px 11px);
display: flex; align-items: center; justify-content: center;
font-family: var(--mono); font-size: .66rem; letter-spacing: .14em; text-transform: uppercase; color: #e08a7e;
}
.reach-band.is-top { top: 0; border-bottom: 1px dashed rgba(176,48,42,.6); }
.reach-band.is-bot { bottom: 0; }
.reach-strip { position: absolute; left: 0; right: 0; height: 8px; display: flex; gap: 2px; z-index: 2; }
.reach-strip.is-top { top: 0; }
.reach-strip.is-bot { bottom: 0; }
.reach-strip i { flex: 1; background: var(--hazard); display: block; }
.reach-strip.is-lit i:nth-child(2) { background: var(--hazard-lit); }
.reach-fig figcaption { margin-top: 12px; font-size: .84rem; color: var(--ink-quiet); }
.reach-fig figcaption b { font-family: var(--mono); font-weight: 400; color: var(--phosphor-2); }
.pos-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(148px, 1fr)); gap: 18px; }
.pos { margin: 0; }
.pos-box { position: relative; aspect-ratio: 16 / 9; background: var(--void-2); border: 1px solid var(--glass-edge); }
.pos-box i, .pos-box u { position: absolute; display: block; }
.pos-box i { width: 16.7%; height: 22.2%; background: #2a100e; border: 1px solid #7d3029; }
.pos-box u { width: 16.9%; height: 40%; background: #251d08; border: 1px solid var(--amber); }
.pos-box i.ul { left: 0; top: 0; }
.pos-box i.uc { left: 41.65%; top: 0; }
.pos-box i.ur { right: 0; top: 0; }
.pos-box i.ll { left: 0; bottom: 0; }
.pos-box i.lr { right: 0; bottom: 0; }
.pos-box u { left: 41.55%; bottom: 0; }
.p-left u { left: 0; bottom: 0; }
.p-left i.ll { left: 16.9%; }
.p-right u { left: auto; right: 0; bottom: 0; }
.p-right i.lr { right: 16.9%; }
.p-midleft u { left: 0; bottom: auto; top: 30%; }
.p-midright u { left: auto; right: 0; bottom: auto; top: 30%; }
.pos figcaption { margin-top: 9px; font-family: var(--mono); font-size: .72rem; letter-spacing: .12em; color: var(--phosphor); }
.pos figcaption small { display: block; font-family: var(--sans); font-size: .76rem; letter-spacing: 0; color: var(--ink-quiet); margin-top: 2px; }
footer {
margin-top: 72px;
padding-top: 22px;
border-top: 1px solid var(--glass-edge);
font-family: var(--mono);
font-size: .74rem;
letter-spacing: .06em;
color: var(--ink-quiet);
display: flex;
flex-wrap: wrap;
gap: 8px 24px;
}
@media (prefers-reduced-motion: reduce) { * { transition: none !important; } }
</style>
<div class="wrap">
<header class="masthead">
<p class="eyebrow">Virtual World Entertainment · Tesla II pod · BT 4.11</p>
<h1>BattleTech<br>Controls Map</h1>
<p class="lede">
Every input the pod answers to. <strong>The keyboard is the button
board</strong>: the letter and number rows are the MFD banks laid out
where they sit on the panel, and flight moved to the number pad so the
board stays free. Every one of the 72 buttons is also a mouse target on
the cockpit itself.
</p>
<p class="provenance">Generated from the default <code>bindings.txt</code> · all of it rebindable</p>
</header>
<dl class="glance">
<div>
<dt>Fly</dt>
<dd>Num 8 2 4 6<small>stick · 7/9 pedals · 0 fires · 5 all-stop</small></dd>
</div>
<div>
<dt>Throttle</dt>
<dd>Shift / Ctrl<small>walks the lever; it sticks where you leave it</small></dd>
</div>
<div>
<dt>Reverse</dt>
<dd>Alt<small>the throttle handle's own button</small></dd>
</div>
<div>
<dt>Control mode</dt>
<dd>F7<small>BAS → MID → ADV. You spawn in BASIC</small></dd>
</div>
</dl>
<div class="callout">
<p>
<strong>YOU SPAWN IN BASIC.</strong> In BASIC the stick steers and the
pedals do nothing. Press <b>F7</b> once for MID, where the pedals steer
and the stick twists the torso — the full pod experience. If "turning
doesn't work", this is why, not a bug. (Control mode is not the menu's
experience setting; that one changes the simulation, not your controls.)
</p>
</div>
<section>
<h2>Keyboard</h2>
<p class="sub">
Colour is the bank each key drives: green flies the mech, red is an MFD
button bank, amber is a column beside the map. Dark keys are unbound and
free to take. Hover a key to read what it does.
</p>
<div class="panel">
<div class="kb" id="kb"></div>
<div class="legend">
<span><i class="lg-flight"></i> flight &amp; fire</span>
<span><i class="lg-mfd"></i> MFD button banks</span>
<span><i class="lg-col"></i> map columns</span>
<span><i class="lg-off"></i> unbound</span>
</div>
</div>
<div class="readout" id="rd-kb" data-idle="true">
<span class="rd-name">Select a key</span>
<span class="rd-desc">Hover a key to read what it drives.</span>
</div>
<div class="callout">
<p>
<strong>COOLANT VALVES BITE.</strong> The valve detents on
<b>1 2 3 Q W E</b> run 1 5 50 (max) <b>0 (CLOSED)</b> back to
1. One press past max shuts that loop off and everything on it
overheats. Boosting one loop starves the others — the supply is
shared. <b>4</b> is the flush (hold it), <b>R</b> re-balances every
valve.
</p>
</div>
</section>
<section>
<h2>Where the keys live on the panel</h2>
<p class="sub">
Each MFD carries eight buttons: a four-wide top row and a four-wide row
beneath it. The keyboard rows run left to right across the upper
displays, then the lower pair — the same order your hand finds them in
the pod.
</p>
<div class="panel">
<div class="cockpit">
<div class="mfd">
<div class="mfd-strip"><b>1</b><b>2</b><b>3</b><b>4</b></div>
<div class="mfd-face"><em>Heat / coolant</em><small>0x2F 0x2C<br>0x2B 0x28</small></div>
<div class="mfd-strip"><b>Q</b><b>W</b><b>E</b><b>R</b></div>
</div>
<div class="mfd">
<div class="mfd-strip"><b>5</b><b>6</b><b>7</b><b>8</b></div>
<div class="mfd-face"><em>Engineering</em><small>0x27 0x24<br>0x23 0x20</small></div>
<div class="mfd-strip"><b>T</b><b>Y</b><b>U</b><b>I</b></div>
</div>
<div class="mfd">
<div class="mfd-strip"><b>9</b><b>0</b><b>-</b><b>=</b></div>
<div class="mfd-face"><em>Comm / hotbox</em><small>0x37 0x34<br>0x33 0x30</small></div>
<div class="mfd-strip"><b>O</b><b>P</b><b>[</b><b>]</b></div>
</div>
<div class="mfd">
<div class="mfd-strip"><b>A</b><b>S</b><b>D</b><b>F</b></div>
<div class="mfd-face"><em>Left Weapons</em><small>0x0F 0x0C<br>0x0B 0x08</small></div>
<div class="mfd-strip"><b>Z</b><b>X</b><b>C</b><b>V</b></div>
</div>
<div class="mfd mfd-map">
<div class="map-body">
<div class="map-col"><b>F1</b><b>F2</b><b>F3</b><b>F4</b><b>F5</b><b>F6</b></div>
<div class="map-face">
<em>Map · radar</em>
<small>
zoom in / out<br>
thermal · CROUCH<br>
searchlight · display<br>
0x10 0x15
</small>
</div>
<div class="map-col"><b>F7</b><b>F8</b><b>F9</b><b>F10</b><b>F11</b><b>F12</b></div>
</div>
</div>
<div class="mfd">
<div class="mfd-strip"><b>H</b><b>J</b><b>K</b><b>L</b></div>
<div class="mfd-face"><em>Right Weapons</em><small>0x07 0x04<br>0x03 0x00</small></div>
<div class="mfd-strip"><b>N</b><b>M</b><b>,</b><b>.</b></div>
</div>
<p class="cockpit-note">
G and B stay unbound on purpose: they are the physical gap between
the lower clusters. The right-hand map column is control mode (F7)
and Generators AD (F9F12); F8 is a column slot the pod never
wired.
</p>
</div>
</div>
<div class="tbl-scroll" style="margin-top:26px">
<table>
<caption>The weapon and engineering displays are page-gated</caption>
<tbody>
<tr><th>Quad page</th><td>The outer buttons jump straight to a populated Eng page.</td></tr>
<tr><th>Eng page</th><td>Generator select AD, bus mode, coolant toggle, trigger configure, and <b>eject</b>.</td></tr>
<tr><th>Jams &amp; fires</th><td>A weapon gone quiet with ammo left has <b>jammed</b> — heat jams ballistics, authentically. Its Eng-page buttons <b>flash</b>. <b>Tap eject</b> to clear the round; <b>hold ~3 s</b> to jettison a burning bay before it detonates. An ammo bay fire gives you about ten seconds and it will kill you.</td></tr>
</tbody>
</table>
</div>
</section>
<section>
<h2>Gamepad</h2>
<p class="sub">An Xbox-compatible pad drives all five axes and the grip buttons. Hover a control for its RIO address.</p>
<div class="panel pad-shell">
<svg class="pad" viewBox="0 0 700 400" role="img" aria-label="Xbox controller with BattleTech bindings">
<rect class="ctl" data-name="LB — Look left" data-addr="0x44" data-desc="Hat left" x="112" y="60" width="86" height="26" rx="13"></rect>
<rect class="ctl" data-name="RB — Look right" data-addr="0x43" data-desc="Hat right" x="502" y="60" width="86" height="26" rx="13"></rect>
<rect class="ctl" data-name="LT — Left pedal" data-addr="axis" data-desc="Turn left (MID/ADV control mode)" x="132" y="26" width="66" height="24" rx="12"></rect>
<rect class="ctl" data-name="RT — Right pedal" data-addr="axis" data-desc="Turn right (MID/ADV control mode)" x="502" y="26" width="66" height="24" rx="12"></rect>
<text class="lbl-q" x="132" y="20">LT · left pedal</text>
<text class="lbl-q" x="502" y="20">RT · right pedal</text>
<text class="lbl-q" x="112" y="102">LB · look left</text>
<text class="lbl-q" x="486" y="102">RB · look right</text>
<path class="body" d="M196 112 h308 c46 0 78 26 92 74 l32 108 c14 48 -10 82 -52 82 c-36 0 -58 -22 -74 -54 l-22 -44 h-180 l-22 44 c-16 32 -38 54 -74 54 c-42 0 -66 -34 -52 -82 l32 -108 c14 -48 46 -74 92 -74 z"></path>
<circle class="ctl" data-name="Left stick — torso twist / aim" data-addr="axes" data-desc="Pod-faithful: X twists the torso, Y aims it (deadzone 0.24)" cx="248" cy="176" r="34"></circle>
<text class="cap" x="222" y="181">STICK</text>
<path class="lead" d="M214 176 H150"></path>
<text class="lbl-q" x="60" y="180">torso twist / aim</text>
<g class="ctl" data-name="D-pad — look &amp; torso centre" data-addr="0x410x44" data-desc="Up = torso centre, down = look behind, left/right = look">
<rect x="286" y="266" width="24" height="66" rx="4"></rect>
<rect x="265" y="287" width="66" height="24" rx="4"></rect>
</g>
<path class="lead" d="M265 299 H180"></path>
<text class="lbl-q" x="86" y="303">look · torso centre</text>
<circle class="ctl" data-name="Right stick Y — Throttle" data-addr="axis" data-desc="Walks the throttle lever; the position sticks" cx="404" cy="266" r="32"></circle>
<text class="cap" x="381" y="271">THRTL</text>
<path class="lead" d="M436 266 H520"></path>
<text class="lbl-q" x="524" y="270">throttle</text>
<circle class="ctl" data-name="Y — Torso centre" data-addr="0x42" data-desc="Re-centre the torso" cx="470" cy="146" r="20"></circle>
<circle class="ctl" data-name="X — Upper thumb" data-addr="0x47" data-desc="Fire group" cx="428" cy="180" r="20"></circle>
<circle class="ctl" data-name="B — Reverse thrust" data-addr="0x3F" data-desc="The throttle handle's button" cx="512" cy="180" r="20"></circle>
<circle class="ctl" data-name="A — Main trigger" data-addr="0x40" data-desc="Fires the selected weapon group" cx="470" cy="214" r="20"></circle>
<text class="lbl" x="465" y="151">Y</text>
<text class="lbl" x="423" y="185">X</text>
<text class="lbl" x="507" y="185">B</text>
<text class="lbl" x="465" y="219">A</text>
<path class="lead" d="M490 214 L556 236"></path>
<text class="lbl-q" x="560" y="240">A · main trigger</text>
<text class="lbl-q" x="560" y="196">X upper · B reverse · Y torso centre</text>
<path class="lead" d="M532 180 H556"></path>
</svg>
</div>
<div class="readout" id="rd-pad" data-idle="true">
<span class="rd-name">Select a control</span>
<span class="rd-desc">Hover the diagram to read its binding.</span>
</div>
<p class="sub" style="margin-top:22px">
<b>Flight sticks, HOTAS and pedals:</b> run <code>joyconfig.bat</code>
once. A wizard asks you to move each control in turn, writes the
bindings, and drops you into the solo menu. It only rewrites its own
section of <code>bindings.txt</code>, so hand edits elsewhere survive.
</p>
</section>
<section>
<h2>The mouse</h2>
<p class="sub">
Every button on every display is clickable and lights when the game
commands its lamp — and each reaches in <em style="font-style:normal;color:var(--phosphor-2)">under the glass</em>
beside it, so the picture is the press target. The lit strip at the edge
is only the lamp. Right-click latches a button held.
</p>
<div class="panel">
<div class="reach">
<figure class="reach-fig">
<div class="reach-glass">
<span class="reach-strip is-top is-lit" aria-hidden="true"><i></i><i></i><i></i><i></i></span>
<span class="reach-band is-top">press — top row</span>
<span class="reach-band is-bot">press — bottom row</span>
<span class="reach-strip is-bot" aria-hidden="true"><i></i><i></i><i></i><i></i></span>
</div>
<figcaption>
One MFD. Four buttons above the glass and four below, each reaching
<b>half the pane</b> deep — so the two banks meet in the middle and
the whole face answers the mouse. Only a thin strip clears the edge
to show its lamp.
</figcaption>
</figure>
</div>
</div>
<h3>Arranging the glass</h3>
<p class="sub">
The pod bolted its displays down at one size. On a desktop panel there is
room to trade viewscreen for instrument, so all six are yours to size —
and the map to place. Set these in <code>content\environ.ini</code>,
which the game writes on first run with every option documented.
</p>
<div class="pos-grid">
<figure class="pos">
<div class="pos-box p-center"><i class="ul"></i><i class="uc"></i><i class="ur"></i><i class="ll"></i><i class="lr"></i><u></u></div>
<figcaption>CENTER<small>as the pod had it</small></figcaption>
</figure>
<figure class="pos">
<div class="pos-box p-left"><i class="ul"></i><i class="uc"></i><i class="ur"></i><i class="ll"></i><i class="lr"></i><u></u></div>
<figcaption>LEFT<small>bottom corner; the MFD slides inboard</small></figcaption>
</figure>
<figure class="pos">
<div class="pos-box p-right"><i class="ul"></i><i class="uc"></i><i class="ur"></i><i class="ll"></i><i class="lr"></i><u></u></div>
<figcaption>RIGHT<small>the same, other side</small></figcaption>
</figure>
<figure class="pos">
<div class="pos-box p-midleft"><i class="ul"></i><i class="uc"></i><i class="ur"></i><i class="ll"></i><i class="lr"></i><u></u></div>
<figcaption>MIDLEFT<small>halfway up; leaves the bottom row alone</small></figcaption>
</figure>
<figure class="pos">
<div class="pos-box p-midright"><i class="ul"></i><i class="uc"></i><i class="ur"></i><i class="ll"></i><i class="lr"></i><u></u></div>
<figcaption>MIDRIGHT<small>halfway up, right</small></figcaption>
</figure>
</div>
<div class="tbl-scroll" style="margin-top:26px">
<table>
<caption>environ.ini — display layout</caption>
<tbody>
<tr><th>BT_MFD_SCALE</th><td>All five MFDs, as a percentage of pod size. <span style="color:var(--ink-quiet)">25200, default 100</span></td></tr>
<tr><th>BT_MFD_SCALE_<span style="color:var(--phosphor)">xx</span></th><td>Any one on its own — <span class="addr" style="font-family:var(--mono)">UL UC UR LL LR</span>. Unset inherits the line above.</td></tr>
<tr><th>BT_RADAR_SCALE</th><td>The map alone.</td></tr>
<tr><th>BT_RADAR_POS</th><td class="mono">CENTER · LEFT · RIGHT · MIDLEFT · MIDRIGHT</td></tr>
<tr><th>BT_GLASS_PANELS</th><td>1 = break every display into its own desktop window instead of the composited cockpit.</td></tr>
</tbody>
</table>
</div>
<div class="callout">
<p>
<strong>Ask for too much and the layout wins.</strong> The surround
bands grow to fit whatever you ask for, and a map in a bottom corner
pushes the lower MFD beside it inboard — nothing overlaps and nothing
comes out stretched.
</p>
</div>
<h3>Filling the screen</h3>
<p class="sub" style="margin-bottom:0">
The cockpit fits itself to whatever window it gets at one uniform scale,
so drag it to any shape and nothing distorts — a window that isn't the
cockpit's shape just gets black bars. Launch with <code>-fit</code> for
borderless over the whole monitor.
</p>
</section>
<section>
<h2>Rebinding</h2>
<p class="sub">
<code>content\bindings.txt</code> is written with the full default layout
on first run. Change a line, restart. Delete the file to restore these
defaults — worth doing after an update, since an existing file is never
overwritten.
</p>
<div class="two-col">
<pre><i># grammar</i>
<b>key</b> &lt;KEY&gt; <b>button</b> &lt;addr&gt;
<b>key</b> &lt;KEY&gt; <b>axis</b> &lt;chan&gt; deflect &lt;+|-&gt; &lt;rate&gt;
<b>key</b> &lt;KEY&gt; <b>axis</b> &lt;chan&gt; slew &lt;+|-&gt; &lt;rate&gt;
<b>key</b> &lt;KEY&gt; <b>axis</b> &lt;chan&gt; set &lt;value&gt;
<b>pad</b> &lt;BTN&gt; <b>button</b> &lt;addr&gt;
<b>padaxis</b> &lt;SRC&gt; <b>axis</b> &lt;chan&gt; [invert] [slew &lt;r&gt;]
<i># deflect springs back, slew walks and sticks
# (that is the throttle lever), set jumps</i></pre>
<pre><i># examples</i>
<b>key</b> G <b>button</b> 0x0E <i># regroup weapon 1 on G</i>
<b>key</b> B <b>button</b> 0x41 <i># look behind on B</i>
<i># put driving back on WASD if you prefer</i>
<b>key</b> W <b>axis</b> Throttle slew + 0.7
<b>key</b> S <b>axis</b> Throttle slew - 0.7
<b>key</b> A <b>axis</b> LeftPedal deflect + 2.5
<b>key</b> D <b>axis</b> RightPedal deflect + 2.5</pre>
</div>
<div class="tbl-scroll" style="margin-top:22px">
<table>
<caption>Reference — channels, addresses, key names</caption>
<tbody>
<tr><th>Channels</th><td class="mono">Throttle · LeftPedal · RightPedal · JoystickX · JoystickY · Turn</td></tr>
<tr><th>Buttons</th><td class="addr">0x00 0x47</td></tr>
<tr><th>No function</th><td class="addr">0x16 0x17 0x19 0x1E 0x1F 0x380x3E <span style="color:var(--ink-quiet);font-family:var(--sans)">— the pod never wired these</span></td></tr>
<tr><th>Key names</th><td class="mono">AZ · 09 · F1F12 · NUMPAD09 · NUMPADDOT · UP DOWN LEFT RIGHT · SPACE · LSHIFT LCTRL LALT · MINUS EQUALS COMMA PERIOD LBRACKET RBRACKET BACKTICK</td></tr>
<tr><th>Mouse</th><td class="mono">MOUSE4 · MOUSE5 · MOUSEMIDDLE <span style="color:var(--ink-quiet);font-family:var(--sans)">— buttons only; left/right also press cockpit buttons</span></td></tr>
<tr><th>Pad</th><td class="mono">A B X Y · DPAD_UP/DOWN/LEFT/RIGHT · LB RB · BACK START · LS RS</td></tr>
<tr><th>Pad axes</th><td class="mono">LX LY RX RY LT RT</td></tr>
</tbody>
</table>
</div>
<div class="callout">
<p>
<strong>A BOUND KEY LOSES ITS 1995 MEANING.</strong> Binding a key
removes it from the authentic typed-hotkey channel so it cannot
double-dispatch — and this board binds nearly everything, so the old
typed hotkeys (5 = Quad page, z = Eng1, t/y/u/i/o = pilot select, +/
= target zoom) are given up by design. Unbind a key to get its 1995
meaning back. Same rule for the built-ins: <b>`</b> is the view
toggle, and J/K/L cycle MFD preset pages only while unbound.
</p>
</div>
</section>
<footer>
<span>BattleTech 4.11</span>
<span>gitea.mysticmachines.com/VWE/BT411</span>
<span>docs/CONTROLS.md · CONTROLS.txt ships with the game</span>
<span>RGB keyboards mirror the pod lamps · BT_KEYLIGHT=0 to disable</span>
</footer>
</div>
<script>
(function () {
"use strict";
var F = "flight", M = "mfd", C = "column", X = null, SP = "spacer";
var rows = [
[
["Esc", "", X], [SP],
["F1", "MAP+", C, "0x10", "Map zoom in"],
["F2", "MAP-", C, "0x11", "Map zoom out"],
["F3", "IR", C, "0x12", "Thermal sight"],
["F4", "CROUCH", C, "0x13", "Duck — present a smaller target"], [SP],
["F5", "LAMP", C, "0x14", "Searchlight"],
["F6", "DISP", C, "0x15", "Cycle the secondary display"],
["F7", "MODE", C, "0x18", "Cycle control mode: BAS / MID / ADV"],
["F8", "", C, "0x19", "Column slot the pod never wired"], [SP],
["F9", "GEN A", C, "0x1A", "Generator A on/off"],
["F10", "GEN B", C, "0x1B", "Generator B on/off"],
["F11", "GEN C", C, "0x1C", "Generator C on/off"],
["F12", "GEN D", C, "0x1D", "Generator D on/off"]
],
[
["`", "VIEW", F, "", "Cockpit / chase camera toggle"],
["1", "VALVE", M, "0x2F", "Condenser 1 valve — detents 1-5-50-CLOSED"],
["2", "VALVE", M, "0x2E", "Condenser 2 valve"],
["3", "VALVE", M, "0x2D", "Condenser 3 valve"],
["4", "FLUSH", M, "0x2C", "Coolant flush — HOLD to dump the reservoir"],
["5", "", M, "0x27", "Engineering · top 1"], ["6", "", M, "0x26", "Engineering · top 2"],
["7", "", M, "0x25", "Engineering · top 3"], ["8", "", M, "0x24", "Engineering · top 4"],
["9", "", M, "0x37", "Comm / hotbox · top 1"], ["0", "", M, "0x36", "Comm / hotbox · top 2"],
["-", "", M, "0x35", "Comm / hotbox · top 3"], ["=", "", M, "0x34", "Comm / hotbox · top 4"],
["Bksp", "", X, "", "", "w2"]
],
[
["Tab", "", X, "", "", "w15"],
["Q", "VALVE", M, "0x2B", "Condenser 4 valve"], ["W", "VALVE", M, "0x2A", "Condenser 5 valve"],
["E", "VALVE", M, "0x29", "Condenser 6 valve"], ["R", "BALANCE", M, "0x28", "Balance every coolant valve"],
["T", "", M, "0x23", "Engineering · lower 1"], ["Y", "", M, "0x22", "Engineering · lower 2"],
["U", "", M, "0x21", "Engineering · lower 3"], ["I", "", M, "0x20", "Engineering · lower 4"],
["O", "", M, "0x33", "Comm / hotbox · lower 1"], ["P", "", M, "0x32", "Comm / hotbox · lower 2"],
["[", "", M, "0x31", "Comm / hotbox · lower 3"], ["]", "", M, "0x30", "Comm / hotbox · lower 4"],
["\\", "", X, "", "", "w15"]
],
[
["Caps", "", X, "", "", "w175"],
["A", "", M, "0x0F", "Left Weapons · top 1"], ["S", "", M, "0x0E", "Left Weapons · top 2"],
["D", "", M, "0x0D", "Left Weapons · top 3"], ["F", "", M, "0x0C", "Left Weapons · top 4"],
["G", "GAP", X, "", "Panel gap between the lower clusters — free"],
["H", "", M, "0x07", "Right Weapons · top 1"], ["J", "", M, "0x06", "Right Weapons · top 2"],
["K", "", M, "0x05", "Right Weapons · top 3"], ["L", "", M, "0x04", "Right Weapons · top 4"],
[";", "", X], ["'", "", X], ["Enter", "", X, "", "", "w225"]
],
[
["Shift", "THR +", F, "axis", "Throttle up — walks the lever, position holds", "w225"],
["Z", "", M, "0x0B", "Left Weapons · lower 1"], ["X", "", M, "0x0A", "Left Weapons · lower 2"],
["C", "", M, "0x09", "Left Weapons · lower 3"], ["V", "", M, "0x08", "Left Weapons · lower 4"],
["B", "GAP", X, "", "Panel gap between the lower clusters — free"],
["N", "", M, "0x03", "Right Weapons · lower 1"], ["M", "", M, "0x02", "Right Weapons · lower 2"],
[",", "", M, "0x01", "Right Weapons · lower 3"], [".", "", M, "0x00", "Right Weapons · lower 4"],
["/", "", X], ["Shift", "THR +", F, "axis", "Throttle up", "w275"]
],
[
["Ctrl", "THR ", F, "axis", "Throttle down — walks the lever, position holds", "w125"],
["Win", "", X, "", "", "w125"],
["Alt", "REV", F, "0x3F", "Reverse thrust — the throttle handle's button", "w125"],
["Space", "TRIGGER", F, "0x40", "Main trigger", "w6"],
["Alt", "REV", F, "0x3F", "Reverse thrust", "w125"],
["Fn", "", X, "", "", "w125"],
["Menu", "", X, "", "", "w125"],
["Ctrl", "THR ", F, "axis", "Throttle down", "w125"]
]
];
var navRows = [
[["Ins", "", X], ["Home", "", X], ["PgUp", "", X]],
[["Del", "", X], ["End", "", X], ["PgDn", "", X]],
"grow",
[["↑", "CENTRE", F, "0x42", "Torso centre"]],
[["←", "LOOK", F, "0x44", "Look left"], ["↓", "BEHIND", F, "0x41", "Look behind"], ["→", "LOOK", F, "0x43", "Look right"]]
];
var padKeys = [
["Num", "", X], ["/", "", X], ["*", "", X], ["", "", X],
["7", "L PED", F, "axis", "Left pedal — turns in MID/ADV"],
["8", "AIM UP", F, "axis", "Torso aim up"],
["9", "R PED", F, "axis", "Right pedal — turns in MID/ADV"],
["+", "", X, "", "", "tall"],
["4", "TWIST", F, "axis", "Torso twist left"],
["5", "ALL STOP", F, "axis", "Throttle to zero"],
["6", "TWIST", F, "axis", "Torso twist right"],
["1", "PINKY", F, "0x45", "Fire — pinky"],
["2", "AIM DN", F, "axis", "Torso aim down"],
["3", "MIDDLE", F, "0x46", "Fire — middle thumb"],
["Ent", "", X, "", "", "tall"],
["0", "TRIGGER", F, "0x40", "Main trigger", "pad0"],
[".", "UPPER", F, "0x47", "Fire — upper thumb"]
];
var kbEl = document.getElementById("kb");
var rdKb = document.getElementById("rd-kb");
var rdPad = document.getElementById("rd-pad");
function makeKey(def) {
if (def[0] === SP) {
var gap = document.createElement("div");
gap.className = "spacer";
gap.setAttribute("aria-hidden", "true");
return gap;
}
var cap = def[0], sub = def[1], role = def[2], addr = def[3] || "", desc = def[4] || "", w = def[5];
var el = document.createElement(role ? "button" : "div");
el.className = "key" + (w ? " " + w : "");
if (role) {
el.type = "button";
el.setAttribute("data-role", role);
el.setAttribute("data-name", cap);
el.setAttribute("data-addr", addr);
el.setAttribute("data-desc", desc);
} else if (desc) {
el.setAttribute("data-name", cap);
el.setAttribute("data-desc", desc);
el.title = desc;
}
var main = document.createElement("span");
main.textContent = cap;
el.appendChild(main);
if (sub) {
var s = document.createElement("span");
s.className = "k-sub";
s.textContent = sub;
el.appendChild(s);
}
return el;
}
var bank = document.createElement("div");
bank.className = "kb-bank";
var main = document.createElement("div");
main.className = "kb-main";
rows.forEach(function (row, index) {
var r = document.createElement("div");
r.className = "kb-row" + (index === 1 || index === 3 ? " zone" : "");
row.forEach(function (def) { r.appendChild(makeKey(def)); });
main.appendChild(r);
});
bank.appendChild(main);
var nav = document.createElement("div");
nav.className = "kb-side";
var navWrap = document.createElement("div");
navWrap.className = "kb-nav";
navRows.forEach(function (row) {
if (row === "grow") {
var g = document.createElement("div");
g.className = "grow";
navWrap.appendChild(g);
return;
}
var r = document.createElement("div");
r.className = "kb-row";
row.forEach(function (def) { r.appendChild(makeKey(def)); });
navWrap.appendChild(r);
});
nav.appendChild(navWrap);
bank.appendChild(nav);
var side = document.createElement("div");
side.className = "kb-side";
var pad = document.createElement("div");
pad.className = "kb-pad";
padKeys.forEach(function (def) { pad.appendChild(makeKey(def)); });
side.appendChild(pad);
bank.appendChild(side);
kbEl.appendChild(bank);
function show(readout, name, addr, desc) {
readout.setAttribute("data-idle", "false");
readout.innerHTML = "";
var n = document.createElement("span");
n.className = "rd-name";
n.textContent = name;
readout.appendChild(n);
if (addr) {
var a = document.createElement("span");
a.className = "rd-addr";
a.textContent = addr;
readout.appendChild(a);
}
if (desc) {
var d = document.createElement("span");
d.className = "rd-desc";
d.textContent = desc;
readout.appendChild(d);
}
}
function bind(root, readout, selector) {
if (!root) return;
var active = null;
function handle(e) {
var t = e.target.closest(selector);
if (!t || !t.getAttribute("data-name")) return;
if (active) active.classList.remove("is-active");
active = t;
t.classList.add("is-active");
show(readout, t.getAttribute("data-name"), t.getAttribute("data-addr"), t.getAttribute("data-desc"));
}
root.addEventListener("pointerover", handle);
root.addEventListener("focusin", handle);
root.addEventListener("click", handle);
}
var byCap = {};
Array.prototype.forEach.call(main.querySelectorAll(".key"), function (el) {
var cap = el.firstChild ? el.firstChild.textContent : "";
if (cap && !byCap[cap]) byCap[cap] = el;
});
var groups = [
{ label: "Map left column", kind: "g-col", top: ["F1", "F6"] },
{ label: "Map right column", kind: "g-col", top: ["F7", "F12"] },
{ label: "Heat / coolant", kind: "g-mfd", top: ["1", "4"], bottom: ["Q", "R"] },
{ label: "Engineering", kind: "g-mfd", top: ["5", "8"], bottom: ["T", "I"] },
{ label: "Comm / hotbox", kind: "g-mfd", top: ["9", "="], bottom: ["O", "]"] },
{ label: "Left Weapons", kind: "g-mfd", top: ["A", "F"], bottom: ["Z", "V"] },
{ label: "Right Weapons", kind: "g-mfd", top: ["H", "L"], bottom: ["N", "."] }
];
function drawGroups() {
var old = kbEl.querySelector(".kb-groups");
if (old) old.remove();
var host = document.createElement("div");
host.className = "kb-groups";
var svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
host.appendChild(svg);
var base = kbEl.getBoundingClientRect();
var padPx = 3;
function box(a, b) {
if (!a || !b) return null;
var ra = a.getBoundingClientRect(), rb = b.getBoundingClientRect();
return {
l: ra.left - base.left - padPx,
r: rb.right - base.left + padPx,
t: ra.top - base.top - padPx,
b: ra.bottom - base.top + padPx
};
}
groups.forEach(function (g) {
var top = box(byCap[g.top[0]], byCap[g.top[1]]);
if (!top) return;
var bot = g.bottom ? box(byCap[g.bottom[0]], byCap[g.bottom[1]]) : null;
var d;
if (bot) {
d = "M" + top.l + " " + top.t + "H" + top.r + "V" + bot.t +
"H" + bot.r + "V" + bot.b + "H" + bot.l + "V" + top.b + "H" + top.l + "Z";
} else {
d = "M" + top.l + " " + top.t + "H" + top.r + "V" + top.b + "H" + top.l + "Z";
}
var path = document.createElementNS("http://www.w3.org/2000/svg", "path");
path.setAttribute("d", d);
path.setAttribute("class", g.kind);
svg.appendChild(path);
var label = document.createElement("span");
label.className = "g-label " + g.kind;
label.textContent = g.label;
label.style.left = top.l + "px";
label.style.top = top.t + "px";
host.appendChild(label);
});
kbEl.appendChild(host);
}
drawGroups();
window.addEventListener("resize", drawGroups);
if (document.fonts && document.fonts.ready) document.fonts.ready.then(drawGroups);
bind(kbEl, rdKb, ".key");
bind(document.querySelector("svg.pad"), rdPad, ".ctl");
})();
</script>