Skip to content

feat(color): expand the palette to 64 tiered slots, with a colors example - #48

Open
dsent wants to merge 3 commits into
aldum:devfrom
dsent:dsent/color-palette-64
Open

feat(color): expand the palette to 64 tiered slots, with a colors example#48
dsent wants to merge 3 commits into
aldum:devfrom
dsent:dsent/color-palette-64

Conversation

@dsent

@dsent dsent commented Aug 4, 2026

Copy link
Copy Markdown
Collaborator

What

  • expand Color from 16 to 64 slots, in four tiers of sixteen
  • add 24 hues with name constants: orange, brown, tan, yellowgreen, skyblue, purple, pink, gray, coral, gold, limegreen, springgreen, turquoise, azure, slateblue, crimson, khaki, salmon, mint, powderblue, lavender, orchid, royalblue, slategray
  • widen Color.valid from c < 16 to c < 64
  • make __index gate on Color.valid, so every index it rejects returns nil
  • correct the legacy hex comments, which claimed #bf01bf for magenta and #01bf01 for green where the values are #BF00BF and #00BF00
  • add a colors example that prints the whole palette and paints a picture using only named colors

Why

Eight hues is a thin box of pencils. Children reach for orange, brown, pink and purple immediately, and there was no way to name any of them — a drawing program either settled for the wrong color or dropped to raw RGB, which is exactly what Color exists to avoid.

The tiers are ordered by how much they earn their place, so the palette can be cut at any 16-slot boundary without losing expressiveness:

Slots What lives there
0-15 the existing eight, unchanged
16-31 the everyday extras a pencil box has
32-47 hues that even out the color wheel
48-63 soft pastels, droppable

How it works

One rule covers all 64 slots. The palette is 32 vivid colors; each sits in a bright slot, and the normal slot below it is that color at 0.75 strength. That is already how 0-15 was built — bright was 1 and plain was 0.75 — so the change generalizes the existing scheme rather than bolting a second one beside it.

A bare name still resolves to the normal slot, as Color.red always has; + Color.bright selects the vivid one. Lazy computation and caching in __index are unchanged.

Naming follows CSS except where the pencil-box meaning differs. brown is #964B00: CSS brown is #A52A2A, which is hue 0 and reads as brick red on the device.

Compatibility

Indices 0-15 keep their values byte for byte, and the 8 existing name constants are untouched. Indices 16-63 were rejected by Color.valid before this change, so nothing correct depended on them.

One behavior change worth stating plainly: Color[n] is now nil for any index Color.valid rejects — negative, fractional, 64 and up, and non-numbers. Previously those fell through to the bit decomposition and came back as a recycled-bit color; Color[-1] returned a gray. Color.valid rejected them before and after, so no correct program is affected, but a program misusing an index now fails on nil at gfx.setColor instead of quietly drawing something wrong.

The example

src/examples/colors has two screens, toggled with Space: every slot with its number and name, and a picture drawn entirely from named colors. Its README teaches the one idea a child needs — a color name is a number, so Color.red + Color.bright is just 2 + 8.

Validation

  • full suite under LuaJIT: 686 successes, 0 failures, 0 errors — identical to the count on unmodified dev
  • all 64 slots return four channels in 0..1; all 32 vivid slots match their intended hex; every normal slot is its vivid slot at 0.75 strength; all 32 names map to a normal slot; 0-15 reproduce the legacy bit formula byte for byte; the nine rejected index forms all yield nil — 662 assertions, 0 failures. The same assertions run against unmodified dev fail 131 times, so they are not vacuous.
  • rendered every slot on a real Compy and sampled the screenshot: 64/64 pixel-exact against the intended palette. The same program on a build without this change renders 16-63 wrong.
  • just zip-examples-all packages colors.compy alongside the existing examples

dsent and others added 2 commits August 4, 2026 18:18
The Color table offered 8 hues at 2 brightness levels, so programs had no
orange, brown, pink, purple, or mid grey at all.

The palette is now defined by 32 vivid colors, each occupying a bright slot,
with its normal slot at 0.75x that value. That is already how indices 0-15
were built, so one rule covers all 64 slots and the existing region stops
being a special case. Indices 0-15 keep their values byte for byte; 16-63
were unused, rejected by Color.valid and returning recycled-bit values, so
the change is additive.

New hues sit in three tiers of eight, ordered essential, hue-wheel coverage,
and nice-to-have, so the palette can be read or trimmed a tier at a time.
Bare names resolve to the normal slot, as Color.red always has, and
+ Color.bright selects the vivid one.

Names follow recognizable pencil-box meanings where those differ from CSS:
brown is #964B00, since CSS brown #A52A2A is hue 0 and reads as brick red.
The palette already differed from CSS, with red at #BF0000 and green at
#00BF00.

Also corrects the hex comments on the legacy names, which claimed #bf01bf
for magenta and #01bf01 for green where the real values are #BF00BF and
#00BF00.

Verified: all 64 slots and 32 name constants match the intended values,
indices 0-15 are unchanged, the suite passes at 756 tests, and an on-device
render of every slot matches the intended palette pixel for pixel.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019xHYUVmU9C8AcqhUsBFje4
Color[64] returned nil while Color[-1] returned a color, because the
guard on the palette lookup only bounded the top. Anything below 16 fell
into the bit decomposition, which never had a bounds check, so negative
and fractional indices came back as recycled-bit colors.

Color.valid already defined what an index may be. It just was not
enforced where indexing happens; it sat beside __index as advice callers
had to remember to take. __index now gates on it, so the contract has
one definition and every rejected index behaves the same.

Color[-1], Color[1.5], Color[64] and Color["7"] are now all nil, while
0-63 and the 32 names are unchanged. This also subsumes the old
type(c) ~= 'number' check.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019xHYUVmU9C8AcqhUsBFje4
@dsent

dsent commented Aug 4, 2026

Copy link
Copy Markdown
Collaborator Author

Screens from the new example program, displaying the full palette.

compy-screenshot-20260804-183002 compy-screenshot-20260804-183009

@dsent
dsent requested a review from aldum August 4, 2026 16:32
Comment thread src/examples/colors/main.lua Outdated
Nothing bundled showed the palette, so the only way to learn what a
number draws was to try it.

The first screen lists all 64 colors, one name per line: the plain color
on the left, the same name plus Color.bright on the right, each entry
printed in the color it names. Space switches to a picture drawn only
with named colors, where the code reads brown and springgreen rather
than raw values.

Both black entries print invisibly against the background, which is what
black on black looks like; the README says so rather than leaving it a
puzzle.

The README teaches the palette as the program shows it: a name is a
number, adding Color.bright is arithmetic on that number, and the four
groups of sixteen are why adding 8 always moves between plain and
bright.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019xHYUVmU9C8AcqhUsBFje4
@dsent
dsent force-pushed the dsent/color-palette-64 branch from 8c64aea to ba587d6 Compare August 4, 2026 17:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants