Structure level numbers: classic bitmap font by default + graphics toggle (#4264)

## What

Structure **level numbers** now render in the **`round_6x6_modified`**
bitmap font by default (matching the old PIXI-based `StructureLayer` /
`v31`), with a graphics setting to switch back to the smooth
`overpass-bold` MSDF font.

Two commits:

1. **Default to the classic bitmap font** — `StructureLevelPass` drew
level digits from the `overpass-bold` MSDF atlas (the one `NamePass`
uses for player names); switch the default to the `round_6x6_modified`
pixel font (white digits with a baked-in dark outline).
2. **Add a runtime toggle** — load both fonts and switch between them
live via a new `Classic level numbers` graphics setting.

## How

- `StructureLevelPass` loads both atlases up front and selects one per
frame from `settings.structureLevel.classicFont`, re-laying-out the
digits when the toggle flips (digit advances differ between the fonts).
The fragment shader is a single program with a `uClassic` branch: direct
bitmap sample (white fill + baked outline) vs. MSDF median + synthesized
outline.
- New override `structure.classicNumbers` in `GraphicsOverrides`
(default `true` = classic), applied onto
`settings.structureLevel.classicFont` in `applyGraphicsOverrides` — so
it switches live, like the existing colorblind/classic-icons toggles.
- `GraphicsSettingsModal` gets a `Classic level numbers` toggle next to
`Classic icons` (with `en.json` strings).

## Testing

- `tsc --noEmit`, ESLint, Prettier, and `npm run build-prod` all pass.
- Ran the game headless, built/upgraded cities to level 2–3, and
confirmed: the classic toggle renders the pixel font, flipping it
renders the smooth MSDF font, and flipping back restores the pixel font
— switching live with no shader errors.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Evan
2026-06-13 19:07:17 -07:00
committed by GitHub
parent f4db4a33c8
commit f76f133589
9 changed files with 245 additions and 69 deletions
+33
View File
@@ -38,6 +38,9 @@ describe("GraphicsOverridesSchema", () => {
{ structure: {} },
{ structure: { classicIcons: true } },
{ structure: { classicIcons: false } },
{ structure: { classicNumbers: true } },
{ structure: { classicNumbers: false } },
{ structure: { classicIcons: true, classicNumbers: false } },
{ name: { darkNames: true }, structure: { classicIcons: true } },
];
for (const c of cases) {
@@ -90,6 +93,11 @@ describe("GraphicsOverridesSchema", () => {
structure: { classicIcons: "yes" },
}).success,
).toBe(false);
expect(
GraphicsOverridesSchema.safeParse({
structure: { classicNumbers: "yes" },
}).success,
).toBe(false);
expect(
GraphicsOverridesSchema.safeParse({
mapOverlay: { territorySaturation: "full" },
@@ -254,6 +262,31 @@ describe("applyGraphicsOverrides", () => {
expect(absent.iconAlpha).toBe(0.9);
});
test("classicNumbers=true → classic bitmap font", () => {
expect(
gen({ structure: { classicNumbers: true } }).structureLevel.classicFont,
).toBe(true);
});
test("classicNumbers=false → smooth MSDF font", () => {
expect(
gen({ structure: { classicNumbers: false } }).structureLevel.classicFont,
).toBe(false);
});
test("classicNumbers absent → defaults to classic bitmap font", () => {
expect(gen({ structure: {} }).structureLevel.classicFont).toBe(true);
expect(gen({}).structureLevel.classicFont).toBe(true);
});
test("classicNumbers is independent of classicIcons", () => {
const s = gen({
structure: { classicIcons: false, classicNumbers: true },
});
expect(s.structureLevel.classicFont).toBe(true);
expect(s.structure.iconDarken).toBe(0);
});
test("applies territorySaturation override (including 0)", () => {
expect(
gen({ mapOverlay: { territorySaturation: 0.4 } }).mapOverlay