openkknd-pitchwell-archive/engine-c/src/snapshot.c
2026-09-19 20:35:41 +02:00

2137 lines
121 KiB
C
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.

/*
* Software snapshot renderer - draws the game world into an RGBA buffer using
* only primitive shapes (no SDL gpu readback, which is broken on this box due
* to an SDL2/SDL3 conflict). Output is saved as PNG via png_save().
* Type-aware silhouettes mirror sprites.c so shot_*.png shows KKND2-like
* unit/building variety (not generic discs/rects).
*/
#include "kknd.h"
#define SW VIEW_W
#define SH VIEW_H
/* Internal render size (pre-upscale). Zoom 2 → ~640×360 world crop, then NN→1280×720.
* Wider crop keeps HQ + clash readable; bright fill + black outline does the rest. */
#define SHOT_ZOOM 2
/* Extra silhouette scale inside the shot crop (1.0 = sprites.c size). */
#define SHOT_UNIT_SCALE 1.45f
static uint8_t *g_buf;
static int g_sw = SW, g_sh = SH;
static void px(int x, int y, uint8_t r, uint8_t g, uint8_t b) {
if (x < 0 || y < 0 || x >= g_sw || y >= g_sh) return;
int i = (y * g_sw + x) * 4;
g_buf[i] = r; g_buf[i+1] = g; g_buf[i+2] = b; g_buf[i+3] = 255;
}
static void darken_px(int x, int y, float keep) {
if (x < 0 || y < 0 || x >= g_sw || y >= g_sh) return;
int i = (y * g_sw + x) * 4;
g_buf[i] = (uint8_t)(g_buf[i] * keep);
g_buf[i+1] = (uint8_t)(g_buf[i+1] * keep);
g_buf[i+2] = (uint8_t)(g_buf[i+2] * keep);
}
static void rectf(int x0, int y0, int x1, int y1, uint8_t r, uint8_t g, uint8_t b) {
if (x0 > x1) { int t = x0; x0 = x1; x1 = t; }
if (y0 > y1) { int t = y0; y0 = y1; y1 = t; }
for (int y = y0; y <= y1; y++)
for (int x = x0; x <= x1; x++)
px(x, y, r, g, b);
}
static void fill_rect(int x, int y, int w, int h, uint8_t r, uint8_t g, uint8_t b) {
if (w <= 0 || h <= 0) return;
rectf(x, y, x + w - 1, y + h - 1, r, g, b);
}
static void disc(int cx, int cy, int rad, uint8_t r, uint8_t g, uint8_t b) {
for (int y = -rad; y <= rad; y++)
for (int x = -rad; x <= rad; x++)
if (x*x + y*y <= rad*rad) px(cx+x, cy+y, r, g, b);
}
static void ring(int cx, int cy, int rad, int t, uint8_t r, uint8_t g, uint8_t b) {
int inner = rad - t;
if (inner < 0) inner = 0;
for (int y = -rad; y <= rad; y++)
for (int x = -rad; x <= rad; x++) {
int d = x*x + y*y;
if (d <= rad*rad && d >= inner*inner) px(cx+x, cy+y, r, g, b);
}
}
static void line(int x0, int y0, int x1, int y1, uint8_t r, uint8_t g, uint8_t b) {
int dx = abs(x1 - x0), dy = -abs(y1 - y0);
int sx = x0 < x1 ? 1 : -1, sy = y0 < y1 ? 1 : -1;
int err = dx + dy;
for (;;) {
px(x0, y0, r, g, b);
if (x0 == x1 && y0 == y1) break;
int e2 = 2 * err;
if (e2 >= dy) { err += dy; x0 += sx; }
if (e2 <= dx) { err += dx; y0 += sy; }
}
}
/* Saturated faction paints — lime Survivors / hot magenta Evolved on bright sand. */
static uint8_t faction_r(Faction f){ return f==FACTION_SURVIVOR?55 : f==FACTION_EVOLVED?255 : 240; }
static uint8_t faction_g(Faction f){ return f==FACTION_SURVIVOR?235: f==FACTION_EVOLVED?40 : 55; }
static uint8_t faction_b(Faction f){ return f==FACTION_SURVIVOR?55 : f==FACTION_EVOLVED?255 : 240; }
static void darken3(uint8_t r, uint8_t g, uint8_t b, float f,
uint8_t *or, uint8_t *og, uint8_t *ob) {
*or = (uint8_t)(r * f); *og = (uint8_t)(g * f); *ob = (uint8_t)(b * f);
}
static void lighten3(uint8_t r, uint8_t g, uint8_t b, float f,
uint8_t *or, uint8_t *og, uint8_t *ob) {
*or = (uint8_t)(r + (255 - r) * f);
*og = (uint8_t)(g + (255 - g) * f);
*ob = (uint8_t)(b + (255 - b) * f);
}
/* Classic KKND2 wasteland: yellow-tan sand (R>>B, G slightly under R). */
static void terrain_color(TerrainType t, uint8_t *r, uint8_t *g, uint8_t *b) {
switch (t) {
case T_GRASS: *r=242; *g=198; *b=92; break; /* sunlit desert */
case T_ROAD: *r=175; *g=148; *b=98; break;
case T_RUBBLE:*r=168; *g=138; *b=88; break;
case T_ROCK: *r=155; *g=142; *b=128; break;
case T_WATER: *r=72; *g=128; *b=178; break;
case T_SCRAP: *r=240; *g=190; *b=78; break;
default: *r=228; *g=188; *b=98; break;
}
}
/* Spoke (wheeled) or hash (tracked) marks on a tyre disc; phase = Unit.anim rad. */
static void tyre_mark(int cx, int cy, int rad, float phase, int wheeled) {
uint8_t sr = 205, sg = 200, sb = 180;
int n = wheeled ? 4 : 3;
float step = (float)(2.0 * M_PI / (double)n);
float r0 = wheeled ? rad * 0.30f : rad * 0.55f;
float r1 = rad * 0.90f;
for (int i = 0; i < n; i++) {
float a = phase + i * step;
int x1 = cx + (int)(cosf(a) * r0);
int y1 = cy + (int)(sinf(a) * r0);
int x2 = cx + (int)(cosf(a) * r1);
int y2 = cy + (int)(sinf(a) * r1);
line(x1, y1, x2, y2, sr, sg, sb);
}
}
/* Classic RTS silhouette: thin black outline, then bright faction fill.
* Dark outer discs previously read as olive-mud blobs under NN zoom. */
static void draw_unit(Unit *u, int sx, int sy) {
uint8_t br = faction_r(u->faction), bg = faction_g(u->faction), bb = faction_b(u->faction);
uint8_t lr, lg, lb;
lighten3(br, bg, bb, 0.45f, &lr, &lg, &lb);
uint8_t ok = 18, og = 16, ob = 14; /* near-black outline */
uint8_t mr = 70, mg = 65, mb = 58; /* tyre */
float c = cosf(u->heading), s = sinf(u->heading);
const float sc = SHOT_UNIT_SCALE;
float ph = u->anim; /* continuous roll/walk phase for wheeled/tracked/footed */
#define SI(n) ((int)((n) * sc + 0.5f))
#define LX(lx, ly) (sx + (int)((((lx)-15)*sc)*c - (((ly)-15)*sc)*s))
#define LY(lx, ly) (sy + (int)((((lx)-15)*sc)*s + (((ly)-15)*sc)*c))
/* Ground shadow — depth cue so bodies don't melt into sand. */
disc(sx + SI(2), sy + SI(3), SI(9), 55, 48, 28);
/* Foot infantry/mutant: vertical body bob; shadow stays grounded. */
if (u->type == UT_INFANTRY || u->type == UT_MUTANT) {
float bob = sinf(ph) * 2.5f * sc;
sy += (int)(bob + (bob >= 0.0f ? 0.5f : -0.5f));
}
switch (u->type) {
case UT_INFANTRY:
/* boots/knees → torso/belt/pouches → pads → pack → helmet/visor/antenna → rifle+bayonet */
disc(LX(11,23), LY(11,23), SI(3), ok, og, ob);
disc(LX(19,23), LY(19,23), SI(3), ok, og, ob);
disc(LX(11,23), LY(11,23), SI(2), br, bg, bb);
disc(LX(19,23), LY(19,23), SI(2), br, bg, bb);
fill_rect(LX(10,24)-SI(1), LY(10,24)-1, SI(3), SI(2), 40, 36, 30); /* boot L */
fill_rect(LX(18,24)-SI(1), LY(18,24)-1, SI(3), SI(2), 40, 36, 30); /* boot R */
fill_rect(LX(10,20)-SI(1), LY(10,20)-1, SI(3), SI(2), 55, 50, 42); /* knee pad L */
fill_rect(LX(18,20)-SI(1), LY(18,20)-1, SI(3), SI(2), 55, 50, 42); /* knee pad R */
disc(sx, sy, SI(9), ok, og, ob);
disc(sx, sy, SI(7), br, bg, bb);
fill_rect(LX(11,17)-SI(1), LY(11,17)-1, SI(8), SI(2), 45, 42, 36); /* belt */
fill_rect(LX(12,16)-SI(1), LY(12,16)-1, SI(2), SI(2), 60, 55, 45); /* pouch L */
fill_rect(LX(16,16)-SI(1), LY(16,16)-1, SI(2), SI(2), 60, 55, 45); /* pouch R */
fill_rect(LX(9,12)-SI(1), LY(9,12)-SI(1), SI(3), SI(3), 50, 46, 40); /* shoulder pad L */
fill_rect(LX(19,12)-SI(1), LY(19,12)-SI(1), SI(3), SI(3), 50, 46, 40); /* shoulder pad R */
disc(LX(10,16), LY(10,16), SI(3), ok, og, ob); /* backpack */
disc(LX(10,16), LY(10,16), SI(2), 50, 46, 40);
fill_rect(LX(8,15)-SI(1), LY(8,15)-SI(1), SI(2), SI(3), 40, 38, 32); /* pack strap */
disc(LX(15,8), LY(15,8), SI(5), ok, og, ob);
disc(LX(15,8), LY(15,8), SI(4), lr, lg, lb); /* helmet */
fill_rect(LX(12,7)-SI(1), LY(12,7)-1, SI(5), SI(2), 30, 40, 50); /* visor */
line(LX(14,6), LY(14,6), LX(14,1), LY(14,1), 50, 46, 40); /* antenna */
line(LX(15,6), LY(15,6), LX(15,0), LY(15,0), lr, lg, lb);
line(LX(16,14), LY(16,14), LX(16,0), LY(16,0), 40, 36, 30);
line(LX(17,14), LY(17,14), LX(17,1), LY(17,1), 55, 50, 40);
line(LX(18,14), LY(18,14), LX(18,2), LY(18,2), 45, 40, 34);
disc(LX(17,1), LY(17,1), SI(2), 60, 55, 45); /* muzzle tip */
line(LX(17,2), LY(17,2), LX(20,0), LY(20,0), 70, 65, 55); /* bayonet */
line(LX(18,2), LY(18,2), LX(21,1), LY(21,1), 55, 50, 40);
break;
case UT_BUGGY:
/* fenders + bumper + cabin + roll bar + spare + exhaust + lights + MG */
fill_rect(sx-SI(12), sy-SI(8), SI(24), SI(16), ok, og, ob);
fill_rect(sx-SI(10), sy-SI(6), SI(20), SI(12), br, bg, bb);
fill_rect(LX(3,6)-SI(1), LY(3,6)-1, SI(6), SI(3), 45, 42, 36); /* front fender L */
fill_rect(LX(21,6)-SI(1), LY(21,6)-1, SI(6), SI(3), 45, 42, 36); /* front fender R */
fill_rect(LX(3,22)-SI(1), LY(3,22)-1, SI(6), SI(3), 45, 42, 36); /* rear fender L */
fill_rect(LX(21,22)-SI(1), LY(21,22)-1, SI(6), SI(3), 45, 42, 36);
fill_rect(LX(1,12)-SI(1), LY(1,12)-SI(1), SI(3), SI(6), 55, 50, 40); /* bumper */
fill_rect(LX(2,13)-SI(1), LY(2,13)-1, SI(1), SI(4), 90, 80, 55);
fill_rect(LX(6,9)-SI(1), LY(6,9)-SI(1), SI(10), SI(6), lr, lg, lb);
fill_rect(LX(7,8)-SI(1), LY(7,8)-1, SI(8), SI(3), 35, 55, 75); /* windshield */
line(LX(8,10), LY(8,10), LX(8,4), LY(8,4), lr, lg, lb); /* roll bar L */
line(LX(9,10), LY(9,10), LX(9,4), LY(9,4), 50, 46, 40);
line(LX(18,10), LY(18,10), LX(18,4), LY(18,4), lr, lg, lb); /* roll bar R */
line(LX(17,10), LY(17,10), LX(17,4), LY(17,4), 50, 46, 40);
line(LX(8,4), LY(8,4), LX(18,4), LY(18,4), lr, lg, lb);
line(LX(8,5), LY(8,5), LX(18,5), LY(18,5), 50, 46, 40);
disc(LX(25,16), LY(25,16), SI(3), ok, og, ob); /* spare tyre */
disc(LX(25,16), LY(25,16), SI(2), mr, mg, mb);
disc(LX(25,16), LY(25,16), SI(1), 90, 85, 75);
fill_rect(LX(26,20)-SI(1), LY(26,20)-1, SI(3), SI(2), 40, 38, 32); /* exhaust */
fill_rect(LX(27,19)-SI(1), LY(27,19)-1, SI(2), SI(1), 70, 65, 55);
disc(LX(4,11), LY(4,11), SI(2), 220, 200, 80); /* headlight L */
disc(LX(4,19), LY(4,19), SI(2), 220, 200, 80); /* headlight R */
disc(LX(8,7), LY(8,7), SI(5), ok, og, ob);
disc(LX(22,7), LY(22,7), SI(5), ok, og, ob);
disc(LX(8,23), LY(8,23), SI(5), ok, og, ob);
disc(LX(22,23), LY(22,23), SI(5), ok, og, ob);
disc(LX(8,7), LY(8,7), SI(4), mr, mg, mb);
disc(LX(22,7), LY(22,7), SI(4), mr, mg, mb);
disc(LX(8,23), LY(8,23), SI(4), mr, mg, mb);
disc(LX(22,23), LY(22,23), SI(4), mr, mg, mb);
disc(LX(8,7), LY(8,7), SI(2), 90, 85, 75);
disc(LX(22,7), LY(22,7), SI(2), 90, 85, 75);
disc(LX(8,23), LY(8,23), SI(2), 90, 85, 75);
disc(LX(22,23), LY(22,23), SI(2), 90, 85, 75);
tyre_mark(LX(8,7), LY(8,7), SI(4), ph, 1);
tyre_mark(LX(22,7), LY(22,7), SI(4), ph, 1);
tyre_mark(LX(8,23), LY(8,23), SI(4), ph, 1);
tyre_mark(LX(22,23), LY(22,23), SI(4), ph, 1);
line(sx, sy, LX(29,14), LY(29,14), 40, 36, 30);
line(sx, sy, LX(29,15), LY(29,15), 50, 45, 35);
line(sx, sy, LX(29,16), LY(29,16), 40, 36, 30);
disc(LX(29,15), LY(29,15), SI(2), 60, 55, 45);
break;
case UT_TANK:
/* skirts + road wheels + glacis + mantlet + coax + exhaust + antenna + barrel */
fill_rect(LX(2,8)-SI(1), LY(2,8)-SI(2), SI(5), SI(12), ok, og, ob);
fill_rect(LX(23,8)-SI(1), LY(23,8)-SI(2), SI(5), SI(12), ok, og, ob);
fill_rect(LX(3,10)-SI(1), LY(3,10)-SI(1), SI(3), SI(10), mr, mg, mb);
fill_rect(LX(24,10)-SI(1), LY(24,10)-SI(1), SI(3), SI(10), mr, mg, mb);
fill_rect(LX(2,9)-SI(1), LY(2,9)-1, SI(5), SI(2), 50, 46, 40); /* side skirt L top */
fill_rect(LX(23,9)-SI(1), LY(23,9)-1, SI(5), SI(2), 50, 46, 40);
fill_rect(LX(2,18)-SI(1), LY(2,18)-1, SI(5), SI(2), 50, 46, 40);
fill_rect(LX(23,18)-SI(1), LY(23,18)-1, SI(5), SI(2), 50, 46, 40);
disc(LX(5,11), LY(5,11), SI(2), 40, 38, 32); /* sprocket */
disc(LX(5,15), LY(5,15), SI(2), 40, 38, 32); /* road wheel */
disc(LX(5,20), LY(5,20), SI(2), 40, 38, 32);
disc(LX(26,11), LY(26,11), SI(2), 40, 38, 32);
disc(LX(26,15), LY(26,15), SI(2), 40, 38, 32);
disc(LX(26,20), LY(26,20), SI(2), 40, 38, 32);
fill_rect(sx-SI(11), sy-SI(5), SI(22), SI(10), ok, og, ob);
fill_rect(sx-SI(9), sy-SI(4), SI(18), SI(8), br, bg, bb);
fill_rect(LX(5,10)-SI(1), LY(5,10)-1, SI(8), SI(3), 55, 50, 42); /* glacis */
fill_rect(LX(6,11)-SI(1), LY(6,11)-1, SI(6), SI(2), lr, lg, lb);
disc(sx, sy, SI(7), ok, og, ob);
disc(sx, sy, SI(6), lr, lg, lb); /* cupola */
fill_rect(LX(18,13)-SI(1), LY(18,13)-SI(1), SI(4), SI(5), 45, 42, 36); /* mantlet */
fill_rect(LX(19,14)-SI(1), LY(19,14)-1, SI(2), SI(3), 60, 55, 45);
disc(LX(15,12), LY(15,12), SI(2), 40, 38, 32); /* hatch */
line(LX(14,12), LY(14,12), LX(14,2), LY(14,2), 50, 46, 40); /* antenna */
line(LX(15,12), LY(15,12), LX(15,1), LY(15,1), lr, lg, lb);
fill_rect(LX(7,20)-SI(1), LY(7,20)-1, SI(3), SI(2), 40, 38, 32); /* exhaust */
fill_rect(LX(8,19)-SI(1), LY(8,19)-1, SI(1), SI(1), 70, 65, 55);
line(LX(15,13), LY(15,13), LX(29,13), LY(29,13), 45, 40, 32);
line(LX(15,14), LY(15,14), LX(29,14), LY(29,14), 50, 45, 35);
line(LX(15,15), LY(15,15), LX(29,15), LY(29,15), 55, 50, 40);
line(LX(15,16), LY(15,16), LX(29,16), LY(29,16), 50, 45, 35);
line(LX(15,17), LY(15,17), LX(28,17), LY(28,17), 45, 40, 32);
line(LX(18,12), LY(18,12), LX(27,11), LY(27,11), 40, 36, 30); /* coax MG */
line(LX(18,12), LY(18,12), LX(27,12), LY(27,12), 50, 45, 35);
disc(LX(29,15), LY(29,15), SI(2), 60, 55, 45); /* muzzle */
disc(LX(8,23), LY(8,23), SI(4), mr, mg, mb);
disc(LX(22,23), LY(22,23), SI(4), mr, mg, mb);
tyre_mark(LX(8,23), LY(8,23), SI(4), ph, 0);
tyre_mark(LX(22,23), LY(22,23), SI(4), ph, 0);
break;
case UT_ARTILLERY:
/* howitzer: trails + cradle + shield + ammo + tube bands + brake + hubs */
fill_rect(LX(1,18)-SI(1), LY(1,18)-1, SI(8), SI(3), ok, og, ob);
fill_rect(LX(21,18)-SI(1), LY(21,18)-1, SI(8), SI(3), ok, og, ob);
fill_rect(LX(0,19)-SI(1), LY(0,19)-1, SI(4), SI(2), 40, 38, 32);
fill_rect(LX(26,19)-SI(1), LY(26,19)-1, SI(4), SI(2), 40, 38, 32);
fill_rect(LX(2,20)-SI(1), LY(2,20)-1, SI(2), SI(2), 55, 50, 40); /* trail pin L */
fill_rect(LX(26,20)-SI(1), LY(26,20)-1, SI(2), SI(2), 55, 50, 40); /* trail pin R */
fill_rect(sx-SI(11), sy-SI(4), SI(22), SI(10), ok, og, ob);
fill_rect(sx-SI(9), sy-SI(2), SI(18), SI(7), br, bg, bb);
fill_rect(LX(5,14)-SI(1), LY(5,14)-SI(1), SI(2), SI(5), 40, 38, 32); /* chassis rail L */
fill_rect(LX(23,14)-SI(1), LY(23,14)-SI(1), SI(2), SI(5), 40, 38, 32); /* chassis rail R */
fill_rect(LX(22,12)-SI(1), LY(22,12)-SI(2), SI(6), SI(8), 55, 50, 40);
line(LX(23,13), LY(23,13), LX(27,13), LY(27,13), 90, 80, 55);
line(LX(23,15), LY(23,15), LX(27,15), LY(27,15), 90, 80, 55);
line(LX(23,17), LY(23,17), LX(27,17), LY(27,17), 90, 80, 55);
fill_rect(LX(23,14)-SI(1), LY(23,14)-1, SI(2), SI(2), 200, 160, 60); /* shell tip */
fill_rect(LX(25,16)-SI(1), LY(25,16)-1, SI(2), SI(2), 200, 160, 60);
fill_rect(LX(10,8)-SI(1), LY(10,8)-SI(1), SI(10), SI(5), ok, og, ob);
fill_rect(LX(11,9)-SI(1), LY(11,9)-1, SI(8), SI(3), lr, lg, lb);
fill_rect(LX(12,8)-SI(1), LY(12,8)-1, SI(6), SI(1), 255, 200, 40); /* warn stripe */
disc(sx, sy-SI(1), SI(4), ok, og, ob);
disc(sx, sy-SI(1), SI(3), lr, lg, lb);
fill_rect(LX(13,13)-SI(1), LY(13,13)-1, SI(4), SI(3), 55, 50, 40); /* breech */
fill_rect(LX(14,14)-SI(1), LY(14,14)-1, SI(2), SI(1), 90, 80, 55);
line(LX(15,11), LY(15,11), LX(3,1), LY(3,1), 45, 40, 32);
line(LX(15,12), LY(15,12), LX(3,2), LY(3,2), 50, 45, 35);
line(LX(15,13), LY(15,13), LX(2,1), LY(2,1), 55, 50, 40);
line(LX(15,14), LY(15,14), LX(3,3), LY(3,3), 50, 45, 35);
line(LX(15,15), LY(15,15), LX(4,4), LY(4,4), 45, 40, 32);
disc(LX(10,8), LY(10,8), SI(1), 70, 65, 55); /* tube band */
disc(LX(7,5), LY(7,5), SI(1), 70, 65, 55);
fill_rect(LX(12,16)-SI(1), LY(12,16)-1, SI(2), SI(3), 70, 65, 55); /* elev hydrau */
fill_rect(LX(16,16)-SI(1), LY(16,16)-1, SI(2), SI(3), 70, 65, 55);
fill_rect(LX(1,0)-SI(1), LY(1,0)-1, SI(5), SI(4), 60, 55, 45);
fill_rect(LX(2,1)-SI(1), LY(2,1)-1, SI(3), SI(2), 30, 28, 24);
disc(LX(3,2), LY(3,2), SI(1), 90, 85, 75); /* brake ring */
line(LX(20,11), LY(20,11), LX(20,6), LY(20,6), 50, 46, 40); /* antenna */
disc(LX(20,5), LY(20,5), SI(1), 200, 200, 80);
disc(LX(8,23), LY(8,23), SI(5), ok, og, ob);
disc(LX(22,23), LY(22,23), SI(5), ok, og, ob);
disc(LX(8,23), LY(8,23), SI(4), mr, mg, mb);
disc(LX(22,23), LY(22,23), SI(4), mr, mg, mb);
disc(LX(8,23), LY(8,23), SI(2), 90, 85, 75);
disc(LX(22,23), LY(22,23), SI(2), 90, 85, 75);
tyre_mark(LX(8,23), LY(8,23), SI(4), ph, 0);
tyre_mark(LX(22,23), LY(22,23), SI(4), ph, 0);
break;
case UT_HARVESTER: {
/* cabin + boom/scoop + conveyor + hopper panels + exhaust/lights + hubbed wheels */
fill_rect(sx-SI(14), sy-SI(9), SI(28), SI(18), ok, og, ob);
fill_rect(sx-SI(12), sy-SI(7), SI(24), SI(14), br, bg, bb);
fill_rect(LX(3,8)-SI(1), LY(3,8)-SI(2), SI(8), SI(8), lr, lg, lb); /* cabin */
fill_rect(LX(4,8)-SI(1), LY(4,8)-SI(1), SI(6), SI(3), 35, 55, 75); /* glass */
fill_rect(LX(5,12)-SI(1), LY(5,12)-1, SI(4), SI(2), 45, 42, 36); /* cabin panel */
line(LX(2,14), LY(2,14), LX(8,11), LY(8,11), 50, 46, 40); /* boom arm */
line(LX(2,15), LY(2,15), LX(8,12), LY(8,12), 70, 65, 55);
fill_rect(LX(0,14)-SI(1), LY(0,14)-SI(1), SI(5), SI(5), 90, 80, 55); /* scoop lip */
fill_rect(LX(1,15)-SI(1), LY(1,15)-SI(1), SI(3), SI(3), 70, 60, 40);
int bed_w = SI(14), bed_h = SI(10);
int bed_x = sx - SI(4), bed_y = sy - SI(5);
fill_rect(bed_x, bed_y, bed_w, bed_h, 55, 48, 32);
float cap = (float)unit_def(u->type, u->faction)->cargo;
float fill = (cap > 0.0f) ? (u->cargo / cap) : 0.0f;
if (fill < 0.0f) fill = 0.0f;
if (fill > 1.0f) fill = 1.0f;
int fw = (int)(bed_w * fill + 0.5f);
if (fw > 0)
fill_rect(bed_x, bed_y, fw, bed_h, 200, 170, 90);
if (fill > 0.15f) {
line(LX(14,10), LY(14,10), LX(24,10), LY(24,10), 160, 130, 70);
line(LX(14,13), LY(14,13), LX(24,13), LY(24,13), 160, 130, 70);
line(LX(14,16), LY(14,16), LX(24,16), LY(24,16), 160, 130, 70);
}
fill_rect(LX(12,9)-SI(1), LY(12,9)-1, SI(12), SI(1), 45, 40, 32); /* hopper rim */
fill_rect(LX(24,11)-SI(1), LY(24,11)-SI(2), SI(3), SI(8), 50, 46, 40); /* side panel */
fill_rect(LX(25,12)-SI(1), LY(25,12)-SI(1), SI(1), SI(6), 70, 65, 55);
fill_rect(LX(20,18)-SI(1), LY(20,18)-1, SI(4), SI(2), 40, 38, 32); /* exhaust */
fill_rect(LX(21,17)-SI(1), LY(21,17)-1, SI(2), SI(1), 70, 65, 55);
disc(LX(4,10), LY(4,10), SI(2), 220, 200, 80); /* cabin light */
disc(LX(2,16), LY(2,16), SI(1), 220, 180, 60); /* scoop lamp */
disc(LX(8,23), LY(8,23), SI(5), ok, og, ob);
disc(LX(22,23), LY(22,23), SI(5), ok, og, ob);
disc(LX(8,23), LY(8,23), SI(4), mr, mg, mb);
disc(LX(22,23), LY(22,23), SI(4), mr, mg, mb);
disc(LX(8,23), LY(8,23), SI(2), 90, 85, 75);
disc(LX(22,23), LY(22,23), SI(2), 90, 85, 75);
tyre_mark(LX(8,23), LY(8,23), SI(4), ph, 1);
tyre_mark(LX(22,23), LY(22,23), SI(4), ph, 1);
line(LX(5,7), LY(5,7), LX(5,2), LY(5,2), lr, lg, lb);
line(LX(6,7), LY(6,7), LX(6,3), LY(6,3), 50, 46, 40);
break;
}
case UT_JEEP:
/* bumper + grill + seats + jerry cans + spare + lights + antenna + cage + MG */
fill_rect(sx-SI(11), sy-SI(6), SI(22), SI(12), ok, og, ob);
fill_rect(sx-SI(9), sy-SI(4), SI(18), SI(8), br, bg, bb);
fill_rect(LX(3,12)-SI(1), LY(3,12)-SI(1), SI(3), SI(6), 55, 50, 40); /* bumper */
fill_rect(LX(4,13)-SI(1), LY(4,13)-1, SI(1), SI(4), 90, 80, 55);
fill_rect(LX(5,13)-SI(1), LY(5,13)-1, SI(3), SI(4), 40, 38, 32); /* grill */
line(LX(5,14), LY(5,14), LX(7,14), LY(7,14), 70, 65, 55);
line(LX(5,16), LY(5,16), LX(7,16), LY(7,16), 70, 65, 55);
fill_rect(LX(7,10)-SI(1), LY(7,10)-SI(1), SI(8), SI(5), lr, lg, lb);
fill_rect(LX(8,12)-SI(1), LY(8,12)-1, SI(3), SI(3), 45, 40, 35); /* seat L */
fill_rect(LX(14,12)-SI(1), LY(14,12)-1, SI(3), SI(3), 45, 40, 35); /* seat R */
fill_rect(LX(22,11)-SI(1), LY(22,11)-SI(1), SI(3), SI(4), 70, 90, 50); /* jerry can */
fill_rect(LX(23,12)-SI(1), LY(23,12)-1, SI(1), SI(2), 90, 110, 60);
disc(LX(24,18), LY(24,18), SI(3), ok, og, ob); /* spare */
disc(LX(24,18), LY(24,18), SI(2), mr, mg, mb);
disc(LX(24,18), LY(24,18), SI(1), 90, 85, 75);
disc(LX(5,11), LY(5,11), SI(2), 220, 200, 80); /* light L */
disc(LX(5,19), LY(5,19), SI(2), 220, 200, 80); /* light R */
line(LX(16,10), LY(16,10), LX(16,1), LY(16,1), 50, 46, 40); /* antenna */
line(LX(17,10), LY(17,10), LX(17,2), LY(17,2), lr, lg, lb);
disc(LX(9,22), LY(9,22), SI(5), ok, og, ob);
disc(LX(21,22), LY(21,22), SI(5), ok, og, ob);
disc(LX(9,22), LY(9,22), SI(4), mr, mg, mb);
disc(LX(21,22), LY(21,22), SI(4), mr, mg, mb);
disc(LX(9,22), LY(9,22), SI(2), 90, 85, 75);
disc(LX(21,22), LY(21,22), SI(2), 90, 85, 75);
tyre_mark(LX(9,22), LY(9,22), SI(4), ph, 1);
tyre_mark(LX(21,22), LY(21,22), SI(4), ph, 1);
line(LX(11,11), LY(11,11), LX(11,3), LY(11,3), lr, lg, lb);
line(LX(12,11), LY(12,11), LX(12,3), LY(12,3), 50, 46, 40);
line(LX(19,11), LY(19,11), LX(19,3), LY(19,3), lr, lg, lb);
line(LX(18,11), LY(18,11), LX(18,3), LY(18,3), 50, 46, 40);
line(LX(11,3), LY(11,3), LX(19,3), LY(19,3), lr, lg, lb);
line(LX(11,4), LY(11,4), LX(19,4), LY(19,4), 50, 46, 40);
line(sx, sy, LX(28,14), LY(28,14), 40, 36, 30);
line(sx, sy, LX(28,15), LY(28,15), 50, 45, 35);
line(sx, sy, LX(28,16), LY(28,16), 40, 36, 30);
disc(LX(28,15), LY(28,15), SI(2), 60, 55, 45);
break;
case UT_MUTANT:
/* plates + eyes/jaw + spikes + dual claws + tail */
disc(sx, sy+SI(2), SI(11), ok, og, ob);
disc(sx, sy, SI(9), br, bg, bb);
fill_rect(LX(10,14)-SI(1), LY(10,14)-SI(1), SI(4), SI(3), 45, 40, 35); /* chest plate L */
fill_rect(LX(17,14)-SI(1), LY(17,14)-SI(1), SI(4), SI(3), 45, 40, 35); /* chest plate R */
disc(LX(15,9), LY(15,9), SI(5), ok, og, ob);
disc(LX(15,9), LY(15,9), SI(4), lr, lg, lb);
fill_rect(LX(11,7)-SI(1), LY(11,7)-1, SI(7), SI(2), 30, 40, 50); /* brow */
disc(LX(12,9), LY(12,9), SI(1), 200, 60, 40); /* eye L */
disc(LX(18,9), LY(18,9), SI(1), 200, 60, 40); /* eye R */
fill_rect(LX(13,11)-SI(1), LY(13,11)-1, SI(5), SI(2), 40, 30, 28); /* jaw */
line(LX(14,12), LY(14,12), LX(14,13), LY(14,13), 70, 55, 45); /* fang */
line(LX(16,12), LY(16,12), LX(16,13), LY(16,13), 70, 55, 45);
line(LX(12,6), LY(12,6), LX(9,0), LY(9,0), 40, 36, 30);
line(LX(13,6), LY(13,6), LX(10,1), LY(10,1), 55, 50, 40);
line(LX(15,5), LY(15,5), LX(15,0), LY(15,0), 40, 36, 30);
line(LX(16,5), LY(16,5), LX(16,0), LY(16,0), 55, 50, 40);
line(LX(18,6), LY(18,6), LX(21,0), LY(21,0), 40, 36, 30);
line(LX(17,6), LY(17,6), LX(20,1), LY(20,1), 55, 50, 40);
line(LX(14,4), LY(14,4), LX(12,0), LY(12,0), 50, 45, 35); /* extra spike */
line(sx, sy+SI(1), LX(26,8), LY(26,8), 40, 36, 30);
line(sx, sy+SI(2), LX(26,9), LY(26,9), 50, 45, 35);
line(LX(24,9), LY(24,9), LX(29,5), LY(29,5), 55, 50, 40);
line(LX(24,9), LY(24,9), LX(29,11), LY(29,11), 55, 50, 40);
line(LX(24,10), LY(24,10), LX(28,8), LY(28,8), 70, 60, 45);
disc(LX(28,8), LY(28,8), SI(2), 70, 60, 45); /* claw tip R */
line(LX(12,16), LY(12,16), LX(4,10), LY(4,10), 40, 36, 30); /* second claw L */
line(LX(12,17), LY(12,17), LX(4,11), LY(4,11), 50, 45, 35);
line(LX(5,10), LY(5,10), LX(1,7), LY(1,7), 55, 50, 40);
line(LX(5,11), LY(5,11), LX(1,13), LY(1,13), 55, 50, 40);
disc(LX(2,10), LY(2,10), SI(2), 70, 60, 45);
line(LX(15,20), LY(15,20), LX(15,27), LY(15,27), 45, 40, 35); /* tail */
line(LX(16,20), LY(16,20), LX(18,28), LY(18,28), 55, 50, 40);
disc(LX(17,27), LY(17,27), SI(2), 60, 50, 40);
disc(LX(10,22), LY(10,22), SI(4), ok, og, ob);
disc(LX(20,22), LY(20,22), SI(4), ok, og, ob);
disc(LX(10,22), LY(10,22), SI(3), br, bg, bb);
disc(LX(20,22), LY(20,22), SI(3), br, bg, bb);
break;
case UT_BEAST:
/* carapace plates/spines + eye + vents + tusks + hubbed multi-wheel */
fill_rect(sx-SI(10), sy-SI(6), SI(20), SI(12), ok, og, ob);
fill_rect(sx-SI(8), sy-SI(4), SI(16), SI(8), br, bg, bb);
fill_rect(sx-SI(6), sy-SI(5), SI(12), SI(3), lr, lg, lb); /* carapace ridge */
fill_rect(LX(8,13)-SI(1), LY(8,13)-SI(1), SI(5), SI(3), 50, 46, 40); /* plate L */
fill_rect(LX(17,13)-SI(1), LY(17,13)-SI(1), SI(5), SI(3), 50, 46, 40); /* plate R */
line(LX(10,9), LY(10,9), LX(10,5), LY(10,5), 40, 36, 30); /* spine */
line(LX(13,9), LY(13,9), LX(13,4), LY(13,4), 55, 50, 40);
line(LX(16,9), LY(16,9), LX(16,4), LY(16,4), 55, 50, 40);
line(LX(19,9), LY(19,9), LX(19,5), LY(19,5), 40, 36, 30);
fill_rect(LX(11,16)-SI(1), LY(11,16)-1, SI(2), SI(2), 30, 28, 24); /* vent L */
fill_rect(LX(17,16)-SI(1), LY(17,16)-1, SI(2), SI(2), 30, 28, 24); /* vent R */
disc(LX(9,21), LY(9,21), SI(4), ok, og, ob);
disc(LX(14,22), LY(14,22), SI(4), ok, og, ob);
disc(LX(19,21), LY(19,21), SI(4), ok, og, ob);
disc(LX(23,22), LY(23,22), SI(4), ok, og, ob);
disc(LX(9,21), LY(9,21), SI(3), mr, mg, mb);
disc(LX(14,22), LY(14,22), SI(3), mr, mg, mb);
disc(LX(19,21), LY(19,21), SI(3), mr, mg, mb);
disc(LX(23,22), LY(23,22), SI(3), mr, mg, mb);
disc(LX(9,21), LY(9,21), SI(1), 90, 85, 75); /* hub */
disc(LX(14,22), LY(14,22), SI(1), 90, 85, 75);
disc(LX(19,21), LY(19,21), SI(1), 90, 85, 75);
disc(LX(23,22), LY(23,22), SI(1), 90, 85, 75);
tyre_mark(LX(9,21), LY(9,21), SI(3), ph, 1);
tyre_mark(LX(14,22), LY(14,22), SI(3), ph, 1);
tyre_mark(LX(19,21), LY(19,21), SI(3), ph, 1);
tyre_mark(LX(23,22), LY(23,22), SI(3), ph, 1);
disc(LX(24,12), LY(24,12), SI(6), ok, og, ob);
disc(LX(24,12), LY(24,12), SI(5), lr, lg, lb); /* snout */
disc(LX(26,11), LY(26,11), SI(1), 200, 80, 40); /* eye */
line(LX(26,10), LY(26,10), LX(29,8), LY(29,8), 40, 36, 30); /* tusk */
line(LX(26,14), LY(26,14), LX(29,16), LY(29,16), 40, 36, 30);
line(LX(26,12), LY(26,12), LX(29,12), LY(29,12), 55, 50, 40);
disc(LX(29,12), LY(29,12), SI(2), 60, 55, 45);
break;
case UT_RAVAGER:
/* prow + ammo boxes + triple gatling + exhaust (vs tank cupola) */
fill_rect(LX(0,7)-SI(1), LY(0,7)-SI(2), SI(5), SI(14), ok, og, ob);
fill_rect(LX(25,7)-SI(1), LY(25,7)-SI(2), SI(5), SI(14), ok, og, ob);
fill_rect(LX(1,10)-SI(1), LY(1,10)-SI(1), SI(3), SI(10), mr, mg, mb);
fill_rect(LX(26,10)-SI(1), LY(26,10)-SI(1), SI(3), SI(10), mr, mg, mb);
disc(LX(3,12), LY(3,12), SI(2), 40, 38, 32);
disc(LX(3,20), LY(3,20), SI(2), 40, 38, 32);
disc(LX(28,12), LY(28,12), SI(2), 40, 38, 32);
disc(LX(28,20), LY(28,20), SI(2), 40, 38, 32);
fill_rect(sx-SI(12), sy-SI(6), SI(24), SI(12), ok, og, ob);
fill_rect(sx-SI(10), sy-SI(5), SI(20), SI(10), br, bg, bb);
fill_rect(LX(24,8)-SI(1), LY(24,8)-SI(1), SI(6), SI(6), 55, 48, 38);
line(LX(25,9), LY(25,9), LX(29,11), LY(29,11), 70, 60, 45);
line(LX(25,13), LY(25,13), LX(29,11), LY(29,11), 70, 60, 45);
fill_rect(LX(4,8)-SI(1), LY(4,8)-1, SI(5), SI(4), 70, 55, 35);
fill_rect(LX(5,9)-SI(1), LY(5,9)-1, SI(3), SI(2), 200, 160, 60);
fill_rect(LX(10,8)-SI(1), LY(10,8)-1, SI(5), SI(4), 70, 55, 35);
fill_rect(LX(11,9)-SI(1), LY(11,9)-1, SI(3), SI(2), 200, 160, 60);
fill_rect(LX(12,12)-SI(1), LY(12,12)-SI(1), SI(10), SI(8), ok, og, ob);
fill_rect(LX(13,13)-SI(1), LY(13,13)-SI(1), SI(8), SI(6), lr, lg, lb);
line(LX(20,13), LY(20,13), LX(29,12), LY(29,12), 45, 40, 32);
line(LX(20,15), LY(20,15), LX(29,15), LY(29,15), 55, 50, 40);
line(LX(20,17), LY(20,17), LX(29,18), LY(29,18), 45, 40, 32);
disc(LX(29,12), LY(29,12), SI(1), 60, 55, 45);
disc(LX(29,15), LY(29,15), SI(2), 60, 55, 45);
disc(LX(29,18), LY(29,18), SI(1), 60, 55, 45);
fill_rect(LX(2,14)-SI(1), LY(2,14)-1, SI(3), SI(4), 40, 38, 32);
disc(LX(2,15), LY(2,15), SI(2), 90, 70, 40);
disc(LX(2,17), LY(2,17), SI(1), 200, 100, 40);
disc(LX(7,23), LY(7,23), SI(5), ok, og, ob);
disc(LX(23,23), LY(23,23), SI(5), ok, og, ob);
disc(LX(7,23), LY(7,23), SI(4), mr, mg, mb);
disc(LX(23,23), LY(23,23), SI(4), mr, mg, mb);
tyre_mark(LX(7,23), LY(7,23), SI(4), ph, 0);
tyre_mark(LX(23,23), LY(23,23), SI(4), ph, 0);
break;
case UT_FLAME:
/* dual tanks + gauges + hose + shield + nozzle jet + boots */
fill_rect(sx-SI(8), sy-SI(3), SI(16), SI(12), ok, og, ob);
fill_rect(sx-SI(6), sy-SI(1), SI(12), SI(9), br, bg, bb);
fill_rect(LX(10,15)-SI(1), LY(10,15)-1, SI(3), SI(4), 45, 42, 36); /* pouch */
fill_rect(LX(18,16)-SI(1), LY(18,16)-1, SI(3), SI(3), 45, 42, 36); /* belt box */
disc(LX(15,9), LY(15,9), SI(5), ok, og, ob);
disc(LX(15,9), LY(15,9), SI(4), lr, lg, lb);
fill_rect(LX(12,8)-SI(1), LY(12,8)-1, SI(5), SI(2), 30, 40, 50);
fill_rect(LX(13,7)-SI(1), LY(13,7)-1, SI(3), SI(1), 90, 100, 110); /* brow plate */
fill_rect(LX(14,11)-SI(1), LY(14,11)-1, SI(2), SI(3), 55, 50, 40); /* shoulder plate */
disc(LX(6,13), LY(6,13), SI(5), ok, og, ob);
disc(LX(6,13), LY(6,13), SI(4), 90, 70, 40);
disc(LX(6,13), LY(6,13), SI(2), 200, 120, 40);
disc(LX(6,19), LY(6,19), SI(5), ok, og, ob);
disc(LX(6,19), LY(6,19), SI(4), 90, 70, 40);
disc(LX(6,19), LY(6,19), SI(2), 200, 120, 40);
fill_rect(LX(4,12)-SI(1), LY(4,12)-1, SI(2), SI(3), 255, 200, 40); /* tank warn stripe */
fill_rect(LX(4,18)-SI(1), LY(4,18)-1, SI(2), SI(3), 255, 200, 40);
disc(LX(6,11), LY(6,11), SI(1), 60, 55, 45);
disc(LX(6,17), LY(6,17), SI(1), 60, 55, 45);
disc(LX(6,21), LY(6,21), SI(1), 60, 55, 45);
disc(LX(8,13), LY(8,13), SI(1), 180, 220, 255); /* gauge */
disc(LX(8,19), LY(8,19), SI(1), 180, 220, 255);
line(LX(8,14), LY(8,14), LX(10,14), LY(10,14), 50, 46, 40); /* strap */
line(LX(8,18), LY(8,18), LX(10,18), LY(10,18), 50, 46, 40);
line(LX(10,14), LY(10,14), LX(16,16), LY(16,16), 50, 45, 35);
line(LX(10,15), LY(10,15), LX(16,17), LY(16,17), 70, 55, 35);
disc(LX(12,15), LY(12,15), SI(1), 70, 65, 55); /* hose clamp */
disc(LX(14,16), LY(14,16), SI(1), 70, 65, 55);
fill_rect(LX(16,12)-SI(1), LY(16,12)-SI(1), SI(5), SI(8), 55, 50, 40);
fill_rect(LX(17,13)-SI(1), LY(17,13)-SI(1), SI(3), SI(6), 90, 70, 40);
fill_rect(LX(18,14)-SI(1), LY(18,14)-1, SI(1), SI(4), 40, 38, 32); /* nozzle grip */
disc(LX(19,13), LY(19,13), SI(1), 255, 180, 60); /* pilot light */
line(LX(20,14), LY(20,14), LX(28,19), LY(28,19), 255, 120, 40);
line(LX(20,15), LY(20,15), LX(28,20), LY(28,20), 255, 150, 50);
line(LX(20,16), LY(20,16), LX(27,21), LY(27,21), 255, 180, 60);
line(LX(20,17), LY(20,17), LX(26,21), LY(26,21), 255, 200, 80);
line(LX(21,13), LY(21,13), LX(27,17), LY(27,17), 255, 100, 30); /* upper jet */
disc(LX(28,20), LY(28,20), SI(3), 255, 200, 80);
disc(LX(28,20), LY(28,20), SI(1), 255, 240, 160);
disc(LX(26,18), LY(26,18), SI(1), 255, 220, 120); /* ember */
disc(LX(27,22), LY(27,22), SI(1), 255, 160, 60);
fill_rect(LX(11,22)-SI(1), LY(11,22)-1, SI(3), SI(3), 40, 36, 30); /* boot L */
fill_rect(LX(17,22)-SI(1), LY(17,22)-1, SI(3), SI(3), 40, 36, 30); /* boot R */
break;
case UT_MISSILE:
/* MLRS box + rails + stripes + cab + pods + hydrau + hubs (KKND rocket truck) */
fill_rect(sx-SI(12), sy-SI(3), SI(24), SI(12), ok, og, ob);
fill_rect(sx-SI(10), sy-SI(1), SI(20), SI(8), br, bg, bb);
fill_rect(LX(4,15)-SI(1), LY(4,15)-SI(1), SI(3), SI(5), 40, 38, 32); /* chassis rail L */
fill_rect(LX(23,15)-SI(1), LY(23,15)-SI(1), SI(3), SI(5), 40, 38, 32); /* chassis rail R */
fill_rect(LX(2,11)-SI(1), LY(2,11)-SI(1), SI(6), SI(7), ok, og, ob); /* cab outline */
fill_rect(LX(3,12)-SI(1), LY(3,12)-SI(1), SI(4), SI(5), 55, 52, 45);
fill_rect(LX(3,12)-SI(1), LY(3,12)-SI(1), SI(4), SI(2), 140, 180, 210); /* cab glass */
fill_rect(LX(8,4)-SI(1), LY(8,4)-SI(2), SI(16), SI(10), ok, og, ob);
fill_rect(LX(9,5)-SI(1), LY(9,5)-SI(1), SI(14), SI(8), 50, 48, 42);
line(LX(10,6), LY(10,6), LX(22,6), LY(22,6), 90, 85, 70);
line(LX(10,8), LY(10,8), LX(22,8), LY(22,8), 90, 85, 70);
line(LX(10,10), LY(10,10), LX(22,10), LY(22,10), 90, 85, 70);
line(LX(10,12), LY(10,12), LX(22,12), LY(22,12), 90, 85, 70);
fill_rect(LX(9,5)-SI(1), LY(9,5)-SI(1), SI(3), SI(8), 255, 200, 40);
fill_rect(LX(12,5)-SI(1), LY(12,5)-SI(1), SI(2), SI(8), 30, 28, 24);
fill_rect(LX(14,5)-SI(1), LY(14,5)-SI(1), SI(3), SI(8), 255, 200, 40);
fill_rect(LX(17,5)-SI(1), LY(17,5)-SI(1), SI(2), SI(8), 30, 28, 24);
fill_rect(LX(19,5)-SI(1), LY(19,5)-SI(1), SI(3), SI(8), 255, 200, 40);
disc(LX(23,7), LY(23,7), SI(2), 255, 120, 80);
disc(LX(23,10), LY(23,10), SI(2), 255, 140, 90);
disc(LX(23,12), LY(23,12), SI(1), 255, 160, 100);
fill_rect(LX(7,14)-SI(1), LY(7,14)-SI(1), SI(2), SI(4), 70, 65, 55); /* hydraulic L */
fill_rect(LX(21,14)-SI(1), LY(21,14)-SI(1), SI(2), SI(4), 70, 65, 55); /* hydraulic R */
line(LX(8,13), LY(8,13), LX(8,5), LY(8,5), 60, 55, 45); /* elevate strut */
disc(LX(4,10), LY(4,10), SI(1), 200, 200, 80); /* antenna tip */
line(LX(4,10), LY(4,10), LX(4,8), LY(4,8), 90, 90, 70);
disc(LX(9,23), LY(9,23), SI(5), ok, og, ob);
disc(LX(21,23), LY(21,23), SI(5), ok, og, ob);
disc(LX(9,23), LY(9,23), SI(4), mr, mg, mb);
disc(LX(21,23), LY(21,23), SI(4), mr, mg, mb);
disc(LX(9,23), LY(9,23), SI(2), 90, 85, 75);
disc(LX(21,23), LY(21,23), SI(2), 90, 85, 75);
tyre_mark(LX(9,23), LY(9,23), SI(4), ph, 1);
tyre_mark(LX(21,23), LY(21,23), SI(4), ph, 1);
line(LX(22,8), LY(22,8), LX(29,4), LY(29,4), 255, 80, 60);
line(LX(22,10), LY(22,10), LX(29,6), LY(29,6), 255, 100, 70);
disc(LX(29,5), LY(29,5), SI(2), 255, 140, 90);
disc(LX(28,7), LY(28,7), SI(1), 255, 180, 100); /* exhaust spark */
break;
case UT_MEDIC:
/* bumper + grille + lights + door/side + mirrors + exhaust + light-bar + stretcher/cross + hubs */
fill_rect(sx-SI(10), sy-SI(5), SI(20), SI(15), ok, og, ob);
fill_rect(sx-SI(8), sy-SI(3), SI(16), SI(11), 245, 245, 245);
fill_rect(sx-SI(7), sy-SI(2), SI(14), SI(9), 255, 255, 255);
fill_rect(LX(2,13)-SI(1), LY(2,13)-SI(1), SI(3), SI(6), 55, 50, 40); /* bumper */
fill_rect(LX(3,14)-SI(1), LY(3,14)-1, SI(1), SI(4), 90, 80, 55);
fill_rect(LX(4,14)-SI(1), LY(4,14)-1, SI(3), SI(4), 40, 38, 32); /* grille */
line(LX(4,15), LY(4,15), LX(6,15), LY(6,15), 70, 65, 55);
line(LX(4,17), LY(4,17), LX(6,17), LY(6,17), 70, 65, 55);
disc(LX(4,12), LY(4,12), SI(2), 220, 200, 80); /* headlight L */
disc(LX(4,20), LY(4,20), SI(2), 220, 200, 80); /* headlight R */
fill_rect(LX(5,8)-SI(1), LY(5,8)-SI(1), SI(7), SI(5), 190, 215, 235); /* cab glass */
fill_rect(LX(6,9)-SI(1), LY(6,9)-1, SI(5), SI(3), 160, 190, 220);
fill_rect(LX(12,12)-SI(1), LY(12,12)-SI(1), SI(2), SI(10), 200, 200, 205); /* side door seam */
fill_rect(LX(13,13)-SI(1), LY(13,13)-SI(1), SI(1), SI(8), 170, 170, 175);
fill_rect(LX(20,13)-SI(1), LY(20,13)-SI(1), SI(4), SI(8), 230, 230, 235); /* rear panel */
fill_rect(LX(21,14)-SI(1), LY(21,14)-SI(1), SI(2), SI(6), 210, 210, 215);
fill_rect(LX(4,10)-SI(1), LY(4,10)-1, SI(2), SI(2), 50, 48, 42); /* mirror L */
fill_rect(LX(4,20)-SI(1), LY(4,20)-1, SI(2), SI(2), 50, 48, 42); /* mirror R */
fill_rect(LX(24,18)-SI(1), LY(24,18)-1, SI(3), SI(2), 40, 38, 32); /* exhaust */
fill_rect(LX(25,17)-SI(1), LY(25,17)-1, SI(2), SI(1), 70, 65, 55);
fill_rect(LX(8,3)-SI(1), LY(8,3)-1, SI(14), SI(3), 240, 40, 40); /* light bar */
fill_rect(LX(10,2)-SI(1), LY(10,2)-1, SI(10), SI(2), 255, 80, 80);
fill_rect(LX(11,1)-SI(1), LY(11,1)-1, SI(3), SI(2), 255, 220, 80); /* amber beacon */
fill_rect(LX(16,1)-SI(1), LY(16,1)-1, SI(3), SI(2), 80, 180, 255); /* blue beacon */
fill_rect(LX(18,11)-SI(1), LY(18,11)-1, SI(6), SI(3), 200, 40, 40); /* stretcher cue */
fill_rect(LX(19,12)-SI(1), LY(19,12)-1, SI(4), SI(1), 255, 200, 200);
disc(LX(9,24), LY(9,24), SI(5), ok, og, ob);
disc(LX(21,24), LY(21,24), SI(5), ok, og, ob);
disc(LX(9,24), LY(9,24), SI(4), mr, mg, mb);
disc(LX(21,24), LY(21,24), SI(4), mr, mg, mb);
disc(LX(9,24), LY(9,24), SI(2), 90, 85, 75);
disc(LX(21,24), LY(21,24), SI(2), 90, 85, 75);
tyre_mark(LX(9,24), LY(9,24), SI(4), ph, 1);
tyre_mark(LX(21,24), LY(21,24), SI(4), ph, 1);
disc(LX(15,10), LY(15,10), SI(7), 230, 35, 35);
fill_rect(LX(13,4)-SI(1), LY(13,4)-SI(1), SI(4), SI(12), 255, 255, 255);
fill_rect(LX(9,8)-SI(1), LY(9,8)-SI(1), SI(12), SI(4), 255, 255, 255);
break;
default:
disc(sx, sy, SI(10), ok, og, ob);
disc(sx, sy, SI(8), br, bg, bb);
break;
}
/* heading tick */
{
int hx = sx + (int)(c * 14 * sc), hy = sy + (int)(s * 14 * sc);
line(sx, sy, hx, hy, 250, 245, 210);
}
/* HP bar above unit (classic RTS strip) */
if (u->max_hp > 0) {
int bw = SI(28), by = sy - SI(18);
float f = u->hp / u->max_hp;
if (f < 0) f = 0; if (f > 1) f = 1;
fill_rect(sx - bw/2 - 1, by - 1, bw + 2, 6, 20, 18, 14);
fill_rect(sx - bw/2, by, bw, 4, 55, 48, 36);
uint8_t hr = (uint8_t)(255 * (1.0f - f)), hg = (uint8_t)(230 * f);
fill_rect(sx - bw/2, by, (int)(bw * f + 0.5f), 4, hr, hg, 40);
}
if (u->selected) {
int rad = SI((u->type == UT_TANK || u->type == UT_BEAST || u->type == UT_RAVAGER) ? 16 : 13);
ring(sx, sy, rad, 2, 255, 245, 60);
}
#undef LX
#undef LY
#undef SI
}
/* Charred wreck silhouettes — dim faction paint + scorch, type-aware hull. */
static void draw_wreck(Wreck *w, int sx, int sy) {
uint8_t br = faction_r(w->faction), bg = faction_g(w->faction), bb = faction_b(w->faction);
uint8_t dr, dg, db;
darken3(br, bg, bb, 0.38f, &dr, &dg, &db);
uint8_t ok = 28, og = 24, ob = 20;
uint8_t ch = 48, cg = 42, cb = 36; /* char */
float c = cosf(w->rot), s = sinf(w->rot);
const float sc = SHOT_UNIT_SCALE * 0.92f;
#define SI(n) ((int)((n) * sc + 0.5f))
#define LX(lx, ly) (sx + (int)((((lx)-15)*sc)*c - (((ly)-15)*sc)*s))
#define LY(lx, ly) (sy + (int)((((lx)-15)*sc)*s + (((ly)-15)*sc)*c))
disc(sx + SI(2), sy + SI(4), SI(10), 42, 36, 28); /* scorch stain */
switch (w->type) {
case UT_HARVESTER:
fill_rect(sx - SI(14), sy - SI(9), SI(28), SI(18), ok, og, ob);
fill_rect(sx - SI(12), sy - SI(7), SI(24), SI(14), dr, dg, db);
fill_rect(LX(6, 12) - SI(1), LY(6, 12) - SI(1), SI(10), SI(8), ch, cg, cb);
disc(LX(8, 8), LY(8, 8), SI(4), ok, og, ob);
disc(LX(22, 8), LY(22, 8), SI(4), ok, og, ob);
disc(LX(8, 22), LY(8, 22), SI(4), ok, og, ob);
disc(LX(22, 22), LY(22, 22), SI(4), ok, og, ob);
fill_rect(sx - SI(4), sy - SI(2), SI(10), SI(5), 55, 48, 40); /* torn bed */
break;
case UT_TANK:
case UT_RAVAGER:
case UT_BEAST:
fill_rect(sx - SI(13), sy - SI(8), SI(26), SI(16), ok, og, ob);
fill_rect(sx - SI(11), sy - SI(6), SI(22), SI(12), dr, dg, db);
fill_rect(sx - SI(12), sy - SI(3), SI(4), SI(8), ch, cg, cb); /* track */
fill_rect(sx + SI(8), sy - SI(3), SI(4), SI(8), ch, cg, cb);
disc(sx, sy - SI(2), SI(6), ok, og, ob);
disc(sx, sy - SI(2), SI(4), ch, cg, cb); /* dead turret */
line(LX(15, 8), LY(15, 8), LX(15, -2), LY(15, -2), 40, 36, 30); /* bent barrel */
break;
case UT_ARTILLERY:
case UT_MISSILE:
fill_rect(sx - SI(12), sy - SI(7), SI(24), SI(14), ok, og, ob);
fill_rect(sx - SI(10), sy - SI(5), SI(20), SI(10), dr, dg, db);
disc(LX(8, 20), LY(8, 20), SI(4), ok, og, ob);
disc(LX(22, 20), LY(22, 20), SI(4), ok, og, ob);
line(LX(12, 12), LY(12, 12), LX(22, 2), LY(22, 2), ch, cg, cb);
break;
case UT_BUGGY:
case UT_JEEP:
case UT_FLAME:
case UT_MEDIC:
fill_rect(sx - SI(11), sy - SI(7), SI(22), SI(14), ok, og, ob);
fill_rect(sx - SI(9), sy - SI(5), SI(18), SI(10), dr, dg, db);
disc(LX(8, 8), LY(8, 8), SI(4), ok, og, ob);
disc(LX(22, 8), LY(22, 8), SI(4), ok, og, ob);
disc(LX(8, 22), LY(8, 22), SI(4), ok, og, ob);
disc(LX(22, 22), LY(22, 22), SI(4), ok, og, ob);
fill_rect(LX(10, 10) - SI(1), LY(10, 10) - SI(1), SI(8), SI(5), ch, cg, cb);
break;
case UT_INFANTRY:
case UT_MUTANT:
disc(sx + SI(1), sy + SI(2), SI(6), ok, og, ob);
disc(sx, sy, SI(5), dr, dg, db);
disc(sx - SI(3), sy + SI(4), SI(3), ch, cg, cb); /* crumpled kit */
disc(sx + SI(4), sy + SI(3), SI(2), 40, 36, 30);
break;
default:
fill_rect(sx - SI(10), sy - SI(6), SI(20), SI(12), ok, og, ob);
fill_rect(sx - SI(8), sy - SI(4), SI(16), SI(8), dr, dg, db);
disc(sx - SI(5), sy + SI(2), SI(3), ch, cg, cb);
disc(sx + SI(6), sy - SI(1), SI(3), ch, cg, cb);
break;
}
/* smoke puff cue */
disc(sx + SI(3), sy - SI(8), SI(3), 70, 68, 64);
disc(sx + SI(5), sy - SI(12), SI(2), 90, 88, 82);
#undef LX
#undef LY
#undef SI
}
/* Building silhouettes: bright pad + faction fill, thin black outline. */
static void draw_bld(Building *b, int sx, int sy) {
int W = b->w * TILE, H = b->h * TILE;
uint8_t br = faction_r(b->faction), bg = faction_g(b->faction), bb = faction_b(b->faction);
uint8_t lr, lg, lb;
lighten3(br, bg, bb, 0.35f, &lr, &lg, &lb);
uint8_t ok = 22, og = 20, ob = 16;
uint8_t wall = 205, wall2 = 175;
/* Bright concrete pad on sand */
fill_rect(sx - 2, sy - 2, W + 4, H + 4, ok, og, ob);
fill_rect(sx, sy, W, H, 210, 185, 130);
fill_rect(sx + 2, sy + 2, W - 4, H - 4, wall2, wall2 - 18, wall2 - 45);
fill_rect(sx + 3, sy + 3, W - 6, H - 6, wall, wall - 22, wall - 55);
fill_rect(sx + 3, sy + 3, W - 6, 6, wall + 30, wall + 10, wall - 20);
switch (b->type) {
case BT_BASE:
if (b->faction == FACTION_EVOLVED) {
/* hive densify: lobes + bone plates + veins + sacs + mouth + pit eye */
disc(sx + 14, sy + 18, 14, ok, og, ob);
disc(sx + 14, sy + 18, 12, br, bg, bb);
disc(sx + W - 14, sy + 18, 14, ok, og, ob);
disc(sx + W - 14, sy + 18, 12, br, bg, bb);
disc(sx + 16, sy + H - 16, 12, ok, og, ob);
disc(sx + 16, sy + H - 16, 10, br, bg, bb);
disc(sx + W - 16, sy + H - 16, 12, ok, og, ob);
disc(sx + W - 16, sy + H - 16, 10, br, bg, bb);
disc(sx + W/2, sy + H/2, 16, ok, og, ob);
disc(sx + W/2, sy + H/2, 14, br, bg, bb);
/* membrane ridges between lobes */
line(sx + 18, sy + 20, sx + W/2 - 8, sy + H/2 - 4, ok, og, ob);
line(sx + W - 18, sy + 20, sx + W/2 + 8, sy + H/2 - 4, ok, og, ob);
line(sx + 18, sy + H - 18, sx + W/2 - 6, sy + H/2 + 6, ok, og, ob);
line(sx + W - 18, sy + H - 18, sx + W/2 + 6, sy + H/2 + 6, ok, og, ob);
/* dorsal spine + tendrils */
fill_rect(sx + W/2 - 2, sy + 4, 4, H/2 - 8, ok, og, ob);
fill_rect(sx + W/2 - 1, sy + 5, 2, H/2 - 10, 90, 40, 55);
line(sx + W/2, sy + 8, sx + W/2 - 10, sy + 18, 70, 30, 45);
line(sx + W/2, sy + 8, sx + W/2 + 10, sy + 18, 70, 30, 45);
/* bone plates + vein strips */
fill_rect(sx + 8, sy + 14, 6, 3, 110, 70, 80);
fill_rect(sx + W - 14, sy + 14, 6, 3, 110, 70, 80);
fill_rect(sx + 10, sy + H - 20, 5, 3, 100, 55, 70);
fill_rect(sx + W - 15, sy + H - 20, 5, 3, 100, 55, 70);
fill_rect(sx + W/2 - 12, sy + H/2 - 2, 8, 2, 80, 35, 50);
fill_rect(sx + W/2 + 4, sy + H/2 - 2, 8, 2, 80, 35, 50);
fill_rect(sx + W/2 - 1, sy + H/2 + 8, 2, 6, 70, 30, 45);
/* membrane pads + egg sacs */
fill_rect(sx + 20, sy + 24, 5, 4, lr, lg, lb);
fill_rect(sx + W - 25, sy + 24, 5, 4, lr, lg, lb);
fill_rect(sx + 22, sy + H - 22, 4, 4, 160, 60, 90);
fill_rect(sx + W - 26, sy + H - 22, 4, 4, 160, 60, 90);
fill_rect(sx + W/2 - 14, sy + H - 18, 4, 3, 140, 50, 80);
fill_rect(sx + W/2 + 10, sy + H - 18, 4, 3, 140, 50, 80);
/* mouth / entry slit */
fill_rect(sx + W/2 - 6, sy + H - 10, 12, 4, 30, 18, 22);
fill_rect(sx + W/2 - 4, sy + H - 9, 8, 2, 200, 40, 70);
/* carapace studs + spore vents + ichor drip + brood pods */
fill_rect(sx + 12, sy + 22, 2, 2, 90, 45, 60);
fill_rect(sx + W - 14, sy + 22, 2, 2, 90, 45, 60);
fill_rect(sx + 14, sy + H - 26, 3, 2, 70, 35, 50);
fill_rect(sx + W - 17, sy + H - 26, 3, 2, 70, 35, 50);
fill_rect(sx + W/2 - 8, sy + 12, 3, 2, 120, 50, 70);
fill_rect(sx + W/2 + 5, sy + 12, 3, 2, 120, 50, 70);
fill_rect(sx + W/2 - 1, sy + H/2 + 14, 2, 4, 180, 40, 70);
fill_rect(sx + 24, sy + H/2, 3, 3, 150, 55, 85);
fill_rect(sx + W - 27, sy + H/2, 3, 3, 150, 55, 85);
/* dark pit + glowing eye */
disc(sx + W/2, sy + H/2, 8, 28, 18, 22);
disc(sx + W/2, sy + H/2, 5, 60, 20, 30);
disc(sx + W/2, sy + H/2, 3, 220, 60, 90);
disc(sx + W/2, sy + H/2, 1, 255, 180, 200);
ring(sx + W/2, sy + H/2, 10, 2, 50, 25, 35); /* pit rim */
/* extra blisters */
disc(sx + W/2 - 10, sy + 10, 5, lr, lg, lb);
disc(sx + W/2 + 12, sy + H - 12, 4, lr, lg, lb);
disc(sx + 10, sy + H/2 + 2, 3, lr, lg, lb);
disc(sx + W - 10, sy + H/2 - 4, 3, lr, lg, lb);
disc(sx + W/2 + 8, sy + 14, 2, 180, 70, 100);
disc(sx + W/2 - 6, sy + H - 14, 2, 200, 80, 110);
} else {
/* Survivors HQ densify: battlements + courtyard + flag + sandbags */
fill_rect(sx + 4, sy + 4, 14, 14, ok, og, ob);
fill_rect(sx + 6, sy + 6, 10, 10, br, bg, bb);
fill_rect(sx + W - 18, sy + 4, 14, 14, ok, og, ob);
fill_rect(sx + W - 16, sy + 6, 10, 10, br, bg, bb);
fill_rect(sx + 4, sy + H - 18, 14, 14, ok, og, ob);
fill_rect(sx + 6, sy + H - 16, 10, 10, br, bg, bb);
fill_rect(sx + W - 18, sy + H - 18, 14, 14, ok, og, ob);
fill_rect(sx + W - 16, sy + H - 16, 10, 10, br, bg, bb);
/* battlement teeth */
fill_rect(sx + 5, sy + 3, 3, 2, 100, 95, 80);
fill_rect(sx + 11, sy + 3, 3, 2, 100, 95, 80);
fill_rect(sx + W - 16, sy + 3, 3, 2, 100, 95, 80);
fill_rect(sx + W - 10, sy + 3, 3, 2, 100, 95, 80);
/* tower windows + lower windows + corner lights */
fill_rect(sx + 8, sy + 8, 3, 3, 200, 230, 255);
fill_rect(sx + W - 13, sy + 8, 3, 3, 200, 230, 255);
fill_rect(sx + 8, sy + H - 13, 3, 3, 200, 230, 255);
fill_rect(sx + W - 13, sy + H - 13, 3, 3, 200, 230, 255);
fill_rect(sx + 8, sy + 12, 2, 2, 180, 210, 240);
fill_rect(sx + W - 12, sy + 12, 2, 2, 180, 210, 240);
fill_rect(sx + 8, sy + H - 10, 2, 2, 180, 210, 240);
fill_rect(sx + W - 12, sy + H - 10, 2, 2, 180, 210, 240);
disc(sx + 7, sy + 5, 2, 255, 220, 80);
disc(sx + W - 7, sy + 5, 2, 255, 220, 80);
disc(sx + 7, sy + H - 6, 2, 255, 200, 60);
disc(sx + W - 7, sy + H - 6, 2, 255, 200, 60);
/* courtyard body + roof band + tiles */
fill_rect(sx + 14, sy + 14, W - 28, H - 28, ok, og, ob);
fill_rect(sx + 16, sy + 16, W - 32, H - 32, br, bg, bb);
fill_rect(sx + 18, sy + 18, W - 36, 8, lr, lg, lb);
fill_rect(sx + 20, sy + 28, 4, 3, 120, 110, 90);
fill_rect(sx + W/2 - 2, sy + 28, 4, 3, 120, 110, 90);
fill_rect(sx + W - 24, sy + 28, 4, 3, 120, 110, 90);
/* walkways between towers */
fill_rect(sx + 16, sy + 8, W - 32, 3, 90, 85, 70);
fill_rect(sx + 16, sy + H - 11, W - 32, 3, 90, 85, 70);
fill_rect(sx + 8, sy + 16, 3, H - 32, 90, 85, 70);
fill_rect(sx + W - 11, sy + 16, 3, H - 32, 90, 85, 70);
/* command dome + mast + radar dish + antenna stubs */
disc(sx + W/2, sy + H/2 - 2, 10, ok, og, ob);
disc(sx + W/2, sy + H/2 - 2, 8, lr, lg, lb);
disc(sx + W/2, sy + H/2 - 2, 4, 255, 250, 180);
fill_rect(sx + W/2 - 2, sy + 2, 4, H/2 - 6, 200, 180, 60);
fill_rect(sx + W/2 - 6, sy + 2, 12, 3, 220, 210, 80);
fill_rect(sx + W/2 - 8, sy + 1, 2, 4, 180, 170, 90);
fill_rect(sx + W/2 + 6, sy + 1, 2, 4, 180, 170, 90);
disc(sx + W/2 + 8, sy + 8, 5, ok, og, ob); /* dish */
disc(sx + W/2 + 8, sy + 8, 3, 180, 200, 220);
line(sx + W/2 + 2, sy + 6, sx + W/2 + 8, sy + 8, 160, 150, 100);
/* flag + sandbags */
fill_rect(sx + W/2 - 14, sy + 6, 2, 8, 160, 40, 40);
fill_rect(sx + W/2 - 12, sy + 6, 5, 3, 200, 50, 50);
fill_rect(sx + W/2 - 10, sy + H - 8, 4, 2, 130, 110, 80);
fill_rect(sx + W/2 + 6, sy + H - 8, 4, 2, 130, 110, 80);
/* entry bunker + blast doors */
fill_rect(sx + W/2 - 8, sy + H - 14, 16, 8, ok, og, ob);
fill_rect(sx + W/2 - 6, sy + H - 12, 12, 6, 40, 38, 34);
fill_rect(sx + W/2 - 4, sy + H - 11, 3, 4, 70, 65, 55);
fill_rect(sx + W/2 + 1, sy + H - 11, 3, 4, 70, 65, 55);
/* side annex windows */
fill_rect(sx + 18, sy + H/2 + 2, 6, 4, 160, 200, 230);
fill_rect(sx + W - 24, sy + H/2 + 2, 6, 4, 160, 200, 230);
/* AA bags + vent grills + armor studs + status LEDs */
fill_rect(sx + 20, sy + H - 10, 5, 2, 120, 100, 70);
fill_rect(sx + W - 25, sy + H - 10, 5, 2, 120, 100, 70);
fill_rect(sx + 22, sy + 34, 4, 2, 80, 75, 65);
fill_rect(sx + W - 26, sy + 34, 4, 2, 80, 75, 65);
fill_rect(sx + 10, sy + 16, 2, 2, 110, 105, 90);
fill_rect(sx + W - 12, sy + 16, 2, 2, 110, 105, 90);
fill_rect(sx + W/2 - 10, sy + H/2 + 6, 2, 2, 40, 200, 80);
fill_rect(sx + W/2 + 8, sy + H/2 + 6, 2, 2, 200, 60, 40);
}
break;
case BT_WARFACTORY:
/* hangar + crane + bay + office/chimney/ramp (KKND factory densify) */
fill_rect(sx + 4, sy + 6, W - 8, H - 12, ok, og, ob);
fill_rect(sx + 6, sy + 8, W - 12, H - 16, br, bg, bb);
fill_rect(sx + 8, sy + 10, W - 16, 12, lr, lg, lb); /* roof ridge */
fill_rect(sx + 10, sy + 12, 5, 5, 55, 50, 42); /* ridge vents */
fill_rect(sx + W/2 - 8, sy + 12, 5, 5, 55, 50, 42);
fill_rect(sx + W/2 + 3, sy + 12, 5, 5, 55, 50, 42);
fill_rect(sx + W - 15, sy + 12, 5, 5, 55, 50, 42);
fill_rect(sx + 9, sy + 11, W - 18, 1, 40, 38, 32); /* roof seam */
fill_rect(sx + 14, sy + 13, 2, 1, 80, 75, 60); /* corrugation L */
fill_rect(sx + W/2 - 1, sy + 13, 2, 1, 80, 75, 60); /* corrugation C */
fill_rect(sx + W - 16, sy + 13, 2, 1, 80, 75, 60); /* corrugation R */
fill_rect(sx + 8, sy + 10, 2, 2, 95, 90, 70); /* roof end-cap L */
fill_rect(sx + W - 10, sy + 10, 2, 2, 95, 90, 70); /* roof end-cap R */
fill_rect(sx + W/2 - 1, sy + 9, 3, 1, 50, 48, 40); /* ridge spine */
/* corner bracing + rivets */
fill_rect(sx + 5, sy + 7, 3, 3, 70, 65, 55);
fill_rect(sx + W - 8, sy + 7, 3, 3, 70, 65, 55);
fill_rect(sx + 5, sy + H - 14, 3, 3, 70, 65, 55);
fill_rect(sx + W - 8, sy + H - 14, 3, 3, 70, 65, 55);
fill_rect(sx + 6, sy + 8, 1, 1, 40, 38, 32); /* rivet TL */
fill_rect(sx + W - 7, sy + 8, 1, 1, 40, 38, 32);
fill_rect(sx + 6, sy + H - 13, 1, 1, 40, 38, 32);
fill_rect(sx + W - 7, sy + H - 13, 1, 1, 40, 38, 32);
/* upper office strip + windows */
{
uint8_t dr, dg, db;
darken3(br, bg, bb, 0.5f, &dr, &dg, &db);
fill_rect(sx + 10, sy + 14, W - 28, 6, dr, dg, db);
fill_rect(sx + 5, sy + H/2 - 2, 8, H/2 - 10, dr, dg, db);
}
fill_rect(sx + 12, sy + 15, 3, 3, 160, 200, 230);
fill_rect(sx + 17, sy + 15, 3, 3, 150, 190, 220);
fill_rect(sx + 22, sy + 15, 3, 3, 170, 210, 235);
fill_rect(sx + W/2 - 2, sy + 15, 3, 3, 155, 195, 225);
fill_rect(sx + W/2 + 3, sy + 15, 3, 3, 165, 205, 235); /* office window R */
fill_rect(sx + 10, sy + 20, 4, 5, 45, 42, 38); /* office door */
fill_rect(sx + 12, sy + 22, 1, 2, 200, 180, 60); /* door knob */
fill_rect(sx + 11, sy + 14, W - 30, 1, 35, 33, 28); /* office sill */
fill_rect(sx + W/2 + 8, sy + 15, 2, 2, 220, 90, 40); /* office warn LED */
fill_rect(sx + W - 18, sy + 14, 3, 2, 70, 68, 60); /* AC pack */
/* chimney + smoke puff */
fill_rect(sx + W/2 + 8, sy + 4, 4, 10, ok, og, ob);
fill_rect(sx + W/2 + 9, sy + 5, 2, 8, 55, 50, 45);
fill_rect(sx + W/2 + 8, sy + 7, 4, 1, 40, 38, 32); /* chimney band */
fill_rect(sx + W/2 + 7, sy + 3, 6, 1, 65, 60, 50); /* chimney cap */
disc(sx + W/2 + 10, sy + 3, 3, 90, 88, 85);
disc(sx + W/2 + 12, sy + 2, 2, 110, 108, 105);
/* side workshop annex + tool rack */
fill_rect(sx + 4, sy + H/2 - 4, 10, H/2 - 6, ok, og, ob);
fill_rect(sx + 6, sy + H/2, 6, 4, 90, 80, 55); /* window slit */
fill_rect(sx + 3, sy + H - 18, 3, 8, 60, 55, 45); /* tool rack */
fill_rect(sx + 3, sy + H - 17, 1, 2, 140, 120, 70);
fill_rect(sx + 5, sy + H - 16, 1, 3, 120, 110, 90);
/* side exhaust stacks */
fill_rect(sx + W - 6, sy + H/2 + 2, 3, 8, ok, og, ob);
fill_rect(sx + W - 5, sy + H/2 + 3, 1, 6, 45, 42, 38);
disc(sx + W - 4, sy + H/2 + 1, 2, 80, 78, 72);
fill_rect(sx + W - 9, sy + H/2 + 4, 2, 6, ok, og, ob); /* 2nd stack */
fill_rect(sx + W - 8, sy + H/2 + 5, 1, 4, 45, 42, 38);
/* overhead crane mast + jib + cable/hook */
fill_rect(sx + W - 14, sy + 4, 6, H - 18, ok, og, ob);
fill_rect(sx + W - 13, sy + 5, 4, H - 20, 95, 88, 70);
fill_rect(sx + 14, sy + 6, W - 28, 4, ok, og, ob);
fill_rect(sx + 15, sy + 7, W - 30, 2, 120, 110, 85);
fill_rect(sx + 22, sy + 5, 5, 3, 85, 78, 60); /* crane trolley */
fill_rect(sx + 23, sy + 6, 3, 1, 200, 160, 40); /* trolley hazard */
line(sx + 18, sy + 10, sx + 18, sy + 22, 70, 65, 50);
line(sx + 19, sy + 10, sx + 19, sy + 26, 55, 50, 40); /* cable */
disc(sx + 18, sy + 24, 3, 80, 75, 55); /* hoist block */
fill_rect(sx + 17, sy + 27, 3, 2, 100, 90, 60); /* hook */
fill_rect(sx + W - 12, sy + 3, 2, 2, 220, 200, 80); /* floodlight */
fill_rect(sx + W - 11, sy + 2, 1, 3, 90, 85, 70); /* antenna stub */
fill_rect(sx + W - 15, sy + H/2 - 2, 4, 3, 70, 65, 50); /* gantry brace */
fill_rect(sx + W - 10, sy + 8, 3, 2, 200, 160, 40); /* crane hazard */
fill_rect(sx + W - 16, sy + H - 22, 5, 4, 55, 50, 42); /* crane counterweight */
fill_rect(sx + W - 17, sy + H - 20, 2, 2, 90, 85, 70); /* CW bolt */
/* segmented bay doors + hazard + panels */
fill_rect(sx + 12, sy + H - 30, W - 28, 20, ok, og, ob);
fill_rect(sx + 14, sy + H - 28, W - 32, 16, 32, 32, 38);
fill_rect(sx + 14, sy + H - 28, W - 32, 2, 200, 160, 40); /* hazard top */
fill_rect(sx + 14, sy + H - 14, W - 32, 2, 200, 160, 40); /* hazard bot */
line(sx + W/2 - 6, sy + H - 28, sx + W/2 - 6, sy + H - 12, 70, 68, 60);
line(sx + W/2, sy + H - 28, sx + W/2, sy + H - 12, 70, 68, 60);
line(sx + W/2 + 6, sy + H - 28, sx + W/2 + 6, sy + H - 12, 70, 68, 60);
line(sx + 14, sy + H - 20, sx + W - 18, sy + H - 20, 55, 52, 48);
fill_rect(sx + 16, sy + H - 24, 4, 3, 45, 44, 40); /* door panel */
fill_rect(sx + W/2 + 2, sy + H - 24, 4, 3, 45, 44, 40);
fill_rect(sx + W - 22, sy + H - 24, 4, 3, 45, 44, 40);
fill_rect(sx + W/2 - 1, sy + H - 18, 2, 2, 90, 85, 70); /* bay lock */
fill_rect(sx + 15, sy + H - 26, 1, 4, 80, 75, 60); /* bay hinge L */
fill_rect(sx + W - 17, sy + H - 26, 1, 4, 80, 75, 60); /* bay hinge R */
fill_rect(sx + 7, sy + H/2 + 4, 4, 2, 50, 48, 42); /* side vent grille */
fill_rect(sx + 7, sy + H/2 + 7, 4, 1, 40, 38, 32); /* vent louver */
fill_rect(sx + 8, sy + H - 28, 3, 8, 60, 55, 45); /* wall pipe riser */
fill_rect(sx + 8, sy + H - 21, 6, 1, 55, 50, 42); /* pipe elbow */
/* ramp lip + rails/tracks */
fill_rect(sx + 10, sy + H - 10, W - 20, 5, 50, 48, 42);
fill_rect(sx + 12, sy + H - 9, W - 24, 2, 90, 75, 45);
fill_rect(sx + 18, sy + H - 8, 3, 1, 200, 160, 40); /* ramp chevron L */
fill_rect(sx + W/2 - 1, sy + H - 8, 3, 1, 200, 160, 40); /* ramp chevron C */
fill_rect(sx + W - 22, sy + H - 8, 3, 1, 200, 160, 40); /* ramp chevron R */
fill_rect(sx + W/2 - 3, sy + H - 4, 6, 2, 35, 30, 22); /* oil stain */
fill_rect(sx + 6, sy + H - 12, 4, 3, 75, 70, 55); /* scrap crate */
fill_rect(sx + 7, sy + H - 11, 2, 1, 120, 100, 50); /* crate strap */
fill_rect(sx + W - 12, sy + H - 12, 3, 3, 55, 52, 45); /* spare tire */
line(sx + 14, sy + H - 8, sx + 14, sy + H - 5, 70, 65, 50);
line(sx + W - 16, sy + H - 8, sx + W - 16, sy + H - 5, 70, 65, 50);
line(sx + 14, sy + H - 6, sx + W - 16, sy + H - 6, 60, 55, 42); /* track */
line(sx + 14, sy + H - 5, sx + W - 16, sy + H - 5, 55, 50, 38);
break;
case BT_REFINERY:
/* denser oil-field: pad, derrick, pumpjack, twin tanks, pipes, hopper (KKND) */
fill_rect(sx + 3, sy + H - 12, W - 6, 10, ok, og, ob); /* slab */
fill_rect(sx + 5, sy + H - 10, W - 10, 6, 70, 62, 48);
fill_rect(sx + 6, sy + H - 9, 2, 2, 35, 32, 28); /* pad bolt L */
fill_rect(sx + W - 8, sy + H - 9, 2, 2, 35, 32, 28); /* pad bolt R */
fill_rect(sx + W/2 - 2, sy + H - 9, 2, 2, 35, 32, 28); /* pad bolt C */
fill_rect(sx + W/2 - 8, sy + H - 9, 2, 2, 35, 32, 28); /* pad bolt CL */
fill_rect(sx + W/2 + 4, sy + H - 9, 2, 2, 35, 32, 28); /* pad bolt CR */
fill_rect(sx + 4, sy + H - 4, W - 8, 2, 45, 38, 28); /* curb */
fill_rect(sx + 8, sy + H - 11, W - 16, 1, 55, 48, 36); /* grate seam */
/* latticed derrick mast + braces */
fill_rect(sx + W/2 - 3, sy + 4, 6, H - 18, ok, og, ob);
fill_rect(sx + W/2 - 2, sy + 5, 4, H - 20, 95, 85, 65);
fill_rect(sx + W/2 - 4, sy + 12, 8, 2, 55, 48, 38); /* cross brace */
fill_rect(sx + W/2 - 4, sy + 26, 8, 2, 55, 48, 38);
fill_rect(sx + W/2 - 4, sy + 40, 8, 2, 55, 48, 38);
fill_rect(sx + W/2 - 4, sy + 19, 8, 1, 50, 44, 34); /* mid brace thin */
fill_rect(sx + W/2 - 4, sy + 33, 8, 1, 50, 44, 34);
fill_rect(sx + W/2 - 5, sy + H - 20, 10, 3, 60, 52, 40); /* mast foot plate */
line(sx + W/2 - 6, sy + 10, sx + W/2 + 6, sy + 22, 55, 48, 38);
line(sx + W/2 + 6, sy + 10, sx + W/2 - 6, sy + 22, 55, 48, 38);
line(sx + W/2 - 6, sy + 24, sx + W/2 + 6, sy + 36, 55, 48, 38);
line(sx + W/2 + 6, sy + 24, sx + W/2 - 6, sy + 36, 55, 48, 38);
line(sx + W/2 - 6, sy + 38, sx + W/2 + 6, sy + H - 18, 55, 48, 38);
line(sx + W/2 + 6, sy + 38, sx + W/2 - 6, sy + H - 18, 55, 48, 38);
line(sx + W/2 - 5, sy + 8, sx + W/2 - 5, sy + H - 16, 40, 36, 30);
line(sx + W/2 + 5, sy + 8, sx + W/2 + 5, sy + H - 16, 40, 36, 30);
fill_rect(sx + W/2 - 7, sy + 2, 14, 5, ok, og, ob); /* crown */
fill_rect(sx + W/2 - 5, sy + 3, 10, 3, 200, 170, 80); /* beacon */
fill_rect(sx + W/2 - 1, sy + 1, 2, 2, 255, 230, 120); /* beacon tip */
fill_rect(sx + W/2 - 6, sy + 7, 2, 2, 70, 60, 45); /* crown rivet L */
fill_rect(sx + W/2 + 4, sy + 7, 2, 2, 70, 60, 45); /* crown rivet R */
/* mast ladder rungs */
fill_rect(sx + W/2 + 4, sy + 14, 3, 1, 80, 70, 50);
fill_rect(sx + W/2 + 4, sy + 20, 3, 1, 80, 70, 50);
fill_rect(sx + W/2 + 4, sy + 28, 3, 1, 80, 70, 50);
fill_rect(sx + W/2 + 4, sy + 36, 3, 1, 80, 70, 50);
/* pumpjack horse-head (left of mast) */
fill_rect(sx + 8, sy + H/2 - 2, 4, H/2 - 8, ok, og, ob);
fill_rect(sx + 9, sy + H/2, 2, H/2 - 12, 90, 80, 60);
fill_rect(sx + 6, sy + H/2 - 6, 14, 4, ok, og, ob);
fill_rect(sx + 7, sy + H/2 - 5, 12, 2, 110, 95, 70);
fill_rect(sx + 10, sy + H/2 - 8, 3, 3, 70, 60, 45); /* pivot block */
disc(sx + 8, sy + H/2 - 8, 4, ok, og, ob); /* horse head */
disc(sx + 8, sy + H/2 - 8, 2, 130, 110, 80);
line(sx + 10, sy + H/2 - 4, sx + W/2 - 4, sy + 16, 80, 70, 50); /* bridle cable */
fill_rect(sx + 7, sy + H - 14, 6, 3, 50, 44, 34); /* pump base */
fill_rect(sx + 5, sy + H/2 + 6, 5, 3, 55, 48, 38); /* counterweight */
fill_rect(sx + 6, sy + H/2 + 7, 3, 1, 90, 80, 60); /* CW stripe */
fill_rect(sx + 4, sy + H - 16, 3, 2, 45, 40, 32); /* pump foot L */
fill_rect(sx + 12, sy + H - 16, 3, 2, 45, 40, 32); /* pump foot R */
/* gantry arm + walkway + bracing toward hopper */
fill_rect(sx + W/2 + 2, sy + 14, W/2 - 10, 4, ok, og, ob);
fill_rect(sx + W/2 + 3, sy + 15, W/2 - 12, 2, 120, 100, 60);
fill_rect(sx + W/2 + 4, sy + 18, W/2 - 16, 2, 70, 60, 45); /* catwalk */
fill_rect(sx + W/2 + 6, sy + 20, 2, 8, 60, 52, 40); /* catwalk post */
fill_rect(sx + W - 18, sy + 20, 2, 8, 60, 52, 40);
fill_rect(sx + W/2 + 14, sy + 20, 2, 8, 60, 52, 40); /* mid post */
fill_rect(sx + W/2 + 8, sy + 22, W/2 - 28, 1, 90, 75, 50); /* rail */
fill_rect(sx + W/2 + 8, sy + 17, W/2 - 28, 1, 85, 70, 48); /* upper rail */
line(sx + W/2 + 4, sy + 18, sx + W - 12, sy + H - 22, 90, 75, 50);
/* hopper / processing shed + windows/door/hazard */
fill_rect(sx + W - 24, sy + H - 32, 18, 18, ok, og, ob);
fill_rect(sx + W - 22, sy + H - 30, 14, 14, br, bg, bb);
fill_rect(sx + W - 20, sy + H - 28, 10, 6, 140, 110, 55);
fill_rect(sx + W - 18, sy + H - 26, 3, 3, 180, 220, 255); /* window */
fill_rect(sx + W - 13, sy + H - 26, 3, 3, 180, 220, 255);
fill_rect(sx + W - 18, sy + H - 22, 3, 3, 160, 200, 240); /* window row2 */
fill_rect(sx + W - 13, sy + H - 22, 3, 3, 160, 200, 240);
fill_rect(sx + W - 16, sy + H - 18, 4, 4, 40, 36, 30); /* chute */
fill_rect(sx + W - 21, sy + H - 16, 5, 6, 35, 32, 38); /* door */
fill_rect(sx + W - 21, sy + H - 16, 5, 1, 200, 160, 40); /* hazard */
fill_rect(sx + W - 19, sy + H - 13, 1, 2, 180, 170, 90); /* handle */
fill_rect(sx + W - 12, sy + H - 34, 4, 3, 90, 80, 55); /* roof vent */
fill_rect(sx + W - 23, sy + H - 33, 16, 1, 70, 60, 45); /* roof ridge */
fill_rect(sx + W - 10, sy + H - 29, 2, 4, 100, 90, 60); /* side vent */
fill_rect(sx + W - 15, sy + H - 19, 2, 2, 200, 160, 40); /* chute hazard */
/* twin scrap tanks + bands/rivets */
disc(sx + 14, sy + H - 18, 9, ok, og, ob);
disc(sx + 14, sy + H - 18, 7, 200, 165, 85);
ring(sx + 14, sy + H - 18, 4, 2, 90, 70, 45);
fill_rect(sx + 8, sy + H - 20, 12, 1, 120, 95, 50); /* tank band */
fill_rect(sx + 8, sy + H - 16, 12, 1, 110, 88, 45);
fill_rect(sx + 8, sy + H - 18, 12, 1, 100, 82, 42); /* mid band */
fill_rect(sx + 10, sy + H - 22, 2, 2, 90, 75, 45); /* rivet */
fill_rect(sx + 16, sy + H - 14, 2, 2, 90, 75, 45);
fill_rect(sx + 12, sy + H - 14, 2, 2, 85, 70, 40); /* rivet mid */
disc(sx + 28, sy + H - 16, 7, ok, og, ob);
disc(sx + 28, sy + H - 16, 5, 180, 150, 75);
ring(sx + 28, sy + H - 16, 3, 1, 80, 60, 40);
fill_rect(sx + 23, sy + H - 18, 10, 1, 100, 80, 40); /* tank2 band */
fill_rect(sx + 23, sy + H - 16, 10, 1, 95, 76, 38); /* tank2 mid band */
fill_rect(sx + 25, sy + H - 14, 2, 2, 85, 70, 40); /* rivet */
fill_rect(sx + 12, sy + H - 28, 4, 3, ok, og, ob); /* tank cap L */
fill_rect(sx + 26, sy + H - 24, 4, 2, ok, og, ob); /* tank cap R */
fill_rect(sx + 13, sy + H - 29, 2, 1, 200, 170, 80); /* cap gauge L */
fill_rect(sx + 27, sy + H - 25, 2, 1, 200, 170, 80); /* cap gauge R */
/* pipe runs tank→mast→hopper + valves */
line(sx + 20, sy + H - 16, sx + W/2 - 4, sy + H - 12, 110, 90, 50);
line(sx + 28, sy + H - 14, sx + W/2 - 2, sy + H - 14, 100, 85, 45);
line(sx + W/2 + 4, sy + H - 14, sx + W - 20, sy + H - 20, 100, 85, 45);
line(sx + 14, sy + H - 26, sx + 14, sy + H - 20, 100, 85, 45); /* riser */
fill_rect(sx + W/2 - 1, sy + H - 16, 3, 4, 80, 70, 50); /* valve */
fill_rect(sx + W/2 - 2, sy + H - 15, 5, 1, 200, 160, 40); /* valve hazard */
fill_rect(sx + 18, sy + H - 15, 3, 3, 75, 65, 48); /* elbow flange */
fill_rect(sx + W - 22, sy + H - 21, 3, 3, 75, 65, 48); /* hopper flange */
fill_rect(sx + W/2 + 6, sy + H - 15, 3, 3, 75, 65, 48); /* mid pipe flange */
fill_rect(sx + W/2 + 2, sy + H - 18, 2, 2, 90, 75, 50); /* pipe clamp */
/* oil stain + drip pans + barrels */
disc(sx + W/2 - 8, sy + H - 6, 4, 40, 32, 20);
disc(sx + W/2 + 6, sy + H - 5, 3, 35, 28, 18);
fill_rect(sx + 10, sy + H - 8, 8, 2, 30, 24, 16); /* drip pan */
fill_rect(sx + 24, sy + H - 7, 6, 2, 28, 22, 14);
fill_rect(sx + W - 28, sy + H - 10, 4, 5, 55, 48, 35); /* oil drum */
fill_rect(sx + W - 27, sy + H - 9, 2, 1, 200, 160, 40); /* drum band */
fill_rect(sx + W - 32, sy + H - 9, 3, 4, 50, 44, 32); /* crate */
break;
case BT_POWER:
/* denser KKND plant: pad, twin cooling, reactor, stack, switchyard, steam */
fill_rect(sx + 2, sy + H - 11, W - 4, 9, ok, og, ob); /* slab */
fill_rect(sx + 3, sy + H - 9, W - 6, 6, 55, 50, 42); /* pad */
fill_rect(sx + 4, sy + H - 8, 2, 2, 35, 32, 28); /* pad bolt L */
fill_rect(sx + W/2 - 1, sy + H - 8, 2, 2, 35, 32, 28); /* pad bolt C */
fill_rect(sx + W - 6, sy + H - 8, 2, 2, 35, 32, 28); /* pad bolt R */
fill_rect(sx + 3, sy + H - 4, W - 6, 2, 45, 40, 32); /* curb */
/* left cooling tower (taller cone + bands) */
fill_rect(sx + 3, sy + 7, 11, H - 18, ok, og, ob);
fill_rect(sx + 4, sy + 8, 9, H - 20, 95, 145, 195);
fill_rect(sx + 5, sy + 5, 7, 5, ok, og, ob);
fill_rect(sx + 6, sy + 4, 5, 4, 70, 120, 170); /* rim */
fill_rect(sx + 5, sy + 14, 1, H - 28, 70, 110, 150); /* cooling fin L */
fill_rect(sx + 11, sy + 14, 1, H - 28, 70, 110, 150);
fill_rect(sx + 5, sy + 18, 7, 1, 80, 125, 170); /* band */
fill_rect(sx + 5, sy + 28, 7, 1, 75, 118, 162);
fill_rect(sx + 6, sy + 22, 2, 2, 180, 220, 255); /* port window */
disc(sx + 8, sy + 3, 3, 240, 248, 255); /* steam puff */
disc(sx + 6, sy + 1, 2, 220, 235, 250);
disc(sx + 10, sy + 2, 2, 230, 240, 252);
disc(sx + 9, sy + 5, 2, 210, 228, 245);
/* right cooling tower */
fill_rect(sx + W - 14, sy + 9, 11, H - 20, ok, og, ob);
fill_rect(sx + W - 13, sy + 10, 9, H - 22, 85, 135, 185);
fill_rect(sx + W - 12, sy + 7, 7, 4, ok, og, ob);
fill_rect(sx + W - 11, sy + 6, 5, 3, 65, 115, 165);
fill_rect(sx + W - 12, sy + 15, 1, H - 30, 65, 105, 145);
fill_rect(sx + W - 6, sy + 15, 1, H - 30, 65, 105, 145);
fill_rect(sx + W - 12, sy + 20, 7, 1, 70, 112, 155); /* band */
fill_rect(sx + W - 12, sy + 30, 7, 1, 68, 108, 150);
fill_rect(sx + W - 10, sy + 24, 2, 2, 170, 210, 245); /* port */
disc(sx + W - 9, sy + 4, 3, 235, 245, 255);
disc(sx + W - 7, sy + 2, 2, 210, 230, 250);
disc(sx + W - 11, sy + 3, 2, 225, 238, 250);
disc(sx + W - 8, sy + 6, 2, 200, 220, 240);
/* central reactor block + glow + panels */
fill_rect(sx + W/2 - 6, sy + H/2 - 5, 12, H/2 - 1, ok, og, ob);
fill_rect(sx + W/2 - 5, sy + H/2 - 3, 10, H/2 - 5, 55, 95, 150);
fill_rect(sx + W/2 - 4, sy + H/2 - 1, 3, 3, 255, 200, 40); /* warn L */
fill_rect(sx + W/2 + 1, sy + H/2 - 1, 3, 3, 255, 200, 40); /* warn R */
fill_rect(sx + W/2 - 3, sy + H/2 + 4, 2, 2, 40, 70, 110); /* panel */
fill_rect(sx + W/2 + 1, sy + H/2 + 4, 2, 2, 40, 70, 110);
fill_rect(sx + W/2 - 2, sy + H/2 + 8, 4, 2, 200, 160, 40); /* hazard belt */
disc(sx + W/2, sy + H/2 + 2, 4, 120, 200, 255);
disc(sx + W/2, sy + H/2 + 2, 2, 250, 252, 255);
ring(sx + W/2, sy + H/2 + 2, 5, 1, 90, 160, 220);
line(sx + W/2, sy + 8, sx + W/2, sy + H - 10, 200, 230, 250);
line(sx + 8, sy + H/2 + 2, sx + W - 8, sy + H/2 + 2, 180, 220, 245);
/* exhaust stack + plume */
fill_rect(sx + W/2 + 6, sy + 6, 4, H/2 - 8, ok, og, ob);
fill_rect(sx + W/2 + 7, sy + 7, 2, H/2 - 12, 60, 58, 52);
fill_rect(sx + W/2 + 6, sy + 4, 4, 3, 50, 48, 42); /* stack cap */
disc(sx + W/2 + 8, sy + 2, 3, 180, 190, 200); /* smoke */
disc(sx + W/2 + 9, sy + 0, 2, 160, 170, 180);
/* switchyard: transformers + bus + insulators */
fill_rect(sx + W/2 - 10, sy + H - 16, 7, 6, ok, og, ob);
fill_rect(sx + W/2 - 9, sy + H - 15, 5, 4, 45, 42, 36);
fill_rect(sx + W/2 + 3, sy + H - 15, 6, 5, ok, og, ob);
fill_rect(sx + W/2 + 4, sy + H - 14, 4, 3, 50, 48, 40);
fill_rect(sx + W/2 - 8, sy + H - 17, 2, 2, 90, 85, 70); /* insulator */
fill_rect(sx + W/2 + 5, sy + H - 16, 2, 2, 90, 85, 70);
fill_rect(sx + W/2 - 1, sy + H - 18, 3, 2, 70, 68, 58); /* bus bar */
line(sx + W/2 - 6, sy + H - 16, sx + W/2 - 6, sy + H/2 + 6, 90, 85, 70);
line(sx + W/2 + 5, sy + H - 14, sx + W - 10, sy + H/2 + 4, 80, 75, 60);
line(sx + W/2 + 2, sy + H - 17, sx + W/2 + 7, sy + H/2 + 8, 85, 80, 65);
/* control booth + fence */
fill_rect(sx + 14, sy + H - 20, 8, 8, ok, og, ob);
fill_rect(sx + 15, sy + H - 19, 6, 6, 40, 55, 70);
fill_rect(sx + 16, sy + H - 18, 2, 2, 180, 220, 255); /* booth window */
fill_rect(sx + 19, sy + H - 18, 2, 2, 160, 200, 240);
fill_rect(sx + 17, sy + H - 15, 2, 3, 35, 32, 38); /* door */
fill_rect(sx + 2, sy + H - 13, 2, 4, 50, 48, 40); /* fence post L */
fill_rect(sx + W/2 - 12, sy + H - 13, 2, 4, 50, 48, 40);
fill_rect(sx + W - 4, sy + H - 13, 2, 4, 50, 48, 40); /* fence post R */
line(sx + 3, sy + H - 12, sx + W - 4, sy + H - 12, 60, 55, 45); /* fence rail */
break;
case BT_TURRET:
/* denser KKND turret: berm+bunker+cupola+barrel+radar+coax */
fill_rect(sx + 1, sy + H - 8, W - 2, 6, 70, 58, 42); /* berm/sandbag ring */
fill_rect(sx + 2, sy + H - 7, 4, 4, 95, 82, 60); /* bag L */
fill_rect(sx + W - 6, sy + H - 7, 4, 4, 95, 82, 60); /* bag R */
fill_rect(sx + W/2 - 4, sy + H - 6, 8, 3, 85, 72, 52); /* bag mid */
fill_rect(sx + 2, sy + H - 16, W - 4, 12, ok, og, ob); /* bunker hull */
fill_rect(sx + 3, sy + H - 14, W - 6, 9, 48, 44, 38); /* bunker slab */
fill_rect(sx + 3, sy + H - 15, W - 6, 2, 90, 82, 68); /* top lip */
fill_rect(sx + 4, sy + H - 13, 5, 5, 70, 65, 55); /* armor plate L */
fill_rect(sx + W - 9, sy + H - 13, 5, 5, 70, 65, 55); /* armor plate R */
fill_rect(sx + W/2 - 6, sy + H - 10, 12, 4, 35, 32, 28); /* embrasure */
fill_rect(sx + W/2 - 5, sy + H - 9, 10, 2, 20, 18, 16); /* slit */
fill_rect(sx + 5, sy + H - 8, 2, 2, 28, 26, 22); /* bolt L */
fill_rect(sx + 8, sy + H - 8, 2, 2, 28, 26, 22);
fill_rect(sx + W - 10, sy + H - 8, 2, 2, 28, 26, 22); /* bolt R */
fill_rect(sx + W - 7, sy + H - 8, 2, 2, 28, 26, 22);
fill_rect(sx + 2, sy + H - 4, 3, 3, 55, 48, 38); /* corner block L */
fill_rect(sx + W - 5, sy + H - 4, 3, 3, 55, 48, 38); /* corner block R */
/* dual ammo crates + feed strut */
fill_rect(sx + 2, sy + H/2 + 1, 7, 9, ok, og, ob);
fill_rect(sx + 3, sy + H/2 + 2, 5, 7, 55, 50, 42);
fill_rect(sx + 4, sy + H/2 + 3, 3, 2, 200, 160, 40); /* hazard stripe */
fill_rect(sx + W - 9, sy + H/2 + 2, 6, 8, ok, og, ob);
fill_rect(sx + W - 8, sy + H/2 + 3, 4, 6, 50, 46, 38);
fill_rect(sx + W - 7, sy + H/2 + 4, 2, 2, 200, 160, 40);
line(sx + 6, sy + H/2 + 2, sx + W/2 - 4, sy + H/2, 40, 36, 30);
line(sx + W - 6, sy + H/2 + 3, sx + W/2 + 4, sy + H/2 + 1, 40, 36, 30);
/* access ladder */
fill_rect(sx + 1, sy + H/2 - 2, 2, 10, 60, 55, 45);
fill_rect(sx + 1, sy + H/2, 3, 1, 90, 85, 70);
fill_rect(sx + 1, sy + H/2 + 3, 3, 1, 90, 85, 70);
fill_rect(sx + 1, sy + H/2 + 6, 3, 1, 90, 85, 70);
/* ringed cupola + viewports */
disc(sx + W/2, sy + H/2, W/3 + 3, ok, og, ob);
disc(sx + W/2, sy + H/2, W/3 + 1, br, bg, bb);
{
uint8_t dr, dg, db;
darken3(br, bg, bb, 0.45f, &dr, &dg, &db);
ring(sx + W/2, sy + H/2, W/4 + 2, 2, dr, dg, db);
}
ring(sx + W/2, sy + H/2, W/4 - 1, 1, 35, 32, 28);
disc(sx + W/2, sy + H/2, W/5, lr, lg, lb);
disc(sx + W/2, sy + H/2 - 1, 3, 35, 32, 28); /* hatch */
fill_rect(sx + W/2 - 1, sy + H/2 - 3, 2, 2, 20, 18, 16); /* hatch handle */
fill_rect(sx + W/2 - 6, sy + H/2 - 1, 3, 2, 25, 22, 18); /* viewport L */
fill_rect(sx + W/2 + 3, sy + H/2 - 1, 3, 2, 25, 22, 18); /* viewport R */
disc(sx + W/2 - 5, sy + H/2 + 4, 1, 90, 85, 70); /* rivet */
disc(sx + W/2 + 5, sy + H/2 + 4, 1, 90, 85, 70);
disc(sx + W/2 - 2, sy + H/2 + 5, 1, 90, 85, 70);
disc(sx + W/2 + 2, sy + H/2 + 5, 1, 90, 85, 70);
/* main barrel shroud + bands + rail */
fill_rect(sx + W/2 + 1, sy + H/2 - 4, 14, 7, ok, og, ob);
fill_rect(sx + W/2 + 2, sy + H/2 - 3, 13, 5, 40, 36, 30);
fill_rect(sx + W/2 + 3, sy + H/2 - 2, 12, 3, 55, 50, 42); /* bore highlight */
fill_rect(sx + W/2 + 4, sy + H/2 - 4, 2, 7, 70, 62, 50); /* band */
fill_rect(sx + W/2 + 8, sy + H/2 - 4, 2, 7, 70, 62, 50);
fill_rect(sx + W/2 + 12, sy + H/2 - 4, 2, 7, 70, 62, 50);
fill_rect(sx + W/2 + 2, sy + H/2 + 3, 10, 2, 30, 28, 24); /* recoil rail */
line(sx + W/2, sy + H/2, sx + W/2 + 15, sy + H/2 - 11, 28, 26, 22);
line(sx + W/2 + 1, sy + H/2 + 1, sx + W/2 + 15, sy + H/2 - 10, 55, 48, 38);
disc(sx + W/2 + 14, sy + H/2 - 11, 2, 255, 200, 80); /* muzzle tip */
fill_rect(sx + W/2 + 13, sy + H/2 - 12, 3, 1, 255, 220, 120); /* flash tip */
/* coax MG under barrel */
fill_rect(sx + W/2 + 3, sy + H/2 + 1, 8, 2, 45, 42, 36);
fill_rect(sx + W/2 + 10, sy + H/2 + 1, 3, 1, 30, 28, 24);
/* radar dish + mast + cable */
fill_rect(sx + W/2 - 2, sy + H/2 - 10, 3, 5, 50, 48, 40); /* mast */
disc(sx + W/2 - 1, sy + H/2 - 11, 4, 90, 88, 78);
disc(sx + W/2 - 1, sy + H/2 - 11, 2, 40, 38, 34);
line(sx + W/2 - 1, sy + H/2 - 7, sx + W/2 - 6, sy + H/2 - 2, 60, 55, 45);
/* searchlight + flood */
fill_rect(sx + W/2 - 9, sy + H/2 - 7, 5, 4, 200, 195, 160);
fill_rect(sx + W/2 - 8, sy + H/2 - 6, 3, 2, 255, 250, 210);
disc(sx + W/2 - 7, sy + H/2 - 8, 2, 255, 250, 210);
fill_rect(sx + W - 6, sy + H/2 - 5, 3, 2, 220, 210, 160); /* side flood */
/* denser: berm stakes, cupola seam, barrel vents, feed belt, casings, IFF */
fill_rect(sx + 3, sy + H - 9, 1, 3, 55, 48, 38); /* berm stake L */
fill_rect(sx + W - 4, sy + H - 9, 1, 3, 55, 48, 38); /* berm stake R */
fill_rect(sx + W/2 - 1, sy + H - 9, 2, 2, 60, 52, 40); /* berm mid peg */
fill_rect(sx + W/2 - 4, sy + H/2 - 4, 2, 1, 80, 75, 65); /* cupola seam L */
fill_rect(sx + W/2 + 2, sy + H/2 - 4, 2, 1, 80, 75, 65); /* cupola seam R */
fill_rect(sx + W/2 + 6, sy + H/2 - 5, 1, 2, 45, 40, 34); /* barrel vent 1 */
fill_rect(sx + W/2 + 9, sy + H/2 - 5, 1, 2, 45, 40, 34); /* barrel vent 2 */
fill_rect(sx + W/2 + 5, sy + H/2 + 2, 6, 1, 35, 32, 28); /* ammo feed belt */
fill_rect(sx + W/2 + 5, sy + H/2 + 3, 1, 1, 200, 160, 40); /* belt link */
fill_rect(sx + W/2 + 7, sy + H/2 + 3, 1, 1, 200, 160, 40);
fill_rect(sx + W/2 + 9, sy + H/2 + 3, 1, 1, 200, 160, 40);
fill_rect(sx + 4, sy + H - 3, 2, 1, 180, 150, 60); /* spent casing L */
fill_rect(sx + 7, sy + H - 3, 2, 1, 180, 150, 60); /* spent casing R */
fill_rect(sx + W/2 - 2, sy + H/2 - 12, 1, 2, 70, 68, 58); /* antenna tip */
fill_rect(sx + W - 5, sy + H/2 - 3, 2, 2, 40, 180, 60); /* IFF LED */
fill_rect(sx + 6, sy + H/2 + 8, 4, 1, 50, 44, 36); /* footplate scar */
/* denser+ : rangefinder, smoke tubes, ammo hatch, heat shield, status, bags */
fill_rect(sx + W/2 + 1, sy + H/2 - 6, 4, 1, 90, 85, 70); /* rangefinder rail */
fill_rect(sx + W/2 + 4, sy + H/2 - 7, 2, 2, 30, 28, 24); /* RF optic */
fill_rect(sx + W/2 - 8, sy + H/2 + 2, 3, 2, 55, 50, 42); /* smoke tube L */
fill_rect(sx + W/2 - 7, sy + H/2 + 2, 1, 1, 200, 180, 80); /* tube cap */
fill_rect(sx + W/2 + 6, sy + H/2 + 4, 3, 2, 55, 50, 42); /* smoke tube R */
fill_rect(sx + W - 8, sy + H/2 + 6, 5, 4, ok, og, ob); /* ammo hatch */
fill_rect(sx + W - 7, sy + H/2 + 7, 3, 2, 35, 32, 28); /* hatch well */
fill_rect(sx + W/2 + 3, sy + H/2 - 1, 9, 1, 65, 58, 48); /* barrel heat shield */
fill_rect(sx + W/2 + 11, sy + H/2 - 5, 1, 2, 45, 40, 34); /* barrel vent 3 */
fill_rect(sx + 4, sy + H/2 - 1, 3, 2, 40, 38, 32); /* status panel */
fill_rect(sx + 5, sy + H/2 - 1, 1, 1, 40, 200, 80); /* status LED */
fill_rect(sx + W/2 - 6, sy + H - 5, 3, 2, 95, 82, 60); /* bag top L */
fill_rect(sx + W/2 + 3, sy + H - 5, 3, 2, 95, 82, 60); /* bag top R */
fill_rect(sx + W - 4, sy + H/2 + 1, 2, 2, 70, 65, 55); /* armor stud R */
fill_rect(sx + 3, sy + H/2 + 6, 2, 2, 70, 65, 55); /* armor stud L */
/* denser++ : berm sandbags, cupola bolts, barrel muzzle, coax feed, radar dish, bunker slits */
fill_rect(sx + 2, sy + H - 4, 5, 2, 110, 95, 70); /* berm sandbag L */
fill_rect(sx + W - 7, sy + H - 4, 5, 2, 110, 95, 70); /* berm sandbag R */
fill_rect(sx + W/2 - 5, sy + H - 3, 4, 1, 90, 78, 55); /* berm lip */
fill_rect(sx + W/2 - 3, sy + H/2 - 9, 1, 1, 50, 48, 42); /* cupola bolt NW */
fill_rect(sx + W/2 + 3, sy + H/2 - 9, 1, 1, 50, 48, 42); /* cupola bolt NE */
fill_rect(sx + W/2 - 3, sy + H/2 - 3, 1, 1, 50, 48, 42); /* cupola bolt SW */
fill_rect(sx + W/2 + 3, sy + H/2 - 3, 1, 1, 50, 48, 42); /* cupola bolt SE */
fill_rect(sx + W/2 + 12, sy + H/2 - 2, 3, 2, 35, 32, 28); /* muzzle brake */
fill_rect(sx + W/2 + 13, sy + H/2 - 2, 1, 2, 200, 190, 140); /* muzzle flash rim */
fill_rect(sx + W/2 + 2, sy + H/2 + 1, 4, 1, 45, 42, 36); /* coax feed chute */
fill_rect(sx + W/2 + 5, sy + H/2 + 1, 1, 1, 180, 160, 50); /* coax brass */
fill_rect(sx + W/2 - 1, sy + H/2 - 11, 3, 2, lr, lg, lb); /* radar dish face */
fill_rect(sx + W/2, sy + H/2 - 12, 1, 1, 220, 220, 200); /* dish highlight */
fill_rect(sx + 5, sy + H/2 + 4, 2, 3, 30, 28, 24); /* bunker slit L */
fill_rect(sx + W - 7, sy + H/2 + 3, 2, 3, 30, 28, 24); /* bunker slit R */
fill_rect(sx + W/2 - 8, sy + H/2 - 5, 2, 1, 60, 55, 45); /* periscope housing */
fill_rect(sx + W/2 - 7, sy + H/2 - 6, 1, 1, 20, 80, 120); /* peri glass */
fill_rect(sx + 8, sy + H - 6, 2, 2, br, bg, bb); /* tread scar L */
fill_rect(sx + W - 10, sy + H - 6, 2, 2, br, bg, bb); /* tread scar R */
break;
case BT_WALL:
/* denser fort: battlement, gate, scars, rubble (KKND fort read) */
fill_rect(sx + 0, sy + 4, W, H - 4, ok, og, ob); /* outer hull */
fill_rect(sx + 1, sy + 6, W - 2, H - 8, 165, 145, 110); /* face */
fill_rect(sx + 2, sy + 7, W - 4, H - 12, 150, 132, 100); /* inner face */
fill_rect(sx + 2, sy + H - 6, W - 4, 4, 90, 78, 58); /* plinth */
fill_rect(sx + 3, sy + H - 5, W - 6, 2, 70, 60, 48); /* plinth shadow */
fill_rect(sx + 1, sy + H - 5, 3, 3, 70, 60, 45); /* corner stone L */
fill_rect(sx + W - 4, sy + H - 5, 3, 3, 70, 60, 45); /* corner stone R */
fill_rect(sx + W/2 - 2, sy + H - 4, 4, 2, 55, 48, 38); /* threshold */
/* thick merlons + caps + 4th tooth */
fill_rect(sx + 1, sy + 1, 7, 10, ok, og, ob);
fill_rect(sx + 2, sy + 2, 5, 8, 195, 175, 140);
fill_rect(sx + 3, sy + 1, 3, 2, 220, 200, 160); /* merlon cap L */
fill_rect(sx + W/2 - 5, sy + 1, 4, 9, ok, og, ob); /* merlon mid-L */
fill_rect(sx + W/2 - 4, sy + 2, 2, 7, 200, 180, 145);
fill_rect(sx + W/2 - 1, sy + 1, 7, 10, ok, og, ob);
fill_rect(sx + W/2, sy + 2, 5, 8, 200, 180, 145);
fill_rect(sx + W/2 + 1, sy + 1, 3, 2, 220, 200, 160); /* merlon cap mid */
fill_rect(sx + W - 8, sy + 1, 7, 10, ok, og, ob);
fill_rect(sx + W - 7, sy + 2, 5, 8, 190, 170, 135);
fill_rect(sx + W - 6, sy + 1, 3, 2, 215, 195, 155); /* merlon cap R */
fill_rect(sx + 2, sy + 10, W - 4, 3, 215, 195, 155); /* coping */
fill_rect(sx + 2, sy + 11, W - 4, 1, 120, 105, 80); /* coping shadow */
fill_rect(sx + 4, sy + 9, 2, 2, 180, 160, 125); /* coping bolt L */
fill_rect(sx + W - 6, sy + 9, 2, 2, 180, 160, 125); /* coping bolt R */
/* buttresses + mid pier */
fill_rect(sx + 0, sy + H/2 - 2, 4, H/2, ok, og, ob);
fill_rect(sx + 1, sy + H/2, 2, H/2 - 4, 140, 125, 95);
fill_rect(sx + W - 4, sy + H/2 - 2, 4, H/2, ok, og, ob);
fill_rect(sx + W - 3, sy + H/2, 2, H/2 - 4, 140, 125, 95);
fill_rect(sx + W/2 - 2, sy + H/2 + 4, 4, H/2 - 8, ok, og, ob); /* mid pier */
fill_rect(sx + W/2 - 1, sy + H/2 + 5, 2, H/2 - 10, 130, 115, 88);
/* gate arch + door + hinges */
fill_rect(sx + W/2 - 4, sy + H/2 + 2, 8, H/2 - 6, 45, 38, 30); /* gate recess */
fill_rect(sx + W/2 - 3, sy + H/2 + 3, 6, H/2 - 8, 55, 48, 38); /* door */
fill_rect(sx + W/2 - 2, sy + H/2 + 4, 4, 3, 35, 30, 24); /* gate grate */
fill_rect(sx + W/2 - 4, sy + H/2 + 2, 1, H/2 - 8, 90, 80, 60); /* hinge L */
fill_rect(sx + W/2 + 3, sy + H/2 + 2, 1, H/2 - 8, 90, 80, 60); /* hinge R */
fill_rect(sx + W/2 - 1, sy + H - 8, 2, 2, 200, 180, 80); /* lock plate */
/* embrasures + slits + scars */
fill_rect(sx + W/2 - 2, sy + H/2 - 4, 4, 5, 35, 30, 24); /* embrasure */
fill_rect(sx + W/2 - 1, sy + H/2 - 2, 2, 3, 20, 18, 14);
fill_rect(sx + W/4 - 1, sy + H/2 - 1, 3, 5, 40, 35, 28); /* side slit L */
fill_rect(sx + 3 * W/4 - 1, sy + H/2 - 1, 3, 5, 40, 35, 28); /* side slit R */
fill_rect(sx + 5, sy + H/2 + 8, 3, 2, 100, 70, 50); /* blast scar L */
fill_rect(sx + W - 8, sy + H/2 + 7, 4, 2, 95, 65, 45); /* blast scar R */
fill_rect(sx + 3, sy + H - 3, 4, 2, 80, 70, 55); /* rubble L */
fill_rect(sx + W - 7, sy + H - 3, 4, 2, 80, 70, 55); /* rubble R */
fill_rect(sx + W/2 + 5, sy + H/2 + 10, 2, 2, 60, 52, 40); /* chip */
line(sx + 2, sy + H/2 + 1, sx + W - 2, sy + H/2 + 1, 55, 48, 38);
line(sx + 2, sy + H/2 + 6, sx + W - 2, sy + H/2 + 6, 75, 65, 50);
line(sx + W/3, sy + 8, sx + W/3, sy + H - 4, 65, 55, 42);
line(sx + 2 * W/3, sy + 8, sx + 2 * W/3, sy + H - 4, 65, 55, 42);
line(sx + 4, sy + H - 7, sx + W - 4, sy + H - 7, 50, 42, 32); /* plinth seam */
disc(sx + 6, sy + H/2 + 10, 1, 70, 55, 40); /* pock L */
disc(sx + W - 6, sy + H/2 + 9, 1, 70, 55, 40); /* pock R */
break;
case BT_REPAIR:
/* dense garage: pad, hull, bay/lift, tool rack, crane, crates (KKND depot) */
fill_rect(sx + 3, sy + H - 8, W - 6, 6, ok, og, ob); /* pad */
fill_rect(sx + 4, sy + H - 7, W - 8, 4, 55, 50, 40);
fill_rect(sx + 5, sy + H - 6, 2, 2, 35, 32, 28); /* pad bolt L */
fill_rect(sx + W - 7, sy + H - 6, 2, 2, 35, 32, 28); /* pad bolt R */
/* main garage hull + roof band + rivets */
fill_rect(sx + 4, sy + 6, W - 8, H - 12, ok, og, ob);
fill_rect(sx + 6, sy + 8, W - 12, H - 16, br, bg, bb);
fill_rect(sx + 8, sy + 10, W - 16, 8, lr, lg, lb); /* roof */
fill_rect(sx + 10, sy + 12, 4, 4, 50, 48, 40); /* roof vents */
fill_rect(sx + W/2 - 2, sy + 12, 4, 4, 50, 48, 40);
fill_rect(sx + W - 14, sy + 12, 4, 4, 50, 48, 40);
fill_rect(sx + 8, sy + 11, 2, 2, 90, 85, 70); /* rivet */
fill_rect(sx + W - 12, sy + 11, 2, 2, 90, 85, 70);
fill_rect(sx + 8, sy + H - 18, 2, 2, 90, 85, 70);
fill_rect(sx + W - 12, sy + H - 18, 2, 2, 90, 85, 70);
line(sx + 7, sy + 20, sx + W - 8, sy + 20, 70, 65, 55); /* roof seam */
/* open bay + lift pad + posts + ramps */
fill_rect(sx + 10, sy + H - 30, W - 28, 20, ok, og, ob);
fill_rect(sx + 12, sy + H - 28, W - 32, 16, 38, 36, 32);
fill_rect(sx + W/2 - 10, sy + H - 24, 20, 8, 70, 68, 58); /* lift platform */
fill_rect(sx + W/2 - 8, sy + H - 22, 16, 4, 110, 100, 70);
fill_rect(sx + W/2 - 6, sy + H - 20, 12, 2, 140, 120, 80); /* lift deck highlight */
line(sx + W/2 - 10, sy + H - 24, sx + W/2 - 10, sy + H - 12, 90, 85, 70);
line(sx + W/2 + 10, sy + H - 24, sx + W/2 + 10, sy + H - 12, 90, 85, 70);
fill_rect(sx + 12, sy + H - 14, 6, 3, 60, 55, 45); /* ramp L */
fill_rect(sx + W - 26, sy + H - 14, 6, 3, 60, 55, 45); /* ramp R */
fill_rect(sx + W/2 - 12, sy + H - 12, 3, 4, 80, 75, 60); /* lift post L */
fill_rect(sx + W/2 + 9, sy + H - 12, 3, 4, 80, 75, 60); /* lift post R */
/* tool rack annex (left) denser */
fill_rect(sx + 4, sy + H/2 - 4, 12, H/2 - 4, ok, og, ob);
{
uint8_t dr, dg, db;
darken3(br, bg, bb, 0.5f, &dr, &dg, &db);
fill_rect(sx + 5, sy + H/2 - 2, 10, H/2 - 8, dr, dg, db);
}
fill_rect(sx + 6, sy + H/2 + 2, 2, 6, 200, 160, 60); /* tools */
fill_rect(sx + 9, sy + H/2 + 1, 2, 7, 180, 180, 190);
fill_rect(sx + 12, sy + H/2 + 2, 2, 5, 160, 140, 80);
fill_rect(sx + 6, sy + H/2 + 10, 8, 2, 90, 85, 70); /* shelf */
fill_rect(sx + 7, sy + H/2 - 1, 6, 2, 120, 110, 90); /* upper shelf */
/* overhead crane mast + jib + hook + trolley */
fill_rect(sx + W - 16, sy + 4, 8, H - 20, ok, og, ob);
fill_rect(sx + W - 14, sy + 5, 5, H - 22, 95, 88, 70);
fill_rect(sx + W - 13, sy + 8, 2, 2, 70, 65, 50); /* mast rivet */
fill_rect(sx + W - 13, sy + H/2, 2, 2, 70, 65, 50);
fill_rect(sx + 16, sy + 6, W - 32, 4, ok, og, ob);
fill_rect(sx + 17, sy + 7, W - 34, 2, 120, 110, 85);
fill_rect(sx + W/2 - 3, sy + 5, 6, 3, 100, 95, 75); /* trolley */
line(sx + W/2, sy + 8, sx + W/2, sy + 20, 70, 65, 50);
disc(sx + W/2, sy + 22, 3, 80, 75, 55);
disc(sx + W/2, sy + 22, 1, 140, 130, 90); /* hook tip */
line(sx + W - 12, sy + 10, sx + W/2 + 4, sy + 8, 90, 85, 65); /* jib brace */
/* green service cross */
ring(sx + W/2 - 6, sy + H/2 + 4, 9, 2, 90, 200, 90);
fill_rect(sx + W/2 - 14, sy + H/2 + 2, 16, 4, 120, 220, 120);
fill_rect(sx + W/2 - 8, sy + H/2 - 4, 4, 16, 120, 220, 120);
/* parts crates + oil drum + scrap pile */
fill_rect(sx + W - 22, sy + H - 14, 8, 6, 140, 100, 55);
fill_rect(sx + W - 20, sy + H - 12, 4, 3, 180, 140, 70);
fill_rect(sx + W - 24, sy + H - 18, 6, 5, 110, 80, 45); /* crate stack */
fill_rect(sx + 18, sy + H - 16, 5, 6, 70, 90, 55); /* oil drum */
disc(sx + 20, sy + H - 16, 3, 60, 80, 45);
fill_rect(sx + 24, sy + H - 14, 6, 3, 90, 75, 55); /* scrap */
fill_rect(sx + W - 18, sy + H - 8, 4, 2, 200, 160, 40); /* hazard stripe */
/* side floodlight + bay hazard lintel */
fill_rect(sx + W - 10, sy + 4, 2, 2, 220, 200, 80);
fill_rect(sx + W - 9, sy + 3, 1, 3, 90, 85, 70);
fill_rect(sx + 12, sy + H - 30, W - 32, 2, 200, 160, 40); /* bay hazard */
/* denser: door rollers, lift rails, wrench rack, torch, tire, jack, rivets, vent */
fill_rect(sx + 14, sy + H - 28, 2, 2, 160, 150, 120); /* door roller L */
fill_rect(sx + W - 16, sy + H - 28, 2, 2, 160, 150, 120); /* door roller R */
fill_rect(sx + 16, sy + H - 26, 1, 8, 100, 95, 80); /* lift rail L */
fill_rect(sx + W - 17, sy + H - 26, 1, 8, 100, 95, 80); /* lift rail R */
fill_rect(sx + 8, sy + H/2 - 2, 3, 1, 180, 170, 140); /* wrench rack bar */
fill_rect(sx + 8, sy + H/2, 2, 4, 140, 130, 100); /* wrench hang */
fill_rect(sx + W - 8, sy + H/2 + 2, 2, 3, 220, 140, 40); /* torch tip */
fill_rect(sx + W - 9, sy + H/2 + 5, 3, 1, 90, 85, 70); /* torch handle */
fill_rect(sx + 10, sy + H - 10, 4, 4, 50, 50, 45); /* tire stack */
fill_rect(sx + 11, sy + H - 11, 2, 1, 80, 80, 70);
fill_rect(sx + W/2 + 8, sy + H - 12, 2, 3, 110, 100, 80); /* jack stand */
fill_rect(sx + W/2 + 6, sy + H - 10, 6, 1, 130, 120, 95); /* jack pad */
fill_rect(sx + 20, sy + 8, 1, 1, 170, 160, 130); /* rivet */
fill_rect(sx + W - 22, sy + 8, 1, 1, 170, 160, 130);
fill_rect(sx + W/2 - 2, sy + 6, 4, 2, 90, 100, 110); /* roof vent */
/* denser: bay panels, hose, tool cart, chain, oil drip, spare parts, pad chevrons */
fill_rect(sx + 16, sy + H - 24, 3, 4, 45, 44, 40); /* bay panel L */
fill_rect(sx + W - 20, sy + H - 24, 3, 4, 45, 44, 40); /* bay panel R */
fill_rect(sx + W/2 - 2, sy + H - 22, 4, 2, 55, 52, 48); /* bay latch */
fill_rect(sx + 6, sy + H/2 + 6, 2, 8, 70, 65, 50); /* air hose riser */
fill_rect(sx + 6, sy + H/2 + 13, 5, 1, 60, 55, 42); /* hose elbow */
fill_rect(sx + W - 14, sy + H - 18, 5, 4, 90, 85, 65); /* tool cart */
fill_rect(sx + W - 13, sy + H - 17, 3, 1, 200, 160, 40); /* cart stripe */
fill_rect(sx + W - 12, sy + H - 14, 1, 1, 40, 40, 35); /* cart wheel */
fill_rect(sx + W/2 + 10, sy + 12, 1, 6, 110, 100, 80); /* chain drop */
fill_rect(sx + W/2 + 9, sy + 17, 3, 1, 90, 85, 70); /* chain link */
fill_rect(sx + W/2 - 10, sy + H - 8, 3, 2, 35, 30, 22); /* oil drip */
fill_rect(sx + 22, sy + H - 20, 3, 2, 120, 90, 50); /* spare plate */
fill_rect(sx + 23, sy + H - 19, 1, 1, 160, 130, 70); /* plate bolt */
fill_rect(sx + W - 26, sy + H/2 + 2, 2, 3, 80, 90, 100); /* coolant can */
fill_rect(sx + W - 25, sy + H/2 + 1, 1, 1, 60, 180, 80); /* can cap */
fill_rect(sx + 14, sy + H - 8, 2, 1, 200, 160, 40); /* pad chevron L */
fill_rect(sx + W/2 - 1, sy + H - 8, 2, 1, 200, 160, 40); /* pad chevron C */
fill_rect(sx + W - 18, sy + H - 8, 2, 1, 200, 160, 40); /* pad chevron R */
fill_rect(sx + 12, sy + 10, 2, 2, 50, 48, 42); /* gutter L */
fill_rect(sx + W - 16, sy + 10, 2, 2, 50, 48, 42); /* gutter R */
fill_rect(sx + W/2 + 4, sy + H/2 - 6, 2, 2, 180, 100, 40); /* weld spark */
break;
case BT_RESEARCH:
/* denser lab: pad, hull, windows, dish mast, annex, instruments (KKND) */
fill_rect(sx + 3, sy + H - 8, W - 6, 6, ok, og, ob); /* pad */
fill_rect(sx + 4, sy + H - 7, W - 8, 4, 55, 50, 40);
fill_rect(sx + 5, sy + H - 6, 2, 2, 35, 32, 28); /* pad bolt L */
fill_rect(sx + W - 7, sy + H - 6, 2, 2, 35, 32, 28); /* pad bolt R */
fill_rect(sx + 7, sy + H - 5, 2, 1, 45, 40, 32); /* pad plate L */
fill_rect(sx + W - 9, sy + H - 5, 2, 1, 45, 40, 32); /* pad plate R */
/* main hull + upper band + rivets */
fill_rect(sx + 5, sy + 12, W - 10, H - 18, ok, og, ob);
fill_rect(sx + 7, sy + 14, W - 14, H - 22, br, bg, bb);
fill_rect(sx + 9, sy + 16, W - 18, 10, lr, lg, lb);
fill_rect(sx + 8, sy + 15, 2, 2, 90, 85, 70); /* rivet */
fill_rect(sx + W - 12, sy + 15, 2, 2, 90, 85, 70);
fill_rect(sx + 8, sy + H - 20, 2, 2, 90, 85, 70);
fill_rect(sx + W - 12, sy + H - 20, 2, 2, 90, 85, 70);
fill_rect(sx + W/2 - 1, sy + 15, 2, 2, 90, 85, 70); /* mid rivet */
line(sx + 7, sy + 26, sx + W - 8, sy + 26, 70, 65, 55); /* hull seam */
fill_rect(sx + 7, sy + 27, W - 14, 1, 50, 48, 42); /* seam shadow */
/* glowing lab windows (2 rows + mullions + glow + frames) */
fill_rect(sx + 10, sy + H/2 - 6, 8, 5, 180, 230, 255);
fill_rect(sx + 22, sy + H/2 - 6, 8, 5, 160, 220, 250);
fill_rect(sx + 34, sy + H/2 - 6, 8, 5, 180, 230, 255);
fill_rect(sx + 10, sy + H/2 + 2, 8, 5, 150, 210, 245);
fill_rect(sx + 22, sy + H/2 + 2, 8, 5, 170, 225, 255);
fill_rect(sx + 34, sy + H/2 + 2, 8, 5, 150, 210, 245);
fill_rect(sx + 12, sy + H/2 - 4, 4, 2, 240, 250, 255);
fill_rect(sx + 24, sy + H/2 - 4, 4, 2, 230, 245, 255);
fill_rect(sx + 36, sy + H/2 - 4, 4, 2, 240, 250, 255);
fill_rect(sx + 12, sy + H/2 + 4, 4, 1, 220, 240, 255); /* lower glow */
fill_rect(sx + 24, sy + H/2 + 4, 4, 1, 210, 235, 255);
fill_rect(sx + 36, sy + H/2 + 4, 4, 1, 220, 240, 255);
line(sx + 14, sy + H/2 - 6, sx + 14, sy + H/2 - 1, 90, 140, 180);
line(sx + 26, sy + H/2 - 6, sx + 26, sy + H/2 - 1, 90, 140, 180);
line(sx + 38, sy + H/2 - 6, sx + 38, sy + H/2 - 1, 90, 140, 180);
/* entry door + hazard */
fill_rect(sx + W/2 - 5, sy + H - 22, 10, 12, ok, og, ob);
fill_rect(sx + W/2 - 4, sy + H - 21, 8, 10, 40, 38, 45);
fill_rect(sx + W/2 - 4, sy + H - 21, 8, 2, 200, 160, 40);
fill_rect(sx + W/2 - 1, sy + H - 16, 2, 3, 180, 170, 90); /* handle */
fill_rect(sx + W/2 - 3, sy + H - 12, 6, 1, 200, 160, 40); /* door stripe */
/* primary dish on tall mast + braces + mount */
fill_rect(sx + W/2 + 2, sy + 18, 9, 3, ok, og, ob); /* mast base */
fill_rect(sx + W/2 + 4, sy + 2, 5, 18, ok, og, ob);
fill_rect(sx + W/2 + 5, sy + 3, 3, 16, 110, 100, 75);
line(sx + W/2 + 2, sy + 14, sx + W/2 + 5, sy + 8, 80, 75, 55);
line(sx + W/2 + 10, sy + 14, sx + W/2 + 7, sy + 8, 80, 75, 55);
disc(sx + W/2 + 6, sy + 12, 13, ok, og, ob);
disc(sx + W/2 + 6, sy + 12, 11, 215, 185, 85);
ring(sx + W/2 + 6, sy + 12, 8, 2, 170, 140, 55);
disc(sx + W/2 + 6, sy + 12, 4, 255, 245, 170);
disc(sx + W/2 + 6, sy + 12, 2, 255, 255, 220); /* feed */
line(sx + W/2 + 6, sy + 2, sx + W/2 + 6, sy + 12, 200, 190, 120);
/* secondary small dish + cable */
fill_rect(sx + 8, sy + 4, 3, 10, ok, og, ob);
fill_rect(sx + 9, sy + 5, 1, 8, 100, 95, 70);
disc(sx + 9, sy + 8, 5, ok, og, ob);
disc(sx + 9, sy + 8, 3, 190, 160, 70);
disc(sx + 9, sy + 8, 1, 255, 240, 160);
line(sx + 12, sy + 10, sx + W/2 + 4, sy + 14, 90, 85, 60); /* coax */
/* roof vents / AC + floodlight + grille */
fill_rect(sx + 16, sy + 8, 6, 4, ok, og, ob);
fill_rect(sx + 17, sy + 9, 4, 2, 120, 115, 95);
fill_rect(sx + 24, sy + 7, 5, 3, 70, 68, 60);
fill_rect(sx + 25, sy + 6, 3, 2, 90, 88, 78);
fill_rect(sx + 18, sy + 10, 2, 1, 40, 38, 35); /* vent slot */
fill_rect(sx + W - 14, sy + 4, 2, 2, 220, 200, 80); /* floodlight */
fill_rect(sx + W - 13, sy + 3, 1, 3, 90, 85, 70);
/* side antenna array */
fill_rect(sx + 10, sy + 6, 3, 10, 200, 200, 140);
fill_rect(sx + 18, sy + 4, 2, 12, 220, 220, 150);
fill_rect(sx + 14, sy + 5, 2, 8, 180, 180, 130);
disc(sx + 11, sy + 5, 2, 255, 240, 120);
disc(sx + 19, sy + 3, 2, 255, 230, 100);
disc(sx + 15, sy + 4, 1, 255, 220, 90);
/* side annex / sample bay + shelf */
{
uint8_t dr, dg, db;
darken3(br, bg, bb, 0.5f, &dr, &dg, &db);
fill_rect(sx + W - 16, sy + H - 28, 12, 14, ok, og, ob);
fill_rect(sx + W - 15, sy + H - 27, 10, 12, dr, dg, db);
}
fill_rect(sx + W - 13, sy + H - 25, 6, 4, 140, 200, 255); /* annex window */
fill_rect(sx + W - 12, sy + H - 24, 2, 2, 230, 245, 255);
fill_rect(sx + W - 14, sy + H - 18, 8, 2, 200, 160, 40); /* hazard */
fill_rect(sx + W - 14, sy + H - 20, 8, 1, 90, 85, 70); /* shelf */
/* cooling pipes along hull + clamps */
line(sx + 6, sy + 20, sx + 6, sy + H - 14, 100, 140, 180);
line(sx + W - 8, sy + 20, sx + W - 8, sy + H - 14, 100, 140, 180);
fill_rect(sx + 5, sy + H/2, 3, 3, 80, 110, 140); /* valve L */
fill_rect(sx + W - 9, sy + H/2 + 4, 3, 3, 80, 110, 140);
fill_rect(sx + 5, sy + H/2 + 8, 3, 2, 70, 100, 130); /* clamp L */
fill_rect(sx + W - 9, sy + H/2 - 2, 3, 2, 70, 100, 130); /* clamp R */
/* instrument bank / console strip denser */
{
uint8_t dr, dg, db;
darken3(br, bg, bb, 0.5f, &dr, &dg, &db);
fill_rect(sx + 10, sy + H - 16, W - 30, 7, dr, dg, db);
}
fill_rect(sx + 12, sy + H - 14, 3, 3, 80, 220, 120);
fill_rect(sx + 17, sy + H - 14, 3, 3, 80, 160, 255);
fill_rect(sx + 22, sy + H - 14, 3, 3, 255, 180, 80);
fill_rect(sx + 27, sy + H - 14, 3, 3, 200, 100, 255);
fill_rect(sx + 32, sy + H - 14, 3, 3, 255, 80, 120);
fill_rect(sx + 37, sy + H - 14, 2, 2, 100, 255, 200); /* extra LED */
fill_rect(sx + 12, sy + H - 10, 22, 1, 60, 55, 50); /* LED rail */
break;
default:
break;
}
/* construction scaffold: yard posts/rails/braces (not flat white wash) */
if (b->build < 1.0f) {
int mid = sy + (int)(H * b->build);
if (mid > sy + 2)
fill_rect(sx + 2, sy + 2, W - 4, mid - sy - 2, 235, 232, 220);
line(sx, sy, sx, sy + H, 175, 145, 85);
line(sx + W - 1, sy, sx + W - 1, sy + H, 175, 145, 85);
line(sx, sy, sx + W, sy, 205, 175, 105);
line(sx, mid, sx + W, mid, 215, 185, 115);
line(sx, sy + H - 1, sx + W, sy + H - 1, 155, 125, 70);
line(sx + 2, sy + 2, sx + W - 2, sy + H - 2, 150, 120, 65);
line(sx + W - 2, sy + 2, sx + 2, sy + H - 2, 150, 120, 65);
if (W >= 48)
line(sx + W / 2, sy, sx + W / 2, sy + H, 165, 135, 75);
/* progress tick above pad */
fill_rect(sx, sy - 7, W, 4, 55, 50, 40);
fill_rect(sx, sy - 7, (int)(W * b->build + 0.5f), 4, 120, 220, 120);
}
/* battle damage: scorch + cracks + rubble + ember (reads in KKND_SHOT) */
if (b->max_hp > 0 && b->hp < b->max_hp * 0.95f) {
float dmg = 1.0f - (b->hp / b->max_hp);
int pw = W / 4; if (pw < 3) pw = 3;
int ph = H / 5; if (ph < 2) ph = 2;
fill_rect(sx + W / 4, sy + H / 3, pw, ph, 45, 35, 25);
disc(sx + W / 3, sy + H / 2, pw / 2 + 1, 38, 30, 22);
if (dmg > 0.25f) {
fill_rect(sx + W / 2, sy + H / 2, pw, ph, 55, 40, 28);
disc(sx + (W * 2) / 3, sy + H / 3, pw / 3 + 1, 48, 36, 24);
}
if (dmg > 0.45f) {
fill_rect(sx + W / 6, sy + (H * 2) / 3, pw - 1, ph - 1, 50, 38, 26);
disc(sx + W / 5, sy + H / 4, 2, 60, 42, 28);
}
line(sx + 3, sy + H / 4, sx + W / 2, sy + (H * 2) / 3, 28, 24, 18);
line(sx + W / 2, sy + (H * 2) / 3, sx + W - 3, sy + H / 3, 28, 24, 18);
line(sx + W / 5, sy + 4, sx + (W * 3) / 5, sy + H / 2, 32, 26, 20);
if (dmg > 0.5f) {
line(sx + W / 3, sy + 2, sx + W / 3 + 3, sy + H - 2, 22, 18, 14);
line(sx + (W * 2) / 3, sy + H / 5, sx + W / 4, sy + H - 3, 24, 20, 16);
fill_rect(sx + 2, sy + H - 4, 5, 3, 95, 78, 55);
fill_rect(sx + W - 9, sy + H - 5, 7, 4, 85, 70, 50);
fill_rect(sx + W / 2 - 2, sy + H - 3, 6, 3, 90, 72, 48);
}
if (dmg > 0.65f) {
/* ember glow in heavy breaches */
disc(sx + W / 2, sy + H / 2, 2, 220, 110, 40);
disc(sx + W / 3 + 2, sy + (H * 2) / 3, 1, 255, 160, 60);
fill_rect(sx + 4, sy + 3, 3, 2, 40, 32, 24);
fill_rect(sx + W - 8, sy + 4, 4, 3, 35, 28, 20);
}
/* thin HP bar so damage reads in KKND_SHOT */
{
int by = sy - 6;
float f = b->hp / b->max_hp;
fill_rect(sx - 1, by - 1, W + 2, 5, 20, 18, 14);
fill_rect(sx, by, W, 3, 55, 48, 36);
fill_rect(sx, by, (int)(W * f + 0.5f), 3, br, bg, 40);
}
}
}
void snapshot_capture(Game *g, const char *path) {
const int zoom = SHOT_ZOOM;
g_sw = SW / zoom;
g_sh = SH / zoom;
if (g_sw < 1) g_sw = 1;
if (g_sh < 1) g_sh = 1;
uint8_t *small = (uint8_t *)malloc((size_t)g_sw * g_sh * 4);
if (!small) return;
g_buf = small;
for (int i = 0; i < g_sw * g_sh; i++) {
small[i*4]=0; small[i*4+1]=0; small[i*4+2]=0; small[i*4+3]=255;
}
/* Lock camera on the expected raid locus (hx+88, hy-48). Mild bias toward
* living enemies still inside the clash radius — never mid-map garrison. */
float hx = 0, hy = 0;
{
int sx = g->map.spawn_x[g->human], sy = g->map.spawn_y[g->human];
map_tile_to_world(sx, sy, &hx, &hy);
}
float clash_x = hx + 88.0f, clash_y = hy - 48.0f;
const float clash_r2 = 220.0f * 220.0f;
float ex = 0, ey = 0;
int ne = 0;
for (int i = 0; i < g->unit_count; i++) {
Unit *u = &g->units[i];
if (u->dead || u->faction == g->human) continue;
float dx = u->x - clash_x, dy = u->y - clash_y;
if (dx * dx + dy * dy > clash_r2) continue;
ex += u->x; ey += u->y; ne++;
}
float ecx = ne ? ex / ne : clash_x;
float ecy = ne ? ey / ne : clash_y;
/* Clash + forward fight; stronger hx so Survivor forward HQ stays in crop. */
float cx = clash_x * 0.40f + ecx * 0.10f + hx * 0.50f;
float cy = clash_y * 0.40f + ecy * 0.10f + hy * 0.50f;
float camx = cx - g_sw / 2.0f, camy = cy - g_sh / 2.0f;
if (camx < 0) camx = 0; if (camy < 0) camy = 0;
if (camx > MAP_PIX_W - g_sw) camx = (float)(MAP_PIX_W - g_sw);
if (camy > MAP_PIX_H - g_sh) camy = (float)(MAP_PIX_H - g_sh);
if (camx < 0) camx = 0; if (camy < 0) camy = 0;
int in_u = 0, in_b = 0, alive_e = 0, alive_h = 0;
for (int i = 0; i < g->unit_count; i++) {
Unit *u = &g->units[i];
if (u->dead) continue;
if (u->faction == g->human) alive_h++; else alive_e++;
int sx = (int)(u->x - camx), sy = (int)(u->y - camy);
if (sx >= -16 && sy >= -16 && sx < g_sw + 16 && sy < g_sh + 16) in_u++;
}
for (int i = 0; i < g->bld_count; i++) {
Building *b = &g->buildings[i];
if (b->dead) continue;
int sx = (int)(b->tx * TILE - camx), sy = (int)(b->ty * TILE - camy);
int w = b->w * TILE, h = b->h * TILE;
if (sx + w >= 0 && sy + h >= 0 && sx < g_sw && sy < g_sh) in_b++;
}
engine_log("shot cam=(%.0f,%.0f) crop=%dx%d clash_enemies=%d "
"in_frame units=%d blds=%d alive H/E=%d/%d",
camx, camy, g_sw, g_sh, ne, in_u, in_b, alive_h, alive_e);
/* terrain */
int tx0 = (int)camx / TILE, ty0 = (int)camy / TILE;
int tx1 = (int)(camx + g_sw) / TILE, ty1 = (int)(camy + g_sh) / TILE;
for (int ty = ty0; ty <= ty1; ty++) {
for (int tx = tx0; tx <= tx1; tx++) {
if (tx < 0 || ty < 0 || tx >= MAP_W || ty >= MAP_H) continue;
Tile *t = map_tile(&g->map, tx, ty);
uint8_t r,gv,b; terrain_color(t->terrain, &r,&gv,&b);
/* Mild checker — keep yellow bias (nudge R/G, never push B). */
if (((tx + ty) & 1) == 0) {
if (r < 250) r += 8; if (gv < 240) gv += 4;
} else {
if (r > 10) r -= 4; if (gv > 10) gv -= 3; if (b > 6) b -= 2;
}
int sx = tx*TILE - (int)camx, sy = ty*TILE - (int)camy;
rectf(sx, sy, sx+TILE-1, sy+TILE-1, r, gv, b);
/* Crack / grit lines so desert reads as KKND2 dirt, not flat fill. */
if (t->terrain == T_GRASS || t->terrain == T_SCRAP) {
line(sx + 4, sy + 10, sx + 14, sy + 18, (uint8_t)(r - 28), (uint8_t)(gv - 24), (uint8_t)(b - 12));
line(sx + 18, sy + 6, sx + 28, sy + 14, (uint8_t)(r - 22), (uint8_t)(gv - 18), (uint8_t)(b - 10));
disc(sx + 9, sy + 22, 1, (uint8_t)(r - 35), (uint8_t)(gv - 30), (uint8_t)(b - 15));
disc(sx + 24, sy + 26, 1, (uint8_t)(r - 30), (uint8_t)(gv - 26), (uint8_t)(b - 12));
}
if (t->terrain == T_RUBBLE) {
disc(sx + 8, sy + 10, 3, 120, 95, 60);
disc(sx + 22, sy + 18, 3, 138, 110, 70);
disc(sx + 14, sy + 24, 2, 105, 85, 55);
disc(sx + 26, sy + 8, 2, 155, 125, 80);
}
if (t->terrain == T_ROAD) {
fill_rect(sx + 3, sy + TILE/2 - 3, TILE - 6, 6, 195, 165, 105);
line(sx + 2, sy + TILE/2, sx + TILE - 3, sy + TILE/2, 225, 195, 130);
line(sx + TILE/2, sy + 2, sx + TILE/2, sy + TILE - 3, 165, 140, 95);
}
if (t->terrain == T_SCRAP && t->scrap > 0) {
/* stacked scrap mounds (KKND2 resource piles) */
int cc = sx + TILE / 2, cr = sy + TILE / 2 + 2;
int n = 3 + t->scrap / 28;
if (n > 9) n = 9;
for (int k = 0; k < n; k++) {
int ox = ((k * 7) % 14) - 7;
int oy = ((k * 5) % 10) - 5 - (k / 3) * 2;
int rad = 4 + (k & 1) + (t->scrap > 120 ? 1 : 0);
disc(cc + ox, cr + oy, rad, 235, 190, 85);
disc(cc + ox - 1, cr + oy - rad / 2, rad - 1, 210, 165, 70);
disc(cc + ox + 1, cr + oy - rad + 1, (rad > 3) ? rad - 2 : 2,
255, 215, 120);
}
}
}
}
/* Showcase shot: force-clear fog in the camera crop so buildings/units
* read like a playable KKND2 battle view (not a black fog plate). */
for (int ty = ty0; ty <= ty1; ty++)
for (int tx = tx0; tx <= tx1; tx++) {
if (tx < 0 || ty < 0 || tx >= MAP_W || ty >= MAP_H) continue;
g->map.fog[ty * MAP_W + tx] = 2;
}
/* Soft edge only outside live vision (kept for minimap consistency). */
for (int ty = ty0; ty <= ty1; ty++)
for (int tx = tx0; tx <= tx1; tx++) {
if (tx < 0 || ty < 0 || tx >= MAP_W || ty >= MAP_H) continue;
int f = g->map.fog[ty*MAP_W + tx];
if (f >= 2) continue;
float keep = (f == 1) ? 0.70f : 0.45f;
int sx = tx*TILE - (int)camx, sy = ty*TILE - (int)camy;
for (int yy = sy; yy < sy + TILE; yy++)
for (int xx = sx; xx < sx + TILE; xx++)
darken_px(xx, yy, keep);
}
/* buildings */
for (int i = 0; i < g->bld_count; i++) {
Building *b = &g->buildings[i];
if (b->dead) continue;
int sx = (int)(b->tx*TILE - camx), sy = (int)(b->ty*TILE - camy);
int w = b->w*TILE, h = b->h*TILE;
if (sx+w < 0 || sy+h < 0 || sx > g_sw || sy > g_sh) continue;
draw_bld(b, sx, sy);
}
/* wrecks under living units (battlefield litter) */
for (int i = 0; i < g->wreck_count; i++) {
Wreck *w = &g->wrecks[i];
int sx = (int)(w->x - camx), sy = (int)(w->y - camy);
if (sx < -24 || sy < -24 || sx > g_sw + 24 || sy > g_sh + 24) continue;
draw_wreck(w, sx, sy);
}
/* units */
for (int i = 0; i < g->unit_count; i++) {
Unit *u = &g->units[i];
if (u->dead) continue;
int sx = (int)(u->x - camx), sy = (int)(u->y - camy);
if (sx < -20 || sy < -20 || sx > g_sw+20 || sy > g_sh+20) continue;
draw_unit(u, sx, sy);
}
/* projectiles — lofted shell/missile sample parabola; else linear streak */
for (int i = 0; i < g->bullet_count; i++) {
Bullet *p = &g->bullets[i];
int sx = (int)(p->x - camx), sy = (int)(p->y - camy);
int lofted = (p->kind == 1 || p->kind == 3) && p->loft > 0.5f;
if (lofted) {
int len = (p->kind == 3) ? 28 : 22;
float step = 0.018f;
for (int t = 1; t <= len; t++) {
float tp = p->prog - step * (float)t;
if (tp < 0.0f) break;
float wx = p->ox + (p->tx - p->ox) * tp;
float wy = p->oy + (p->ty - p->oy) * tp
- p->loft * 4.0f * tp * (1.0f - tp);
int tx = (int)(wx - camx);
int ty = (int)(wy - camy);
if (p->kind == 3)
disc(tx, ty, (t < 4) ? 2 : 1, 255, (uint8_t)(120 - t * 3), 30);
else
disc(tx, ty, 1, 255, (uint8_t)(200 - t * 7), 50);
}
} else {
float dx = p->tx - p->x, dy = p->ty - p->y;
float d = sqrtf(dx * dx + dy * dy);
if (d > 1.0f) {
float ux = dx / d, uy = dy / d;
int len = (p->kind == 2) ? 32 : 14;
for (int t = 1; t <= len; t++) {
int tx = sx - (int)(ux * (float)t * 0.75f);
int ty = sy - (int)(uy * (float)t * 0.75f);
uint8_t fade = (uint8_t)(255 - t * 9);
if (p->kind == 2) {
disc(tx, ty, (t < 3) ? 2 : 1, fade, 80, 255);
px(tx, ty, 220, 180, 255);
} else {
disc(tx, ty, 1, 255, (uint8_t)(200 - t * 8), 50);
}
}
}
}
int rad = (p->kind == 1 || p->kind == 3) ? 8 : 5;
disc(sx, sy, rad + 1, 255, 160, 40);
disc(sx, sy, rad, 255, 240, 140);
px(sx + 1, sy, 255, 255, 220);
if (p->kind == 3) {
disc(sx - 3, sy + 1, 4, 255, 70, 30);
disc(sx + 2, sy - 2, 3, 255, 200, 80);
} else if (p->kind == 2) {
disc(sx, sy, rad + 1, 120, 200, 255);
disc(sx, sy, rad - 1, 240, 220, 255);
}
}
/* particles (smoke / fire / sparks / debris) — larger combat FX */
for (int i = 0; i < g->particle_count; i++) {
Particle *p = &g->particles[i];
if (p->life <= 0) continue;
int sx = (int)(p->x - camx), sy = (int)(p->y - camy);
if (sx < -12 || sy < -12 || sx > g_sw + 12 || sy > g_sh + 12) continue;
float a = p->life / (p->max_life > 0 ? p->max_life : 1.0f);
if (a < 0.10f) continue;
/* larger bloom discs — fire/smoke read as KKND2 explosion cores */
int sz = (p->kind == 1) ? 14 : (p->kind == 3) ? 7 : (p->kind == 0) ? 11 : 5;
uint8_t r = (uint8_t)(p->r * a), gv = (uint8_t)(p->g * a), b = (uint8_t)(p->b * a);
if (p->kind == 0 || p->kind == 1) {
disc(sx, sy, sz + 3, r / 2, gv / 2, b / 2);
disc(sx, sy, sz + 1, r, gv, b);
if (p->kind == 1 && a > 0.40f)
disc(sx, sy - 2, sz / 2 + 2, 255, (uint8_t)(230 * a), 90);
if (p->kind == 1 && a > 0.65f)
disc(sx + 1, sy - 3, sz / 3 + 1, 255, 255, 180);
} else {
fill_rect(sx - sz/2, sy - sz/2, sz + 1, sz + 1, r, gv, b);
}
}
/* KKND2-like chrome: top resource bar + corner minimap + bottom command strip.
* Procedural bars (no font) so the shot still reads as classic RTS UI. */
{
Faction hf = g->human;
uint8_t hr = faction_r(hf), hg = faction_g(hf), hb = faction_b(hf);
Faction ef = enemy_of(hf);
uint8_t er = faction_r(ef), eg = faction_g(ef), eb = faction_b(ef);
int scrap_h = g->scrap[hf];
if (scrap_h < 0) scrap_h = 0;
int scrap_cap = 8000;
if (scrap_h > scrap_cap) scrap_cap = scrap_h + 500;
float scrap_frac = (float)scrap_h / (float)scrap_cap;
if (scrap_frac > 1.0f) scrap_frac = 1.0f;
int pwr_gen = 0, pwr_use = 0;
for (int i = 0; i < g->bld_count; i++) {
Building *b = &g->buildings[i];
if (b->dead || b->faction != hf || b->build < 1.0f) continue;
if (b->type == BT_POWER) pwr_gen += 100;
else if (b->type == BT_BASE) pwr_use += 20;
else if (b->type == BT_WARFACTORY || b->type == BT_REFINERY) pwr_use += 40;
else if (b->type == BT_RESEARCH || b->type == BT_REPAIR) pwr_use += 30;
else if (b->type == BT_TURRET) pwr_use += 15;
else pwr_use += 5;
}
if (pwr_gen < 1) pwr_gen = 1;
float pwr_frac = (float)pwr_gen / (float)(pwr_gen + pwr_use);
if (pwr_frac > 1.0f) pwr_frac = 1.0f;
int pwr_ok = (pwr_gen >= pwr_use);
int n_sel_u = 0, n_sel_b = 0;
for (int i = 0; i < g->sel_count; i++) {
if (unit_by_id(g, g->selection[i])) n_sel_u++;
else if (bld_by_id(g, g->selection[i])) n_sel_b++;
}
/* —— top bar —— */
int bar_h = 22;
fill_rect(0, 0, g_sw, bar_h, 22, 26, 32);
fill_rect(0, 0, g_sw, 2, 55, 62, 70);
fill_rect(0, bar_h - 1, g_sw, 1, 90, 110, 70);
fill_rect(0, bar_h, g_sw, 1, 40, 48, 40);
/* faction badge */
fill_rect(3, 3, 16, 16, 12, 14, 16);
fill_rect(5, 5, 12, 12, hr, hg, hb);
fill_rect(7, 7, 8, 8, (uint8_t)(hr / 2 + 40), (uint8_t)(hg / 2 + 40),
(uint8_t)(hb / 2 + 40));
/* scrap icon (gold nugget) + segmented bar */
fill_rect(24, 5, 8, 12, 40, 34, 18);
fill_rect(26, 7, 4, 8, 220, 190, 70);
disc(28, 11, 3, 255, 220, 90);
int scrap_x = 36, scrap_w = (int)(g_sw * 0.28f);
if (scrap_w < 60) scrap_w = 60;
fill_rect(scrap_x, 6, scrap_w, 10, 28, 26, 18);
fill_rect(scrap_x, 6, (int)(scrap_w * scrap_frac), 10, 230, 200, 70);
/* segment ticks */
for (int s = 1; s < 8; s++) {
int sx = scrap_x + (scrap_w * s) / 8;
line(sx, 6, sx, 15, 18, 16, 12);
}
fill_rect(scrap_x, 6, scrap_w, 1, 255, 240, 160);
fill_rect(scrap_x, 15, scrap_w, 1, 80, 60, 20);
/* power bolt + bar (green surplus / red deficit) */
int pwr_x = scrap_x + scrap_w + 10;
fill_rect(pwr_x, 5, 3, 5, 240, 240, 120);
fill_rect(pwr_x - 1, 9, 5, 3, 240, 240, 120);
fill_rect(pwr_x + 1, 11, 3, 5, 240, 240, 120);
int pwr_bx = pwr_x + 10, pwr_bw = (int)(g_sw * 0.16f);
if (pwr_bw < 40) pwr_bw = 40;
fill_rect(pwr_bx, 6, pwr_bw, 10, 24, 28, 24);
uint8_t pr = pwr_ok ? 70 : 220, pg = pwr_ok ? 210 : 70, pb = pwr_ok ? 90 : 60;
fill_rect(pwr_bx, 6, (int)(pwr_bw * pwr_frac), 10, pr, pg, pb);
for (int s = 1; s < 5; s++) {
int sx = pwr_bx + (pwr_bw * s) / 5;
line(sx, 6, sx, 15, 14, 16, 14);
}
/* unit / building count chips */
int chip_x = pwr_bx + pwr_bw + 12;
fill_rect(chip_x, 4, 34, 14, 30, 36, 30);
fill_rect(chip_x + 2, 6, 6, 10, hr, hg, hb); /* unit glyph */
disc(chip_x + 5, 8, 2, 255, 255, 255);
int ufill = alive_h;
if (ufill > 20) ufill = 20;
fill_rect(chip_x + 12, 8, ufill, 6, hr, hg, hb);
fill_rect(chip_x + 38, 4, 34, 14, 30, 36, 30);
fill_rect(chip_x + 40, 7, 8, 8, hr, hg, hb); /* bld glyph */
int bfill = 0;
for (int i = 0; i < g->bld_count; i++)
if (!g->buildings[i].dead && g->buildings[i].faction == hf) bfill++;
if (bfill > 20) bfill = 20;
fill_rect(chip_x + 52, 8, bfill, 6, 180, 160, 70);
/* enemy threat pip */
fill_rect(g_sw - 48, 4, 42, 14, 36, 22, 28);
fill_rect(g_sw - 44, 6, 8, 10, er, eg, eb);
int eth = alive_e;
if (eth > 28) eth = 28;
fill_rect(g_sw - 34, 8, eth, 6, er, eg, eb);
disc(g_sw - 10, 11, 4, 255, 90, 40);
disc(g_sw - 10, 11, 2, 255, 220, 120);
/* —— corner minimap (compact, fight-plate friendly) —— */
int mw = 72, mh = 72;
int mx = g_sw - mw - 6, my = bar_h + 6;
fill_rect(mx - 2, my - 2, mw + 4, mh + 4, 8, 10, 12);
fill_rect(mx - 1, my - 1, mw + 2, mh + 2, 70, 90, 70);
fill_rect(mx, my, mw, mh, 28, 32, 28);
/* sample terrain around camera */
float map_sx = (float)mw / (float)g_sw;
float map_sy = (float)mh / (float)g_sh;
for (int py = 0; py < mh; py += 2) {
for (int px_ = 0; px_ < mw; px_ += 2) {
float wx = camx + (float)px_ / map_sx;
float wy = camy + (float)py / map_sy;
int tx = (int)(wx / TILE), ty = (int)(wy / TILE);
if (!map_in_bounds(&g->map, tx, ty)) continue;
Tile *t = map_tile(&g->map, tx, ty);
uint8_t cr = 120, cg = 104, cb = 78;
switch (t->terrain) {
case T_WATER: cr = 44; cg = 78; cb = 120; break;
case T_ROCK: cr = 110; cg = 104; cb = 98; break;
case T_SCRAP: cr = 200; cg = 170; cb = 70; break;
case T_GRASS: cr = 210; cg = 170; cb = 78; break;
case T_RUBBLE: cr = 90; cg = 78; cb = 60; break;
default: break;
}
fill_rect(mx + px_, my + py, 2, 2, cr, cg, cb);
}
}
for (int i = 0; i < g->bld_count; i++) {
Building *b = &g->buildings[i];
if (b->dead) continue;
int bx = mx + (int)((b->tx * TILE - camx) * map_sx);
int by = my + (int)((b->ty * TILE - camy) * map_sy);
if (bx < mx || by < my || bx >= mx + mw || by >= my + mh) continue;
uint8_t br = faction_r(b->faction), bg = faction_g(b->faction),
bb = faction_b(b->faction);
fill_rect(bx, by, 3, 3, br, bg, bb);
}
for (int i = 0; i < g->unit_count; i++) {
Unit *u = &g->units[i];
if (u->dead) continue;
int ux = mx + (int)((u->x - camx) * map_sx);
int uy = my + (int)((u->y - camy) * map_sy);
if (ux < mx || uy < my || ux >= mx + mw || uy >= my + mh) continue;
px(ux, uy, faction_r(u->faction), faction_g(u->faction),
faction_b(u->faction));
}
/* viewport outline = full minimap (crop view) */
line(mx, my, mx + mw - 1, my, 255, 255, 255);
line(mx, my + mh - 1, mx + mw - 1, my + mh - 1, 255, 255, 255);
line(mx, my, mx, my + mh - 1, 255, 255, 255);
line(mx + mw - 1, my, mx + mw - 1, my + mh - 1, 255, 255, 255);
/* —— bottom command / selection strip —— */
int bot_h = 28;
int bot_y = g_sh - bot_h;
fill_rect(0, bot_y, g_sw, bot_h, 18, 20, 24);
fill_rect(0, bot_y, g_sw, 1, 70, 90, 70);
fill_rect(0, bot_y + 1, g_sw, 1, 40, 48, 40);
/* portrait panel */
fill_rect(4, bot_y + 3, 22, 22, 12, 14, 16);
fill_rect(6, bot_y + 5, 18, 18, hr, hg, hb);
disc(15, bot_y + 12, 4, 255, 255, 255);
fill_rect(13, bot_y + 16, 4, 5, 40, 40, 40);
/* selection count bars */
int slots = (n_sel_u > 0 || n_sel_b > 0) ? (n_sel_u + n_sel_b) : 6;
if (slots > 10) slots = 10;
for (int s = 0; s < slots; s++) {
int sx = 32 + s * 22;
fill_rect(sx, bot_y + 4, 18, 20, 28, 32, 28);
fill_rect(sx + 1, bot_y + 5, 16, 18, 40, 48, 40);
if (s < n_sel_u || (n_sel_u == 0 && n_sel_b == 0 && s < 4)) {
fill_rect(sx + 4, bot_y + 8, 10, 12, hr, hg, hb);
disc(sx + 9, bot_y + 11, 2, 255, 255, 255);
} else if (s < n_sel_u + n_sel_b) {
fill_rect(sx + 3, bot_y + 8, 12, 12, hr, hg, hb);
}
}
/* ability / build button stubs (right) */
for (int s = 0; s < 4; s++) {
int sx = g_sw - 96 + s * 22;
fill_rect(sx, bot_y + 4, 18, 20, 32, 28, 22);
fill_rect(sx + 2, bot_y + 6, 14, 16, 70, 60, 40);
if (s == 0) fill_rect(sx + 6, bot_y + 10, 6, 8, 200, 180, 60); /* build */
if (s == 1) disc(sx + 9, bot_y + 14, 4, 255, 80, 40); /* attack */
if (s == 2) fill_rect(sx + 5, bot_y + 11, 8, 6, 80, 180, 255); /* move */
if (s == 3) ring(sx + 9, bot_y + 14, 5, 2, 180, 255, 120); /* stop */
}
}
/* Nearest-neighbour upscale → full VIEW (chunky KKND pixels) */
uint8_t *big = (uint8_t *)malloc((size_t)SW * SH * 4);
if (!big) { free(small); return; }
for (int y = 0; y < SH; y++) {
int sy = y / zoom;
if (sy >= g_sh) sy = g_sh - 1;
for (int x = 0; x < SW; x++) {
int sx = x / zoom;
if (sx >= g_sw) sx = g_sw - 1;
int di = (y * SW + x) * 4;
int si = (sy * g_sw + sx) * 4;
big[di]=small[si]; big[di+1]=small[si+1];
big[di+2]=small[si+2]; big[di+3]=255;
}
}
png_save(path, SW, SH, big);
free(big);
free(small);
g_buf = NULL;
g_sw = SW; g_sh = SH;
}