Skip to content

Commit 1580293

Browse files
committed
Complete Human palette and Step Range behavior - PR_26159_042-colors-human-step-range-followup
1 parent 956ba12 commit 1580293

5 files changed

Lines changed: 226 additions & 25 deletions

File tree

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
# PR_26159_042 Colors Human Step Range Follow-up Report
2+
3+
Generated: 2026-06-08
4+
5+
## Executive Summary
6+
7+
Status: PASS
8+
9+
This follow-up completes the two incomplete PR_26159_041 behaviors: Step Range now visibly moves generated rows from subtle tint/shade to near-white/near-black extremes while keeping the center row stable, and the Human palette now includes a fuller curated character palette.
10+
11+
## Missing from PR_26159_041
12+
13+
| Item | PR041 Status | Follow-up Result |
14+
| --- | --- | --- |
15+
| Step Range max should visibly approach white at the top and black at the bottom. | PARTIAL | PR041 only scaled the old contrast distance and still clipped final lightness to 10-90, so max range could not approach true extremes. PR042 fixes the generation math and final lightness clamp path. |
16+
| Step Range min should be subtle tint/shade around the center color. | PARTIAL | PR041 minimum collapsed too close to no visible movement. PR042 keeps a subtle 2-point tint/shade floor. |
17+
| Center row remains the base palette color. | PARTIAL | PR041 still clipped center colors through the old 10-90 final clamp. PR042 preserves base lightness for the center row. |
18+
| Human mixed palette should be a fuller character palette. | PARTIAL | PR041 had separate support groups but the `Human` mixed type was only an 8-swatch strip. PR042 expands it to 20 named character swatches. |
19+
20+
## Step Range Evidence
21+
22+
Validated sample: Nature / Forest / Full, grid column 4 (`#5D8A3E` base), Steps = 1, Contrast = 40.
23+
24+
| Step Range | Top Row | Center Row | Bottom Row | Evidence |
25+
| --- | --- | --- | --- | --- |
26+
| 0 | `#629141` | `#5D8A3E` | `#58833B` | Subtle tint/shade around center. |
27+
| 50 | `#80B65C` | `#5D8A3E` | `#3B5727` | Default preserves prior visual distance. |
28+
| 100 | `#FFFFFF` | `#5D8A3E` | `#000000` | Maximum intentionally reaches white/black limits for top/bottom extremes. |
29+
30+
Playwright verifies the same behavior by checking that Step Range 0 has a smaller lightness spread than default, Step Range 100 has a larger spread than default, top rows get lighter as Step Range increases, bottom rows get darker as Step Range increases, and the center color stays unchanged.
31+
32+
## Human Palette Review
33+
34+
PR041 Human coverage was useful but not sufficient. It had good grouped support types, but the combined `Human` palette was too small for a character-building source. PR042 expands the combined Human palette to the requested 20 named base swatches:
35+
36+
- Deep Skin: `#3A2118`
37+
- Dark Skin: `#5A3224`
38+
- Medium Skin: `#8A5A3D`
39+
- Olive Skin: `#9A7B4F`
40+
- Light Skin: `#D7A982`
41+
- Pale Skin: `#F0D1BA`
42+
- Warm Highlight: `#FFE0C2`
43+
- Cool Shadow: `#2A2E38`
44+
- Black Hair: `#0D0A08`
45+
- Brown Hair: `#4B2C1A`
46+
- Auburn Hair: `#8A3E24`
47+
- Blonde Hair: `#C6A15D`
48+
- Gray Hair: `#B8B8B2`
49+
- Eye Blue: `#3B6EA5`
50+
- Eye Green: `#3F7A52`
51+
- Eye Brown: `#5A321E`
52+
- Cloth Navy: `#273D5F`
53+
- Cloth Red: `#8F2F3D`
54+
- Cloth Green: `#4F6B45`
55+
- Cloth Neutral: `#B2A08A`
56+
57+
Generated names remain human-readable and include the Hex value through the existing generated-name format.
58+
59+
## Requirement Checklist
60+
61+
| Requirement | Status | Evidence |
62+
| --- | --- | --- |
63+
| Read `docs_build/dev/PROJECT_INSTRUCTIONS.md` first. | PASS | Project instructions were read before branch validation and edits. |
64+
| HARD STOP if current branch is not `main`. | PASS | `git branch --show-current` returned `main`; local branches found: `backup-before-workspace-cleanup`, `docs/engine-core-boundary`, `main`. |
65+
| Ask/report requested PR041 items still missing or incomplete. | PASS | See `Missing from PR_26159_041` section. |
66+
| Step Range moves rows closer to white at the top and black at the bottom. | PASS | `toolbox/colors/colors.js` interpolates from default target toward 100/0 extremes; Playwright validates top and bottom lightness movement. |
67+
| Step Range minimum is subtle tint/shade around center. | PASS | Step Range 0 evidence: top `#629141`, center `#5D8A3E`, bottom `#58833B`. |
68+
| Step Range maximum approaches white and black. | PASS | Step Range 100 evidence: top `#FFFFFF`, center `#5D8A3E`, bottom `#000000`. |
69+
| Center row remains base palette color. | PASS | Generator returns unclipped base lightness for center rows; Playwright validates center stability across min/default/max. |
70+
| Do not force pure white/black unless maximum reaches that limit. | PASS | Non-maximum values remain tinted/shaded; maximum intentionally reaches white/black for extreme rows. |
71+
| Add report evidence for min/default/max behavior. | PASS | See `Step Range Evidence` section. |
72+
| Review Human swatches and report sufficiency. | PASS | See `Human Palette Review` section. |
73+
| Expand Human into fuller curated character palette. | PASS | `toolbox/colors/colors.js` expands the `Human` type to 20 named base swatches. |
74+
| Include deep/dark/medium/olive/light/pale skin tones. | PASS | Human type includes all requested skin-tone entries. |
75+
| Include warm highlight and cool shadow. | PASS | Human type includes `Warm Highlight` and `Cool Shadow`. |
76+
| Include black/brown/auburn/blonde/gray hair. | PASS | Human type includes all requested hair entries. |
77+
| Include blue/green/brown eyes. | PASS | Human type includes `Eye Blue`, `Eye Green`, and `Eye Brown`. |
78+
| Include navy/red/green/neutral cloth tones. | PASS | Human type includes all requested cloth entries. |
79+
| Keep generated names human-readable and include Hex. | PASS | `generatorSwatchName()` still uses curated names plus `swatchColorKey(hex)`; Playwright validates generated Human names. |
80+
| Validate Step Range min/default/max visibly changes distance. | PASS | Targeted Palette Playwright passed. |
81+
| Validate top rows move closer to white as Step Range increases. | PASS | Targeted Palette Playwright passed. |
82+
| Validate bottom rows move closer to black as Step Range increases. | PASS | Targeted Palette Playwright passed. |
83+
| Validate center/base row remains stable. | PASS | Targeted Palette Playwright passed. |
84+
| Validate Human has curated swatches for requested groups. | PASS | Targeted Palette Playwright validates exact 20 Human hexes and names. |
85+
| Validate Add/Update/Clear show no Symbol validation. | PASS | Targeted Palette Playwright passed; focused scan found no active Symbol validation. |
86+
| Validate no console errors. | PASS | Targeted Palette Playwright passed with existing console-error checks. |
87+
| Playwright impacted: Yes. | PASS | Tool runtime/UI behavior changed; targeted Palette Tool lane was run. |
88+
89+
## Validation Evidence
90+
91+
| Lane | Status | Command | Evidence |
92+
| --- | --- | --- | --- |
93+
| Branch gate | PASS | `git branch --show-current` | Returned `main`. |
94+
| Runtime syntax | PASS | `node --check toolbox/colors/colors.js` | Step Range and Human palette runtime parse. |
95+
| Test syntax | PASS | `node --check tests/playwright/tools/PaletteToolMockRepository.spec.mjs` | Follow-up assertions parse. |
96+
| Repository syntax | PASS | `node --check src/dev-runtime/persistence/tool-repositories/palette-workspace-repository.js` | Metadata helper remains parse-clean. |
97+
| Targeted Palette Playwright | PASS | `npx playwright test tests/playwright/tools/PaletteToolMockRepository.spec.mjs` | 8 passed. |
98+
| Static whitespace | PASS | `git diff --check` | No whitespace errors; Git reported line-ending warnings only. |
99+
| Active Symbol scan | PASS | `rg -n "Symbol: Enter a symbol|data-palette-symbol|palette.*symbol|symbol" toolbox/colors src/dev-runtime/persistence/tool-repositories/palette-workspace-repository.js tests/playwright/tools/PaletteToolMockRepository.spec.mjs` | Remaining hits are intentional Symbol-free test fixture names only. |
100+
101+
## Skipped Lanes
102+
103+
| Lane | Reason |
104+
| --- | --- |
105+
| Full samples validation | Safe to skip because this PR does not touch samples, sample loader/runtime code, or shared sample framework behavior. |
106+
| Broad Playwright suite | Safe to skip because the targeted Palette Tool lane covers the impacted Colors runtime/UI behavior and console assertions. |

docs_build/dev/reports/coverage_changed_js_guardrail.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ Source: Playwright/Chromium built-in V8 coverage from the active Playwright run.
77

88
Changed runtime JS files considered:
99
(0%) src/dev-runtime/persistence/tool-repositories/palette-workspace-repository.js - WARNING: changed runtime JS file was not collected by Playwright V8 coverage; advisory only
10-
(86%) toolbox/colors/colors.js - executed lines 2061/2061; executed functions 177/206
10+
(86%) toolbox/colors/colors.js - executed lines 2093/2093; executed functions 177/206
1111

1212
Guardrail warnings:
1313
(0%) src/dev-runtime/persistence/tool-repositories/palette-workspace-repository.js - WARNING: changed runtime JS file missing from coverage; advisory only

docs_build/dev/reports/playwright_v8_coverage_report.txt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,14 +18,14 @@ Exercised tool entry points detected:
1818

1919
Changed runtime JS files covered:
2020
(0%) src/dev-runtime/persistence/tool-repositories/palette-workspace-repository.js - WARNING: changed runtime JS file was not collected by Playwright V8 coverage; advisory only
21-
(86%) toolbox/colors/colors.js - executed lines 2061/2061; executed functions 177/206
21+
(86%) toolbox/colors/colors.js - executed lines 2093/2093; executed functions 177/206
2222

2323
Files with executed line/function counts where available:
2424
(58%) src/engine/api/server-api-client.js - executed lines 159/159; executed functions 11/19
2525
(64%) assets/theme-v2/js/gamefoundry-partials.js - executed lines 525/525; executed functions 28/44
2626
(64%) assets/theme-v2/js/tool-display-mode.js - executed lines 201/201; executed functions 9/14
2727
(78%) toolbox/tool-registry-api-client.js - executed lines 148/148; executed functions 21/27
28-
(86%) toolbox/colors/colors.js - executed lines 2061/2061; executed functions 177/206
28+
(86%) toolbox/colors/colors.js - executed lines 2093/2093; executed functions 177/206
2929
(100%) toolbox/colors/palette-api-client.js - executed lines 19/19; executed functions 4/4
3030

3131
Uncovered or low-coverage changed JS files:

tests/playwright/tools/PaletteToolMockRepository.spec.mjs

Lines changed: 76 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -181,18 +181,24 @@ function hexLightness(hex) {
181181
return (max + min) / 2;
182182
}
183183

184-
async function firstColumnLightnessSpread(page) {
184+
async function columnLightnessSpread(page, column = 0) {
185185
const rows = await page.locator("[data-palette-generator-preview-row]").count();
186-
const top = (await currentPreviewHexes(page, 0))[0];
187-
const center = (await currentPreviewHexes(page, Math.floor(rows / 2)))[0];
188-
const bottom = (await currentPreviewHexes(page, rows - 1))[0];
186+
const top = (await currentPreviewHexes(page, 0))[column];
187+
const center = (await currentPreviewHexes(page, Math.floor(rows / 2)))[column];
188+
const bottom = (await currentPreviewHexes(page, rows - 1))[column];
189+
const topLightness = hexLightness(top);
190+
const centerLightness = hexLightness(center);
191+
const bottomLightness = hexLightness(bottom);
189192
return {
190193
bottom,
191-
bottomDistance: Math.abs(hexLightness(center) - hexLightness(bottom)),
194+
bottomDistance: Math.abs(centerLightness - bottomLightness),
195+
bottomLightness,
192196
center,
193-
spread: Math.abs(hexLightness(top) - hexLightness(bottom)),
197+
centerLightness,
198+
spread: Math.abs(topLightness - bottomLightness),
194199
top,
195-
topDistance: Math.abs(hexLightness(top) - hexLightness(center))
200+
topDistance: Math.abs(topLightness - centerLightness),
201+
topLightness
196202
};
197203
}
198204

@@ -661,24 +667,35 @@ test("Palette Tool renders curated swatch selector controls and live preview", a
661667
const bottomColors = await currentPreviewHexes(page, 2);
662668
expect(topColors).not.toContain("#FFFFFF");
663669
expect(bottomColors).not.toContain("#000000");
664-
const defaultStepRangeSpread = await firstColumnLightnessSpread(page);
670+
const stepRangeEvidenceColumn = 3;
671+
const defaultStepRangeSpread = await columnLightnessSpread(page, stepRangeEvidenceColumn);
665672
await page.locator("[data-palette-generator-step-range]").evaluate((control) => {
666673
control.value = "0";
667674
control.dispatchEvent(new Event("input", { bubbles: true }));
668675
});
669-
const tightStepRangeSpread = await firstColumnLightnessSpread(page);
676+
const tightStepRangeSpread = await columnLightnessSpread(page, stepRangeEvidenceColumn);
677+
expect(tightStepRangeSpread.topDistance).toBeGreaterThan(0);
678+
expect(tightStepRangeSpread.bottomDistance).toBeGreaterThan(0);
670679
expect(tightStepRangeSpread.spread).toBeLessThan(defaultStepRangeSpread.spread);
680+
expect(tightStepRangeSpread.topLightness).toBeLessThan(defaultStepRangeSpread.topLightness);
681+
expect(tightStepRangeSpread.bottomLightness).toBeGreaterThan(defaultStepRangeSpread.bottomLightness);
682+
expect(tightStepRangeSpread.center).toBe(defaultStepRangeSpread.center);
671683
await page.locator("[data-palette-generator-step-range]").evaluate((control) => {
672684
control.value = "100";
673685
control.dispatchEvent(new Event("input", { bubbles: true }));
674686
});
675-
const wideStepRangeSpread = await firstColumnLightnessSpread(page);
687+
const wideStepRangeSpread = await columnLightnessSpread(page, stepRangeEvidenceColumn);
676688
expect(wideStepRangeSpread.spread).toBeGreaterThan(defaultStepRangeSpread.spread);
689+
expect(wideStepRangeSpread.topLightness).toBeGreaterThan(defaultStepRangeSpread.topLightness);
690+
expect(wideStepRangeSpread.bottomLightness).toBeLessThan(defaultStepRangeSpread.bottomLightness);
691+
expect(wideStepRangeSpread.topLightness).toBeGreaterThan(0.98);
692+
expect(wideStepRangeSpread.bottomLightness).toBeLessThan(0.02);
693+
expect(wideStepRangeSpread.center).toBe(defaultStepRangeSpread.center);
677694
await page.locator("[data-palette-generator-step-range]").evaluate((control) => {
678695
control.value = "50";
679696
control.dispatchEvent(new Event("input", { bubbles: true }));
680697
});
681-
const restoredStepRangeSpread = await firstColumnLightnessSpread(page);
698+
const restoredStepRangeSpread = await columnLightnessSpread(page, stepRangeEvidenceColumn);
682699
expect(restoredStepRangeSpread.top).toBe(defaultStepRangeSpread.top);
683700
expect(restoredStepRangeSpread.center).toBe(defaultStepRangeSpread.center);
684701
expect(restoredStepRangeSpread.bottom).toBe(defaultStepRangeSpread.bottom);
@@ -726,11 +743,57 @@ test("Palette Tool renders curated swatch selector controls and live preview", a
726743
expect(humanSkinHexes).toEqual(["#3A2118", "#5A3224", "#7A4B35", "#9D6B4D", "#B88661", "#D0A37A", "#E4BF9D", "#F3D8C4"]);
727744
await page.locator("[data-palette-generator-type]").selectOption("Human");
728745
await expect(page.locator("[data-palette-generator-swatch]").first()).toHaveAttribute("data-palette-generator-type-name", "Human");
746+
await setGeneratorColors(page, "20");
729747
const humanCombinedNames = await page.locator("[data-palette-generator-swatch]").evaluateAll((swatches) => (
730748
swatches.map((swatch) => swatch.dataset.paletteGeneratorName)
731749
));
732-
expect(humanCombinedNames.join(" ")).toContain("Deep Skin");
733-
expect(humanCombinedNames.join(" ")).toContain("Cloth Navy");
750+
const humanCombinedHexes = await currentPreviewHexes(page, 0);
751+
expect(humanCombinedHexes).toEqual([
752+
"#3A2118",
753+
"#5A3224",
754+
"#8A5A3D",
755+
"#9A7B4F",
756+
"#D7A982",
757+
"#F0D1BA",
758+
"#FFE0C2",
759+
"#2A2E38",
760+
"#0D0A08",
761+
"#4B2C1A",
762+
"#8A3E24",
763+
"#C6A15D",
764+
"#B8B8B2",
765+
"#3B6EA5",
766+
"#3F7A52",
767+
"#5A321E",
768+
"#273D5F",
769+
"#8F2F3D",
770+
"#4F6B45",
771+
"#B2A08A"
772+
]);
773+
[
774+
"Deep Skin",
775+
"Dark Skin",
776+
"Medium Skin",
777+
"Olive Skin",
778+
"Light Skin",
779+
"Pale Skin",
780+
"Warm Highlight",
781+
"Cool Shadow",
782+
"Black Hair",
783+
"Brown Hair",
784+
"Auburn Hair",
785+
"Blonde Hair",
786+
"Gray Hair",
787+
"Eye Blue",
788+
"Eye Green",
789+
"Eye Brown",
790+
"Cloth Navy",
791+
"Cloth Red",
792+
"Cloth Green",
793+
"Cloth Neutral"
794+
].forEach((name) => {
795+
expect(humanCombinedNames.join(" ")).toContain(name);
796+
});
734797

735798
await setGeneratorSteps(page, "8");
736799
await page.locator("[data-palette-theme-collection]").selectOption("Sci-Fi");

toolbox/colors/colors.js

Lines changed: 41 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -262,8 +262,29 @@ const CURATED_PALETTE_COLLECTIONS = Object.freeze([
262262
},
263263
{
264264
name: "Human",
265-
names: Object.freeze(["Deep Skin", "Medium Skin", "Light Skin", "Dark Hair", "Auburn Hair", "Eye Blue", "Cloth Navy", "Warm Highlight"]),
266-
swatches: swatches("#4B2A1E #8A5A3D #E2B891 #1A120F #8A4A2C #3B6EA5 #273D5F #F2D3B8")
265+
names: Object.freeze([
266+
"Deep Skin",
267+
"Dark Skin",
268+
"Medium Skin",
269+
"Olive Skin",
270+
"Light Skin",
271+
"Pale Skin",
272+
"Warm Highlight",
273+
"Cool Shadow",
274+
"Black Hair",
275+
"Brown Hair",
276+
"Auburn Hair",
277+
"Blonde Hair",
278+
"Gray Hair",
279+
"Eye Blue",
280+
"Eye Green",
281+
"Eye Brown",
282+
"Cloth Navy",
283+
"Cloth Red",
284+
"Cloth Green",
285+
"Cloth Neutral"
286+
]),
287+
swatches: swatches("#3A2118 #5A3224 #8A5A3D #9A7B4F #D7A982 #F0D1BA #FFE0C2 #2A2E38 #0D0A08 #4B2C1A #8A3E24 #C6A15D #B8B8B2 #3B6EA5 #3F7A52 #5A321E #273D5F #8F2F3D #4F6B45 #B2A08A")
267288
}
268289
])
269290
},
@@ -868,7 +889,7 @@ function initializePaletteGeneratorSelectors() {
868889
renderPaletteVariantOptions();
869890
}
870891

871-
function variantAdjustedHsl(hsl, variant, column, columns) {
892+
function variantAdjustedHsl(hsl, variant, column, columns, lightnessBounds = { minimum: 0, maximum: 100 }) {
872893
const safeHueSequence = [210, 35, 265, 175, 45, 320, 95, 15];
873894
const colorBlindHue = safeHueSequence[column % safeHueSequence.length];
874895
const variantTransforms = {
@@ -929,20 +950,31 @@ function variantAdjustedHsl(hsl, variant, column, columns) {
929950
return {
930951
hue: positiveHue(transformed.hue),
931952
saturation: clampNumber(transformed.saturation, 0, 100),
932-
lightness: clampNumber(transformed.lightness, 10, 90)
953+
lightness: clampNumber(transformed.lightness, lightnessBounds.minimum, lightnessBounds.maximum)
933954
};
934955
}
935956

936957
function generatorLightness(baseLightness, row, rows, contrast, stepRange = PALETTE_GENERATOR_DEFAULTS.stepRange) {
937958
if (rows <= 1) {
938-
return clampNumber(baseLightness, 10, 90);
959+
return clampNumber(baseLightness, 0, 100);
939960
}
940961
const rowPosition = (row / (rows - 1)) * 2 - 1;
941962
const normalizedStepRange = clampNumber(stepRange, 0, 100);
942-
const distance = (2 + clampNumber(contrast, 0, 100) * 0.31) * (normalizedStepRange / PALETTE_GENERATOR_DEFAULTS.stepRange);
943-
const lightnessMinimum = normalizedStepRange >= 100 ? 0 : 10;
944-
const lightnessMaximum = normalizedStepRange >= 100 ? 100 : 90;
945-
return clampNumber(baseLightness - rowPosition * distance, lightnessMinimum, lightnessMaximum);
963+
const magnitude = Math.abs(rowPosition);
964+
if (magnitude === 0) {
965+
return clampNumber(baseLightness, 0, 100);
966+
}
967+
const direction = rowPosition < 0 ? 1 : -1;
968+
const defaultDistance = 2 + clampNumber(contrast, 0, 100) * 0.31;
969+
const subtleTarget = clampNumber(baseLightness + direction * 2 * magnitude, 10, 90);
970+
const defaultTarget = clampNumber(baseLightness + direction * defaultDistance * magnitude, 10, 90);
971+
const extremeTarget = baseLightness + direction * (direction > 0 ? 100 - baseLightness : baseLightness) * magnitude;
972+
if (normalizedStepRange <= PALETTE_GENERATOR_DEFAULTS.stepRange) {
973+
const defaultRatio = normalizedStepRange / PALETTE_GENERATOR_DEFAULTS.stepRange;
974+
return subtleTarget + (defaultTarget - subtleTarget) * defaultRatio;
975+
}
976+
const extremeRatio = (normalizedStepRange - PALETTE_GENERATOR_DEFAULTS.stepRange) / PALETTE_GENERATOR_DEFAULTS.stepRange;
977+
return defaultTarget + (extremeTarget - defaultTarget) * extremeRatio;
946978
}
947979

948980
function actualPaletteGeneratorRows(steps) {

0 commit comments

Comments
 (0)