Files
OpenClaw-bot-review/lib/pixel-office/sprites/spriteData.ts
T
xmanrui a497240ee7 feat: add camera emoji furniture with flash effect on right room desk
Add emoji-based furniture rendering support. Camera placed at center of
right room desk with a radial flash effect every 10 seconds. Fix surface
item z-sorting for fractional positions.
2026-02-25 05:41:59 +08:00

1176 lines
53 KiB
TypeScript

import type { Direction, SpriteData, FloorColor } from '../types'
import { Direction as Dir } from '../types'
import { adjustSprite } from '../colorize'
// ── Color Palettes ──────────────────────────────────────────────
const _ = '' // transparent
// ── Furniture Sprites ───────────────────────────────────────────
/** Square desk: 32x32 pixels (2x2 tiles) — top-down wood surface */
export const DESK_SQUARE_SPRITE: SpriteData = (() => {
const W = '#8B6914' // wood edge
const L = '#A07828' // lighter wood
const S = '#B8922E' // surface
const D = '#6B4E0A' // dark edge
const rows: string[][] = []
// Row 0: empty
rows.push(new Array(32).fill(_))
// Row 1: top edge
rows.push([_, ...new Array(30).fill(W), _])
// Rows 2-5: top surface
for (let r = 0; r < 4; r++) {
rows.push([_, W, ...new Array(28).fill(r < 1 ? L : S), W, _])
}
// Row 6: horizontal divider
rows.push([_, D, ...new Array(28).fill(W), D, _])
// Rows 7-12: middle surface area
for (let r = 0; r < 6; r++) {
rows.push([_, W, ...new Array(28).fill(S), W, _])
}
// Row 13: center line
rows.push([_, W, ...new Array(28).fill(L), W, _])
// Rows 14-19: lower surface
for (let r = 0; r < 6; r++) {
rows.push([_, W, ...new Array(28).fill(S), W, _])
}
// Row 20: horizontal divider
rows.push([_, D, ...new Array(28).fill(W), D, _])
// Rows 21-24: bottom surface
for (let r = 0; r < 4; r++) {
rows.push([_, W, ...new Array(28).fill(r > 2 ? L : S), W, _])
}
// Row 25: bottom edge
rows.push([_, ...new Array(30).fill(W), _])
// Rows 26-31: legs/shadow
for (let r = 0; r < 4; r++) {
const row = new Array(32).fill(_) as string[]
row[1] = D; row[2] = D; row[29] = D; row[30] = D
rows.push(row)
}
rows.push(new Array(32).fill(_))
rows.push(new Array(32).fill(_))
return rows
})()
/** Plant in pot: 16x24 */
export const PLANT_SPRITE: SpriteData = (() => {
const G = '#3D8B37'
const D = '#2D6B27'
const T = '#6B4E0A'
const P = '#B85C3A'
const R = '#8B4422'
return [
[_, _, _, _, _, _, G, G, _, _, _, _, _, _, _, _],
[_, _, _, _, _, G, G, G, G, _, _, _, _, _, _, _],
[_, _, _, _, G, G, D, G, G, G, _, _, _, _, _, _],
[_, _, _, G, G, D, G, G, D, G, G, _, _, _, _, _],
[_, _, G, G, G, G, G, G, G, G, G, G, _, _, _, _],
[_, G, G, D, G, G, G, G, G, G, D, G, G, _, _, _],
[_, G, G, G, G, D, G, G, D, G, G, G, G, _, _, _],
[_, _, G, G, G, G, G, G, G, G, G, G, _, _, _, _],
[_, _, _, G, G, G, D, G, G, G, G, _, _, _, _, _],
[_, _, _, _, G, G, G, G, G, G, _, _, _, _, _, _],
[_, _, _, _, _, G, G, G, G, _, _, _, _, _, _, _],
[_, _, _, _, _, _, T, T, _, _, _, _, _, _, _, _],
[_, _, _, _, _, _, T, T, _, _, _, _, _, _, _, _],
[_, _, _, _, _, _, T, T, _, _, _, _, _, _, _, _],
[_, _, _, _, _, R, R, R, R, R, _, _, _, _, _, _],
[_, _, _, _, R, P, P, P, P, P, R, _, _, _, _, _],
[_, _, _, _, R, P, P, P, P, P, R, _, _, _, _, _],
[_, _, _, _, R, P, P, P, P, P, R, _, _, _, _, _],
[_, _, _, _, R, P, P, P, P, P, R, _, _, _, _, _],
[_, _, _, _, R, P, P, P, P, P, R, _, _, _, _, _],
[_, _, _, _, R, P, P, P, P, P, R, _, _, _, _, _],
[_, _, _, _, _, R, P, P, P, R, _, _, _, _, _, _],
[_, _, _, _, _, _, R, R, R, _, _, _, _, _, _, _],
[_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _],
]
})()
/** Bookshelf: 16x32 (1 tile wide, 2 tiles tall) */
export const BOOKSHELF_SPRITE: SpriteData = (() => {
const W = '#8B6914'
const D = '#6B4E0A'
const R = '#CC4444'
const B = '#4477AA'
const G = '#44AA66'
const Y = '#CCAA33'
const P = '#9955AA'
return [
[_, W, W, W, W, W, W, W, W, W, W, W, W, W, W, _],
[W, D, D, D, D, D, D, D, D, D, D, D, D, D, D, W],
[W, D, R, R, B, B, G, G, Y, Y, R, R, B, B, D, W],
[W, D, R, R, B, B, G, G, Y, Y, R, R, B, B, D, W],
[W, D, R, R, B, B, G, G, Y, Y, R, R, B, B, D, W],
[W, D, R, R, B, B, G, G, Y, Y, R, R, B, B, D, W],
[W, D, R, R, B, B, G, G, Y, Y, R, R, B, B, D, W],
[W, W, W, W, W, W, W, W, W, W, W, W, W, W, W, W],
[W, D, D, D, D, D, D, D, D, D, D, D, D, D, D, W],
[W, D, P, P, Y, Y, B, B, G, G, P, P, R, R, D, W],
[W, D, P, P, Y, Y, B, B, G, G, P, P, R, R, D, W],
[W, D, P, P, Y, Y, B, B, G, G, P, P, R, R, D, W],
[W, D, P, P, Y, Y, B, B, G, G, P, P, R, R, D, W],
[W, D, P, P, Y, Y, B, B, G, G, P, P, R, R, D, W],
[W, D, P, P, Y, Y, B, B, G, G, P, P, R, R, D, W],
[W, W, W, W, W, W, W, W, W, W, W, W, W, W, W, W],
[W, D, D, D, D, D, D, D, D, D, D, D, D, D, D, W],
[W, D, G, G, R, R, P, P, B, B, Y, Y, G, G, D, W],
[W, D, G, G, R, R, P, P, B, B, Y, Y, G, G, D, W],
[W, D, G, G, R, R, P, P, B, B, Y, Y, G, G, D, W],
[W, D, G, G, R, R, P, P, B, B, Y, Y, G, G, D, W],
[W, D, G, G, R, R, P, P, B, B, Y, Y, G, G, D, W],
[W, D, G, G, R, R, P, P, B, B, Y, Y, G, G, D, W],
[W, W, W, W, W, W, W, W, W, W, W, W, W, W, W, W],
[W, D, D, D, D, D, D, D, D, D, D, D, D, D, D, W],
[W, D, D, D, D, D, D, D, D, D, D, D, D, D, D, W],
[W, D, D, D, D, D, D, D, D, D, D, D, D, D, D, W],
[W, D, D, D, D, D, D, D, D, D, D, D, D, D, D, W],
[W, D, D, D, D, D, D, D, D, D, D, D, D, D, D, W],
[W, D, D, D, D, D, D, D, D, D, D, D, D, D, D, W],
[W, W, W, W, W, W, W, W, W, W, W, W, W, W, W, W],
[_, W, W, W, W, W, W, W, W, W, W, W, W, W, W, _],
]
})()
/** Water cooler: 16x24 */
export const COOLER_SPRITE: SpriteData = (() => {
const W = '#CCDDEE'
const L = '#88BBDD'
const D = '#999999'
const B = '#666666'
return [
[_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _],
[_, _, _, _, _, D, D, D, D, D, D, _, _, _, _, _],
[_, _, _, _, D, L, L, L, L, L, L, D, _, _, _, _],
[_, _, _, _, D, L, L, L, L, L, L, D, _, _, _, _],
[_, _, _, _, D, L, L, L, L, L, L, D, _, _, _, _],
[_, _, _, _, D, L, L, L, L, L, L, D, _, _, _, _],
[_, _, _, _, D, L, L, L, L, L, L, D, _, _, _, _],
[_, _, _, _, _, D, D, D, D, D, D, _, _, _, _, _],
[_, _, _, _, _, D, W, W, W, W, D, _, _, _, _, _],
[_, _, _, _, _, D, W, W, W, W, D, _, _, _, _, _],
[_, _, _, _, _, D, W, W, W, W, D, _, _, _, _, _],
[_, _, _, _, _, D, W, W, W, W, D, _, _, _, _, _],
[_, _, _, _, _, D, W, W, W, W, D, _, _, _, _, _],
[_, _, _, _, D, D, W, W, W, W, D, D, _, _, _, _],
[_, _, _, _, D, W, W, W, W, W, W, D, _, _, _, _],
[_, _, _, _, D, W, W, W, W, W, W, D, _, _, _, _],
[_, _, _, _, D, D, D, D, D, D, D, D, _, _, _, _],
[_, _, _, _, _, D, B, B, B, B, D, _, _, _, _, _],
[_, _, _, _, _, D, B, B, B, B, D, _, _, _, _, _],
[_, _, _, _, _, D, B, B, B, B, D, _, _, _, _, _],
[_, _, _, _, D, D, B, B, B, B, D, D, _, _, _, _],
[_, _, _, _, D, B, B, B, B, B, B, D, _, _, _, _],
[_, _, _, _, D, D, D, D, D, D, D, D, _, _, _, _],
[_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _],
]
})()
/** Whiteboard: 32x16 (2 tiles wide, 1 tile tall) — hangs on wall */
export const WHITEBOARD_SPRITE: SpriteData = (() => {
const F = '#AAAAAA'
const W = '#EEEEFF'
const M = '#CC4444'
const B = '#4477AA'
return [
[_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _],
[_, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, _],
[_, F, W, W, W, W, W, W, W, W, W, W, W, W, W, W, W, W, W, W, W, W, W, W, W, W, W, W, W, W, F, _],
[_, F, W, W, M, M, M, W, W, W, W, W, B, B, B, B, W, W, W, W, W, W, W, M, W, W, W, W, W, W, F, _],
[_, F, W, W, W, W, W, W, W, W, W, W, W, W, W, W, W, W, W, B, B, W, W, M, W, W, W, W, W, W, F, _],
[_, F, W, W, W, W, M, M, M, M, W, W, W, W, W, W, W, W, W, W, W, W, W, W, W, W, B, B, W, W, F, _],
[_, F, W, W, W, W, W, W, W, W, W, W, W, B, B, B, W, W, W, W, W, W, W, W, W, W, W, W, W, W, F, _],
[_, F, W, W, W, W, W, W, W, W, W, W, W, W, W, W, W, W, W, W, M, M, M, W, W, W, W, W, W, W, F, _],
[_, F, W, M, M, W, W, W, W, W, W, W, W, W, W, W, B, B, W, W, W, W, W, W, W, W, W, W, W, W, F, _],
[_, F, W, W, W, W, W, W, B, B, B, W, W, W, W, W, W, W, W, W, W, W, W, W, M, M, M, M, W, W, F, _],
[_, F, W, W, W, W, W, W, W, W, W, W, W, W, W, W, W, W, W, W, W, W, W, W, W, W, W, W, W, W, F, _],
[_, F, W, W, W, W, W, W, W, W, W, W, W, W, W, W, W, W, W, W, W, W, W, W, W, W, W, W, W, W, F, _],
[_, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, _],
[_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _],
[_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _],
[_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _],
]
})()
/** Chair: 16x16 — top-down desk chair */
export const CHAIR_SPRITE: SpriteData = (() => {
const W = '#8B6914'
const D = '#6B4E0A'
const B = '#5C3D0A'
const S = '#A07828'
return [
[_, _, _, _, _, D, D, D, D, D, D, _, _, _, _, _],
[_, _, _, _, D, B, B, B, B, B, B, D, _, _, _, _],
[_, _, _, _, D, B, S, S, S, S, B, D, _, _, _, _],
[_, _, _, _, D, B, S, S, S, S, B, D, _, _, _, _],
[_, _, _, _, D, B, S, S, S, S, B, D, _, _, _, _],
[_, _, _, _, D, B, S, S, S, S, B, D, _, _, _, _],
[_, _, _, _, D, B, S, S, S, S, B, D, _, _, _, _],
[_, _, _, _, D, B, S, S, S, S, B, D, _, _, _, _],
[_, _, _, _, D, B, S, S, S, S, B, D, _, _, _, _],
[_, _, _, _, D, B, B, B, B, B, B, D, _, _, _, _],
[_, _, _, _, _, D, D, D, D, D, D, _, _, _, _, _],
[_, _, _, _, _, _, D, W, W, D, _, _, _, _, _, _],
[_, _, _, _, _, _, D, W, W, D, _, _, _, _, _, _],
[_, _, _, _, _, D, D, D, D, D, D, _, _, _, _, _],
[_, _, _, _, _, D, _, _, _, _, D, _, _, _, _, _],
[_, _, _, _, _, D, _, _, _, _, D, _, _, _, _, _],
]
})()
/** PC monitor: 16x16 — top-down monitor on stand */
export const PC_SPRITE: SpriteData = (() => {
const F = '#555555'
const S = '#3A3A5C'
const B = '#6688CC'
const D = '#444444'
return [
[_, _, _, F, F, F, F, F, F, F, F, F, F, _, _, _],
[_, _, _, F, S, S, S, S, S, S, S, S, F, _, _, _],
[_, _, _, F, S, B, B, B, B, B, B, S, F, _, _, _],
[_, _, _, F, S, B, B, B, B, B, B, S, F, _, _, _],
[_, _, _, F, S, B, B, B, B, B, B, S, F, _, _, _],
[_, _, _, F, S, B, B, B, B, B, B, S, F, _, _, _],
[_, _, _, F, S, B, B, B, B, B, B, S, F, _, _, _],
[_, _, _, F, S, B, B, B, B, B, B, S, F, _, _, _],
[_, _, _, F, S, S, S, S, S, S, S, S, F, _, _, _],
[_, _, _, F, F, F, F, F, F, F, F, F, F, _, _, _],
[_, _, _, _, _, _, _, D, D, _, _, _, _, _, _, _],
[_, _, _, _, _, _, _, D, D, _, _, _, _, _, _, _],
[_, _, _, _, _, _, D, D, D, D, _, _, _, _, _, _],
[_, _, _, _, _, D, D, D, D, D, D, _, _, _, _, _],
[_, _, _, _, _, D, D, D, D, D, D, _, _, _, _, _],
[_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _],
]
})()
/** PC monitor back: 16x16 — top-down monitor viewed from behind */
export const PC_BACK_SPRITE: SpriteData = (() => {
const F = '#555555'
const K = '#4A4A4A' // back panel
const D = '#444444'
return [
[_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _],
[_, _, _, _, _, D, D, D, D, D, D, _, _, _, _, _],
[_, _, _, _, _, D, D, D, D, D, D, _, _, _, _, _],
[_, _, _, _, _, _, D, D, D, D, _, _, _, _, _, _],
[_, _, _, _, _, _, _, D, D, _, _, _, _, _, _, _],
[_, _, _, _, _, _, _, D, D, _, _, _, _, _, _, _],
[_, _, _, F, F, F, F, F, F, F, F, F, F, _, _, _],
[_, _, _, F, K, K, K, K, K, K, K, K, F, _, _, _],
[_, _, _, F, K, K, K, K, K, K, K, K, F, _, _, _],
[_, _, _, F, K, K, K, K, K, K, K, K, F, _, _, _],
[_, _, _, F, K, K, K, K, K, K, K, K, F, _, _, _],
[_, _, _, F, K, K, K, K, K, K, K, K, F, _, _, _],
[_, _, _, F, K, K, K, K, K, K, K, K, F, _, _, _],
[_, _, _, F, K, K, K, K, K, K, K, K, F, _, _, _],
[_, _, _, F, F, F, F, F, F, F, F, F, F, _, _, _],
[_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _],
]
})()
/** Sony mirrorless camera: 16x16 — top-down view */
export const CAMERA_SPRITE: SpriteData = (() => {
const B = '#222222' // body
const G = '#333333' // grip
const L = '#444466' // lens ring
const R = '#5566AA' // lens glass
const S = '#888888' // silver accent
const D = '#1A1A1A' // dark
return [
[_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _],
[_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _],
[_, _, _, _, _, B, B, B, B, B, B, B, _, _, _, _],
[_, _, _, G, G, B, S, S, S, S, S, B, B, _, _, _],
[_, _, _, G, G, B, B, B, B, B, B, B, B, _, _, _],
[_, _, _, G, G, B, B, L, L, L, B, B, B, _, _, _],
[_, _, _, G, G, B, L, R, R, R, L, B, B, _, _, _],
[_, _, _, G, G, B, L, R, R, R, L, B, B, _, _, _],
[_, _, _, G, G, B, L, R, R, R, L, B, B, _, _, _],
[_, _, _, G, G, B, B, L, L, L, B, B, B, _, _, _],
[_, _, _, G, G, B, B, B, B, B, B, B, B, _, _, _],
[_, _, _, D, D, B, B, B, B, B, B, B, D, _, _, _],
[_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _],
[_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _],
[_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _],
[_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _],
]
})()
/** Desk lamp: 16x16 — top-down lamp with light cone */
export const LAMP_SPRITE: SpriteData = (() => {
const Y = '#FFDD55'
const L = '#FFEE88'
const D = '#888888'
const B = '#555555'
const G = '#FFFFCC'
return [
[_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _],
[_, _, _, _, _, _, G, G, G, G, _, _, _, _, _, _],
[_, _, _, _, _, G, Y, Y, Y, Y, G, _, _, _, _, _],
[_, _, _, _, G, Y, Y, L, L, Y, Y, G, _, _, _, _],
[_, _, _, _, Y, Y, L, L, L, L, Y, Y, _, _, _, _],
[_, _, _, _, Y, Y, L, L, L, L, Y, Y, _, _, _, _],
[_, _, _, _, _, Y, Y, Y, Y, Y, Y, _, _, _, _, _],
[_, _, _, _, _, _, D, D, D, D, _, _, _, _, _, _],
[_, _, _, _, _, _, _, D, D, _, _, _, _, _, _, _],
[_, _, _, _, _, _, _, D, D, _, _, _, _, _, _, _],
[_, _, _, _, _, _, _, D, D, _, _, _, _, _, _, _],
[_, _, _, _, _, _, _, D, D, _, _, _, _, _, _, _],
[_, _, _, _, _, _, D, D, D, D, _, _, _, _, _, _],
[_, _, _, _, _, B, B, B, B, B, B, _, _, _, _, _],
[_, _, _, _, _, B, B, B, B, B, B, _, _, _, _, _],
[_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _],
]
})()
// ── Speech Bubble Sprites ───────────────────────────────────────
/** Permission bubble: white square with "..." in amber, and a tail pointer (11x13) */
export const BUBBLE_PERMISSION_SPRITE: SpriteData = (() => {
const B = '#555566' // border
const F = '#EEEEFF' // fill
const A = '#CCA700' // amber dots
return [
[B, B, B, B, B, B, B, B, B, B, B],
[B, F, F, F, F, F, F, F, F, F, B],
[B, F, F, F, F, F, F, F, F, F, B],
[B, F, F, F, F, F, F, F, F, F, B],
[B, F, F, F, F, F, F, F, F, F, B],
[B, F, F, A, F, A, F, A, F, F, B],
[B, F, F, F, F, F, F, F, F, F, B],
[B, F, F, F, F, F, F, F, F, F, B],
[B, F, F, F, F, F, F, F, F, F, B],
[B, B, B, B, B, B, B, B, B, B, B],
[_, _, _, _, B, B, B, _, _, _, _],
[_, _, _, _, _, B, _, _, _, _, _],
[_, _, _, _, _, _, _, _, _, _, _],
]
})()
/** Waiting bubble: white square with green checkmark, and a tail pointer (11x13) */
export const BUBBLE_WAITING_SPRITE: SpriteData = (() => {
const B = '#555566' // border
const F = '#EEEEFF' // fill
const G = '#44BB66' // green check
return [
[_, B, B, B, B, B, B, B, B, B, _],
[B, F, F, F, F, F, F, F, F, F, B],
[B, F, F, F, F, F, F, F, F, F, B],
[B, F, F, F, F, F, F, F, G, F, B],
[B, F, F, F, F, F, F, G, F, F, B],
[B, F, F, G, F, F, G, F, F, F, B],
[B, F, F, F, G, G, F, F, F, F, B],
[B, F, F, F, F, F, F, F, F, F, B],
[B, F, F, F, F, F, F, F, F, F, B],
[_, B, B, B, B, B, B, B, B, B, _],
[_, _, _, _, B, B, B, _, _, _, _],
[_, _, _, _, _, B, _, _, _, _, _],
[_, _, _, _, _, _, _, _, _, _, _],
]
})()
// ── Character Sprites ───────────────────────────────────────────
// 16x24 characters with palette substitution
/** Palette colors for 6 distinct agent characters */
export const CHARACTER_PALETTES = [
{ skin: '#FFCC99', shirt: '#4488CC', pants: '#334466', hair: '#553322', shoes: '#222222' },
{ skin: '#FFCC99', shirt: '#CC4444', pants: '#333333', hair: '#FFD700', shoes: '#222222' },
{ skin: '#DEB887', shirt: '#44AA66', pants: '#334444', hair: '#222222', shoes: '#333333' },
{ skin: '#FFCC99', shirt: '#AA55CC', pants: '#443355', hair: '#AA4422', shoes: '#222222' },
{ skin: '#DEB887', shirt: '#CCAA33', pants: '#444433', hair: '#553322', shoes: '#333333' },
{ skin: '#FFCC99', shirt: '#FF8844', pants: '#443322', hair: '#111111', shoes: '#222222' },
] as const
interface CharPalette {
skin: string
shirt: string
pants: string
hair: string
shoes: string
}
// Template keys for character pixel data
const H = 'hair'
const K = 'skin'
const S = 'shirt'
const P = 'pants'
const O = 'shoes'
const E = '#FFFFFF' // eyes
type TemplateCell = typeof H | typeof K | typeof S | typeof P | typeof O | typeof E | typeof _
/** Resolve a template to SpriteData using a palette */
function resolveTemplate(template: TemplateCell[][], palette: CharPalette): SpriteData {
return template.map((row) =>
row.map((cell) => {
if (cell === _) return ''
if (cell === E) return E
if (cell === H) return palette.hair
if (cell === K) return palette.skin
if (cell === S) return palette.shirt
if (cell === P) return palette.pants
if (cell === O) return palette.shoes
return cell
}),
)
}
/** Flip a template horizontally (for generating left sprites from right) */
function flipHorizontal(template: TemplateCell[][]): TemplateCell[][] {
return template.map((row) => [...row].reverse())
}
// ════════════════════════════════════════════════════════════════
// DOWN-FACING SPRITES
// ════════════════════════════════════════════════════════════════
// Walk down: 4 frames (1, 2=standing, 3=mirror legs, 2 again)
const CHAR_WALK_DOWN_1: TemplateCell[][] = [
[_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _],
[_, _, _, _, _, _, H, H, H, H, _, _, _, _, _, _],
[_, _, _, _, _, H, H, H, H, H, H, _, _, _, _, _],
[_, _, _, _, _, H, H, H, H, H, H, _, _, _, _, _],
[_, _, _, _, _, K, K, K, K, K, K, _, _, _, _, _],
[_, _, _, _, _, K, E, K, K, E, K, _, _, _, _, _],
[_, _, _, _, _, K, K, K, K, K, K, _, _, _, _, _],
[_, _, _, _, _, K, K, K, K, K, K, _, _, _, _, _],
[_, _, _, _, _, _, S, S, S, S, _, _, _, _, _, _],
[_, _, _, _, _, S, S, S, S, S, S, _, _, _, _, _],
[_, _, _, _, S, S, S, S, S, S, S, S, _, _, _, _],
[_, _, _, _, S, S, S, S, S, S, S, S, _, _, _, _],
[_, _, _, _, K, S, S, S, S, S, S, K, _, _, _, _],
[_, _, _, _, _, S, S, S, S, S, S, _, _, _, _, _],
[_, _, _, _, _, _, P, P, P, P, _, _, _, _, _, _],
[_, _, _, _, _, P, P, P, P, P, P, _, _, _, _, _],
[_, _, _, _, _, P, P, P, P, P, P, _, _, _, _, _],
[_, _, _, _, P, P, _, _, _, _, P, P, _, _, _, _],
[_, _, _, _, P, P, _, _, _, _, P, P, _, _, _, _],
[_, _, _, _, O, O, _, _, _, _, _, O, O, _, _, _],
[_, _, _, _, O, O, _, _, _, _, _, O, O, _, _, _],
[_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _],
[_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _],
[_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _],
]
const CHAR_WALK_DOWN_2: TemplateCell[][] = [
[_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _],
[_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _],
[_, _, _, _, _, _, H, H, H, H, _, _, _, _, _, _],
[_, _, _, _, _, H, H, H, H, H, H, _, _, _, _, _],
[_, _, _, _, _, H, H, H, H, H, H, _, _, _, _, _],
[_, _, _, _, _, K, K, K, K, K, K, _, _, _, _, _],
[_, _, _, _, _, K, E, K, K, E, K, _, _, _, _, _],
[_, _, _, _, _, K, K, K, K, K, K, _, _, _, _, _],
[_, _, _, _, _, K, K, K, K, K, K, _, _, _, _, _],
[_, _, _, _, _, _, S, S, S, S, _, _, _, _, _, _],
[_, _, _, _, _, S, S, S, S, S, S, _, _, _, _, _],
[_, _, _, _, S, S, S, S, S, S, S, S, _, _, _, _],
[_, _, _, _, S, S, S, S, S, S, S, S, _, _, _, _],
[_, _, _, _, K, S, S, S, S, S, S, K, _, _, _, _],
[_, _, _, _, _, S, S, S, S, S, S, _, _, _, _, _],
[_, _, _, _, _, _, P, P, P, P, _, _, _, _, _, _],
[_, _, _, _, _, P, P, P, P, P, P, _, _, _, _, _],
[_, _, _, _, _, P, P, _, _, P, P, _, _, _, _, _],
[_, _, _, _, _, P, P, _, _, P, P, _, _, _, _, _],
[_, _, _, _, _, P, P, _, _, P, P, _, _, _, _, _],
[_, _, _, _, _, O, O, _, _, O, O, _, _, _, _, _],
[_, _, _, _, _, O, O, _, _, O, O, _, _, _, _, _],
[_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _],
[_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _],
]
const CHAR_WALK_DOWN_3: TemplateCell[][] = [
[_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _],
[_, _, _, _, _, _, H, H, H, H, _, _, _, _, _, _],
[_, _, _, _, _, H, H, H, H, H, H, _, _, _, _, _],
[_, _, _, _, _, H, H, H, H, H, H, _, _, _, _, _],
[_, _, _, _, _, K, K, K, K, K, K, _, _, _, _, _],
[_, _, _, _, _, K, E, K, K, E, K, _, _, _, _, _],
[_, _, _, _, _, K, K, K, K, K, K, _, _, _, _, _],
[_, _, _, _, _, K, K, K, K, K, K, _, _, _, _, _],
[_, _, _, _, _, _, S, S, S, S, _, _, _, _, _, _],
[_, _, _, _, _, S, S, S, S, S, S, _, _, _, _, _],
[_, _, _, _, S, S, S, S, S, S, S, S, _, _, _, _],
[_, _, _, _, S, S, S, S, S, S, S, S, _, _, _, _],
[_, _, _, _, K, S, S, S, S, S, S, K, _, _, _, _],
[_, _, _, _, _, S, S, S, S, S, S, _, _, _, _, _],
[_, _, _, _, _, _, P, P, P, P, _, _, _, _, _, _],
[_, _, _, _, _, P, P, P, P, P, P, _, _, _, _, _],
[_, _, _, _, _, P, P, P, P, P, P, _, _, _, _, _],
[_, _, _, O, O, _, _, _, _, _, _, P, P, _, _, _],
[_, _, _, O, O, _, _, _, _, _, _, P, P, _, _, _],
[_, _, _, _, _, _, _, _, _, _, O, O, _, _, _, _],
[_, _, _, _, _, _, _, _, _, _, O, O, _, _, _, _],
[_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _],
[_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _],
[_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _],
]
// Down typing: front-facing sitting, arms on keyboard
const CHAR_DOWN_TYPE_1: TemplateCell[][] = [
[_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _],
[_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _],
[_, _, _, _, _, _, H, H, H, H, _, _, _, _, _, _],
[_, _, _, _, _, H, H, H, H, H, H, _, _, _, _, _],
[_, _, _, _, _, H, H, H, H, H, H, _, _, _, _, _],
[_, _, _, _, _, K, K, K, K, K, K, _, _, _, _, _],
[_, _, _, _, _, K, E, K, K, E, K, _, _, _, _, _],
[_, _, _, _, _, K, K, K, K, K, K, _, _, _, _, _],
[_, _, _, _, _, K, K, K, K, K, K, _, _, _, _, _],
[_, _, _, _, _, _, S, S, S, S, _, _, _, _, _, _],
[_, _, _, _, _, S, S, S, S, S, S, _, _, _, _, _],
[_, _, _, _, S, S, S, S, S, S, S, S, _, _, _, _],
[_, _, _, K, K, S, S, S, S, S, S, K, K, _, _, _],
[_, _, _, _, K, S, S, S, S, S, S, K, _, _, _, _],
[_, _, _, _, _, S, S, S, S, S, S, _, _, _, _, _],
[_, _, _, _, _, _, P, P, P, P, _, _, _, _, _, _],
[_, _, _, _, _, P, P, P, P, P, P, _, _, _, _, _],
[_, _, _, _, _, P, P, P, P, P, P, _, _, _, _, _],
[_, _, _, _, _, P, P, _, _, P, P, _, _, _, _, _],
[_, _, _, _, _, O, O, _, _, O, O, _, _, _, _, _],
[_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _],
[_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _],
[_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _],
[_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _],
]
const CHAR_DOWN_TYPE_2: TemplateCell[][] = [
[_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _],
[_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _],
[_, _, _, _, _, _, H, H, H, H, _, _, _, _, _, _],
[_, _, _, _, _, H, H, H, H, H, H, _, _, _, _, _],
[_, _, _, _, _, H, H, H, H, H, H, _, _, _, _, _],
[_, _, _, _, _, K, K, K, K, K, K, _, _, _, _, _],
[_, _, _, _, _, K, E, K, K, E, K, _, _, _, _, _],
[_, _, _, _, _, K, K, K, K, K, K, _, _, _, _, _],
[_, _, _, _, _, K, K, K, K, K, K, _, _, _, _, _],
[_, _, _, _, _, _, S, S, S, S, _, _, _, _, _, _],
[_, _, _, _, _, S, S, S, S, S, S, _, _, _, _, _],
[_, _, _, _, S, S, S, S, S, S, S, S, _, _, _, _],
[_, _, _, _, K, S, S, S, S, S, S, K, K, _, _, _],
[_, _, _, _, K, S, S, S, S, S, S, _, K, _, _, _],
[_, _, _, _, _, S, S, S, S, S, S, _, _, _, _, _],
[_, _, _, _, _, _, P, P, P, P, _, _, _, _, _, _],
[_, _, _, _, _, P, P, P, P, P, P, _, _, _, _, _],
[_, _, _, _, _, P, P, P, P, P, P, _, _, _, _, _],
[_, _, _, _, _, P, P, _, _, P, P, _, _, _, _, _],
[_, _, _, _, _, O, O, _, _, O, O, _, _, _, _, _],
[_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _],
[_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _],
[_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _],
[_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _],
]
// Down reading: front-facing sitting, arms at sides, looking at screen
const CHAR_DOWN_READ_1: TemplateCell[][] = [
[_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _],
[_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _],
[_, _, _, _, _, _, H, H, H, H, _, _, _, _, _, _],
[_, _, _, _, _, H, H, H, H, H, H, _, _, _, _, _],
[_, _, _, _, _, H, H, H, H, H, H, _, _, _, _, _],
[_, _, _, _, _, K, K, K, K, K, K, _, _, _, _, _],
[_, _, _, _, _, K, E, K, K, E, K, _, _, _, _, _],
[_, _, _, _, _, K, K, K, K, K, K, _, _, _, _, _],
[_, _, _, _, _, K, K, K, K, K, K, _, _, _, _, _],
[_, _, _, _, _, _, S, S, S, S, _, _, _, _, _, _],
[_, _, _, _, _, S, S, S, S, S, S, _, _, _, _, _],
[_, _, _, _, S, S, S, S, S, S, S, S, _, _, _, _],
[_, _, _, _, S, S, S, S, S, S, S, S, _, _, _, _],
[_, _, _, _, K, S, S, S, S, S, S, K, _, _, _, _],
[_, _, _, _, _, S, S, S, S, S, S, _, _, _, _, _],
[_, _, _, _, _, _, P, P, P, P, _, _, _, _, _, _],
[_, _, _, _, _, P, P, P, P, P, P, _, _, _, _, _],
[_, _, _, _, _, P, P, P, P, P, P, _, _, _, _, _],
[_, _, _, _, _, P, P, _, _, P, P, _, _, _, _, _],
[_, _, _, _, _, O, O, _, _, O, O, _, _, _, _, _],
[_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _],
[_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _],
[_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _],
[_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _],
]
const CHAR_DOWN_READ_2: TemplateCell[][] = [
[_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _],
[_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _],
[_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _],
[_, _, _, _, _, _, H, H, H, H, _, _, _, _, _, _],
[_, _, _, _, _, H, H, H, H, H, H, _, _, _, _, _],
[_, _, _, _, _, H, H, H, H, H, H, _, _, _, _, _],
[_, _, _, _, _, K, K, K, K, K, K, _, _, _, _, _],
[_, _, _, _, _, K, E, K, K, E, K, _, _, _, _, _],
[_, _, _, _, _, K, K, K, K, K, K, _, _, _, _, _],
[_, _, _, _, _, K, K, K, K, K, K, _, _, _, _, _],
[_, _, _, _, _, _, S, S, S, S, _, _, _, _, _, _],
[_, _, _, _, _, S, S, S, S, S, S, _, _, _, _, _],
[_, _, _, _, S, S, S, S, S, S, S, S, _, _, _, _],
[_, _, _, _, S, S, S, S, S, S, S, S, _, _, _, _],
[_, _, _, _, K, S, S, S, S, S, S, K, _, _, _, _],
[_, _, _, _, _, S, S, S, S, S, S, _, _, _, _, _],
[_, _, _, _, _, _, P, P, P, P, _, _, _, _, _, _],
[_, _, _, _, _, P, P, P, P, P, P, _, _, _, _, _],
[_, _, _, _, _, P, P, _, _, P, P, _, _, _, _, _],
[_, _, _, _, _, O, O, _, _, O, O, _, _, _, _, _],
[_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _],
[_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _],
[_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _],
[_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _],
]
// ════════════════════════════════════════════════════════════════
// UP-FACING SPRITES (back of head, no face)
// ════════════════════════════════════════════════════════════════
// Walk up: back view, legs alternate
const CHAR_WALK_UP_1: TemplateCell[][] = [
[_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _],
[_, _, _, _, _, _, H, H, H, H, _, _, _, _, _, _],
[_, _, _, _, _, H, H, H, H, H, H, _, _, _, _, _],
[_, _, _, _, _, H, H, H, H, H, H, _, _, _, _, _],
[_, _, _, _, _, H, H, H, H, H, H, _, _, _, _, _],
[_, _, _, _, _, K, K, K, K, K, K, _, _, _, _, _],
[_, _, _, _, _, K, K, K, K, K, K, _, _, _, _, _],
[_, _, _, _, _, K, K, K, K, K, K, _, _, _, _, _],
[_, _, _, _, _, _, S, S, S, S, _, _, _, _, _, _],
[_, _, _, _, _, S, S, S, S, S, S, _, _, _, _, _],
[_, _, _, _, S, S, S, S, S, S, S, S, _, _, _, _],
[_, _, _, _, S, S, S, S, S, S, S, S, _, _, _, _],
[_, _, _, _, K, S, S, S, S, S, S, K, _, _, _, _],
[_, _, _, _, _, S, S, S, S, S, S, _, _, _, _, _],
[_, _, _, _, _, _, P, P, P, P, _, _, _, _, _, _],
[_, _, _, _, _, P, P, P, P, P, P, _, _, _, _, _],
[_, _, _, _, _, P, P, P, P, P, P, _, _, _, _, _],
[_, _, _, _, P, P, _, _, _, _, P, P, _, _, _, _],
[_, _, _, _, P, P, _, _, _, _, P, P, _, _, _, _],
[_, _, _, O, O, _, _, _, _, _, _, O, O, _, _, _],
[_, _, _, O, O, _, _, _, _, _, _, O, O, _, _, _],
[_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _],
[_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _],
[_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _],
]
const CHAR_WALK_UP_2: TemplateCell[][] = [
[_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _],
[_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _],
[_, _, _, _, _, _, H, H, H, H, _, _, _, _, _, _],
[_, _, _, _, _, H, H, H, H, H, H, _, _, _, _, _],
[_, _, _, _, _, H, H, H, H, H, H, _, _, _, _, _],
[_, _, _, _, _, H, H, H, H, H, H, _, _, _, _, _],
[_, _, _, _, _, K, K, K, K, K, K, _, _, _, _, _],
[_, _, _, _, _, K, K, K, K, K, K, _, _, _, _, _],
[_, _, _, _, _, K, K, K, K, K, K, _, _, _, _, _],
[_, _, _, _, _, _, S, S, S, S, _, _, _, _, _, _],
[_, _, _, _, _, S, S, S, S, S, S, _, _, _, _, _],
[_, _, _, _, S, S, S, S, S, S, S, S, _, _, _, _],
[_, _, _, _, S, S, S, S, S, S, S, S, _, _, _, _],
[_, _, _, _, K, S, S, S, S, S, S, K, _, _, _, _],
[_, _, _, _, _, S, S, S, S, S, S, _, _, _, _, _],
[_, _, _, _, _, _, P, P, P, P, _, _, _, _, _, _],
[_, _, _, _, _, P, P, P, P, P, P, _, _, _, _, _],
[_, _, _, _, _, P, P, _, _, P, P, _, _, _, _, _],
[_, _, _, _, _, P, P, _, _, P, P, _, _, _, _, _],
[_, _, _, _, _, P, P, _, _, P, P, _, _, _, _, _],
[_, _, _, _, _, O, O, _, _, O, O, _, _, _, _, _],
[_, _, _, _, _, O, O, _, _, O, O, _, _, _, _, _],
[_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _],
[_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _],
]
const CHAR_WALK_UP_3: TemplateCell[][] = [
[_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _],
[_, _, _, _, _, _, H, H, H, H, _, _, _, _, _, _],
[_, _, _, _, _, H, H, H, H, H, H, _, _, _, _, _],
[_, _, _, _, _, H, H, H, H, H, H, _, _, _, _, _],
[_, _, _, _, _, H, H, H, H, H, H, _, _, _, _, _],
[_, _, _, _, _, K, K, K, K, K, K, _, _, _, _, _],
[_, _, _, _, _, K, K, K, K, K, K, _, _, _, _, _],
[_, _, _, _, _, K, K, K, K, K, K, _, _, _, _, _],
[_, _, _, _, _, _, S, S, S, S, _, _, _, _, _, _],
[_, _, _, _, _, S, S, S, S, S, S, _, _, _, _, _],
[_, _, _, _, S, S, S, S, S, S, S, S, _, _, _, _],
[_, _, _, _, S, S, S, S, S, S, S, S, _, _, _, _],
[_, _, _, _, K, S, S, S, S, S, S, K, _, _, _, _],
[_, _, _, _, _, S, S, S, S, S, S, _, _, _, _, _],
[_, _, _, _, _, _, P, P, P, P, _, _, _, _, _, _],
[_, _, _, _, _, P, P, P, P, P, P, _, _, _, _, _],
[_, _, _, _, _, P, P, P, P, P, P, _, _, _, _, _],
[_, _, _, O, O, _, _, _, _, _, _, P, P, _, _, _],
[_, _, _, O, O, _, _, _, _, _, _, P, P, _, _, _],
[_, _, _, _, _, _, _, _, _, _, O, O, _, _, _, _],
[_, _, _, _, _, _, _, _, _, _, O, O, _, _, _, _],
[_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _],
[_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _],
[_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _],
]
// Up typing: back view, arms out to keyboard
const CHAR_UP_TYPE_1: TemplateCell[][] = [
[_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _],
[_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _],
[_, _, _, _, _, _, H, H, H, H, _, _, _, _, _, _],
[_, _, _, _, _, H, H, H, H, H, H, _, _, _, _, _],
[_, _, _, _, _, H, H, H, H, H, H, _, _, _, _, _],
[_, _, _, _, _, H, H, H, H, H, H, _, _, _, _, _],
[_, _, _, _, _, K, K, K, K, K, K, _, _, _, _, _],
[_, _, _, _, _, K, K, K, K, K, K, _, _, _, _, _],
[_, _, _, _, _, _, S, S, S, S, _, _, _, _, _, _],
[_, _, _, _, _, S, S, S, S, S, S, _, _, _, _, _],
[_, _, _, _, S, S, S, S, S, S, S, S, _, _, _, _],
[_, _, _, K, K, S, S, S, S, S, S, K, K, _, _, _],
[_, _, _, _, K, S, S, S, S, S, S, K, _, _, _, _],
[_, _, _, _, _, S, S, S, S, S, S, _, _, _, _, _],
[_, _, _, _, _, _, P, P, P, P, _, _, _, _, _, _],
[_, _, _, _, _, P, P, P, P, P, P, _, _, _, _, _],
[_, _, _, _, _, P, P, P, P, P, P, _, _, _, _, _],
[_, _, _, _, _, P, P, _, _, P, P, _, _, _, _, _],
[_, _, _, _, _, O, O, _, _, O, O, _, _, _, _, _],
[_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _],
[_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _],
[_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _],
[_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _],
[_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _],
]
const CHAR_UP_TYPE_2: TemplateCell[][] = [
[_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _],
[_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _],
[_, _, _, _, _, _, H, H, H, H, _, _, _, _, _, _],
[_, _, _, _, _, H, H, H, H, H, H, _, _, _, _, _],
[_, _, _, _, _, H, H, H, H, H, H, _, _, _, _, _],
[_, _, _, _, _, H, H, H, H, H, H, _, _, _, _, _],
[_, _, _, _, _, K, K, K, K, K, K, _, _, _, _, _],
[_, _, _, _, _, K, K, K, K, K, K, _, _, _, _, _],
[_, _, _, _, _, _, S, S, S, S, _, _, _, _, _, _],
[_, _, _, _, _, S, S, S, S, S, S, _, _, _, _, _],
[_, _, _, _, S, S, S, S, S, S, S, S, _, _, _, _],
[_, _, _, _, K, S, S, S, S, S, S, K, K, _, _, _],
[_, _, _, _, K, S, S, S, S, S, S, _, K, _, _, _],
[_, _, _, _, _, S, S, S, S, S, S, _, _, _, _, _],
[_, _, _, _, _, _, P, P, P, P, _, _, _, _, _, _],
[_, _, _, _, _, P, P, P, P, P, P, _, _, _, _, _],
[_, _, _, _, _, P, P, P, P, P, P, _, _, _, _, _],
[_, _, _, _, _, P, P, _, _, P, P, _, _, _, _, _],
[_, _, _, _, _, O, O, _, _, O, O, _, _, _, _, _],
[_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _],
[_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _],
[_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _],
[_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _],
[_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _],
]
// Up reading: back view, arms at sides
const CHAR_UP_READ_1: TemplateCell[][] = [
[_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _],
[_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _],
[_, _, _, _, _, _, H, H, H, H, _, _, _, _, _, _],
[_, _, _, _, _, H, H, H, H, H, H, _, _, _, _, _],
[_, _, _, _, _, H, H, H, H, H, H, _, _, _, _, _],
[_, _, _, _, _, H, H, H, H, H, H, _, _, _, _, _],
[_, _, _, _, _, K, K, K, K, K, K, _, _, _, _, _],
[_, _, _, _, _, K, K, K, K, K, K, _, _, _, _, _],
[_, _, _, _, _, _, S, S, S, S, _, _, _, _, _, _],
[_, _, _, _, _, S, S, S, S, S, S, _, _, _, _, _],
[_, _, _, _, S, S, S, S, S, S, S, S, _, _, _, _],
[_, _, _, _, S, S, S, S, S, S, S, S, _, _, _, _],
[_, _, _, _, K, S, S, S, S, S, S, K, _, _, _, _],
[_, _, _, _, _, S, S, S, S, S, S, _, _, _, _, _],
[_, _, _, _, _, _, P, P, P, P, _, _, _, _, _, _],
[_, _, _, _, _, P, P, P, P, P, P, _, _, _, _, _],
[_, _, _, _, _, P, P, P, P, P, P, _, _, _, _, _],
[_, _, _, _, _, P, P, _, _, P, P, _, _, _, _, _],
[_, _, _, _, _, O, O, _, _, O, O, _, _, _, _, _],
[_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _],
[_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _],
[_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _],
[_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _],
[_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _],
]
const CHAR_UP_READ_2: TemplateCell[][] = [
[_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _],
[_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _],
[_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _],
[_, _, _, _, _, _, H, H, H, H, _, _, _, _, _, _],
[_, _, _, _, _, H, H, H, H, H, H, _, _, _, _, _],
[_, _, _, _, _, H, H, H, H, H, H, _, _, _, _, _],
[_, _, _, _, _, H, H, H, H, H, H, _, _, _, _, _],
[_, _, _, _, _, K, K, K, K, K, K, _, _, _, _, _],
[_, _, _, _, _, K, K, K, K, K, K, _, _, _, _, _],
[_, _, _, _, _, _, S, S, S, S, _, _, _, _, _, _],
[_, _, _, _, _, S, S, S, S, S, S, _, _, _, _, _],
[_, _, _, _, S, S, S, S, S, S, S, S, _, _, _, _],
[_, _, _, _, S, S, S, S, S, S, S, S, _, _, _, _],
[_, _, _, _, K, S, S, S, S, S, S, K, _, _, _, _],
[_, _, _, _, _, S, S, S, S, S, S, _, _, _, _, _],
[_, _, _, _, _, _, P, P, P, P, _, _, _, _, _, _],
[_, _, _, _, _, P, P, P, P, P, P, _, _, _, _, _],
[_, _, _, _, _, P, P, _, _, P, P, _, _, _, _, _],
[_, _, _, _, _, O, O, _, _, O, O, _, _, _, _, _],
[_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _],
[_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _],
[_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _],
[_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _],
[_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _],
]
// ════════════════════════════════════════════════════════════════
// RIGHT-FACING SPRITES (side profile, one eye visible)
// Left sprites are generated by flipHorizontal()
// ════════════════════════════════════════════════════════════════
// Right walk: side view, legs step
const CHAR_WALK_RIGHT_1: TemplateCell[][] = [
[_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _],
[_, _, _, _, _, _, _, H, H, H, H, _, _, _, _, _],
[_, _, _, _, _, _, H, H, H, H, H, _, _, _, _, _],
[_, _, _, _, _, _, H, H, H, H, H, _, _, _, _, _],
[_, _, _, _, _, _, K, K, K, K, K, _, _, _, _, _],
[_, _, _, _, _, _, K, K, K, E, K, _, _, _, _, _],
[_, _, _, _, _, _, K, K, K, K, K, _, _, _, _, _],
[_, _, _, _, _, _, K, K, K, K, _, _, _, _, _, _],
[_, _, _, _, _, _, _, S, S, S, _, _, _, _, _, _],
[_, _, _, _, _, _, S, S, S, S, S, _, _, _, _, _],
[_, _, _, _, _, S, S, S, S, S, S, _, _, _, _, _],
[_, _, _, _, _, S, S, S, S, S, S, _, _, _, _, _],
[_, _, _, _, _, K, S, S, S, S, K, _, _, _, _, _],
[_, _, _, _, _, _, S, S, S, S, _, _, _, _, _, _],
[_, _, _, _, _, _, _, P, P, _, _, _, _, _, _, _],
[_, _, _, _, _, _, P, P, P, P, _, _, _, _, _, _],
[_, _, _, _, _, _, P, P, P, P, _, _, _, _, _, _],
[_, _, _, _, _, P, P, _, _, _, P, P, _, _, _, _],
[_, _, _, _, _, P, P, _, _, _, P, P, _, _, _, _],
[_, _, _, _, _, O, O, _, _, _, _, O, O, _, _, _],
[_, _, _, _, _, O, O, _, _, _, _, O, O, _, _, _],
[_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _],
[_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _],
[_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _],
]
const CHAR_WALK_RIGHT_2: TemplateCell[][] = [
[_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _],
[_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _],
[_, _, _, _, _, _, _, H, H, H, H, _, _, _, _, _],
[_, _, _, _, _, _, H, H, H, H, H, _, _, _, _, _],
[_, _, _, _, _, _, H, H, H, H, H, _, _, _, _, _],
[_, _, _, _, _, _, K, K, K, K, K, _, _, _, _, _],
[_, _, _, _, _, _, K, K, K, E, K, _, _, _, _, _],
[_, _, _, _, _, _, K, K, K, K, K, _, _, _, _, _],
[_, _, _, _, _, _, K, K, K, K, _, _, _, _, _, _],
[_, _, _, _, _, _, _, S, S, S, _, _, _, _, _, _],
[_, _, _, _, _, _, S, S, S, S, S, _, _, _, _, _],
[_, _, _, _, _, S, S, S, S, S, S, _, _, _, _, _],
[_, _, _, _, _, S, S, S, S, S, S, _, _, _, _, _],
[_, _, _, _, _, K, S, S, S, S, K, _, _, _, _, _],
[_, _, _, _, _, _, S, S, S, S, _, _, _, _, _, _],
[_, _, _, _, _, _, _, P, P, _, _, _, _, _, _, _],
[_, _, _, _, _, _, P, P, P, P, _, _, _, _, _, _],
[_, _, _, _, _, _, P, P, _, P, P, _, _, _, _, _],
[_, _, _, _, _, _, P, P, _, P, P, _, _, _, _, _],
[_, _, _, _, _, _, P, P, _, P, P, _, _, _, _, _],
[_, _, _, _, _, _, O, O, _, O, O, _, _, _, _, _],
[_, _, _, _, _, _, O, O, _, O, O, _, _, _, _, _],
[_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _],
[_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _],
]
const CHAR_WALK_RIGHT_3: TemplateCell[][] = [
[_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _],
[_, _, _, _, _, _, _, H, H, H, H, _, _, _, _, _],
[_, _, _, _, _, _, H, H, H, H, H, _, _, _, _, _],
[_, _, _, _, _, _, H, H, H, H, H, _, _, _, _, _],
[_, _, _, _, _, _, K, K, K, K, K, _, _, _, _, _],
[_, _, _, _, _, _, K, K, K, E, K, _, _, _, _, _],
[_, _, _, _, _, _, K, K, K, K, K, _, _, _, _, _],
[_, _, _, _, _, _, K, K, K, K, _, _, _, _, _, _],
[_, _, _, _, _, _, _, S, S, S, _, _, _, _, _, _],
[_, _, _, _, _, _, S, S, S, S, S, _, _, _, _, _],
[_, _, _, _, _, S, S, S, S, S, S, _, _, _, _, _],
[_, _, _, _, _, S, S, S, S, S, S, _, _, _, _, _],
[_, _, _, _, _, K, S, S, S, S, K, _, _, _, _, _],
[_, _, _, _, _, _, S, S, S, S, _, _, _, _, _, _],
[_, _, _, _, _, _, _, P, P, _, _, _, _, _, _, _],
[_, _, _, _, _, _, P, P, P, P, _, _, _, _, _, _],
[_, _, _, _, _, _, P, P, P, P, _, _, _, _, _, _],
[_, _, _, _, _, _, _, _, P, P, P, _, _, _, _, _],
[_, _, _, _, _, _, _, _, P, P, P, _, _, _, _, _],
[_, _, _, _, _, O, O, _, _, O, O, _, _, _, _, _],
[_, _, _, _, _, O, O, _, _, _, _, _, _, _, _, _],
[_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _],
[_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _],
[_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _],
]
// Right typing: side profile sitting, one arm on keyboard
const CHAR_RIGHT_TYPE_1: TemplateCell[][] = [
[_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _],
[_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _],
[_, _, _, _, _, _, _, H, H, H, H, _, _, _, _, _],
[_, _, _, _, _, _, H, H, H, H, H, _, _, _, _, _],
[_, _, _, _, _, _, H, H, H, H, H, _, _, _, _, _],
[_, _, _, _, _, _, K, K, K, K, K, _, _, _, _, _],
[_, _, _, _, _, _, K, K, K, E, K, _, _, _, _, _],
[_, _, _, _, _, _, K, K, K, K, K, _, _, _, _, _],
[_, _, _, _, _, _, K, K, K, K, _, _, _, _, _, _],
[_, _, _, _, _, _, _, S, S, S, _, _, _, _, _, _],
[_, _, _, _, _, _, S, S, S, S, S, _, _, _, _, _],
[_, _, _, _, _, S, S, S, S, S, S, K, _, _, _, _],
[_, _, _, _, _, S, S, S, S, S, K, _, _, _, _, _],
[_, _, _, _, _, _, S, S, S, S, _, _, _, _, _, _],
[_, _, _, _, _, _, S, S, S, S, _, _, _, _, _, _],
[_, _, _, _, _, _, _, P, P, _, _, _, _, _, _, _],
[_, _, _, _, _, _, P, P, P, P, _, _, _, _, _, _],
[_, _, _, _, _, _, P, P, P, P, _, _, _, _, _, _],
[_, _, _, _, _, _, P, P, _, P, P, _, _, _, _, _],
[_, _, _, _, _, _, O, O, _, O, O, _, _, _, _, _],
[_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _],
[_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _],
[_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _],
[_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _],
]
const CHAR_RIGHT_TYPE_2: TemplateCell[][] = [
[_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _],
[_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _],
[_, _, _, _, _, _, _, H, H, H, H, _, _, _, _, _],
[_, _, _, _, _, _, H, H, H, H, H, _, _, _, _, _],
[_, _, _, _, _, _, H, H, H, H, H, _, _, _, _, _],
[_, _, _, _, _, _, K, K, K, K, K, _, _, _, _, _],
[_, _, _, _, _, _, K, K, K, E, K, _, _, _, _, _],
[_, _, _, _, _, _, K, K, K, K, K, _, _, _, _, _],
[_, _, _, _, _, _, K, K, K, K, _, _, _, _, _, _],
[_, _, _, _, _, _, _, S, S, S, _, _, _, _, _, _],
[_, _, _, _, _, _, S, S, S, S, S, _, _, _, _, _],
[_, _, _, _, _, S, S, S, S, S, S, _, K, _, _, _],
[_, _, _, _, _, S, S, S, S, S, _, _, K, _, _, _],
[_, _, _, _, _, _, S, S, S, S, _, _, _, _, _, _],
[_, _, _, _, _, _, S, S, S, S, _, _, _, _, _, _],
[_, _, _, _, _, _, _, P, P, _, _, _, _, _, _, _],
[_, _, _, _, _, _, P, P, P, P, _, _, _, _, _, _],
[_, _, _, _, _, _, P, P, P, P, _, _, _, _, _, _],
[_, _, _, _, _, _, P, P, _, P, P, _, _, _, _, _],
[_, _, _, _, _, _, O, O, _, O, O, _, _, _, _, _],
[_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _],
[_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _],
[_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _],
[_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _],
]
// Right reading: side sitting, arms at side
const CHAR_RIGHT_READ_1: TemplateCell[][] = [
[_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _],
[_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _],
[_, _, _, _, _, _, _, H, H, H, H, _, _, _, _, _],
[_, _, _, _, _, _, H, H, H, H, H, _, _, _, _, _],
[_, _, _, _, _, _, H, H, H, H, H, _, _, _, _, _],
[_, _, _, _, _, _, K, K, K, K, K, _, _, _, _, _],
[_, _, _, _, _, _, K, K, K, E, K, _, _, _, _, _],
[_, _, _, _, _, _, K, K, K, K, K, _, _, _, _, _],
[_, _, _, _, _, _, K, K, K, K, _, _, _, _, _, _],
[_, _, _, _, _, _, _, S, S, S, _, _, _, _, _, _],
[_, _, _, _, _, _, S, S, S, S, S, _, _, _, _, _],
[_, _, _, _, _, S, S, S, S, S, S, _, _, _, _, _],
[_, _, _, _, _, S, S, S, S, S, S, _, _, _, _, _],
[_, _, _, _, _, K, S, S, S, S, K, _, _, _, _, _],
[_, _, _, _, _, _, S, S, S, S, _, _, _, _, _, _],
[_, _, _, _, _, _, _, P, P, _, _, _, _, _, _, _],
[_, _, _, _, _, _, P, P, P, P, _, _, _, _, _, _],
[_, _, _, _, _, _, P, P, P, P, _, _, _, _, _, _],
[_, _, _, _, _, _, P, P, _, P, P, _, _, _, _, _],
[_, _, _, _, _, _, O, O, _, O, O, _, _, _, _, _],
[_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _],
[_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _],
[_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _],
[_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _],
]
const CHAR_RIGHT_READ_2: TemplateCell[][] = [
[_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _],
[_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _],
[_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _],
[_, _, _, _, _, _, _, H, H, H, H, _, _, _, _, _],
[_, _, _, _, _, _, H, H, H, H, H, _, _, _, _, _],
[_, _, _, _, _, _, H, H, H, H, H, _, _, _, _, _],
[_, _, _, _, _, _, K, K, K, K, K, _, _, _, _, _],
[_, _, _, _, _, _, K, K, K, E, K, _, _, _, _, _],
[_, _, _, _, _, _, K, K, K, K, K, _, _, _, _, _],
[_, _, _, _, _, _, K, K, K, K, _, _, _, _, _, _],
[_, _, _, _, _, _, _, S, S, S, _, _, _, _, _, _],
[_, _, _, _, _, _, S, S, S, S, S, _, _, _, _, _],
[_, _, _, _, _, S, S, S, S, S, S, _, _, _, _, _],
[_, _, _, _, _, S, S, S, S, S, S, _, _, _, _, _],
[_, _, _, _, _, K, S, S, S, S, K, _, _, _, _, _],
[_, _, _, _, _, _, S, S, S, S, _, _, _, _, _, _],
[_, _, _, _, _, _, _, P, P, _, _, _, _, _, _, _],
[_, _, _, _, _, _, P, P, P, P, _, _, _, _, _, _],
[_, _, _, _, _, _, P, P, _, P, P, _, _, _, _, _],
[_, _, _, _, _, _, O, O, _, O, O, _, _, _, _, _],
[_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _],
[_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _],
[_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _],
[_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _],
]
// ════════════════════════════════════════════════════════════════
// Template export (for export-characters script)
// ════════════════════════════════════════════════════════════════
/** All character templates grouped by direction, for use by the export script.
* Frame order per direction: walk1, walk2, walk3, type1, type2, read1, read2 */
export const CHARACTER_TEMPLATES = {
down: [
CHAR_WALK_DOWN_1, CHAR_WALK_DOWN_2, CHAR_WALK_DOWN_3,
CHAR_DOWN_TYPE_1, CHAR_DOWN_TYPE_2,
CHAR_DOWN_READ_1, CHAR_DOWN_READ_2,
],
up: [
CHAR_WALK_UP_1, CHAR_WALK_UP_2, CHAR_WALK_UP_3,
CHAR_UP_TYPE_1, CHAR_UP_TYPE_2,
CHAR_UP_READ_1, CHAR_UP_READ_2,
],
right: [
CHAR_WALK_RIGHT_1, CHAR_WALK_RIGHT_2, CHAR_WALK_RIGHT_3,
CHAR_RIGHT_TYPE_1, CHAR_RIGHT_TYPE_2,
CHAR_RIGHT_READ_1, CHAR_RIGHT_READ_2,
],
} as const
// ════════════════════════════════════════════════════════════════
// Loaded character sprites (from PNG assets)
// ════════════════════════════════════════════════════════════════
export interface LoadedCharacterData {
down: SpriteData[]
up: SpriteData[]
right: SpriteData[]
}
let loadedCharacters: LoadedCharacterData[] | null = null
/** Set pre-colored character sprites loaded from PNG assets. Call this when characterSpritesLoaded message arrives. */
export function setCharacterTemplates(data: LoadedCharacterData[]): void {
loadedCharacters = data
// Clear cache so sprites are rebuilt from loaded data
spriteCache.clear()
}
/** Flip a SpriteData horizontally (for generating left sprites from right) */
function flipSpriteHorizontal(sprite: SpriteData): SpriteData {
return sprite.map((row) => [...row].reverse())
}
// ════════════════════════════════════════════════════════════════
// Sprite resolution + caching
// ════════════════════════════════════════════════════════════════
export interface CharacterSprites {
walk: Record<Direction, [SpriteData, SpriteData, SpriteData, SpriteData]>
typing: Record<Direction, [SpriteData, SpriteData]>
reading: Record<Direction, [SpriteData, SpriteData]>
}
const spriteCache = new Map<string, CharacterSprites>()
/** Apply hue shift to every sprite in a CharacterSprites set */
function hueShiftSprites(sprites: CharacterSprites, hueShift: number): CharacterSprites {
const color: FloorColor = { h: hueShift, s: 0, b: 0, c: 0 }
const shift = (s: SpriteData) => adjustSprite(s, color)
const shiftWalk = (arr: [SpriteData, SpriteData, SpriteData, SpriteData]): [SpriteData, SpriteData, SpriteData, SpriteData] =>
[shift(arr[0]), shift(arr[1]), shift(arr[2]), shift(arr[3])]
const shiftPair = (arr: [SpriteData, SpriteData]): [SpriteData, SpriteData] =>
[shift(arr[0]), shift(arr[1])]
return {
walk: {
[Dir.DOWN]: shiftWalk(sprites.walk[Dir.DOWN]),
[Dir.UP]: shiftWalk(sprites.walk[Dir.UP]),
[Dir.RIGHT]: shiftWalk(sprites.walk[Dir.RIGHT]),
[Dir.LEFT]: shiftWalk(sprites.walk[Dir.LEFT]),
} as Record<Direction, [SpriteData, SpriteData, SpriteData, SpriteData]>,
typing: {
[Dir.DOWN]: shiftPair(sprites.typing[Dir.DOWN]),
[Dir.UP]: shiftPair(sprites.typing[Dir.UP]),
[Dir.RIGHT]: shiftPair(sprites.typing[Dir.RIGHT]),
[Dir.LEFT]: shiftPair(sprites.typing[Dir.LEFT]),
} as Record<Direction, [SpriteData, SpriteData]>,
reading: {
[Dir.DOWN]: shiftPair(sprites.reading[Dir.DOWN]),
[Dir.UP]: shiftPair(sprites.reading[Dir.UP]),
[Dir.RIGHT]: shiftPair(sprites.reading[Dir.RIGHT]),
[Dir.LEFT]: shiftPair(sprites.reading[Dir.LEFT]),
} as Record<Direction, [SpriteData, SpriteData]>,
}
}
export function getCharacterSprites(paletteIndex: number, hueShift = 0): CharacterSprites {
const cacheKey = `${paletteIndex}:${hueShift}`
const cached = spriteCache.get(cacheKey)
if (cached) return cached
let sprites: CharacterSprites
if (loadedCharacters) {
// Use pre-colored character sprites directly (no palette swapping)
const char = loadedCharacters[paletteIndex % loadedCharacters.length]
const d = char.down
const u = char.up
const rt = char.right
const flip = flipSpriteHorizontal
sprites = {
walk: {
[Dir.DOWN]: [d[0], d[1], d[2], d[1]],
[Dir.UP]: [u[0], u[1], u[2], u[1]],
[Dir.RIGHT]: [rt[0], rt[1], rt[2], rt[1]],
[Dir.LEFT]: [flip(rt[0]), flip(rt[1]), flip(rt[2]), flip(rt[1])],
},
typing: {
[Dir.DOWN]: [d[3], d[4]],
[Dir.UP]: [u[3], u[4]],
[Dir.RIGHT]: [rt[3], rt[4]],
[Dir.LEFT]: [flip(rt[3]), flip(rt[4])],
},
reading: {
[Dir.DOWN]: [d[5], d[6]],
[Dir.UP]: [u[5], u[6]],
[Dir.RIGHT]: [rt[5], rt[6]],
[Dir.LEFT]: [flip(rt[5]), flip(rt[6])],
},
}
} else {
// Fallback: use hardcoded templates with palette swapping
const pal = CHARACTER_PALETTES[paletteIndex % CHARACTER_PALETTES.length]
const r = (t: TemplateCell[][]) => resolveTemplate(t, pal)
const rf = (t: TemplateCell[][]) => resolveTemplate(flipHorizontal(t), pal)
sprites = {
walk: {
[Dir.DOWN]: [r(CHAR_WALK_DOWN_1), r(CHAR_WALK_DOWN_2), r(CHAR_WALK_DOWN_3), r(CHAR_WALK_DOWN_2)],
[Dir.UP]: [r(CHAR_WALK_UP_1), r(CHAR_WALK_UP_2), r(CHAR_WALK_UP_3), r(CHAR_WALK_UP_2)],
[Dir.RIGHT]: [r(CHAR_WALK_RIGHT_1), r(CHAR_WALK_RIGHT_2), r(CHAR_WALK_RIGHT_3), r(CHAR_WALK_RIGHT_2)],
[Dir.LEFT]: [rf(CHAR_WALK_RIGHT_1), rf(CHAR_WALK_RIGHT_2), rf(CHAR_WALK_RIGHT_3), rf(CHAR_WALK_RIGHT_2)],
},
typing: {
[Dir.DOWN]: [r(CHAR_DOWN_TYPE_1), r(CHAR_DOWN_TYPE_2)],
[Dir.UP]: [r(CHAR_UP_TYPE_1), r(CHAR_UP_TYPE_2)],
[Dir.RIGHT]: [r(CHAR_RIGHT_TYPE_1), r(CHAR_RIGHT_TYPE_2)],
[Dir.LEFT]: [rf(CHAR_RIGHT_TYPE_1), rf(CHAR_RIGHT_TYPE_2)],
},
reading: {
[Dir.DOWN]: [r(CHAR_DOWN_READ_1), r(CHAR_DOWN_READ_2)],
[Dir.UP]: [r(CHAR_UP_READ_1), r(CHAR_UP_READ_2)],
[Dir.RIGHT]: [r(CHAR_RIGHT_READ_1), r(CHAR_RIGHT_READ_2)],
[Dir.LEFT]: [rf(CHAR_RIGHT_READ_1), rf(CHAR_RIGHT_READ_2)],
},
}
}
// Apply hue shift if non-zero
if (hueShift !== 0) {
sprites = hueShiftSprites(sprites, hueShift)
}
spriteCache.set(cacheKey, sprites)
return sprites
}