branding: one mark, and PSOGM draws it

Guru and SchCotie are gone. Shift is the only mark, and the crash screen draws
it where the face used to be.

Guru was an icon -- a pictogram with a face, denoting one state -- that lived
on the crash screen and nowhere else. Its argument was identity: `:(` is
recognisably Microsoft's, and a crash screen is the single screenshot most
likely to leave the machine, so it is the worst place to be wearing somebody
else's face. That part holds, and it is kept in the branding README because it
is the argument against any second mark. What does not follow is that the
answer is a *new* mark. Shift already signs the system, it is already what the
machine drew on the splash it started with, and a reader who has seen it twice
before the crash recognises it faster than a face they have never seen.

SchCotie was a mascot, and never existed: the file said so itself under "what
this does not cover". A brief for a character nobody is about to draw is not
worth carrying.

So `draw_guru()` and its SVG-space geometry leave panic.c, the two `guru.svg`
files are deleted, and PSOGM calls gfx_draw_logo() -- the same include/logo.h
mask the loader splash and the boot console composite. At its native 128 px:
there is no resampler in the crash path, and the face was scalable in a place
that only ever drew it at one size.

The name stays. "Purple Screen of Guru Meditation" is the Amiga nod, which
panic.c's own header already called out as a separate thing from the mark.

**And taking the screenshot to check it caught a regression from the TTF
console work.** `gfx_draw_char()` -- the pixel-positioned path, which only
PSOGM uses -- still walked `font_data[c][row]` for `row < CHAR_HEIGHT`.
font_data has exactly 16 rows per glyph; CHAR_HEIGHT is the *TrueType* cell
height now, about 21. So every line of the crash report drew five rows of the
next character's bitmap underneath itself, and it looked like each line had
been printed twice. The grid path had been updated when the cell size stopped
being a constant; this one had not, because nothing routine draws text in pixel
coordinates. Both go through one draw_glyph_px() now, and the bitmap's own
geometry is spelled BITMAP_W/BITMAP_H so it cannot be mistaken for a cell size
again.

Two stale claims corrected while in there, in panic.c's header and in
graphics.md: PSOGM has not dimmed-and-floated over the old screen for a while
-- it clears to full purple, and the file's own code comment says why -- and
there is no longer "a face that is obviously a cartoon".

The website's crash section shows the mark in flat white rather than its own
violets, which is what the kernel actually draws: PSOGM composites a coverage
mask in one colour, and those violets sit close enough to #4B1D9A to
half-disappear against it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Nexoniarz 2026-09-21 21:35:58 +02:00
commit 953f9c5c3c
11 changed files with 158 additions and 216 deletions

View file

@ -5,15 +5,16 @@
// The name is a nod to the Amiga's "Guru Meditation", but nothing else about
// this is a nod to 1985. Three things it is built around:
//
// *It does not clear the screen.* Whatever was being displayed stays there,
// dimmed and pushed back, with the report floating over it. A crash screen that
// wipes the display tells you the machine is gone; one that leaves your work
// visible underneath tells you it stopped, which is both truer and much less
// alarming.
// *It takes the whole screen.* An earlier version dimmed what was there and
// floated a panel over it, on the argument that a crash screen which wipes the
// display tells you the machine is gone while one that leaves your work visible
// tells you it stopped. That looked good and was wrong: the thing is called a
// Purple Screen, and half a screen of somebody's abandoned work behind the
// report is a distraction at the worst possible moment.
//
// *It is not frightening.* No red, no capitals, no "FATAL". A calm sentence, a
// face that is obviously a cartoon, and then the facts. Whoever is reading this
// is already having a bad time.
// *It is not frightening.* No red, no capitals, no "FATAL". A calm sentence,
// the system's own mark, and then the facts. Whoever is reading this is
// already having a bad time.
//
// *It says considerably more than it needs to.* The screen is the only artefact
// of a kernel panic — there is no core dump and no log to go back to — so
@ -40,6 +41,7 @@
// same names but with different values, which meant theme.h described a crash
// screen nobody had ever seen.
#include "theme.h"
#include "logo.h" /* LOGO_H -- the mark's height, for the layout below */
#include "qr.h"
extern uint ticks;
@ -87,80 +89,6 @@ put_hex(char *p, char *end, uint64 v, int digits)
return p;
}
/* ------------------------------------------------------------------ */
/* The face */
/* */
/* Guru: a chamfered head with two eyes and a flat mouth. See */
/* docs/branding/ -- it is the system's icon, and the only place it is */
/* allowed to appear is here. */
/* */
/* This replaced ":(", two dots and a parenthesis, whose argument was */
/* that it is "legible as a mood without being a picture of anything, */
/* and does not pretend the machine has feelings about what just */
/* happened." That reasoning was good and is recorded because it is */
/* the thing being traded away: a face is a louder claim than a */
/* punctuation mark, and it has to earn the volume. */
/* */
/* What it buys is identity. ":(" is Microsoft's, recognisably, and a */
/* crash screen is the single screenshot most likely to leave the */
/* machine -- so it is the worst place to be wearing somebody else's */
/* face. The octagon also quotes the chamfered corners of the old */
/* SchOS letterforms, which is the only part of that mark still in use. */
/* */
/* Still drawn from primitives rather than stored as a bitmap, for the */
/* original reason: it scales with the display and costs no space in a */
/* kernel image. Two rectangles and a run of spans -- no allocation, no */
/* locks, nothing that can fail halfway. */
/* ------------------------------------------------------------------ */
/* Geometry, in the 200-unit box docs/branding/guru.svg is drawn in, so the
* two cannot drift apart without somebody editing a number here on purpose.
* The head spans 24..176 of that box; everything is expressed against it. */
#define GURU_BOX_LO 24 /* head's left/top edge in the SVG box */
#define GURU_BOX_SPAN 152 /* head's width and height there */
#define GURU_CHAMFER 34 /* corner cut */
/* SVG coordinate -> pixel offset within a head `h` tall. */
static int
gmap(int v, int h)
{
return (v - GURU_BOX_LO) * h / GURU_BOX_SPAN;
}
static void
draw_guru(int x, int y, int h, uint32 color, uint32 bg)
{
/* The head, as horizontal spans. A scanline fill rather than a polygon
* routine: the shape is convex and its left edge is one subtraction, so
* this is the whole of it. */
for (int py = 0; py < h; py++) {
int sy = GURU_BOX_LO + py * GURU_BOX_SPAN / h; /* back to SVG space */
int inset = 0;
if (sy < GURU_BOX_LO + GURU_CHAMFER)
inset = (GURU_BOX_LO + GURU_CHAMFER) - sy;
else if (sy > GURU_BOX_LO + GURU_BOX_SPAN - GURU_CHAMFER)
inset = sy - (GURU_BOX_LO + GURU_BOX_SPAN - GURU_CHAMFER);
int x0 = gmap(GURU_BOX_LO + inset, h);
int x1 = gmap(GURU_BOX_LO + GURU_BOX_SPAN - inset, h);
if (x1 > x0)
gfx_fill_rect(x + x0, y + py, x1 - x0, 1, color);
}
/* The eyes and the mouth are knocked out of the head rather than drawn on
* top of it, which is what keeps them legible when the head is small: a
* hole in a solid reads at sizes a stroke does not. */
int ey = gmap(82, h), eh = gmap(104, h) - gmap(82, h);
int e1x = gmap(74, h), e2x = gmap(110, h);
int ew = gmap(90, h) - gmap(74, h);
gfx_fill_rect(x + e1x, y + ey, ew, eh, bg);
gfx_fill_rect(x + e2x, y + ey, ew, eh, bg);
int mx = gmap(72, h), mw = gmap(128, h) - gmap(72, h);
int my = gmap(128, h), mh = gmap(139, h) - gmap(128, h);
gfx_fill_rect(x + mx, y + my, mw, mh, bg);
}
/* ------------------------------------------------------------------ */
/* The report */
/* ------------------------------------------------------------------ */
@ -458,9 +386,24 @@ paint(char *reason, addr_t *pcs, int npcs)
if (left > 110) left = 110;
int y = sh / 8;
int face_h = 96;
draw_guru(left, y, face_h, PSOGM_FG, PSOGM_BG);
y += face_h + ch * 2;
// Shift, the system's own mark, from the same include/logo.h the loader
// splash and the boot console draw. It replaces Guru, a chamfered face that
// lived only here and was drawn from primitives a few lines up this file.
//
// Guru was an *icon*: a pictogram denoting one state, with a face. The
// argument for it was identity -- ":(" is recognisably Microsoft's, and a
// crash screen is the single screenshot most likely to leave the machine, so
// it is the worst place to be wearing somebody else's face. That argument
// holds. It just does not follow that the answer is a second mark. The logo
// already signs the system, it is already the thing on the splash the
// machine started with, and a reader who has seen it twice before the crash
// recognises it faster than a face they have never seen.
//
// A fixed 128 px rather than scaled: the mask is 128x128 and there is no
// resampler here. The face was scalable and that bought nothing -- it was
// drawn at one size.
gfx_draw_logo(left, y, PSOGM_FG, PSOGM_BG);
y += LOGO_H + ch * 2;
// Left-aligned, not centred. Centred text reads as a poster; this is a
// report, and a report is read down the left edge.

View file

@ -39,8 +39,16 @@
// its own metrics. These start at the bitmap's size because the console has to
// work from the first panic onwards -- before gfx_font_init() has run there is
// no font, and 8x16 against font_data is exactly what there always was.
static int cell_w = 8;
static int cell_h = 16;
// The bitmap's own geometry, which is fixed forever: font_data is
// `uchar[256][16]` and each row is eight bits. Naming it separately from the
// cell size is not pedantry -- the two were the same number, then one of them
// started moving, and the code that kept indexing font_data by the moving one
// read straight off the end of every glyph.
#define BITMAP_W 8
#define BITMAP_H 16
static int cell_w = BITMAP_W;
static int cell_h = BITMAP_H;
#define CHAR_WIDTH cell_w
#define CHAR_HEIGHT cell_h
@ -52,6 +60,7 @@ static struct font *cfont;
static int cfont_baseline;
static uint32 blend(uint32 bg, uint32 fg, uint a);
static void draw_glyph_px(int x, int y, uint32 cp, uint32 fg, uint32 bg);
// The scanout, as published by whichever driver owns the display. Zeroed until
// one does, and every entry point checks `fb` before touching memory -- a
@ -141,20 +150,10 @@ gfx_clear(uint32 color)
void
gfx_draw_char(int x, int y, uchar c, uint32 color)
{
// No `c > 127` rejection any more: font_data holds 256 Latin-1 glyphs, and
// a uchar cannot index past them.
if (!fb) return;
for (int row = 0; row < CHAR_HEIGHT; row++) {
uchar row_data = font_data[c][row];
for (int col = 0; col < CHAR_WIDTH; col++) {
if (row_data & (0x80 >> col)) {
gfx_draw_pixel(x + col, y + row, color);
} else {
gfx_draw_pixel(x + col, y + row, gfx_bg);
}
}
}
// Pixel-positioned rather than on the grid -- the crash screen lays itself
// out in pixels because it is a report, not a terminal. Same glyph path as
// the grid uses, which it did not used to be; see draw_glyph_px().
draw_glyph_px(x, y, c, color, gfx_bg);
}
void
@ -246,13 +245,21 @@ gfx_putc(int c, uint32 color)
// gfx_draw_char paints its background from the file-static gfx_bg, which is
// right for a console that has one background and wrong for a terminal where
// every cell carries its own.
void
gfx_draw_cell(int col, int row, uint32 cp, uint32 fg, uint32 bg)
// One glyph, at a pixel position, in a cell-sized box.
//
// Both the console grid and the pixel-positioned text in the crash screen come
// through here, which is the point: they used to be two implementations, and
// only one of them was updated when the cell size stopped being 8x16.
// gfx_draw_char() still walked `font_data[c][row]` for `row < CHAR_HEIGHT` --
// and CHAR_HEIGHT is the *TrueType* cell height now, while font_data has
// exactly 16 rows per glyph. Every line of PSOGM therefore drew five rows of
// the *next character's* bitmap underneath itself. It looked like each line
// had been printed twice.
static void
draw_glyph_px(int x, int y, uint32 cp, uint32 fg, uint32 bg)
{
if (!fb)
return;
int x = col * cell_w, y = row * cell_h;
if (x < 0 || y < 0 ||
x + cell_w > (int)scanout.width || y + cell_h > (int)scanout.height)
return;
@ -261,11 +268,15 @@ gfx_draw_cell(int col, int row, uint32 cp, uint32 fg, uint32 bg)
// drawn as a space rather than as the wrong glyph -- `?` was at least honest
// about being a substitute, and a CP437 picture of something unrelated is
// not.
//
// BITMAP_W/H and not cell_w/h: the bitmap's geometry is a property of
// font_data, not of whatever the console decided a cell is. They agree
// whenever cfont is null, and the assert below is what keeps that true.
if (!cfont) {
uchar g = cp < 256 ? (uchar)cp : ' ';
for (int r = 0; r < cell_h; r++) {
for (int r = 0; r < BITMAP_H; r++) {
uchar bits = font_data[g][r];
for (int c = 0; c < cell_w; c++)
for (int c = 0; c < BITMAP_W; c++)
gfx_draw_pixel(x + c, y + r, (bits & (0x80 >> c)) ? fg : bg);
}
return;
@ -303,6 +314,12 @@ gfx_draw_cell(int col, int row, uint32 cp, uint32 fg, uint32 bg)
}
}
void
gfx_draw_cell(int col, int row, uint32 cp, uint32 fg, uint32 bg)
{
draw_glyph_px(col * cell_w, row * cell_h, cp, fg, bg);
}
// Bring the console typeface up. Called once, early, from the display path.
//
// The font is linked into the kernel (see CONSFONT in the Makefile) because