Commit Graph
3 Commits
Author SHA1 Message Date
Nicholas ButzkeandClaude Opus 5 414026f001 feat(ui): every control state is checked for readability, not trusted
The palette has two light accents and one very dark one, and a control
changes its FILL on hover and press. Paper text that reads at 18:1 on the
resting near-black chip inverts to paper-on-yellow the moment the pointer
arrives, which is 1.1:1 — invisible. That is the standard way a stylised UI
becomes unreadable, and it was live on the OptionButton dropdown that every
settings row uses: PopupMenu draws its papaya hover fill but keeps
`font_color` unless `font_hover_color` is set, and it was not set.

So the label now follows the fill. `ink_for(fill)` picks the legible glyph
colour by contrast ratio, and every state's text, icon and outline is derived
from its own fill through it — including the states nobody remembers exist:
`hover_pressed` on a toggle (a CheckButton read as OFF while you touched it),
icon colours on a CheckBox that is all icon, and the list hover that used to
be the same papaya as list SELECTION, so the row you pointed at looked like
the row you had chosen.

debug/ui_contrast_check.gd interrogates the BUILT theme rather than the
palette — a table compared against itself agrees by construction and catches
nothing — and fails below the WCAG floor. It found the PopupMenu gap, a Tree
hover asking for a `font_hovered_color` Godot does not have, and the health
bar's readout at 2.4:1 over its own fill.

That last one is not fixable as a colour pair: a centred readout straddles a
hot papaya fill and a near-black trough, and no single colour beats both. What
carries it is the heavy ink outline this theme puts on every glyph, which is
its first stated rule and the same mechanism that keeps menu text legible
straight over the 3D scene. The check models that as a fallback route —
outline vs backdrop 3:1, glyph vs outline 4.5:1, at least 4 px — granted only
where the backdrop genuinely varies, and substituting two ratios for one
rather than waiving the requirement.

Disabled text moves from 3.2:1 to 5.2:1 on the way past. A greyed-out
"Start Match" is information; an illegible smudge is not.

Also styled, because the theme had simply never mentioned them and Godot's
defaults are grey-on-grey: scrollbars, Tree, SpinBox, ProgressBar, tooltips,
LineEdit read-only and selected text.

108 pairs checked, all passing.

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-07-28 02:39:25 -04:00
Nicholas ButzkeandClaude Opus 5 26f7c2c622 feat(ui): one high-voltage theme, and pick your character from the escape menu
The theme was a comic one — cream paper, ink borders, papaya. It is now a
charged one: near-black violet, hot papaya, and a lightning yellow spent
nowhere except the instant a button is pressed. Chips are cut with two sharp
corners and two round ones on a diagonal, which is as close to a skew as a
StyleBoxFlat gets and is the difference between a button that reads calm and
one that reads fast. BoltRule draws the motif itself, struck a third of the way
along its rule rather than centred, so it reads as something that HIT the line.

The pause menu was 900 lines of hand-rolled UI that never referenced UITheme at
all, so it rendered in Godot's default grey. It now applies the theme — and
applies it to its own root Control, not only to the Window, because a Control
inherits from its nearest Control ANCESTOR and this screen hangs off a
CanvasLayer, which is not one. That was invisible at first: the parts built with
UITheme.title() carry their own overrides and looked right next to a list and a
button that did not.

And it now has a Character screen. The roster on the left, the character
themselves on the right, turning — a name in a dropdown is not a character
selection screen. The preview is a real SkinnedPlayerModel in its own world, so
it shows exactly what will spawn: the same cel look, the same per-class
outlines, the same cloth and hair on springs. Selecting applies immediately;
there is nothing destructive to confirm, and applying on selection means the
character behind the menu changes as you arrow the list, which IS the
comparison.

PlayerMovementController.set_skin() is the supported way in. Both halves of a
skin change are easy to do by halves — `synced_skin_id` is what REMOTE peers
rebuild from, and only their _process watches it, so setting the property alone
would change everyone else's view of you and not your own.

Checked rather than asserted. debug/character_picker_check.gd walks the whole
roster and proves each entry loads a skeleton, animations, a surface table and
body surfaces — and that the skeleton is MOVING, because the clip name is a
variable this class sets on itself and reads "Idle" just as happily when nothing
is ticking. debug/ui_capture.gd and debug/roster_capture.gd photograph the
screens and every character, which is how three things were found that no
assertion could see: the theme break above, a preview showing the back of the
character's head, and a turntable that carried on from the last character so the
third one you looked at was side-on.

Known and not fixed here: momo's idle pose is wrong — arms overhead and a
pinched waist. Her rig, not the picker; every other character is correct.

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-07-27 14:46:14 -04:00
Nicholas ButzkeandClaude Fable 5 6c8b0ddd28 feat: cel-shaded look & feel overhaul — real audio, comic UI, themed VFX
Audio (all CC0 — Kenney packs + OpenGameArt, see assets/sounds/SOURCES.md):
- Replace every procedural synth WAV with forged sounds (ffmpeg pitch/layer
  mixes): distinct fire sounds per weapon with 2-3 variations, explosions,
  footsteps x5, land/jump/dash/vault, knife swing, hit/kill confirms,
  bullet impacts, reload, flight loops, UI hover/click/confirm/error,
  match jingles, menu music
- AudioManager.stream_for(): AudioStreamRandomizer per sound id — every
  weapon now gets variation + pitch randomization on each shot
- Explosions and bullet impacts play positional audio; landing has its own
  sound instead of a pitched footstep; ambient wind bed on every map

Visuals:
- ExplosionVFX: shared cel-shaded burst (white-hot stepped core, ink
  shockwave ring, star spikes, flat smoke puffs) replaces the orange
  sphere in both local and remote-replay paths
- Comic star muzzle flashes on all weapons; unified cel tracer bolts with
  ink outlines across hitscan/shotgun/remote paths
- Impact decals: hard-stepped ink-splat gradients instead of soft airbrush
- Toon shading on first-person view models + arms, third-person weapons,
  and the procedural humanoid fallback (no hull outlines on FBX weapons —
  their hard normals tear the inverted hull)
- Fix: giant soft "blob" highlight on floors — toon rim/specular disabled
  on level-geometry materials (pre-existing artifact since the cel commit)
- Fix: own third-person weapon rendered into the first-person camera
  (shadows-only now covers first_person_mode)

UI:
- UITheme: comic theme on the root window — Bangers display font (OFL),
  paper panels with thick ink borders + hard drop shadows, papaya accent,
  themed buttons/inputs/popups; hover/click sounds on all menu buttons
- Main menu: tilted comic wordmark, sunset toon diorama, looping menu music
- Match HUD: themed scoreboard/killfeed/kill counter

Viewports:
- 4x MSAA project-wide + viewmodel/diorama viewports (crisp ink lines)
- Viewmodel camera near plane 0.01; third-person camera FOV syncs settings
- debug/visual_capture.gd: dev tool to screenshot menu + level for review

Verified: 11/11 movement tests, spawn smoke test 0 failures, before/after
screenshot comparison of menu and level.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-07-17 18:15:42 -04:00