diff --git a/services/api/src/matches/daily/bug-company.mms b/services/api/src/matches/daily/bug-company.mms index a934cbd..8f7dc7a 100644 --- a/services/api/src/matches/daily/bug-company.mms +++ b/services/api/src/matches/daily/bug-company.mms @@ -2,23 +2,29 @@ # Princess (OPFOR). SinglePlayer=true, so desktop MegaMek sets it up # that way on its own. # +# The player takes the lone assault; Princess gets the swarm. Challenger is +# the API's player slot, so the Alpha Wolf is listed first, and it deploys +# south like the player's force in every other daily. +# # The twelve machines are the BattleTech: Bug 'Mech Company box, one for one: # two Stingers, two Wasps, two Spiders, two Fireflys, two Locusts, a Cicada -# and a Flea. Every one is a 3025-eligible variant at introductory rules - -# nothing primitive, nothing from a later refit - which is what a company of -# these actually looked like when they were the cheapest thing a house could -# field. +# and a Flea. Each is the variant people picture when the chassis is named - +# the LCT-1V, not the LRM-carrying 1M - because a box of plastic bugs is the +# stock line, not a parts bin. +# +# They are listed in pairs, lightest first, so the card draws like beside +# like rather than interleaving them. # -# The lone assault is matched to them on battle value, not on tonnage: 300 -# tons of bugs against 90, and an elite pilot is what closes a gap no stock -# assault 'Mech closes on its own. The Alpha Wolf carries a supercharger, so -# it can run at twice its walking speed and actually catch a Locust. +# Matched on battle value, not tonnage: 90 tons against 300. Gunnery 2 / +# piloting 1 puts the Alpha Wolf within ten points of the company. Its +# supercharger runs it at twice walking speed, so no Locust can simply kite +# it. # # MegaMek Data (C) 2025 by The MegaMek Team is licensed under CC BY-NC-SA 4.0. # https://creativecommons.org/licenses/by-nc-sa/4.0/ MMSVersion=1 Name=Bug Company -Description=Twelve light machines against one assault. The company has three times the tonnage spread across twelve targets, and the Alpha Wolf can only shoot at one of them at a time - but its supercharger means none of them can simply outrun it. +Description=One assault against twelve light machines. You have the tonnage and the guns; they have three times your targets and the legs to flank you. The supercharger is what stops any of them simply walking away. SinglePlayer=true BoardWidth=2 BoardHeight=1 @@ -26,16 +32,16 @@ Maps=AGoAC Maps/16x17 Grassland 2,AGoAC Maps/16x17 Grassland 2 Factions=Challenger,OPFOR Location_Challenger=S Location_OPFOR=N -Unit_Challenger_1=Locust LCT-1M,Challenger 1,4,5,N,3,16 -Unit_Challenger_2=Stinger STG-3R,Challenger 2,4,5,N,8,16 -Unit_Challenger_3=Wasp WSP-1L,Challenger 3,4,5,N,13,16 -Unit_Challenger_4=Wasp WSP-1L,Challenger 4,4,5,N,18,16 -Unit_Challenger_5=Stinger STG-3R,Challenger 5,4,5,N,23,16 -Unit_Challenger_6=Locust LCT-1M,Challenger 6,4,5,N,28,16 -Unit_Challenger_7=Flea FLE-15,Challenger 7,4,5,N,5,15 -Unit_Challenger_8=Spider SDR-5K,Challenger 8,4,5,N,10,15 -Unit_Challenger_9=Firefly FFL-4A,Challenger 9,4,5,N,15,15 -Unit_Challenger_10=Firefly FFL-4A,Challenger 10,4,5,N,20,15 -Unit_Challenger_11=Spider SDR-5K,Challenger 11,4,5,N,25,15 -Unit_Challenger_12=Cicada CDA-2C,Challenger 12,4,5,N,30,15 -Unit_OPFOR_1=Alpha Wolf A,OPFOR 1,2,3,S,16,2 +Unit_Challenger_1=Alpha Wolf A,Challenger 1,2,1,N,16,16 +Unit_OPFOR_1=Locust LCT-1V,OPFOR 1,4,5,S,3,2 +Unit_OPFOR_2=Locust LCT-1V,OPFOR 2,4,5,S,8,2 +Unit_OPFOR_3=Stinger STG-3R,OPFOR 3,4,5,S,13,2 +Unit_OPFOR_4=Stinger STG-3R,OPFOR 4,4,5,S,18,2 +Unit_OPFOR_5=Wasp WSP-1A,OPFOR 5,4,5,S,23,2 +Unit_OPFOR_6=Wasp WSP-1A,OPFOR 6,4,5,S,28,2 +Unit_OPFOR_7=Flea FLE-15,OPFOR 7,4,5,S,5,3 +Unit_OPFOR_8=Spider SDR-5V,OPFOR 8,4,5,S,10,3 +Unit_OPFOR_9=Spider SDR-5V,OPFOR 9,4,5,S,15,3 +Unit_OPFOR_10=Firefly FFL-4A,OPFOR 10,4,5,S,20,3 +Unit_OPFOR_11=Firefly FFL-4A,OPFOR 11,4,5,S,25,3 +Unit_OPFOR_12=Cicada CDA-2A,OPFOR 12,4,5,S,30,3 diff --git a/services/api/src/matches/daily/report.json b/services/api/src/matches/daily/report.json index a61f582..9d15282 100644 --- a/services/api/src/matches/daily/report.json +++ b/services/api/src/matches/daily/report.json @@ -76,7 +76,7 @@ { "file": "bug-company.mms", "name": "Bug Company", - "description": "Twelve light machines against one assault. The company has three times the tonnage spread across twelve targets, and the Alpha Wolf can only shoot at one of them at a time - but its supercharger means none of them can simply outrun it.", + "description": "One assault against twelve light machines. You have the tonnage and the guns; they have three times your targets and the legs to flank you. The supercharger is what stops any of them simply walking away.", "singlePlayer": true, "board": { "width": 32, @@ -87,24 +87,27 @@ "name": "Challenger", "team": 1, "start": "S", - "bv": 5884, - "tons": 300, - "cost": 24718467, + "bv": 6355, + "tons": 90, + "cost": 40261000, "units": [ { - "name": "Wasp WSP-1L", - "year": 2799, - "bv": 335, - "tons": 20, - "cost": 1656720 - }, - { - "name": "Wasp WSP-1L", - "year": 2799, - "bv": 335, - "tons": 20, - "cost": 1656720 - }, + "name": "Alpha Wolf A", + "year": 3150, + "bv": 6355, + "tons": 90, + "cost": 40261000 + } + ] + }, + { + "name": "OPFOR", + "team": 2, + "start": "N", + "bv": 6345, + "tons": 300, + "cost": 25150657, + "units": [ { "name": "Stinger STG-3R", "year": 2479, @@ -113,32 +116,39 @@ "cost": 1615440 }, { - "name": "Locust LCT-1M", - "year": 2571, - "bv": 424, + "name": "Firefly FFL-4A", + "year": 2801, + "bv": 831, + "tons": 30, + "cost": 2203500 + }, + { + "name": "Locust LCT-1V", + "year": 2499, + "bv": 432, "tons": 20, - "cost": 1571200 + "cost": 1512400 }, { - "name": "Cicada CDA-2C", + "name": "Cicada CDA-2A", "year": 2740, - "bv": 550, + "bv": 659, "tons": 40, - "cost": 3647467 + "cost": 3705217 }, { - "name": "Spider SDR-5K", - "year": 2850, - "bv": 503, - "tons": 30, - "cost": 2728440 + "name": "Wasp WSP-1A", + "year": 2471, + "bv": 384, + "tons": 20, + "cost": 1646640 }, { - "name": "Firefly FFL-4A", - "year": 2801, - "bv": 831, - "tons": 30, - "cost": 2203500 + "name": "Stinger STG-3R", + "year": 2479, + "bv": 359, + "tons": 20, + "cost": 1615440 }, { "name": "Firefly FFL-4A", @@ -147,13 +157,6 @@ "tons": 30, "cost": 2203500 }, - { - "name": "Spider SDR-5K", - "year": 2850, - "bv": 503, - "tons": 30, - "cost": 2728440 - }, { "name": "Flea FLE-15", "year": 2523, @@ -162,35 +165,32 @@ "cost": 1520400 }, { - "name": "Locust LCT-1M", - "year": 2571, - "bv": 424, + "name": "Wasp WSP-1A", + "year": 2471, + "bv": 384, "tons": 20, - "cost": 1571200 + "cost": 1646640 }, { - "name": "Stinger STG-3R", - "year": 2479, - "bv": 359, - "tons": 20, - "cost": 1615440 - } - ] - }, - { - "name": "OPFOR", - "team": 2, - "start": "N", - "bv": 5771, - "tons": 90, - "cost": 40261000, - "units": [ + "name": "Spider SDR-5V", + "year": 2650, + "bv": 622, + "tons": 30, + "cost": 2984540 + }, { - "name": "Alpha Wolf A", - "year": 3150, - "bv": 5771, - "tons": 90, - "cost": 40261000 + "name": "Spider SDR-5V", + "year": 2650, + "bv": 622, + "tons": 30, + "cost": 2984540 + }, + { + "name": "Locust LCT-1V", + "year": 2499, + "bv": 432, + "tons": 20, + "cost": 1512400 } ] } diff --git a/web/public/daily/bug-company.mms b/web/public/daily/bug-company.mms index a934cbd..8f7dc7a 100644 --- a/web/public/daily/bug-company.mms +++ b/web/public/daily/bug-company.mms @@ -2,23 +2,29 @@ # Princess (OPFOR). SinglePlayer=true, so desktop MegaMek sets it up # that way on its own. # +# The player takes the lone assault; Princess gets the swarm. Challenger is +# the API's player slot, so the Alpha Wolf is listed first, and it deploys +# south like the player's force in every other daily. +# # The twelve machines are the BattleTech: Bug 'Mech Company box, one for one: # two Stingers, two Wasps, two Spiders, two Fireflys, two Locusts, a Cicada -# and a Flea. Every one is a 3025-eligible variant at introductory rules - -# nothing primitive, nothing from a later refit - which is what a company of -# these actually looked like when they were the cheapest thing a house could -# field. +# and a Flea. Each is the variant people picture when the chassis is named - +# the LCT-1V, not the LRM-carrying 1M - because a box of plastic bugs is the +# stock line, not a parts bin. +# +# They are listed in pairs, lightest first, so the card draws like beside +# like rather than interleaving them. # -# The lone assault is matched to them on battle value, not on tonnage: 300 -# tons of bugs against 90, and an elite pilot is what closes a gap no stock -# assault 'Mech closes on its own. The Alpha Wolf carries a supercharger, so -# it can run at twice its walking speed and actually catch a Locust. +# Matched on battle value, not tonnage: 90 tons against 300. Gunnery 2 / +# piloting 1 puts the Alpha Wolf within ten points of the company. Its +# supercharger runs it at twice walking speed, so no Locust can simply kite +# it. # # MegaMek Data (C) 2025 by The MegaMek Team is licensed under CC BY-NC-SA 4.0. # https://creativecommons.org/licenses/by-nc-sa/4.0/ MMSVersion=1 Name=Bug Company -Description=Twelve light machines against one assault. The company has three times the tonnage spread across twelve targets, and the Alpha Wolf can only shoot at one of them at a time - but its supercharger means none of them can simply outrun it. +Description=One assault against twelve light machines. You have the tonnage and the guns; they have three times your targets and the legs to flank you. The supercharger is what stops any of them simply walking away. SinglePlayer=true BoardWidth=2 BoardHeight=1 @@ -26,16 +32,16 @@ Maps=AGoAC Maps/16x17 Grassland 2,AGoAC Maps/16x17 Grassland 2 Factions=Challenger,OPFOR Location_Challenger=S Location_OPFOR=N -Unit_Challenger_1=Locust LCT-1M,Challenger 1,4,5,N,3,16 -Unit_Challenger_2=Stinger STG-3R,Challenger 2,4,5,N,8,16 -Unit_Challenger_3=Wasp WSP-1L,Challenger 3,4,5,N,13,16 -Unit_Challenger_4=Wasp WSP-1L,Challenger 4,4,5,N,18,16 -Unit_Challenger_5=Stinger STG-3R,Challenger 5,4,5,N,23,16 -Unit_Challenger_6=Locust LCT-1M,Challenger 6,4,5,N,28,16 -Unit_Challenger_7=Flea FLE-15,Challenger 7,4,5,N,5,15 -Unit_Challenger_8=Spider SDR-5K,Challenger 8,4,5,N,10,15 -Unit_Challenger_9=Firefly FFL-4A,Challenger 9,4,5,N,15,15 -Unit_Challenger_10=Firefly FFL-4A,Challenger 10,4,5,N,20,15 -Unit_Challenger_11=Spider SDR-5K,Challenger 11,4,5,N,25,15 -Unit_Challenger_12=Cicada CDA-2C,Challenger 12,4,5,N,30,15 -Unit_OPFOR_1=Alpha Wolf A,OPFOR 1,2,3,S,16,2 +Unit_Challenger_1=Alpha Wolf A,Challenger 1,2,1,N,16,16 +Unit_OPFOR_1=Locust LCT-1V,OPFOR 1,4,5,S,3,2 +Unit_OPFOR_2=Locust LCT-1V,OPFOR 2,4,5,S,8,2 +Unit_OPFOR_3=Stinger STG-3R,OPFOR 3,4,5,S,13,2 +Unit_OPFOR_4=Stinger STG-3R,OPFOR 4,4,5,S,18,2 +Unit_OPFOR_5=Wasp WSP-1A,OPFOR 5,4,5,S,23,2 +Unit_OPFOR_6=Wasp WSP-1A,OPFOR 6,4,5,S,28,2 +Unit_OPFOR_7=Flea FLE-15,OPFOR 7,4,5,S,5,3 +Unit_OPFOR_8=Spider SDR-5V,OPFOR 8,4,5,S,10,3 +Unit_OPFOR_9=Spider SDR-5V,OPFOR 9,4,5,S,15,3 +Unit_OPFOR_10=Firefly FFL-4A,OPFOR 10,4,5,S,20,3 +Unit_OPFOR_11=Firefly FFL-4A,OPFOR 11,4,5,S,25,3 +Unit_OPFOR_12=Cicada CDA-2A,OPFOR 12,4,5,S,30,3 diff --git a/web/src/content/daily.json b/web/src/content/daily.json index ac7bf1f..03c172e 100644 --- a/web/src/content/daily.json +++ b/web/src/content/daily.json @@ -38,37 +38,37 @@ "slug": "bug-company", "date": "2026-08-16", "name": "Bug Company", - "blurb": "Twelve light machines against one assault. The company has three times the tonnage spread across twelve targets, and the Alpha Wolf can only shoot at one of them at a time - but its supercharger means none of them can simply outrun it.", - "years": { "from": 2479, "to": 3150 }, + "blurb": "One assault against twelve light machines. You have the tonnage and the guns; they have three times your targets and the legs to flank you. The supercharger is what stops any of them simply walking away.", + "years": { "from": 2471, "to": 3150 }, "board": { "columns": 8, "rows": 3 }, "sides": [ { "name": "Challenger", - "bv": 5884, - "tons": 300, - "cost": 24718467, + "bv": 6355, + "tons": 90, + "cost": 40261000, "units": [ - { "name": "Locust LCT-1M", "row": 2, "facing": "N" }, - { "name": "Flea FLE-15", "row": 2, "facing": "N" }, - { "name": "Stinger STG-3R", "row": 2, "facing": "N" }, - { "name": "Spider SDR-5K", "row": 2, "facing": "N" }, - { "name": "Wasp WSP-1L", "row": 2, "facing": "N" }, - { "name": "Firefly FFL-4A", "row": 2, "facing": "N" }, - { "name": "Wasp WSP-1L", "row": 2, "facing": "N" }, - { "name": "Firefly FFL-4A", "row": 2, "facing": "N" }, - { "name": "Stinger STG-3R", "row": 2, "facing": "N" }, - { "name": "Spider SDR-5K", "row": 2, "facing": "N" }, - { "name": "Locust LCT-1M", "row": 2, "facing": "N" }, - { "name": "Cicada CDA-2C", "row": 2, "facing": "N" } + { "name": "Alpha Wolf A", "row": 2, "facing": "N" } ] }, { "name": "OPFOR", - "bv": 5771, - "tons": 90, - "cost": 40261000, + "bv": 6345, + "tons": 300, + "cost": 25150657, "units": [ - { "name": "Alpha Wolf A", "row": 0, "facing": "S" } + { "name": "Locust LCT-1V", "row": 0, "facing": "S" }, + { "name": "Flea FLE-15", "row": 0, "facing": "S" }, + { "name": "Locust LCT-1V", "row": 0, "facing": "S" }, + { "name": "Spider SDR-5V", "row": 0, "facing": "S" }, + { "name": "Stinger STG-3R", "row": 0, "facing": "S" }, + { "name": "Spider SDR-5V", "row": 0, "facing": "S" }, + { "name": "Stinger STG-3R", "row": 0, "facing": "S" }, + { "name": "Firefly FFL-4A", "row": 0, "facing": "S" }, + { "name": "Wasp WSP-1A", "row": 0, "facing": "S" }, + { "name": "Firefly FFL-4A", "row": 0, "facing": "S" }, + { "name": "Wasp WSP-1A", "row": 0, "facing": "S" }, + { "name": "Cicada CDA-2A", "row": 0, "facing": "S" } ] } ] diff --git a/web/src/content/eras.ts b/web/src/content/eras.ts index a871b09..2d05dad 100644 --- a/web/src/content/eras.ts +++ b/web/src/content/eras.ts @@ -68,3 +68,30 @@ export function eraFor(year: number): Era | null { if (!Number.isInteger(year) || year <= 0) return null; return ERAS.find((era) => era.end === null || year <= era.end) ?? null; } + +/** + * The years an era covers, as the card writes them: `2781-3019`, or `3131+` + * for the open-ended one. + * + * Measured across every consecutive row sharing the era's name, not just the + * row a year matched. Two rows are called Pre-Star League and two are called + * Republic Age, and quoting one row's dates under the shared name would give + * the Republic Age as 3081-3100 - half of it - which is worse than saying + * nothing. + * + * The first era has no start year to quote: nothing it could name is older + * than it, so it is written open at the bottom. + */ +export function eraSpan(era: Era): string { + const first = ERAS.findIndex((row) => row.name === era.name); + if (first < 0) return ""; + let last = first; + while (last + 1 < ERAS.length && ERAS[last + 1]!.name === era.name) last++; + + const before = first > 0 ? ERAS[first - 1]!.end : null; + const start = before === null ? null : before + 1; + const end = ERAS[last]!.end; + + if (start === null) return end === null ? "" : `to ${end}`; + return end === null ? `${start}+` : `${start}–${end}`; +} diff --git a/web/src/screens/daily.ts b/web/src/screens/daily.ts index 08e662b..eb99aad 100644 --- a/web/src/screens/daily.ts +++ b/web/src/screens/daily.ts @@ -38,7 +38,7 @@ import dailyJson from "../content/daily.json"; // sprite the site has, and importing it here would put the whole map in the // front page's eager bundle - which analytics.test.mjs fails the build over. import { chassisOf, modelOf } from "../scenario/designation"; -import { eraFor } from "../content/eras"; +import { eraFor, eraSpan } from "../content/eras"; import { ERA_ICONS } from "../content/trivia"; /** @@ -214,11 +214,23 @@ function rowOnField(row: number, rows: number): number { } /** - * How far a staggered unit steps off its rank. A third of a sprite: enough - * that the eye reads two ranks rather than a wobbly line, small enough that - * the stepped-back machines stay clear of the other force's band. + * How far a staggered unit steps off its rank. + * + * A whole sprite, not a fraction of one: at a third the two ranks overlapped + * and read as one wobbly line. A full step separates them into two legible + * rows, and the taller field has the room for it. + */ +const STAGGER = SPRITE_H; + +/** + * How much clear ground to leave at either end of a rank, as a fraction of + * the board's width. + * + * A rank spread edge to edge puts its outermost machines half off the + * picture and leaves their tags nowhere to go. An eighth of the board at each + * end is enough that the outermost tag has its own room. */ -const STAGGER = Math.round(SPRITE_H / 3); +const SIDE_MARGIN = 0.08; /** * The hex field, drawn rather than tiled. @@ -296,18 +308,24 @@ function drawHexField( export function spotsFor(challenge: Challenge, side: Side): Spot[] { const width = challenge.board.columns * SPRITE_W; const drawn = renderRows(challenge.board.rows); + // The band a rank is spread across, inset from both edges. Spread edge to + // edge, the outermost machine of a rank sits half off the picture and its + // tag has nowhere to go; inset, every machine has ground on both sides. + const inset = Math.round(width * SIDE_MARGIN); + const span = width - inset * 2; + const spots: Spot[] = []; for (const row of new Set(side.units.map((unit) => unit.row))) { const line = side.units.filter((unit) => unit.row === row); const field = rowOnField(row, challenge.board.rows); const towards = field < (drawn - 1) / 2 ? 1 : -1; - // A rank only holds as many machines as the board is sprites wide. A + // A rank only holds as many machines as the band is sprites wide. A // company of twelve on an eight-wide board would draw them 56 pixels // apart and 84 pixels wide, which is a row of machines standing inside // each other. Over the limit it becomes as many ranks as it takes, each // one a full step further from the force's own edge. - const perRank = Math.floor(width / SPRITE_W); + const perRank = Math.max(1, Math.floor(span / SPRITE_W)); const ranks = Math.max(1, Math.ceil(line.length / perRank)); for (const [i, unit] of line.entries()) { @@ -319,16 +337,24 @@ export function spotsFor(challenge: Challenge, side: Side): Spot[] { const rank = i % ranks; const place = Math.floor(i / ranks); const inRank = Math.ceil((line.length - rank) / ranks); + const slot = span / inRank; + // Alternate ranks step half a slot sideways, so the machines interlock + // rather than standing in straight columns - the same brick the hexes + // themselves are laid in, and much easier to count at a glance. The + // half-step is split either side of centre so both margins stay equal. + const brick = ((rank % 2) - 0.5) * slot * 0.5; spots.push({ unit, - x: Math.round(((place + 0.5) * width) / inRank - SPRITE_W / 2), + x: Math.round(inset + (place + 0.5) * slot + brick - SPRITE_W / 2), // The ranks are the stagger when there is more than one of them; // stepping alternate machines as well would only blur the two apart. + // With a single rank, alternate machines step a whole sprite towards + // the middle, so six in a line read as two rows of three. y: FIELD_MARGIN + (field + rank * towards) * SPRITE_H + (ranks > 1 ? 0 : (place % 2) * STAGGER * towards), - slot: width / inRank, + slot, }); } } @@ -535,7 +561,23 @@ export function boardFigure(challenge: Challenge): HTMLElement { }); icon.hidden = !url; if (url) icon.style.setProperty("--era-icon", `url("${url}")`); - watermark.append(icon, el("span", { textContent: era.name })); + // The era's own years, not the fight's: the years line in the text column + // already says when these designs are from, and this says what period the + // name covers - so "Star League" is dated without anyone having to know + // the chronology. + const span = eraSpan(era); + watermark.append( + icon, + el("span", { textContent: era.name }), + ...(span + ? [ + el("span", { + className: "daily-era-span", + textContent: `(${span})`, + }), + ] + : []), + ); } watermark.hidden = !era; diff --git a/web/src/styles.css b/web/src/styles.css index 7281fa0..5cbf011 100644 --- a/web/src/styles.css +++ b/web/src/styles.css @@ -1773,6 +1773,17 @@ body:has(.hero) { flex: none; } +/* The years the era covers, quieter than its name and without the tracking - + a date read as letterspaced caps is hard to take in as a number. */ +.daily-era-span { + font-size: 0.72rem; + font-weight: 400; + letter-spacing: 0.06em; + text-indent: 0; + opacity: 0.72; + font-variant-numeric: tabular-nums; +} + /* --- the three facts ----------------------------------------------------- What is true today, including the part that is a limitation. */