Presentation only. No generation file is touched — Topography, TerrainNoise, IslandFalloff, Pass1Result, TerrainGenConfig and GenerationScale are all unchanged. Batch naming, fixed in code. The prefix is the AUTHORING TASK number, not a running counter: 04_review means "the batch task 04 authored", not "the fifth batch". It had already drifted — tasks 02 and 03 produced 00_smoke through 05_trophy_10240 across two tasks, so no folder name said which task made what. Now the task number is an explicit argument to ToolingPaths.BatchRoot(taskNumber, descriptor), which composes the prefix itself and REFUSES a descriptor that carries its own. All three tools take it as ISLA_TASK. Verified: ISLA_BATCH=05_foo is refused with a stated reason and exit 2, and creates no folder. Also fixed: an exception out of _Ready does not stop Godot — it logs and the process sits there with no main loop, so a misconfigured run HUNG rather than failing. A hang looks like slow work, which is worse than a crash. The batch tools now catch, print what was refused, and exit non-zero. Grayscale mode: normalize a field to its own [min,max]. This is the one place per-image normalization is correct — everywhere else anchors are fixed so images compare, but here the point is to see one field at full contrast. The range is printed and indexed so a shade reads back to a height. You cannot judge noise through a palette: a ramp bends the distribution, a hillshade adds shape the data does not have. The wide Costa-Rica palette, and the reframing the developer asked for: the pretty map is the WIDE GRADIENT RENDERED FLAT, and relief is no longer the hero. On raw pass-1 noise a hillshade has nothing coherent to shade, so it renders fine fractal bumpiness as fuzz that actively hides the elevation the colour is showing. Strong hillshade is retained as diagnostic_relief and labelled a dev view — its bumpiness is exaggerated slope, not extra terrain. Subtle relief is kept for comparison, with the honest note that it still fuzzes until Phase 2 carves coherent landforms. Legend: a colour bar with ticks drawn from the palette's own ramp, so it cannot drift from the map beside it. Ticks are RELATIVE height and the image says NOT METRES — the conversion is Phase 2's, and labelling it "m" would invent a fact in the artefact a reader most trusts. Text comes from a 5x7 bitmap font written for this, specifically so a legend does not drag in the SubViewport capture path.
114 lines
6 KiB
C#
114 lines
6 KiB
C#
using System.Collections.Generic;
|
||
using Godot;
|
||
|
||
namespace IslaApocalypse.Tools
|
||
{
|
||
/// <summary>
|
||
/// A 5x7 bitmap font, drawn straight into a <see cref="Image"/>.
|
||
///
|
||
/// ═══ WHY A HAND-ROLLED FONT AND NOT GODOT'S ═══
|
||
///
|
||
/// Godot's <c>Font</c> renders through a CanvasItem, which means a viewport — the SubViewport
|
||
/// capture path the renderer deliberately avoids, because it awaits render frames and is why
|
||
/// `--headless` hangs. A legend is a handful of digits; paying the whole capture path for it
|
||
/// would be a poor trade, and would drag a frame-loop dependency into a tool that currently
|
||
/// needs no display at all.
|
||
///
|
||
/// So: 5x7 glyphs, integer-scaled, drawn per-pixel. Digits, a few punctuation marks and A–Z.
|
||
/// Unknown characters draw as blank rather than throwing — a legend with a missing letter is a
|
||
/// cosmetic problem, not a reason to lose the render.
|
||
/// </summary>
|
||
public static class TinyFont
|
||
{
|
||
public const int GlyphW = 5;
|
||
public const int GlyphH = 7;
|
||
|
||
// Each glyph is 7 rows of 5 columns. '#' = ink. Written out rather than hex-packed so a
|
||
// reader can verify a letter by looking at it.
|
||
private static readonly Dictionary<char, string[]> Glyphs = new()
|
||
{
|
||
[' '] = new[] { ".....", ".....", ".....", ".....", ".....", ".....", "....." },
|
||
['.'] = new[] { ".....", ".....", ".....", ".....", ".....", "..##.", "..##." },
|
||
['-'] = new[] { ".....", ".....", ".....", "#####", ".....", ".....", "....." },
|
||
[':'] = new[] { ".....", "..##.", "..##.", ".....", "..##.", "..##.", "....." },
|
||
['/'] = new[] { "....#", "...#.", "...#.", "..#..", ".#...", ".#...", "#...." },
|
||
['('] = new[] { "...#.", "..#..", ".#...", ".#...", ".#...", "..#..", "...#." },
|
||
[')'] = new[] { ".#...", "..#..", "...#.", "...#.", "...#.", "..#..", ".#..." },
|
||
|
||
['0'] = new[] { ".###.", "#...#", "#..##", "#.#.#", "##..#", "#...#", ".###." },
|
||
['1'] = new[] { "..#..", ".##..", "..#..", "..#..", "..#..", "..#..", ".###." },
|
||
['2'] = new[] { ".###.", "#...#", "....#", "...#.", "..#..", ".#...", "#####" },
|
||
['3'] = new[] { "#####", "...#.", "..#..", "...#.", "....#", "#...#", ".###." },
|
||
['4'] = new[] { "...#.", "..##.", ".#.#.", "#..#.", "#####", "...#.", "...#." },
|
||
['5'] = new[] { "#####", "#....", "####.", "....#", "....#", "#...#", ".###." },
|
||
['6'] = new[] { "..##.", ".#...", "#....", "####.", "#...#", "#...#", ".###." },
|
||
['7'] = new[] { "#####", "....#", "...#.", "..#..", ".#...", ".#...", ".#..." },
|
||
['8'] = new[] { ".###.", "#...#", "#...#", ".###.", "#...#", "#...#", ".###." },
|
||
['9'] = new[] { ".###.", "#...#", "#...#", ".####", "....#", "...#.", ".##.." },
|
||
|
||
['A'] = new[] { ".###.", "#...#", "#...#", "#####", "#...#", "#...#", "#...#" },
|
||
['B'] = new[] { "####.", "#...#", "#...#", "####.", "#...#", "#...#", "####." },
|
||
['C'] = new[] { ".###.", "#...#", "#....", "#....", "#....", "#...#", ".###." },
|
||
['D'] = new[] { "###..", "#..#.", "#...#", "#...#", "#...#", "#..#.", "###.." },
|
||
['E'] = new[] { "#####", "#....", "#....", "####.", "#....", "#....", "#####" },
|
||
['F'] = new[] { "#####", "#....", "#....", "####.", "#....", "#....", "#...." },
|
||
['G'] = new[] { ".###.", "#...#", "#....", "#.###", "#...#", "#...#", ".####" },
|
||
['H'] = new[] { "#...#", "#...#", "#...#", "#####", "#...#", "#...#", "#...#" },
|
||
['I'] = new[] { ".###.", "..#..", "..#..", "..#..", "..#..", "..#..", ".###." },
|
||
['J'] = new[] { "..###", "...#.", "...#.", "...#.", "...#.", "#..#.", ".##.." },
|
||
['K'] = new[] { "#...#", "#..#.", "#.#..", "##...", "#.#..", "#..#.", "#...#" },
|
||
['L'] = new[] { "#....", "#....", "#....", "#....", "#....", "#....", "#####" },
|
||
['M'] = new[] { "#...#", "##.##", "#.#.#", "#.#.#", "#...#", "#...#", "#...#" },
|
||
['N'] = new[] { "#...#", "##..#", "#.#.#", "#..##", "#...#", "#...#", "#...#" },
|
||
['O'] = new[] { ".###.", "#...#", "#...#", "#...#", "#...#", "#...#", ".###." },
|
||
['P'] = new[] { "####.", "#...#", "#...#", "####.", "#....", "#....", "#...." },
|
||
['Q'] = new[] { ".###.", "#...#", "#...#", "#...#", "#.#.#", "#..#.", ".##.#" },
|
||
['R'] = new[] { "####.", "#...#", "#...#", "####.", "#.#..", "#..#.", "#...#" },
|
||
['S'] = new[] { ".####", "#....", "#....", ".###.", "....#", "....#", "####." },
|
||
['T'] = new[] { "#####", "..#..", "..#..", "..#..", "..#..", "..#..", "..#.." },
|
||
['U'] = new[] { "#...#", "#...#", "#...#", "#...#", "#...#", "#...#", ".###." },
|
||
['V'] = new[] { "#...#", "#...#", "#...#", "#...#", "#...#", ".#.#.", "..#.." },
|
||
['W'] = new[] { "#...#", "#...#", "#...#", "#.#.#", "#.#.#", "##.##", "#...#" },
|
||
['X'] = new[] { "#...#", "#...#", ".#.#.", "..#..", ".#.#.", "#...#", "#...#" },
|
||
['Y'] = new[] { "#...#", "#...#", ".#.#.", "..#..", "..#..", "..#..", "..#.." },
|
||
['Z'] = new[] { "#####", "....#", "...#.", "..#..", ".#...", "#....", "#####" },
|
||
};
|
||
|
||
/// <summary>Pixel width of <paramref name="text"/> at the given integer scale.</summary>
|
||
public static int Width(string text, int scale) => text.Length * (GlyphW + 1) * scale;
|
||
|
||
/// <summary>Pixel height of one line at the given integer scale.</summary>
|
||
public static int Height(int scale) => GlyphH * scale;
|
||
|
||
/// <summary>
|
||
/// Draw <paramref name="text"/> with its top-left at (x, y). Uppercases automatically —
|
||
/// the font has no lowercase, and silently dropping letters would be worse than shouting.
|
||
/// </summary>
|
||
public static void Draw(Image img, string text, int x, int y, int scale, Color color)
|
||
{
|
||
int cursor = x;
|
||
foreach (char raw in text.ToUpperInvariant())
|
||
{
|
||
if (Glyphs.TryGetValue(raw, out string[] g))
|
||
{
|
||
for (int row = 0; row < GlyphH; row++)
|
||
for (int col = 0; col < GlyphW; col++)
|
||
if (g[row][col] == '#')
|
||
FillCell(img, cursor + col * scale, y + row * scale, scale, color);
|
||
}
|
||
cursor += (GlyphW + 1) * scale;
|
||
}
|
||
}
|
||
|
||
private static void FillCell(Image img, int px, int py, int scale, Color color)
|
||
{
|
||
for (int dy = 0; dy < scale; dy++)
|
||
for (int dx = 0; dx < scale; dx++)
|
||
{
|
||
int tx = px + dx, ty = py + dy;
|
||
if (tx >= 0 && ty >= 0 && tx < img.GetWidth() && ty < img.GetHeight())
|
||
img.SetPixel(tx, ty, color);
|
||
}
|
||
}
|
||
}
|
||
}
|