Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 | 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 112x 112x 112x 866x 866x 112x 112x 1x 56x 56x 56x 1x 56x 56x 56x 1x 1x 4x 4x 1x | /**
* Deterministic per-agent appearance helpers.
*
* Kept on the renderer side (not in session-store) so the store stays free of
* visual concerns. The hash is stable across runs so the same agent id always
* gets the same hair colour, which matches how `skinColor` is already derived.
*/
const HAIR_PALETTE: readonly string[] = [
"#2b1a0a", // dark brown
"#6b3e1e", // brown
"#c9a227", // blond
"#1a1a1a", // black
"#a85f3c" // ginger
];
const TROUSERS_PALETTE: readonly string[] = [
"#2f3a5b", // dark navy
"#3a2a1a", // dark brown
"#1f3a2a", // forest green
"#2a2a2a", // charcoal
"#4a2a3a" // deep plum
];
/**
* Simple 32-bit string hash (djb2-ish). Sufficient for palette bucketing;
* not intended for anything security-sensitive.
*/
function stringHash(s: string): number {
let h = 5381;
for (let i = 0; i < s.length; i++) {
// h * 33 + c, kept inside 32-bit range.
h = ((h << 5) + h + s.charCodeAt(i)) | 0;
}
// Force unsigned so modulo is always non-negative.
return h >>> 0;
}
/**
* Pick a hair colour from a small palette keyed by the agent id. Stable across
* runs for the same id; distributes reasonably across the palette.
*/
export function hairColor(id: string): string {
const idx = stringHash(id) % HAIR_PALETTE.length;
return HAIR_PALETTE[idx] ?? HAIR_PALETTE[0]!;
}
/**
* Pick a trousers / leg colour from a small palette keyed by the agent id.
* Stable across runs for the same id. A different palette from hair so that
* hair and legs read as distinct Minecraft-style voxel layers rather than
* matching by accident.
*/
export function trousersColor(id: string): string {
const idx = stringHash(id) % TROUSERS_PALETTE.length;
return TROUSERS_PALETTE[idx] ?? TROUSERS_PALETTE[0]!;
}
/**
* The mayor (main agent) wears a near-white shirt regardless of id hash so it
* visually stands out from the per-agent hashed shirt colours that subagents
* wear. Pure `#ffffff` works but reads slightly too bright against the green
* grass; `#f2f2f2` keeps the contrast while still reading as "white".
*/
const MAYOR_SHIRT_COLOR = "#f2f2f2";
/**
* Pick the body / shirt colour for a given agent. Main agents (the mayor)
* always wear a fixed near-white shirt so they are instantly recognisable in
* the scene. Subagents keep their hashed per-id colour (stored in
* `agent.skinColor`, which the render pipeline actually uses for the body
* mesh - the field name is a historical misnomer).
*/
export function shirtColorFor(agent: { kind: "main" | "subagent"; skinColor: string }): string {
return agent.kind === "main" ? MAYOR_SHIRT_COLOR : agent.skinColor;
}
export const __test = { HAIR_PALETTE, TROUSERS_PALETTE, stringHash, MAYOR_SHIRT_COLOR };
|