All files / renderer/village appearance.ts

100% Statements 34/34
77.77% Branches 7/9
100% Functions 4/4
100% Lines 34/34

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 };