diff --git a/assets/branding/boot_splash.png b/assets/branding/boot_splash.png index 9e9d936..72fa92e 100644 Binary files a/assets/branding/boot_splash.png and b/assets/branding/boot_splash.png differ diff --git a/docs/BRANDING.md b/docs/BRANDING.md index eae4b06..acf6e52 100644 --- a/docs/BRANDING.md +++ b/docs/BRANDING.md @@ -9,10 +9,11 @@ The stock Godot boot splash (logo plus "GODOT Game engine") is replaced by the ## Asset -`assets/branding/boot_splash.png`, 1024x1185, palette PNG with alpha -(256 colors, 95 alpha levels), about 159 KB. Subject: cartoon zombie head and bust, -bald scalp with a sparse hair fringe on the sides, unsettling grin, thumbs up, with -"MatGames" below (one word, no space) in a phosphor-terminal typeface. +`assets/branding/boot_splash.png`, 1024x1315, palette PNG with alpha +(256 colors, 95 alpha levels), about 171 KB. Subject: cartoon zombie head and bust, +bald scalp with a sparse hair fringe on the sides, thick nerd glasses, unsettling grin, +thumbs up, with "MatGames" (one word, no space) in a phosphor-terminal typeface and +the subtitle `"...because he was bored..."` beneath it. Provenance is recorded in `tools/branding/manifest.json`: @@ -20,8 +21,10 @@ Provenance is recorded in `tools/branding/manifest.json`: `gemini-3.1-flash-image`) on 2026-10-11 from no third-party source material; the provider terms do not claim ownership of the output. A second AI pass (`agy_edit`) kept the face, grin, thumbs-up hand, shirt, pose and flat background and changed - only the hair to a bald scalp with a side fringe; both files and their SHA256 are - kept in `tools/branding/raw/` and the edit is recorded under `mascot_derived_from`. + only the hair to a bald scalp with a side fringe, then a third pass added thick nerd + glasses while keeping the face, grin, hand, shirt, pose and flat background. All + files and their SHA256 are kept in `tools/branding/raw/` and the chain is recorded + under `mascot_provenance`. Note that an image generator does not emit real alpha, so the flat magenta background is keyed out locally; - lettering: drawn by script, not by the generator, because generated text is @@ -45,8 +48,11 @@ packed into the export (`tools/*` is already excluded by the export preset). pink tongue or the blue shirt because their red and blue are not both above green; 4. crops to the subject, applies a 50 px fade at the bottom to soften the frame cut of the generated bust, and scales to 1024 px wide with 40 px side padding; -5. draws the title with VT323 in amber over a blurred glow layer. The title defaults - to the one-word form `MatGames`; pass `--title` to change it; +5. draws the title with VT323 in amber over a blurred glow layer, plus an optional + second line. Defaults are the one-word `MatGames` and the subtitle + `"...because he was bored..."`; `--title`, `--subtitle` (empty to omit) and + `--subtitle-height` change them. A 30 px `--bottom-padding` band is left under the + text so the shell progress bar does not cross the lettering; 6. quantizes to 256 colors (no dither) and writes the PNG plus the manifest. ```sh @@ -71,11 +77,11 @@ ratio; the exported shell confirms the result with the classes ## Verification -- Exported `index.png` has the same 1024x1185 dimensions as the master. It is - re-encoded by the exporter to 32-bit RGBA (about 496 KB), so the bytes are not - identical; composited on black the two images differ by at most 6.4/255 on - 0.13% of pixels, all with alpha below 20, i.e. invisible. The release keeps the - 159 KB master in the repository and ships a gzip sidecar. +- Exported `index.png` has the same 1024x1315 dimensions as the master. It is + re-encoded by the exporter to 32-bit RGBA (about 528 KB), so the bytes are not + identical; composited on black the two images differ by at most 5.8/255 on + 0.11% of pixels, all with alpha below 20, i.e. invisible. The release keeps the + 171 KB master in the repository and ships a gzip sidecar. - The exported shell contains no "Game engine" text and the splash image has an empty `alt`. - `html/export_icon=false`, so no favicon is exported: the browser tab still shows diff --git a/docs/WEB-DEPLOY.md b/docs/WEB-DEPLOY.md index f9c8426..7edace7 100644 --- a/docs/WEB-DEPLOY.md +++ b/docs/WEB-DEPLOY.md @@ -1,5 +1,19 @@ # Web build and compressed deployment +## Nerd glasses and subtitle release: f77b7ba7b89364dd + +The mascot now wears thick nerd glasses (third AI edit pass) and carries the subtitle +`"...because he was bored..."` under the title, see `docs/BRANDING.md`. A 30 px band +was left under the lettering because the shell progress bar crossed the subtitle in the +first attempt, deployed minutes earlier as cd86c85387c1c9f3 and superseded by this one. +PCK 4,373,568 bytes; gzip payload total 12,821,256 bytes. Backup +`index-before-nerd-bar.html` (the earlier `index-before-nerd.html` holds the +cd86c85387c1c9f3 shell). + +Obsolete releases after this deploy: 097e6c64b42640ad and 1194f239fcbd91d6, both +deletable under the retention policy once approved. + + ## Bald mascot release: 1194f239fcbd91d6 Title is now the one-word `MatGames` and the mascot has a bald scalp with a side diff --git a/tools/branding/manifest.json b/tools/branding/manifest.json index 0ce12fd..0415e05 100644 --- a/tools/branding/manifest.json +++ b/tools/branding/manifest.json @@ -3,43 +3,52 @@ "file": "assets/branding/boot_splash.png", "size": [ 1024, - 1185 + 1315 ], "mode": "P", "palette_colors": 256, - "sha256": "4b4bab87b5038915d5a7493c6ee134cd654293036d093e71d210cbad6359bdb2" + "sha256": "715bd5f1dd3a651b1b5f6ee0560db232ddfdad676091f07ea34696787e27fbee" }, "mascot_source": { - "file": "tools/branding/raw/agy_edit_20261010235510_0.jpg", - "sha256": "913184c2e2e3dd5357f7729988859c8e038e4561ba18af7cb60f12644f7f6994" + "file": "tools/branding/raw/agy_edit_20261011000309_0.jpg", + "sha256": "57bc1e46eb0876c02008038981c9b01a6bf4e02da78418de7b44f4fcbd93895e" }, "generator": { "tool": "agy_generate (Antigravity direct API)", "model": "gemini-3.1-flash-image", "date": "2026-10-11", - "prompt": "single cartoon zombie head-and-bust mascot, unsettling wide grin, thumbs up, 2D cartoon style with bold outlines, flat magenta chroma-key background, no text and no logo; then edited to a bald scalp with a sparse hair fringe on the sides only", + "prompt": "single cartoon zombie head-and-bust mascot, unsettling wide grin, thumbs up, 2D cartoon style with bold outlines, flat magenta chroma-key background, no text and no logo; then edited to a bald scalp with a sparse hair fringe on the sides only, then edited again to wear thick nerd glasses", "terms_note": "generated from no third-party source material; the provider terms do not claim ownership of the output. The generator emits no real alpha channel, so the background is keyed out locally by this script." }, "chroma_key": { "background_rgb": [ - 246, - 1, - 237 + 244, + 0, + 233 ], "transparent_below": 55, "opaque_above": 130 }, "title": { "text": "MatGames", + "subtitle": "\"...because he was bored...\"", "font": "tools/branding/fonts/VT323-Regular.ttf", "license": "SIL Open Font License 1.1 (tools/branding/fonts/OFL.txt)" }, "bottom_fade_px": 50, - "mascot_derived_from": [ + "mascot_provenance": [ { "file": "tools/branding/raw/agy_generate_20261010234423_0.jpg", - "sha256": "5929495ff606fbcdd74f52a37304f3b569315bcd1fc0fef812b5fd7689dc1396", - "edit": "AI edit (agy_edit): keep face, grin, thumbs up, shirt, pose and flat background; change the hair to a bald scalp with a sparse fringe on the sides only" + "sha256": "5929495ff606fbcdd74f52a37304f3b569315bcd1fc0fef812b5fd7689dc1396" + }, + { + "file": "tools/branding/raw/agy_edit_20261010235510_0.jpg", + "sha256": "913184c2e2e3dd5357f7729988859c8e038e4561ba18af7cb60f12644f7f6994" + }, + { + "file": "tools/branding/raw/agy_edit_20261011000309_0.jpg", + "sha256": "57bc1e46eb0876c02008038981c9b01a6bf4e02da78418de7b44f4fcbd93895e" } - ] + ], + "bottom_padding_px": 30 } diff --git a/tools/branding/raw/agy_edit_20261011000309_0.jpg b/tools/branding/raw/agy_edit_20261011000309_0.jpg new file mode 100644 index 0000000..b6b1bc6 Binary files /dev/null and b/tools/branding/raw/agy_edit_20261011000309_0.jpg differ diff --git a/tools/branding/raw/agy_edit_20261011000309_0.jpg.import b/tools/branding/raw/agy_edit_20261011000309_0.jpg.import new file mode 100644 index 0000000..039a819 --- /dev/null +++ b/tools/branding/raw/agy_edit_20261011000309_0.jpg.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://b5pj4ipovpbo" +path="res://.godot/imported/agy_edit_20261011000309_0.jpg-c4ed4776ca2aca40794a7d66a2e8befc.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://tools/branding/raw/agy_edit_20261011000309_0.jpg" +dest_files=["res://.godot/imported/agy_edit_20261011000309_0.jpg-c4ed4776ca2aca40794a7d66a2e8befc.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/tools/make_boot_splash.py b/tools/make_boot_splash.py index 17b121d..e1503d8 100644 --- a/tools/make_boot_splash.py +++ b/tools/make_boot_splash.py @@ -14,14 +14,19 @@ from PIL import Image, ImageDraw, ImageFilter, ImageFont parser = argparse.ArgumentParser() parser.add_argument('raw', type=pathlib.Path, help='raw generated image (magenta background)') parser.add_argument('--title', default='MatGames') +parser.add_argument('--subtitle', default='"...because he was bored..."', + help='second line under the title; empty string for none') +parser.add_argument('--subtitle-height', type=int, default=100) +parser.add_argument('--bottom-padding', type=int, default=30, + help='empty band under the text: the shell progress bar sits there') parser.add_argument('--canvas', type=int, default=1024) parser.add_argument('--title-height', type=int, default=190) parser.add_argument('--bottom-fade', type=int, default=50, help='soften the generator frame cut at the bottom of the bust') parser.add_argument('--colors', type=int, default=256, help='palette colors for the shipped PNG; 0 keeps 32-bit RGBA') -parser.add_argument('--generated-from', type=pathlib.Path, default=None, - help='pre-edit generation this file was derived from, for provenance') +parser.add_argument('--provenance', action='append', type=pathlib.Path, default=[], + help='earlier files of the generation/edit chain, in order; repeatable') args = parser.parse_args() args.raw = args.raw.resolve() root = pathlib.Path(__file__).resolve().parents[1] @@ -70,27 +75,34 @@ if args.bottom_fade > 0: # ---------------------------------------------------------------- composition canvas_width = args.canvas -title_area = args.title_height +title_area = args.title_height + (args.subtitle_height if args.subtitle else 0) scale = (canvas_width - 2 * 40) / mascot.width mascot = mascot.resize((round(mascot.width * scale), round(mascot.height * scale)), Image.LANCZOS) -height = mascot.height + title_area +height = mascot.height + title_area + args.bottom_padding canvas = Image.new('RGBA', (canvas_width, height), (0, 0, 0, 0)) canvas.alpha_composite(mascot, ((canvas_width - mascot.width) // 2, 0)) -font_size = int(title_area * 0.95) -font = ImageFont.truetype(str(font_path), font_size) -text = args.title -while font.getbbox(text)[2] - font.getbbox(text)[0] > canvas_width - 60 and font_size > 8: - font_size -= 2 - font = ImageFont.truetype(str(font_path), font_size) -box = font.getbbox(text) -text_width = box[2] - box[0] -position = ((canvas_width - text_width) // 2 - box[0], mascot.height + (title_area - box[3]) // 2 - box[1]) -glow = Image.new('RGBA', canvas.size, (0, 0, 0, 0)) -ImageDraw.Draw(glow).text(position, text, font=font, fill=(255, 150, 20, 190)) -glow = glow.filter(ImageFilter.GaussianBlur(9)) -canvas.alpha_composite(glow) -ImageDraw.Draw(canvas).text(position, text, font=font, fill=(255, 196, 64, 255)) + +def _draw_line(text: str, block_top: int, block_height: int, size: int, + glow_color: tuple, text_color: tuple, blur: int) -> None: + font = ImageFont.truetype(str(font_path), size) + while font.getbbox(text)[2] - font.getbbox(text)[0] > canvas_width - 60 and size > 8: + size -= 2 + font = ImageFont.truetype(str(font_path), size) + box = font.getbbox(text) + position = ((canvas_width - (box[2] - box[0])) // 2 - box[0], + block_top + (block_height - box[3]) // 2 - box[1]) + glow = Image.new('RGBA', canvas.size, (0, 0, 0, 0)) + ImageDraw.Draw(glow).text(position, text, font=font, fill=glow_color) + canvas.alpha_composite(glow.filter(ImageFilter.GaussianBlur(blur))) + ImageDraw.Draw(canvas).text(position, text, font=font, fill=text_color) + + +_draw_line(args.title, mascot.height, args.title_height, int(args.title_height * 0.95), + (255, 150, 20, 190), (255, 196, 64, 255), 9) +if args.subtitle: + _draw_line(args.subtitle, mascot.height + args.title_height, args.subtitle_height, + int(args.subtitle_height * 0.8), (255, 150, 20, 150), (255, 176, 58, 240), 6) out = branding / 'boot_splash.png' if args.colors > 0: @@ -111,23 +123,22 @@ manifest.update({ 'prompt': ('single cartoon zombie head-and-bust mascot, unsettling wide grin, thumbs up, ' '2D cartoon style with bold outlines, flat magenta chroma-key background, ' 'no text and no logo; then edited to a bald scalp with a sparse hair fringe ' - 'on the sides only'), + 'on the sides only, then edited again to wear thick nerd glasses'), 'terms_note': ('generated from no third-party source material; the provider terms do not claim ' 'ownership of the output. The generator emits no real alpha channel, so the ' 'background is keyed out locally by this script.'), }, 'chroma_key': {'background_rgb': list(background), 'transparent_below': T0, 'opaque_above': T1}, - 'title': {'text': args.title, 'font': 'tools/branding/fonts/VT323-Regular.ttf', + 'title': {'text': args.title, 'subtitle': args.subtitle, + 'font': 'tools/branding/fonts/VT323-Regular.ttf', 'license': 'SIL Open Font License 1.1 (tools/branding/fonts/OFL.txt)'}, 'bottom_fade_px': args.bottom_fade, + 'bottom_padding_px': args.bottom_padding, }) -if args.generated_from is not None: - chain = manifest.get('mascot_derived_from', []) - manifest['mascot_derived_from'] = chain + [{ - 'file': str(args.generated_from.resolve().relative_to(root)), - 'sha256': hashlib.sha256(args.generated_from.read_bytes()).hexdigest(), - 'edit': 'AI edit (agy_edit): keep face, grin, thumbs up, shirt, pose and flat background; ' - 'change the hair to a bald scalp with a sparse fringe on the sides only', - }] +chain = list(args.provenance) + [args.raw] +manifest['mascot_provenance'] = [ + {'file': str(path.resolve().relative_to(root)), + 'sha256': hashlib.sha256(path.read_bytes()).hexdigest()} for path in chain] +manifest.pop('mascot_derived_from', None) (source_tools / 'manifest.json').write_text(json.dumps(manifest, indent=2) + '\n') print(out, canvas.size, canvas.mode, 'mascot', mascot.size)