diff --git a/Cargo.lock b/Cargo.lock index 6e9f772..fb379ca 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,22 @@ # It is not intended for manual editing. version = 4 +[[package]] +name = "ab_glyph" +version = "0.2.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01c0457472c38ea5bd1c3b5ada5e368271cb550be7a4ca4a0b4634e9913f6cc2" +dependencies = [ + "ab_glyph_rasterizer", + "owned_ttf_parser", +] + +[[package]] +name = "ab_glyph_rasterizer" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "366ffbaa4442f4684d91e2cd7c5ea7c4ed8add41959a31447066e279e432b618" + [[package]] name = "adler2" version = "2.0.1" @@ -1503,6 +1519,7 @@ dependencies = [ name = "headquarters-api" version = "0.1.0" dependencies = [ + "ab_glyph", "atrium-api", "atrium-common", "atrium-identity", @@ -2228,6 +2245,15 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1a80800c0488c3a21695ea981a54918fbb37abf04f4d0720c453632255e2ff0e" +[[package]] +name = "owned_ttf_parser" +version = "0.25.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36820e9051aca1014ddc75770aab4d68bc1e9e632f0f5627c4086bc216fb583b" +dependencies = [ + "ttf-parser", +] + [[package]] name = "p256" version = "0.13.2" @@ -3429,6 +3455,12 @@ version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" +[[package]] +name = "ttf-parser" +version = "0.25.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2df906b07856748fa3f6e0ad0cbaa047052d4a7dd609e231c4f72cee8c36f31" + [[package]] name = "tungstenite" version = "0.29.0" diff --git a/Cargo.toml b/Cargo.toml index 0302b02..ad13334 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -46,6 +46,10 @@ cookie = "0.18" # Decoding and re-encoding a camo PNG. Pure Rust, no system library. png = "0.18" +# Glyph outlines for the social card the API draws. Pure Rust, no system +# fonts and no C: the faces it rasterises are the two in +# services/api/assets/fonts, vendored beside their licences. +ab_glyph = "0.2" hmac = "0.12" sha2 = "0.10" diff --git a/plan/post-to-bluesky.md b/plan/post-to-bluesky.md index 10e79ce..886419b 100644 --- a/plan/post-to-bluesky.md +++ b/plan/post-to-bluesky.md @@ -264,6 +264,19 @@ this epic does not answer it. plain sheet that says nobody has drawn its class one yet. The match report inside the container hands the player here rather than drawing any of it. +- [x] **The card is drawn, per match and per point of view.** `card.rs` is a + drawing kit - canvas, glyphs through `ab_glyph`, silhouettes, bars - and + a match card is one composition of it, so a later card (a profile, a + scenario) is another. Four layouts: the dossier every match can use, a + duel's two plates, one force in detail, and the daily challenge with its + own stamp. Each machine is painted in its force's own camo, by MegaMek's + rule, from the bytes the match staged. + + `?perspective=@handle` is what makes it shareable: "@a beat @b" is a + report, not something a person posts about themselves, and it says + nothing useful in a 2v2 where two won and two lost. With a point of view + the verdict is theirs, their force is the one drawn in detail, and the + share menu offers the cards that make sense for the match they were in. - [x] **The page says what was played, and can be shared from.** The heading names how the match was reached - daily challenge, duel or scenario - with the scenario's name and the day it ran, all read off the match row diff --git a/services/api/Cargo.toml b/services/api/Cargo.toml index 956656c..f5d651f 100644 --- a/services/api/Cargo.toml +++ b/services/api/Cargo.toml @@ -44,6 +44,7 @@ axum-extra.workspace = true cookie.workspace = true png.workspace = true +ab_glyph.workspace = true hmac.workspace = true sha2.workspace = true base64.workspace = true diff --git a/services/api/assets/fonts/BarlowSemiCondensed-Medium.ttf b/services/api/assets/fonts/BarlowSemiCondensed-Medium.ttf new file mode 100644 index 0000000000000000000000000000000000000000..415429b64420872f198e76cbb1d309db5bfb1df4 GIT binary patch literal 105560 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 literal 0 HcmV?d00001 diff --git a/services/api/assets/fonts/ChakraPetch-Bold.ttf b/services/api/assets/fonts/ChakraPetch-Bold.ttf new file mode 100644 index 0000000000000000000000000000000000000000..76369fffa44ba7a70dd85b0f8563e1726b96182c GIT binary patch literal 78384 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 { + pub name: &'a str, + /// The silhouette's bytes, from the set the page serves. + pub sprite: &'static [u8], + /// What became of it, in a word: "unhurt", "heavy damage", "destroyed". + pub condition: &'a str, + /// Whether it came home. Drawn struck through when it did not. + pub alive: bool, +} + +/// One force, as the card needs it: who, what is left, and what it was made +/// of. The page's own model carries more than a card can show. +pub struct Force<'a> { + pub name: &'a str, + pub victor: bool, + /// Whose side the reader is on. Exactly one force carries this when the + /// card was asked for from somebody's point of view, and none does + /// otherwise. + pub mine: bool, + pub units_remaining: i64, + pub units_start: i64, + pub bv_share: i64, + /// The force's own camo, as the match staged it. Every machine on the + /// card is painted in it, the way MegaMek painted them on the board. + pub camo: Option>, + pub machines: Vec>, +} + +/// Which card this is. +/// +/// A finished match is not one picture. The player who wants to post it is +/// posting *their* fight, and what is worth showing differs: a duel is two +/// columns of machines, a lance is one force in detail, a daily challenge is +/// a challenge with a date on it, and a match with four seats in it is a +/// dossier. The share menu offers these; the URL names one. +#[derive(Clone, Copy, PartialEq)] +pub enum Layout { + /// Everyone who fought, with bars and silhouettes. Scales to any number + /// of seats and is what an unnamed match gets. + Dossier, + /// One force in detail: its machines named, with what became of each. + /// Wants a perspective, and is the "my lance" card. + Lance, + /// Two forces facing each other, machine by machine. Only for a match + /// with exactly two. + Duel, + /// The day's challenge, stamped with its date. + Daily, +} + +/// Everything the card says. +pub struct Card<'a> { + pub layout: Layout, + /// How the match was reached: "Daily challenge", "Duel", "Scenario". + pub eyebrow: &'a str, + /// The verdict, already phrased for whoever asked: "@a won", or + /// "@a beat @b" when nobody's point of view was named. + pub title: &'a str, + pub sub: &'a str, + pub forces: Vec>, + /// The final board, as arena uploaded it. Absent for a match that sent + /// none, and the card is then drawn on the site's own dark ground. + pub board: Option>, +} + +impl Layout { + /// The name in a URL, and back. + pub fn slug(self) -> &'static str { + match self { + Layout::Dossier => "dossier", + Layout::Lance => "lance", + Layout::Duel => "duel", + Layout::Daily => "daily", + } + } + + pub fn from_slug(slug: &str) -> Option { + Some(match slug { + "dossier" => Layout::Dossier, + "lance" => Layout::Lance, + "duel" => Layout::Duel, + "daily" => Layout::Daily, + _ => return None, + }) + } + + /// What the share menu calls it. + pub fn label(self) -> &'static str { + match self { + Layout::Dossier => "The match", + Layout::Lance => "My lance", + Layout::Duel => "Head to head", + Layout::Daily => "The challenge", + } + } +} + +/// A canvas of straight RGB. No alpha: the card is opaque by the time +/// anything looks at it, and blending against a known ground is simpler than +/// carrying a channel nothing reads. +struct Canvas { + width: u32, + height: u32, + pixels: Vec, +} + +impl Canvas { + fn new(width: u32, height: u32, fill: [u8; 3]) -> Canvas { + let mut pixels = Vec::with_capacity((width * height * 3) as usize); + for _ in 0..(width * height) { + pixels.extend_from_slice(&fill); + } + Canvas { + width, + height, + pixels, + } + } + + /// One pixel, blended by coverage. `alpha` is 0..=1. + fn blend(&mut self, x: i32, y: i32, colour: [u8; 3], alpha: f32) { + if alpha <= 0.0 || x < 0 || y < 0 || x >= self.width as i32 || y >= self.height as i32 { + return; + } + let alpha = alpha.min(1.0); + let index = ((y as u32 * self.width + x as u32) * 3) as usize; + for (pixel, over) in self.pixels[index..index + 3].iter_mut().zip(colour) { + let under = *pixel as f32; + *pixel = (under + (over as f32 - under) * alpha).round() as u8; + } + } + + fn rect(&mut self, x: i32, y: i32, width: i32, height: i32, colour: [u8; 3], alpha: f32) { + for row in y..y + height { + for column in x..x + width { + self.blend(column, row, colour, alpha); + } + } + } + + fn into_png(self) -> Option> { + let mut out = Vec::new(); + { + let mut encoder = png::Encoder::new(&mut out, self.width, self.height); + encoder.set_color(png::ColorType::Rgb); + encoder.set_depth(png::BitDepth::Eight); + let mut writer = encoder.write_header().ok()?; + writer.write_image_data(&self.pixels).ok()?; + } + Some(out) + } +} + +/// One decoded image: straight RGBA, as `png_util` hands it over. +struct Picture { + width: u32, + height: u32, + rgba: Vec, +} + +fn decode(bytes: &[u8]) -> Option { + let mut decoder = png::Decoder::new(std::io::Cursor::new(bytes)); + decoder.set_limits(png::Limits { + // A board render is the largest thing here and is a few megapixels; + // this is the same ceiling the flare path uses. + bytes: 64 * 1024 * 1024, + }); + decoder.set_transformations( + png::Transformations::normalize_to_color8() | png::Transformations::ALPHA, + ); + let mut reader = decoder.read_info().ok()?; + let mut buffer = vec![0; reader.output_buffer_size()?]; + let frame = reader.next_frame(&mut buffer).ok()?; + // The colour type of what came *out*, not what the file declared. The + // transformations above expand a palette to eight-bit colour with an + // alpha channel, and the file still says "indexed": read that and every + // paletted sprite is quietly dropped from the card. Two of the nine are + // paletted, which is how this got as far as a rendered picture with two + // machines missing from a lance of four. + let (colour, _) = reader.output_color_type(); + let rgba = png_util::to_rgba( + &buffer[..frame.buffer_size()], + colour, + frame.width, + frame.height, + )?; + Some(Picture { + width: frame.width, + height: frame.height, + rgba, + }) +} + +/// Draw a picture over the whole canvas, cropped to fill it, dimmed. +/// +/// Cover rather than fit: a letterboxed board leaves bars the card cannot use +/// and the middle of a battlefield is the interesting part of it. Nearest +/// neighbour, because the board is pixel art and smoothing it turns MegaMek's +/// hexes into mush. +fn draw_backdrop(canvas: &mut Canvas, picture: &Picture, dim: f32) { + if picture.width == 0 || picture.height == 0 { + return; + } + let scale = (canvas.width as f32 / picture.width as f32) + .max(canvas.height as f32 / picture.height as f32); + let offset_x = (canvas.width as f32 - picture.width as f32 * scale) / 2.0; + let offset_y = (canvas.height as f32 - picture.height as f32 * scale) / 2.0; + for y in 0..canvas.height as i32 { + for x in 0..canvas.width as i32 { + let source_x = ((x as f32 - offset_x) / scale) as i32; + let source_y = ((y as f32 - offset_y) / scale) as i32; + if source_x < 0 + || source_y < 0 + || source_x >= picture.width as i32 + || source_y >= picture.height as i32 + { + continue; + } + let index = ((source_y as u32 * picture.width + source_x as u32) * 4) as usize; + let alpha = picture.rgba[index + 3] as f32 / 255.0; + let colour = [ + picture.rgba[index], + picture.rgba[index + 1], + picture.rgba[index + 2], + ]; + canvas.blend(x, y, colour, alpha * (1.0 - dim)); + } + } +} + +/// One flat colour as a camo, for the test that painting reaches a machine. +#[cfg(test)] +pub fn flat_camo(colour: [u8; 3]) -> Vec { + let mut pixels = Vec::with_capacity(84 * 72 * 4); + for _ in 0..(84 * 72) { + pixels.extend_from_slice(&[colour[0], colour[1], colour[2], 255]); + } + let mut out = Vec::new(); + { + let mut encoder = png::Encoder::new(&mut out, 84, 72); + encoder.set_color(png::ColorType::Rgba); + encoder.set_depth(png::BitDepth::Eight); + let mut writer = encoder.write_header().expect("header"); + writer.write_image_data(&pixels).expect("pixels"); + } + out +} + +/// A sprite's size, for the test that every one of them decodes. +#[cfg(test)] +pub fn probe(bytes: &[u8]) -> Option<(u32, u32)> { + decode(bytes).map(|picture| (picture.width, picture.height)) +} + +/// A player's colours on a machine, the way MegaMek paints them. +/// +/// The same rule as `web/src/camo/mech.ts` and MegaMek's own +/// `EntityImage.applyColor`: only grey pixels take the camo, and the sprite's +/// grey level scales the colour, so the shading stays and the hue comes from +/// the player. Both images are authored at 84x72, so the sample is the same +/// pixel and there is nothing to interpolate. +/// +/// Third copy of this rule, and the last one worth having: the editor paints +/// it in the browser, the match paints it in MegaMek, and a card that painted +/// it differently would show colours the player never chose. +fn paint(sprite: &Picture, camo: Option<&Picture>) -> Picture { + let Some(camo) = camo else { + return Picture { + width: sprite.width, + height: sprite.height, + rgba: sprite.rgba.clone(), + }; + }; + let mut rgba = sprite.rgba.clone(); + for y in 0..sprite.height { + for x in 0..sprite.width { + let index = ((y * sprite.width + x) * 4) as usize; + let (r, g, b, a) = ( + rgba[index], + rgba[index + 1], + rgba[index + 2], + rgba[index + 3], + ); + if a == 0 || r != g || g != b { + continue; + } + let cx = (x % camo.width.max(1)).min(camo.width.saturating_sub(1)); + let cy = (y % camo.height.max(1)).min(camo.height.saturating_sub(1)); + let source = ((cy * camo.width + cx) * 4) as usize; + let grey = b as u32; + for channel in 0..3 { + rgba[index + channel] = ((camo.rgba[source + channel] as u32 * grey) / 255) as u8; + } + } + } + Picture { + width: sprite.width, + height: sprite.height, + rgba, + } +} + +/// One machine's picture, at whatever size the layout wants it. +/// +/// A machine that did not come home is drawn dimmed and struck through: it +/// was in the fight, and a card that leaves it out says a lance of four was a +/// lance of three. +fn draw_sprite(canvas: &mut Canvas, picture: &Picture, x: i32, y: i32, size: i32, alive: bool) { + if picture.width == 0 || picture.height == 0 { + return; + } + let scale = size as f32 / picture.width as f32; + let height = (picture.height as f32 * scale) as i32; + // A wreck is still on the card - it was in the fight - but it reads as + // one: dimmed towards the ground and struck through. Not dimmed further + // than this, because at 0.34 on this background it disappeared entirely + // and a lance of four looked like a lance of two. + let fade = if alive { 1.0 } else { 0.62 }; + for row in 0..height { + for column in 0..size { + let source_x = (column as f32 / scale) as u32; + let source_y = (row as f32 / scale) as u32; + if source_x >= picture.width || source_y >= picture.height { + continue; + } + let index = ((source_y * picture.width + source_x) * 4) as usize; + let alpha = picture.rgba[index + 3] as f32 / 255.0; + let colour = [ + picture.rgba[index], + picture.rgba[index + 1], + picture.rgba[index + 2], + ]; + canvas.blend(x + column, y + row, colour, alpha * fade); + } + } + if !alive { + canvas.rect(x, y + height / 2 - 1, size, 3, DANGER, 0.95); + } +} + +/// One line of text, drawn at a baseline. Returns where it ended. +/// How a line is set: its size, its colour and the air between its letters. +#[derive(Clone, Copy)] +struct Ink { + size: f32, + colour: [u8; 3], + tracking: f32, +} + +fn ink(size: f32, colour: [u8; 3], tracking: f32) -> Ink { + Ink { + size, + colour, + tracking, + } +} + +fn draw_text( + canvas: &mut Canvas, + font: &FontRef, + text: &str, + x: i32, + baseline: i32, + ink: Ink, +) -> i32 { + let Ink { + size, + colour, + tracking, + } = ink; + let scaled = font.as_scaled(PxScale::from(size)); + let mut pen = x as f32; + let mut previous: Option = None; + for character in text.chars() { + let id = font.glyph_id(character); + if let Some(last) = previous { + pen += scaled.kern(last, id); + } + let glyph = + id.with_scale_and_position(PxScale::from(size), ab_glyph::point(pen, baseline as f32)); + if let Some(outline) = font.outline_glyph(glyph) { + let bounds = outline.px_bounds(); + outline.draw(|gx, gy, coverage| { + canvas.blend( + bounds.min.x as i32 + gx as i32, + bounds.min.y as i32 + gy as i32, + colour, + coverage, + ); + }); + } + pen += scaled.h_advance(id) + tracking; + previous = Some(id); + } + pen as i32 +} + +/// How wide a line would be, for centring, truncating and right-aligning. +fn measure(font: &FontRef, text: &str, size: f32, tracking: f32) -> f32 { + let scaled = font.as_scaled(PxScale::from(size)); + let mut width = 0.0; + let mut previous: Option = None; + for character in text.chars() { + let id = font.glyph_id(character); + if let Some(last) = previous { + width += scaled.kern(last, id); + } + width += scaled.h_advance(id) + tracking; + previous = Some(id); + } + width +} + +/// The largest size at or under `size` that fits `room`, so a long pair of +/// handles shrinks rather than running off the card. +fn fitted(font: &FontRef, text: &str, size: f32, tracking: f32, room: f32) -> f32 { + let mut size = size; + while size > 18.0 && measure(font, text, size, tracking) > room { + size -= 2.0; + } + size +} + +/// Everything a layout needs to draw with, resolved once. +struct Kit<'a> { + display: FontRef<'a>, + text: FontRef<'a>, +} + +/// The ground every card stands on: the board, well back, under a wash that +/// keeps the type legible whatever the battlefield looks like. +fn draw_ground(canvas: &mut Canvas, card: &Card, dim: f32, wash_from: f32) { + if let Some(board) = card.board.as_ref().and_then(|bytes| decode(bytes)) { + draw_backdrop(canvas, &board, dim); + } + for y in 0..HEIGHT as i32 { + let depth = (y as f32 / HEIGHT as f32 - wash_from).max(0.0) / (1.0 - wash_from); + canvas.rect(0, y, WIDTH as i32, 1, BACKDROP, depth * 0.80); + } + canvas.rect(0, 0, WIDTH as i32, 4, BRAND, 1.0); +} + +/// The site's name, quietly, where every card carries it. +fn draw_mark(canvas: &mut Canvas, kit: &Kit, x: i32, baseline: i32) { + let mark = "lance.blue"; + let width = measure(&kit.display, mark, 26.0, 2.0); + draw_text( + canvas, + &kit.display, + mark, + x - width as i32, + baseline, + ink(26.0, MUTED, 2.0), + ); +} + +/// The line at the top of every card: how the match was reached, and what it +/// was. Returns the baseline it ended on. +fn draw_heading(canvas: &mut Canvas, kit: &Kit, card: &Card) -> i32 { + let y = MARGIN + 30; + draw_text( + canvas, + &kit.display, + &card.eyebrow.to_uppercase(), + MARGIN, + y, + ink(24.0, BRAND, 4.0), + ); + draw_mark(canvas, kit, WIDTH as i32 - MARGIN, y); + y +} + +/// Draw the card. +/// +/// `None` only if the fonts do not parse or the PNG will not encode, neither +/// of which depends on the match: the caller falls back to the board itself. +pub fn draw(card: &Card) -> Option> { + let kit = Kit { + display: FontRef::try_from_slice(DISPLAY).ok()?, + text: FontRef::try_from_slice(TEXT).ok()?, + }; + let mut canvas = Canvas::new(WIDTH, HEIGHT, BACKDROP); + match card.layout { + Layout::Dossier => draw_dossier(&mut canvas, &kit, card), + Layout::Lance => draw_lance(&mut canvas, &kit, card), + Layout::Duel => draw_duel(&mut canvas, &kit, card), + Layout::Daily => draw_daily(&mut canvas, &kit, card), + } + canvas.into_png() +} + +/// Everyone who fought, one row each. The card an unnamed match gets, and the +/// only one that does not care how many seats there were. +fn draw_dossier(canvas: &mut Canvas, kit: &Kit, card: &Card) { + draw_ground(canvas, card, 0.74, 0.30); + let y = draw_heading(canvas, kit, card); + draw_text( + canvas, + &kit.text, + card.sub, + MARGIN, + y + 38, + ink(28.0, MUTED, 0.0), + ); + + // From the bottom up, so a fourth force pushes into the space under the + // heading rather than off the card. + let rows = card.forces.len().max(1) as i32; + let block = ((HEIGHT as i32 - MARGIN - 150) / rows).clamp(78, 112); + let mut row_y = HEIGHT as i32 - MARGIN - block * rows; + for force in &card.forces { + draw_force_row(canvas, kit, force, MARGIN, row_y, block); + row_y += block; + } +} + +/// One force in a dossier row: who, what is left, and its machines. +fn draw_force_row(canvas: &mut Canvas, kit: &Kit, force: &Force, x: i32, y: i32, block: i32) { + let tint = if force.victor { ACCENT } else { MUTED }; + canvas.rect(x, y, 3, block - 18, tint, 1.0); + + let name_x = x + 16; + let end = draw_text( + canvas, + &kit.display, + force.name, + name_x, + y + 26, + ink(30.0, INK, 0.0), + ); + if force.victor { + draw_text( + canvas, + &kit.display, + "VICTOR", + end + 14, + y + 26, + ink(18.0, ACCENT, 3.0), + ); + } + // Whose card this is, said once and quietly. + if force.mine { + draw_text( + canvas, + &kit.display, + "YOU", + end + 100, + y + 26, + ink(18.0, BRAND, 3.0), + ); + } + + let line = format!( + "{} of {} standing · {}% battle value", + force.units_remaining, force.units_start, force.bv_share, + ); + draw_text( + canvas, + &kit.text, + &line, + name_x, + y + 52, + ink(24.0, MUTED, 0.0), + ); + + let bar_width = 420; + canvas.rect(name_x, y + 62, bar_width, 4, LINE, 1.0); + let filled = (bar_width as i64 * force.bv_share.clamp(0, 100) / 100) as i32; + canvas.rect(name_x, y + 62, filled, 4, tint, 1.0); + + draw_machines(canvas, force, name_x + bar_width + 34, y + 2, 56, 8); +} + +/// A force's machines in a row, painted in its camo. +fn draw_machines(canvas: &mut Canvas, force: &Force, x: i32, y: i32, size: i32, gap: i32) { + let camo = force.camo.as_ref().and_then(|bytes| decode(bytes)); + let mut machine_x = x; + for machine in &force.machines { + let Some(sprite) = decode(machine.sprite) else { + continue; + }; + let painted = paint(&sprite, camo.as_ref()); + draw_sprite(canvas, &painted, machine_x, y, size, machine.alive); + machine_x += size + gap; + if machine_x > WIDTH as i32 - MARGIN - size { + break; + } + } +} + +/// One force in detail: the card a player posts about their own lance. +fn draw_lance(canvas: &mut Canvas, kit: &Kit, card: &Card) { + draw_ground(canvas, card, 0.82, 0.16); + let y = draw_heading(canvas, kit, card); + + let Some(force) = card.forces.iter().find(|f| f.mine).or(card.forces.first()) else { + return; + }; + + let room = (WIDTH as i32 - MARGIN * 2) as f32; + let size = fitted(&kit.display, card.title, 54.0, 0.0, room); + draw_text( + canvas, + &kit.display, + card.title, + MARGIN, + y + 60, + ink(size, INK, 0.0), + ); + draw_text( + canvas, + &kit.text, + card.sub, + MARGIN, + y + 96, + ink(26.0, MUTED, 0.0), + ); + + // The machines, big enough to be looked at rather than counted. + let camo = force.camo.as_ref().and_then(|bytes| decode(bytes)); + let count = force.machines.len().max(1) as i32; + let column = ((WIDTH as i32 - MARGIN * 2) / count.min(4)).min(280); + let mut x = MARGIN; + for machine in force.machines.iter().take(4) { + if let Some(sprite) = decode(machine.sprite) { + let painted = paint(&sprite, camo.as_ref()); + draw_sprite(canvas, &painted, x, y + 140, 132, machine.alive); + } + let name_size = fitted(&kit.display, machine.name, 26.0, 0.0, (column - 16) as f32); + draw_text( + canvas, + &kit.display, + machine.name, + x, + y + 300, + ink(name_size, if machine.alive { INK } else { MUTED }, 0.0), + ); + draw_text( + canvas, + &kit.text, + machine.condition, + x, + y + 328, + ink(22.0, if machine.alive { MUTED } else { DANGER }, 0.0), + ); + x += column; + } + + let line = format!( + "{} of {} standing · {}% battle value", + force.units_remaining, force.units_start, force.bv_share, + ); + draw_text( + canvas, + &kit.display, + &line, + MARGIN, + HEIGHT as i32 - MARGIN, + ink(30.0, INK, 0.0), + ); +} + +/// Two forces facing each other, machine by machine. +fn draw_duel(canvas: &mut Canvas, kit: &Kit, card: &Card) { + draw_ground(canvas, card, 0.86, 0.10); + let y = draw_heading(canvas, kit, card); + draw_text( + canvas, + &kit.text, + card.sub, + MARGIN, + y + 34, + ink(24.0, MUTED, 0.0), + ); + + let top = y + 62; + let height = HEIGHT as i32 - top - MARGIN; + let column = (WIDTH as i32 - MARGIN * 2 - 24) / 2; + for (index, force) in card.forces.iter().take(2).enumerate() { + let x = MARGIN + index as i32 * (column + 24); + draw_duel_plate(canvas, kit, force, x, top, column, height); + } +} + +/// One plate in a duel: the notched card the site cuts its own plates as. +fn draw_duel_plate( + canvas: &mut Canvas, + kit: &Kit, + force: &Force, + x: i32, + y: i32, + width: i32, + height: i32, +) { + let tint = if force.victor { ACCENT } else { MUTED }; + canvas.rect(x, y, width, height, PANEL, 0.92); + // The edge, and the corner the site notches out of its plates. + canvas.rect(x, y, width, 2, tint, if force.victor { 1.0 } else { 0.5 }); + canvas.rect(x, y, 2, height, tint, if force.victor { 1.0 } else { 0.5 }); + + let end = draw_text( + canvas, + &kit.display, + force.name, + x + 20, + y + 40, + ink(30.0, INK, 0.0), + ); + if force.victor { + draw_text( + canvas, + &kit.display, + "VICTOR", + end + 14, + y + 40, + ink(18.0, ACCENT, 3.0), + ); + } + + let camo = force.camo.as_ref().and_then(|bytes| decode(bytes)); + let mut row_y = y + 62; + for machine in force.machines.iter().take(4) { + if let Some(sprite) = decode(machine.sprite) { + let painted = paint(&sprite, camo.as_ref()); + draw_sprite(canvas, &painted, x + 18, row_y, 52, machine.alive); + } + let name_size = fitted(&kit.display, machine.name, 24.0, 0.0, (width - 100) as f32); + draw_text( + canvas, + &kit.display, + machine.name, + x + 82, + row_y + 24, + ink(name_size, if machine.alive { INK } else { MUTED }, 0.0), + ); + draw_text( + canvas, + &kit.text, + machine.condition, + x + 82, + row_y + 46, + ink(20.0, if machine.alive { MUTED } else { DANGER }, 0.0), + ); + row_y += 62; + } + + let line = format!( + "{} of {} standing · {}%", + force.units_remaining, force.units_start, force.bv_share, + ); + draw_text( + canvas, + &kit.text, + &line, + x + 20, + y + height - 22, + ink(23.0, MUTED, 0.0), + ); + let bar_width = width - 40; + canvas.rect(x + 20, y + height - 14, bar_width, 4, LINE, 1.0); + let filled = (bar_width as i64 * force.bv_share.clamp(0, 100) / 100) as i32; + canvas.rect(x + 20, y + height - 14, filled, 4, tint, 1.0); +} + +/// The day's challenge, stamped. +fn draw_daily(canvas: &mut Canvas, kit: &Kit, card: &Card) { + draw_ground(canvas, card, 0.80, 0.14); + let y = draw_heading(canvas, kit, card); + + let Some(force) = card.forces.iter().find(|f| f.mine).or(card.forces.first()) else { + return; + }; + + // The challenge's own name is the loudest thing on this card: it is what + // another player recognises, having played the same one that day. + let room = (WIDTH as i32 - MARGIN * 2 - 260) as f32; + let size = fitted(&kit.display, card.title, 76.0, 0.0, room); + draw_text( + canvas, + &kit.display, + card.title, + MARGIN, + y + 82, + ink(size, INK, 0.0), + ); + draw_text( + canvas, + &kit.text, + card.sub, + MARGIN, + y + 122, + ink(26.0, MUTED, 0.0), + ); + + // The stamp, top right, in the colour of the outcome. + let stamp = if force.victor { "CLEARED" } else { "FAILED" }; + let tint = if force.victor { BRAND } else { DANGER }; + let stamp_width = measure(&kit.display, stamp, 34.0, 4.0) as i32 + 40; + let stamp_x = WIDTH as i32 - MARGIN - stamp_width; + let stamp_y = y + 34; + canvas.rect(stamp_x, stamp_y, stamp_width, 2, tint, 1.0); + canvas.rect(stamp_x, stamp_y + 62, stamp_width, 2, tint, 1.0); + canvas.rect(stamp_x, stamp_y, 2, 64, tint, 1.0); + canvas.rect(stamp_x + stamp_width - 2, stamp_y, 2, 64, tint, 1.0); + draw_text( + canvas, + &kit.display, + stamp, + stamp_x + 20, + stamp_y + 44, + ink(34.0, tint, 4.0), + ); + + // The force under its own line rather than at the foot of the card: with + // a board behind it the space below is the battlefield, and without one + // it should not be a hole. + let machines_y = y + 168; + draw_machines(canvas, force, MARGIN, machines_y, 132, 20); + let line = format!( + "{} of {} standing · {}% battle value", + force.units_remaining, force.units_start, force.bv_share, + ); + draw_text( + canvas, + &kit.display, + &line, + MARGIN, + machines_y + 156, + ink(30.0, INK, 0.0), + ); +} diff --git a/services/api/src/main.rs b/services/api/src/main.rs index a661e35..643fd05 100644 --- a/services/api/src/main.rs +++ b/services/api/src/main.rs @@ -1,5 +1,6 @@ mod atproto; mod camo; +mod card; mod config; mod db; mod flare; diff --git a/services/api/src/share.rs b/services/api/src/share.rs index 1d5dd92..c0e67af 100644 --- a/services/api/src/share.rs +++ b/services/api/src/share.rs @@ -23,6 +23,7 @@ use axum::extract::{Path, State}; use axum::http::{StatusCode, header}; use axum::response::{Html, IntoResponse, Response}; +use crate::card; use crate::routes::AppState; /// The artifact arena uploads for exactly this: the board as it stood at the @@ -112,6 +113,24 @@ impl Context { parts.join(" · ") } + /// The line under the verdict on a card: what was played, when, and how + /// long it took. Unescaped - a card is pixels, not markup. + fn card_line(&self, round: i64) -> String { + let mut parts: Vec = Vec::new(); + if let Some(name) = &self.scenario { + parts.push(name.clone()); + } + if let Entry::Daily { day } = &self.entry + && !day.is_empty() + { + parts.push(day.clone()); + } else if let Some(played) = &self.played { + parts.push(played.clone()); + } + parts.push(format!("decided in round {round}")); + parts.join(" · ") + } + /// The same, unescaped, for the card's own text. fn plain(&self) -> String { let mut parts: Vec = vec![self.entry.eyebrow().to_owned()]; @@ -209,6 +228,58 @@ fn day_of(seconds: i64) -> Option { Some(format!("{year:04}-{m:02}-{d:02}")) } +/// Whose match this is, when somebody asked for their own view of it. +/// +/// "@a beat @b" is the honest phrasing of a result and an awkward thing to +/// post about yourself, and it falls apart entirely in a 2v2, where two +/// people won and two lost and each of the four has a different sentence to +/// write. So a card can be asked for from one player's point of view: they +/// are "you", their side is the one drawn in detail, and the verdict is +/// theirs. Named by handle in the URL because that is what a player would +/// type and paste; matched against the handles the result recorded. +#[derive(Clone)] +pub struct Perspective { + handle: String, +} + +impl Perspective { + /// From `?perspective=`, with or without its leading `@`. + fn read(raw: Option<&str>) -> Option { + let handle = raw?.trim().trim_start_matches('@').to_lowercase(); + if handle.is_empty() { + return None; + } + Some(Perspective { handle }) + } + + /// Whether a force is theirs. Forces are named "@handle" in the result. + fn owns(&self, force: &Force) -> bool { + force.name.trim_start_matches('@').to_lowercase() == self.handle + } +} + +/// The verdict, phrased for whoever asked. +/// +/// From nobody's point of view it is a report: "@a beat @b". From a player's +/// it is theirs to post: "you won", said with their own handle so the card +/// still names somebody when it is read by a stranger. +fn verdict_line(summary: &Summary, perspective: Option<&Perspective>) -> String { + let Some(perspective) = perspective else { + return summary.title(); + }; + let Some(mine) = summary.forces.iter().find(|f| perspective.owns(f)) else { + return summary.title(); + }; + if !summary.decided() { + return format!("{} drew", mine.name); + } + if summary.won(mine) { + format!("{} won", mine.name) + } else { + format!("{} lost", mine.name) + } +} + /// The line beside the way in, for a reader who arrived from a shared post /// and has never heard of any of this. /// @@ -299,6 +370,9 @@ const WATCHERS: [&str; 2] = ["ArenaObserver", "ArenaSpectator"]; struct Force { name: String, + /// The manifest's slot key, which is what the match staged this force's + /// camo under. + slot: String, team: i64, id: i64, units_remaining: i64, @@ -350,6 +424,11 @@ fn summarise(result: &serde_json::Value) -> Option { } forces.push(Force { name, + slot: player + .get("slot") + .and_then(serde_json::Value::as_str) + .unwrap_or_default() + .to_owned(), team: int(player.get("team"), 0), id: int(player.get("id"), PLAYER_NONE), units_remaining: int(player.get("unitsRemaining"), 0), @@ -1115,6 +1194,59 @@ fn unit_id(force: usize, unit: usize) -> String { format!("u{force}-{unit}") } +/// The share row: which card to post, and the link. +/// +/// A finished match is not one picture (see `card::Layout`), so this is a +/// menu rather than a button: the cards this match can offer, the one being +/// shown, and a composer aimed at whichever is chosen. Plain links - picking +/// a card reloads this page with it, which is also what makes the choice +/// shareable, because the address carries it. +fn share_menu(summary: &Summary, context: &Context, page: &Page) -> String { + let mut choices = String::new(); + if page.offered.len() > 1 { + for layout in page.offered { + let query = card_query(page.perspective, Some(*layout)); + let current = if *layout == page.chosen { " on" } else { "" }; + choices.push_str(&format!( + "{}", + escape(page.base), + escape(&query), + escape(layout.label()), + )); + } + choices = format!("
{choices}
"); + } + let compose = compose_url(summary, context, page.here); + format!( + "
{choices}\ +
\ + Post to Bluesky\ + \ +
\"The\ +
", + compose = escape(&compose), + here = escape(page.here), + image = escape(page.image), + ) +} + +/// The query that names a point of view and a card. +fn card_query(perspective: Option<&Perspective>, layout: Option) -> String { + let mut parts: Vec = Vec::new(); + if let Some(perspective) = perspective { + parts.push(format!("perspective={}", urlencode(&perspective.handle))); + } + if let Some(layout) = layout { + parts.push(format!("card={}", layout.slug())); + } + if parts.is_empty() { + String::new() + } else { + format!("?{}", parts.join("&")) + } +} + /// The Bluesky composer, prefilled. /// /// A link and a sentence, because that is all an intent link carries - no @@ -1170,7 +1302,11 @@ fn escape(text: &str) -> String { } /// The result of a match, for anyone with the link. -pub async fn page(State(state): State, Path(id): Path) -> Response { +pub async fn page( + State(state): State, + Path(id): Path, + axum::extract::Query(ask): axum::extract::Query, +) -> Response { let Some(matches) = state.matches.as_ref() else { return StatusCode::NOT_FOUND.into_response(); }; @@ -1190,15 +1326,27 @@ pub async fn page(State(state): State, Path(id): Path) -> Resp return StatusCode::NOT_FOUND.into_response(); }; - let here = format!("{}/m/{}", matches.public_url(), id); + let perspective = Perspective::read(ask.perspective.as_deref()); + let offered = layouts(&context, &summary, perspective.is_some()); + let chosen = ask + .card + .as_deref() + .and_then(card::Layout::from_slug) + .filter(|wanted| offered.contains(wanted)) + .unwrap_or(offered[0]); + // The address this page is at, ask and all: it is what an unfurler reads + // and what a share link has to reproduce, so a card someone chose is the + // card their post carries. + let base = format!("{}/m/{}", matches.public_url(), id); + let here = format!("{base}{}", ask.query()); // The match's own board when arena sent one, and the site's card when it // did not. Never a missing og:image: an unfurler that fetches nothing // shows a bare link, which is the whole thing this page exists to avoid. - let image = if matches.artifact(&id, CARD_ARTIFACT).await.is_some() { - format!("{here}/card.png") - } else { - format!("{}/social-card.png", state.web_origin) - }; + // Drawn per match, and per ask: the card names the layout and the point of + // view it was asked from. The site's own card only stands in for a match + // whose result cannot be summarised at all, which the page has already + // refused by here. + let image = format!("{base}/card.png{}", ask.query()); ( // A result never changes once it exists, so this is worth caching - @@ -1208,9 +1356,15 @@ pub async fn page(State(state): State, Path(id): Path) -> Resp Html(document( &summary, &context, - &here, - &image, - &state.web_origin, + &Page { + here: &here, + base: &base, + image: &image, + home: &state.web_origin, + perspective: perspective.as_ref(), + offered: &offered, + chosen, + }, )), ) .into_response() @@ -1219,8 +1373,26 @@ pub async fn page(State(state): State, Path(id): Path) -> Resp /// The page itself. Pure text in, pure text out: an AppState is not needed to /// decide what a finished match looks like, and a document that can be built /// without one can be tested and looked at without one too. -fn document(summary: &Summary, context: &Context, here: &str, image: &str, home: &str) -> String { - let title = summary.title(); +/// Where the page is, what it is showing, and for whom. +struct Page<'a> { + /// This page's own address, ask and all. + here: &'a str, + /// The same without the ask, for building links that carry a different + /// one. + base: &'a str, + image: &'a str, + home: &'a str, + perspective: Option<&'a Perspective>, + /// The cards this match can offer, and the one being shown. + offered: &'a [card::Layout], + chosen: card::Layout, +} + +fn document(summary: &Summary, context: &Context, page: &Page) -> String { + let (here, image, home) = (page.here, page.image, page.home); + // Named for whoever asked, when somebody did: a player posting their own + // match should not have to write "@them beat @me". + let title = verdict_line(summary, page.perspective); let description = summary.description(); let mut rows = String::new(); // Built alongside the list and appended after it: a :target panel that @@ -1301,11 +1473,7 @@ fn document(summary: &Summary, context: &Context, here: &str, image: &str, home:

{context_line}{context_sep}Round {round}

\n\ \"The\n\
    {rows}
{modals}\n\ -
\n\ - Post to Bluesky\n\ - \n\ -
\n\ + {share}\n\

Play lance.blue\ {tagline}

\n\

lance.blue

\n\ @@ -1322,7 +1490,7 @@ fn document(summary: &Summary, context: &Context, here: &str, image: &str, home: } else { " · " }, - compose = escape(&compose_url(summary, context, here)), + share = share_menu(summary, context, page), tagline = TAGLINE, rows = rows, modals = modals, @@ -1337,20 +1505,69 @@ fn document(summary: &Summary, context: &Context, here: &str, image: &str, home: /// that follows a redirect to a signed S3 address caches the signature, and /// the picture stops loading when it expires. This one is a plain image at a /// plain address. -pub async fn card(State(state): State, Path(id): Path) -> Response { +pub async fn card( + State(state): State, + Path(id): Path, + axum::extract::Query(ask): axum::extract::Query, +) -> Response { let Some(matches) = state.matches.as_ref() else { return StatusCode::NOT_FOUND.into_response(); }; - if !matches!(state.db.get_match(&id).await, Ok(Some(_))) { + let Ok(Some(row)) = state.db.get_match(&id).await else { return StatusCode::NOT_FOUND.into_response(); - } - // Gated on the result being there, so a card is only public once the page - // that shows it is: the picture is the match's final board, and it should - // not be readable before the match it belongs to has one. - if matches.result(&id).await.is_none() { + }; + // Gated on the result, so a card is only public once the page that shows + // it is: it is a picture of how somebody's match went. + let Some(result) = matches.result(&id).await else { + return StatusCode::NOT_FOUND.into_response(); + }; + let Some(summary) = summarise(&result) else { return StatusCode::NOT_FOUND.into_response(); + }; + let seats = state.db.match_players(&id).await.unwrap_or_default(); + let context = Context::read(&row, &seats); + let perspective = Perspective::read(ask.perspective.as_deref()); + + // The board it was fought on, and each force's own colours. Both are + // artifacts of the match; both are optional, and the card is drawn + // without whichever is missing. + let board = matches.artifact(&id, CARD_ARTIFACT).await; + let mut camo = Vec::with_capacity(summary.forces.len()); + for force in &summary.forces { + camo.push( + matches + .artifact(&id, &format!("camo-{}.png", force.slot)) + .await, + ); } - let Some(bytes) = matches.artifact(&id, CARD_ARTIFACT).await else { + + let offered = layouts(&context, &summary, perspective.is_some()); + let layout = ask + .card + .as_deref() + .and_then(card::Layout::from_slug) + .filter(|wanted| offered.contains(wanted)) + .unwrap_or(offered[0]); + + let verdict = verdict_line(&summary, perspective.as_ref()); + let (title, sub) = headline(layout, &context, &verdict, summary.round); + let drawn = card::draw(&compose( + &summary, + &context, + Drawn { + layout, + title: &title, + sub: &sub, + perspective: perspective.as_ref(), + camo: &camo, + board: board.clone(), + }, + )); + + // A card that could not be drawn falls back to the board itself rather + // than to nothing: an unfurl with no picture is the thing this exists to + // avoid. + let Some(bytes) = drawn.or(board) else { return StatusCode::NOT_FOUND.into_response(); }; ( @@ -1363,6 +1580,188 @@ pub async fn card(State(state): State, Path(id): Path) -> Resp .into_response() } +/// What a reader asked for: whose view, and which card. +#[derive(serde::Deserialize, Default)] +pub struct Ask { + /// A handle, with or without its `@`. + pub perspective: Option, + /// A layout's slug. + pub card: Option, +} + +impl Ask { + /// The query string that reproduces this ask, for a link or an og:url. + fn query(&self) -> String { + let mut parts: Vec = Vec::new(); + if let Some(perspective) = &self.perspective { + parts.push(format!("perspective={}", urlencode(perspective))); + } + if let Some(card) = &self.card { + parts.push(format!("card={}", urlencode(card))); + } + if parts.is_empty() { + String::new() + } else { + format!("?{}", parts.join("&")) + } + } +} + +/// Which card a match should offer, given how it was reached and who was in +/// it. The share menu lists these; the first is the one a bare link gets. +fn layouts(context: &Context, summary: &Summary, perspective: bool) -> Vec { + let mut out = Vec::new(); + if matches!(context.entry, Entry::Daily { .. }) { + out.push(card::Layout::Daily); + } + if summary.forces.len() == 2 { + out.push(card::Layout::Duel); + } + out.push(card::Layout::Dossier); + // One force in detail only makes sense as somebody's own force. + if perspective { + out.push(card::Layout::Lance); + } + out +} + +/// Build the picture from the result. +/// +/// The silhouette a machine gets is the one the page draws it with, and the +/// camo is the force's own, read back out of the artifacts the match staged +/// for arena. A force whose camo is missing is painted in MegaMek's grey, +/// which is what an unpainted lance looks like in the game. +/// What the card is drawn from, beyond the result itself: the words already +/// phrased for whoever asked, and the pictures the match left behind. +struct Drawn<'a> { + layout: card::Layout, + title: &'a str, + sub: &'a str, + perspective: Option<&'a Perspective>, + /// One entry per force, in the summary's order. + camo: &'a [Option>], + board: Option>, +} + +fn compose<'a>(summary: &'a Summary, context: &'a Context, drawn: Drawn<'a>) -> card::Card<'a> { + let Drawn { + layout, + title, + sub, + perspective, + camo, + board, + } = drawn; + let forces = summary + .forces + .iter() + .enumerate() + .map(|(index, force)| card::Force { + name: &force.name, + victor: summary.won(force), + mine: perspective.map(|p| p.owns(force)).unwrap_or(false), + units_remaining: force.units_remaining, + units_start: force.units_start, + bv_share: if force.bv_start > 0 { + force.bv_remaining * 100 / force.bv_start + } else { + 0 + }, + camo: camo.get(index).cloned().flatten(), + machines: force + .units + .iter() + .map(|unit| card::Machine { + name: unit + .get("name") + .and_then(serde_json::Value::as_str) + .unwrap_or("Unknown machine"), + sprite: sprite_bytes(sprite_for(unit)), + condition: condition_word(unit), + alive: !lost( + unit.get("status") + .and_then(serde_json::Value::as_str) + .unwrap_or("active"), + ), + }) + .collect(), + }) + .collect(); + card::Card { + layout, + eyebrow: context.entry.eyebrow(), + title, + sub, + forces, + board, + } +} + +/// What a card leads with, and what it says underneath. +/// +/// Every layout leads with the verdict except the daily challenge, which +/// leads with the challenge: its name is what another player recognises, +/// having played the same one that day, and the verdict goes under it. +fn headline( + layout: card::Layout, + context: &Context, + verdict: &str, + round: i64, +) -> (String, String) { + if layout == card::Layout::Daily + && let Some(name) = &context.scenario + { + let mut sub = verdict.to_owned(); + if let Entry::Daily { day } = &context.entry + && !day.is_empty() + { + sub.push_str(" · "); + sub.push_str(day); + } + sub.push_str(&format!(" · round {round}")); + return (name.clone(), sub); + } + (verdict.to_owned(), context.card_line(round)) +} + +/// A machine's condition as one short phrase, for a card that has room for a +/// line and not a sentence. The page's own `condition` returns an owned +/// String; a card borrows, so this is the static half of the same words. +fn condition_word(unit: &serde_json::Value) -> &'static str { + let status = unit + .get("status") + .and_then(serde_json::Value::as_str) + .unwrap_or("active"); + if status != "active" { + return match status { + "destroyed" => "destroyed", + "devastated" => "devastated", + "salvage" => "salvage", + "retreated" => "withdrew", + "ejected" => "ejected", + "captured" => "captured", + "pushed" => "pushed off", + _ => "out of the fight", + }; + } + match unit.get("damage").and_then(serde_json::Value::as_str) { + Some("crippled") => "crippled", + Some("heavy") => "heavy damage", + Some("moderate") => "damaged", + Some("light") => "light damage", + _ => "unhurt", + } +} + +/// The bytes behind a silhouette's name. +fn sprite_bytes(name: &str) -> &'static [u8] { + SPRITES + .iter() + .find(|(id, _)| *id == name) + .map(|(_, bytes)| *bytes) + .unwrap_or(SPRITES[0].1) +} + /// One silhouette. Immutable and tiny, so it is cached hard and served to /// anyone: it is the same picture whichever match asked for it. pub async fn sprite(Path(name): Path) -> Response { @@ -1553,6 +1952,42 @@ mod tests { ) } + /// The page as a bare link gets it: no point of view, the first card. + fn rendered(summary: &Summary) -> String { + let context = fixture_context(); + let offered = layouts(&context, summary, false); + document( + summary, + &context, + &Page { + here: "https://api.lance.blue/m/m1", + base: "https://api.lance.blue/m/m1", + image: "https://api.lance.blue/m/m1/card.png", + home: "https://lance.blue", + perspective: None, + offered: &offered, + chosen: offered[0], + }, + ) + } + + /// The same match, reached as the day's challenge. + fn daily_context() -> Context { + Context::read( + &crate::db::MatchRow { + id: "m1".into(), + owner_did: "did:plc:a".into(), + status: "over".into(), + token: "t".into(), + task_arn: None, + task_ip: None, + created_at: 1_787_097_600, + scenario: Some("daily:bug-company".into()), + }, + &[], + ) + } + fn result() -> serde_json::Value { serde_json::json!({ "round": 7, @@ -1639,13 +2074,7 @@ mod tests { doc["players"][0]["units"] = serde_json::json!([damaged_griffin(), fresh, packrat(), wreck]); let summary = summarise(&doc).expect("a summary"); - document( - &summary, - &fixture_context(), - "https://api.lance.blue/m/m1", - "https://api.lance.blue/m/m1/card.png", - "https://lance.blue", - ) + rendered(&summary) } /// A vehicle is not a Mek, and its sheet says so: the armour diagram it @@ -1657,13 +2086,7 @@ mod tests { let mut doc = result(); doc["players"][0]["units"] = serde_json::json!([packrat()]); let summary = summarise(&doc).expect("a summary"); - let html = document( - &summary, - &fixture_context(), - "https://api.lance.blue/m/m1", - "https://api.lance.blue/m/m1/card.png", - "https://lance.blue", - ); + let html = rendered(&summary); assert!( html.contains("Vehicle damage"), "the vehicle's own criticals" @@ -1682,13 +2105,7 @@ mod tests { let mut doc = result(); doc["players"][0]["units"] = serde_json::json!([unit]); let summary = summarise(&doc).expect("a summary"); - let html = document( - &summary, - &fixture_context(), - "https://api.lance.blue/m/m1", - "https://api.lance.blue/m/m1/card.png", - "https://lance.blue", - ); + let html = rendered(&summary); assert!(html.contains("No record sheet for this class")); assert!(html.contains("Weapons and equipment")); } @@ -1700,13 +2117,7 @@ mod tests { let mut doc = result(); doc["players"][0]["units"] = serde_json::json!([damaged_griffin()]); let summary = summarise(&doc).expect("a summary"); - let html = document( - &summary, - &fixture_context(), - "https://api.lance.blue/m/m1", - "https://api.lance.blue/m/m1/card.png", - "https://lance.blue", - ); + let html = rendered(&summary); assert!(html.contains("Griffin GRF-1N")); // The stripped torso, the blown-off arm and the engine, each read // back out of the page rather than out of the document. @@ -1852,6 +2263,194 @@ mod tests { assert!(html.contains("2026-08-19"), "the day"); } + /// Every silhouette the card can pick has to decode, or a machine is + /// simply missing from the picture with nothing said. + #[test] + fn every_silhouette_decodes() { + for (name, bytes) in SPRITES { + let decoded = crate::card::probe(bytes); + assert!(decoded.is_some(), "{name} does not decode"); + let (width, height) = decoded.unwrap(); + assert_eq!((width, height), (84, 72), "{name} is not sprite-sized"); + } + } + + /// A force's camo reaches its machines. Without this the whole painting + /// path can rot silently: the card still draws, in MegaMek's grey. + #[test] + fn a_camo_paints_the_machines() { + // A force with machines in it: the base fixture has none, and a card + // with nothing to paint is the same card either way. + let mut doc = result(); + doc["players"][0]["units"] = serde_json::json!([damaged_griffin()]); + let summary = summarise(&doc).expect("a summary"); + let context = fixture_context(); + // One flat red camo, which is the easiest thing to see in the pixels. + let camo = crate::card::flat_camo([220, 40, 40]); + let painted = |camo: Vec>>| { + card::draw(&compose( + &summary, + &context, + Drawn { + layout: card::Layout::Dossier, + title: "@a.example won", + sub: "First Run", + perspective: None, + camo: &camo, + board: None, + }, + )) + .expect("the card draws") + }; + let plain = painted(summary.forces.iter().map(|_| None).collect()); + let red = painted( + summary + .forces + .iter() + .enumerate() + .map(|(i, _)| if i == 0 { Some(camo.clone()) } else { None }) + .collect(), + ); + assert_ne!(plain, red, "the camo changed nothing"); + } + + /// Every card, for all three shapes a match comes in, written out as + /// PNGs to look at. Ignored like `preview`: this is for eyes. + /// + /// cargo test -p headquarters-api share::tests::cards -- --ignored + #[test] + #[ignore = "writes the cards to /tmp for eyeballing"] + fn cards() { + // A machine's silhouette follows its tonnage, so the shapes differ. + let machine = |name: &str, tons: f64, kind: &str, dead: bool| { + let mut unit = damaged_griffin(); + unit["name"] = name.into(); + unit["tons"] = tons.into(); + unit["kind"] = kind.into(); + unit["status"] = if dead { "destroyed" } else { "active" }.into(); + unit + }; + let force = |name: &str, + slot: &str, + team: i64, + left: i64, + start: i64, + bv: i64, + units: Vec| { + serde_json::json!({ + "id": 1, "name": name, "slot": slot, "observer": false, "team": team, + "unitsRemaining": left, "unitsStart": start, + "bvRemaining": bv, "bvStart": 2000, "units": units, + }) + }; + + let lance = |dead: usize| { + let all = vec![ + machine("Atlas AS7-D", 100.0, "mek", dead > 3), + machine("Griffin GRF-1N", 55.0, "mek", dead > 2), + machine("Vedette Medium Tank", 50.0, "vehicle", dead > 1), + machine("Locust LCT-1V", 20.0, "mek", dead > 0), + ]; + all + }; + + let cases: Vec<(&str, serde_json::Value, Option<&str>)> = vec![ + // One player against Princess, which is what a daily challenge is. + ( + "1vbot", + serde_json::json!({ + "round": 7, "victoryTeam": 1, "victoryPlayerId": -1, + "players": [ + force("@permadeath.com", "TraineeA", 1, 3, 4, 1420, lance(1)), + force("@lance.blue", "OPFOR", 2, 0, 4, 0, lance(4)), + ], + }), + Some("permadeath.com"), + ), + // Two people. + ( + "1v1", + serde_json::json!({ + "round": 11, "victoryTeam": 2, "victoryPlayerId": -1, + "players": [ + force("@permadeath.com", "TraineeA", 1, 1, 4, 480, lance(3)), + force("@a.example", "TraineeB", 2, 3, 4, 1610, lance(1)), + ], + }), + Some("permadeath.com"), + ), + // Four, two a side: the case "@a beat @b" cannot say at all. + ( + "2v2", + serde_json::json!({ + "round": 9, "victoryTeam": 1, "victoryPlayerId": -1, + "players": [ + force("@permadeath.com", "TraineeA", 1, 2, 4, 1180, lance(2)), + force("@ally.example", "TraineeB", 1, 4, 4, 1890, lance(0)), + force("@a.example", "TraineeC", 2, 0, 4, 0, lance(4)), + force("@b.example", "TraineeD", 2, 1, 4, 320, lance(3)), + ], + }), + Some("permadeath.com"), + ), + ]; + + for (shape, result, who) in cases { + let summary = summarise(&result).expect("a summary"); + let perspective = Perspective::read(who); + let verdict = verdict_line(&summary, perspective.as_ref()); + // The context is per layout, below: a daily card is drawn against + // a daily's own. + for layout in [ + card::Layout::Dossier, + card::Layout::Lance, + card::Layout::Duel, + card::Layout::Daily, + ] { + // A duel card only means anything with two forces in it. + if layout == card::Layout::Duel && summary.forces.len() != 2 { + continue; + } + // A camo on the first force when CARD_CAMO names one, so the + // painting path can be looked at as well as asserted on: + // CARD_CAMO=/path/to/camo.png cargo test ... -- --ignored + let sample = std::env::var("CARD_CAMO") + .ok() + .and_then(|path| std::fs::read(path).ok()); + let camo: Vec>> = summary + .forces + .iter() + .enumerate() + .map(|(index, _)| if index == 0 { sample.clone() } else { None }) + .collect(); + // A daily card is drawn against a daily's own context, which + // is where its eyebrow, its name and its date come from. + let context = if layout == card::Layout::Daily { + daily_context() + } else { + fixture_context() + }; + let (title, sub) = headline(layout, &context, &verdict, summary.round); + let drawn = card::draw(&compose( + &summary, + &context, + Drawn { + layout, + title: &title, + sub: &sub, + perspective: perspective.as_ref(), + camo: &camo, + board: None, + }, + )) + .expect("the card draws"); + let path = format!("/tmp/card-{shape}-{}.png", layout.slug()); + std::fs::write(&path, drawn).expect("written"); + println!("wrote {path}"); + } + } + } + /// The tags an unfurler reads, and the one that decides whether a post /// carries a picture at all. #[test]