feat(totalannihilation): add Megatank unit with multi-barrel weapons and rebalance combat

- Introduce Megatank: medium commander with Twin 150mm Cannon and Side Rocket Pods
- Implement multi-barrel weapon system (barrels + barrelSpacing) in TALogic and TARules
- Lower DEFENCE_MIN_SKILL from 4 to 3; make first Laser Tower proactive, not reactive
- Rebalance unit triangle: Tank now counters Rocket Tank; rockets weak vs vehicle armour
- Re-seed campaign missions m05 (67890) and m06 (80808) with updated maps and briefings
- Skirmish default theme uses rules default (tropics) instead of hardcoded grasslands
- Add intro video before main menu
- Update sprite docs and verification tests for new balance
This commit is contained in:
Brian Fertig 2026-08-16 14:30:04 -06:00
parent 045327d38a
commit c690d80e68
11 changed files with 72 additions and 14 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 MiB

After

Width:  |  Height:  |  Size: 2.0 MiB

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 287 KiB

After

Width:  |  Height:  |  Size: 318 KiB

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 MiB

After

Width:  |  Height:  |  Size: 2.1 MiB

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 258 KiB

After

Width:  |  Height:  |  Size: 288 KiB

Binary file not shown.

View File

@ -67,7 +67,7 @@
}, },
"icons": { "icons": {
"key": "ta-icons", "path": null, "kind": "icon", "key": "ta-icons", "path": null, "kind": "icon",
"frameWidth": 44, "frameHeight": 44, "cols": 10, "frameWidth": 64, "frameHeight": 64, "cols": 10,
"rows": 4 "rows": 4
} }
}, },

View File

@ -273,6 +273,9 @@ export default class TAHud {
this._clearGrid(); this._clearGrid();
const s = this.scene; const s = this.scene;
const army = this.state.armies[this.playerArmy]; const army = this.state.armies[this.playerArmy];
// Real art or the procedural fallback (TAArt.js always supplies one or the other), so this
// is only null for a def that predates the `icon` field — see the fallback layout below.
const iconKey = this.view.sheetKeys?.icons;
options.forEach((def, i) => { options.forEach((def, i) => {
const col = i % GRID_COLS, row = Math.floor(i / GRID_COLS); const col = i % GRID_COLS, row = Math.floor(i / GRID_COLS);
// The container is placed at the button's CENTRE, not its top-left — see below. // The container is placed at the button's CENTRE, not its top-left — see below.
@ -286,13 +289,23 @@ export default class TAHud {
// child here is centred on (0,0) so the visual and the hit area coincide. // child here is centred on (0,0) so the visual and the hit area coincide.
const box = s.add.rectangle(0, 0, BTN_W, BTN_H, affordable ? 0x22304a : 0x1a2130, 1) const box = s.add.rectangle(0, 0, BTN_W, BTN_H, affordable ? 0x22304a : 0x1a2130, 1)
.setStrokeStyle(2, affordable ? EDGE : 0x2a3244); .setStrokeStyle(2, affordable ? EDGE : 0x2a3244);
const label = s.add.text(0, -BTN_H / 2 + 12, shortName(def.name), { const children = [box];
fontFamily: FONT, fontSize: '20px', color: affordable ? TEXT : '#5d6b80', const hasIcon = iconKey != null && def.icon != null;
// The icon carries the button's identity at a glance once one exists; name and cost drop
// underneath it rather than owning the whole button the way they did before icons.
if (hasIcon) {
const icon = s.add.image(0, -18, iconKey, def.icon).setDisplaySize(36, 36);
if (!affordable) icon.setAlpha(0.5);
children.push(icon);
}
const label = s.add.text(0, hasIcon ? 2 : -BTN_H / 2 + 12, shortName(def.name), {
fontFamily: FONT, fontSize: hasIcon ? '14px' : '20px', color: affordable ? TEXT : '#5d6b80',
}).setOrigin(0.5, 0); }).setOrigin(0.5, 0);
const cost = s.add.text(0, -BTN_H / 2 + 44, `${def.cost.mass}m ${def.cost.energy}e`, { const cost = s.add.text(0, hasIcon ? 18 : -BTN_H / 2 + 44, `${def.cost.mass}m ${def.cost.energy}e`, {
fontFamily: FONT, fontSize: '15px', color: affordable ? DIM : '#4e5a6d', fontFamily: FONT, fontSize: hasIcon ? '12px' : '15px', color: affordable ? DIM : '#4e5a6d',
}).setOrigin(0.5, 0); }).setOrigin(0.5, 0);
c.add([box, label, cost]); children.push(label, cost);
c.add(children);
c.setSize(BTN_W, BTN_H); c.setSize(BTN_W, BTN_H);
c.setInteractive({ useHandCursor: true }); c.setInteractive({ useHandCursor: true });
c.on('pointerover', () => box.setFillStyle(affordable ? 0x2e3f5f : 0x1a2130, 1)); c.on('pointerover', () => box.setFillStyle(affordable ? 0x2e3f5f : 0x1a2130, 1));

View File

@ -30,14 +30,14 @@ capacity the verify script holds every `frame` index to, and it is measured off
| Sheet name (JSON key) | Texture key | Cell | Cols | Contents | | Sheet name (JSON key) | Texture key | Cell | Cols | Contents |
|---|---|---|---|---| |---|---|---|---|---|
| `arm-units` | `ta-arm-units` | 64×64 | 8 | ARM mobile units | | `arm-units` | `ta-arm-units` | 128×128 | 8 | ARM mobile units |
| `core-units` | `ta-core-units` | 64×64 | 8 | CORE mobile units | | `core-units` | `ta-core-units` | 128×128 | 8 | CORE mobile units |
| `arm-structures` | `ta-arm-structures` | 192×192 | 6×4 | ARM buildings | | `arm-structures` | `ta-arm-structures` | 192×192 | 6×4 | ARM buildings |
| `core-structures` | `ta-core-structures` | 192×192 | 6×4 | CORE buildings | | `core-structures` | `ta-core-structures` | 192×192 | 6×4 | CORE buildings |
| `terrain-grasslands` | `ta-terrain-grasslands` | 64×64 | 9 | Grassland tiles | | `terrain-grasslands` | `ta-terrain-grasslands` | 64×64 | 9 | Grassland tiles |
| `terrain-snowfields` | `ta-terrain-snowfields` | 64×64 | 9 | Snow tiles | | `terrain-snowfields` | `ta-terrain-snowfields` | 64×64 | 9 | Snow tiles |
| `terrain-tropics` | `ta-terrain-tropics` | 64×64 | 9 | Jungle tiles | | `terrain-tropics` | `ta-terrain-tropics` | 64×64 | 9 | Jungle tiles |
| `icons` | `ta-icons` | 44×44 | 10 | Command/build glyphs | | `icons` | `ta-icons` | 64×64 | 10 | Command/build glyphs — shared, NOT per-army |
Frames are numbered **left to right, then top to bottom**, starting at 0. Frames are numbered **left to right, then top to bottom**, starting at 0.
@ -228,24 +228,69 @@ The theme then appears in the skirmish setup screen automatically.
--- ---
## Icons (44×44 cells, 10 per row) ## Icons (64×64 cells, 10 per row)
Build-menu and command glyphs. Frame numbers come from each def's `icon` in the rules JSON Build-menu and command glyphs. Frame numbers come from each def's `icon` in the rules JSON
and from `commandIcons`, so this table is a reflection of that file, not a second source of and from `commandIcons`, so this table is a reflection of that file, not a second source of
truth. Purely decorative today — the build menu labels itself from unit names — so this sheet truth.
is the lowest priority of the eight.
**Shared across both armies — this is the one sheet that does NOT get the "paint every army's
sheet in its own finished colours" treatment** from the top of this doc. There's a single
`icons` entry in the artwork JSON, not an `arm-icons`/`core-icons` pair, and the same frame
renders in either player's build menu. Paint it in a neutral palette (dark plate + light
linework is what the procedural fallback already does) rather than ARM blue or CORE red/orange.
**Wired into the build menu** (`TAHud._drawGrid`, since 2026-08-16): every button in the
build/production grid — buildings AND units — now draws this sheet's `def.icon` frame above the
shortened name and cost, at a **36×36 display size**. The command bar (Move/Attack/Patrol/…)
still renders text-only for now; its glyphs (frames 2029) only exist for the procedural
fallback painter today.
**The 64×64 source is intentionally bigger than that 36×36 display size.** Painting at exactly
the display size (the old 44×44 spec was already close to that trap) means every icon gets
upscaled a little and softens; painting bigger and letting Phaser scale down is what keeps it
crisp. Draw at the full 64×64 canvas — don't pre-shrink your art to "look right" at a smaller
preview size.
| Frames | Contents | | Frames | Contents |
|---|---| |---|---|
| 09 | Units: commander, infantry, sniper, jeep, tank, rocket tank, rocket trooper, construction vehicle, fighter, bomber | | 09 | Units: commander, infantry, sniper, jeep, tank, rocket tank, rocket trooper, construction vehicle, fighter, bomber |
| 1016 | Buildings: energy gen, mass gen, barracks, vehicle plant, laser tower, missile launcher, advanced vehicle plant | | 1016 | Buildings: energy gen, mass gen, barracks, vehicle plant, laser tower, missile launcher, advanced vehicle plant |
| 1719 | Overflow: hover constructor (unit), airfield, radar tower | | 1719 | Overflow: hover constructor (unit), airfield, radar tower |
| 3031 | Advanced radar, radar jammer |
| 2029 | Commands: move, attack, attack-move, stop, hold, patrol, guard, assist, repair, rally | | 2029 | Commands: move, attack, attack-move, stop, hold, patrol, guard, assist, repair, rally |
| 3031 | Advanced radar, radar jammer |
| 32 | Megatank (unit) |
Row 0 filled up before the Airfield units were added, which is why the hover constructor sits Row 0 filled up before the Airfield units were added, which is why the hover constructor sits
next to the buildings at 17 rather than with the other units. Nothing reads a row as a next to the buildings at 17 rather than with the other units. Nothing reads a row as a
category — only the `icon` numbers matter. category — only the `icon` numbers matter. Frames 3339 are free.
### Painting priority: buildings the Commander and Construction Vehicle put up
This is the set that actually appears in the build-menu grid during a normal game — the union
of everything `commander`, `constructor` and `hoverconstructor` can build — in a sensible
painting order (the 8 the Commander itself can queue from turn one, then the 3 that need a
Construction Vehicle or Hover Constructor first):
| Frame | Building | Also needs a Constructor? | Footprint |
|---|---|---|---|
| 10 | Energy Generator | no — Commander builds it too | 2×2 |
| 11 | Mass Generator | no | 2×2 |
| 12 | Barracks | no | 3×3 |
| 13 | Vehicle Plant | no | 3×3 |
| 14 | Laser Tower | no | 1×1 |
| 15 | Missile Launcher | no | 2×2 |
| 18 | Airfield | no | 3×3 |
| 19 | Radar Tower | no | 2×2 |
| 16 | Advanced Vehicle Plant | **yes** | 4×4 |
| 30 | Advanced Radar | **yes** | 2×2 |
| 31 | Radar Jammer | **yes** | 2×2 |
Draw each as a simplified, high-contrast read of its full structure-sheet art (see Structure
sheets below), not a literal crop of it — a 128192px building shrunk straight down to 64px
loses its silhouette to surface detail. Keep the same silhouette and colour language (Laser
Tower's plinth-and-emitter, the Radar family's dish/mast/broken-rings distinction, etc.) so an
icon and its full-size building are still recognisably the same thing at a glance.
--- ---