const _spriteCache = {};

const SPRITE_CELL = 4;
const SPRITE_W = 64;
const SPRITE_H = 80;
const SPRITE_CANVAS_W = SPRITE_W * SPRITE_CELL;
const SPRITE_CANVAS_H = SPRITE_H * SPRITE_CELL + 64;

const SPRITE_SKINS = {
    trilero:    { skin:'#d4a87a', hair:{color:'#1a1a1a', style:'cap'},      shirt:'#a04444', shirtAccent:'#7a2a2a', pants:'#2a2a2a', shoes:'#0a0a0a', face:'sneaky',    accessory:'cards' },
    flyers:     { skin:'#e8c896', hair:{color:'#7a5030', style:'short'},    shirt:'#f4a460', shirtAccent:'#d08a40', pants:'#3a3a4a', shoes:'#1a1a1a', face:'tired',     accessory:'flyer' },
    ong:        { skin:'#d8b894', hair:{color:'#a08060', style:'ponytail'}, shirt:'#3eaa67', shirtAccent:'#1e6e44', pants:'#2a2a3a', shoes:'#1a1a1a', face:'cheerful',  accessory:'clipboard' },
    selfies:    { skin:'#e8bc8a', hair:{color:'#1a1a1a', style:'short'},    shirt:'#daa520', shirtAccent:'#b08418', pants:'#2a3050', shoes:'#3a3a3a', face:'eager',     accessory:'selfiestick' },
    carterista: { skin:'#c89878', hair:{color:'#0a0a0a', style:'hood'},     shirt:'#2c2c2c', shirtAccent:'#1a1a1a', pants:'#1a1a1a', shoes:'#0a0a0a', face:'shifty',    accessory:'sneakyhand' },
    predicador: { skin:'#d4b090', hair:{color:'#cccccc', style:'longgray'}, shirt:'#7b4f9b', shirtAccent:'#5a3a78', pants:'#5a3a78', shoes:'#3a2a4a', face:'angry',     accessory:'megaphone' },
    borracho:   { skin:'#e8a888', hair:{color:'#c0a070', style:'messy'},    shirt:'#c08040', shirtAccent:'#a06030', pants:'#3a4060', shoes:'#5a4a3a', face:'drunk',     accessory:'beer' },
    scooter:    { skin:'#d8b890', hair:{color:'#3a2a1a', style:'helmet'},   shirt:'#4682b4', shirtAccent:'#2a5a8a', pants:'#2a2a2a', shoes:'#1a1a1a', face:'focused',   accessory:'scooter' },
    charlatan:  { skin:'#dab098', hair:{color:'#3a2a1a', style:'short'},    shirt:'#a04060', shirtAccent:'#7a2a48', pants:'#2a2a2a', shoes:'#1a1a1a', face:'shouting',  accessory:'pointing' },
    mendigo:    { skin:'#b88860', hair:{color:'#5a3a20', style:'messy'},    shirt:'#5a4030', shirtAccent:'#3a2a1a', pants:'#3a2a20', shoes:'#1a1a1a', face:'desperate', accessory:'cup' },
    amego:      { skin:'#d8b078', hair:{color:'#3a2010', style:'cap'},      shirt:'#a08060', shirtAccent:'#705840', pants:'#6a4a30', shoes:'#3a2a1a', face:'pidiendo',  accessory:'scooter' },
    mantero:    { skin:'#5a3520', hair:{color:'#1a1a1a', style:'short'},    shirt:'#3a3a3a', shirtAccent:'#202020', pants:'#1a1a2a', shoes:'#0a0a0a', face:'tired',     accessory:'flyer' },
    rosas:      { skin:'#dab098', hair:{color:'#3a2a1a', style:'short'},    shirt:'#c2185b', shirtAccent:'#8a0a3c', pants:'#2a2a3a', shoes:'#1a1a1a', face:'cheerful',  accessory:'flyer' },
    discoteca:  { skin:'#e8c896', hair:{color:'#1a1a1a', style:'short'},    shirt:'#7c4dff', shirtAccent:'#5028c0', pants:'#2a2a3a', shoes:'#0a0a0a', face:'eager',     accessory:'flyer' },
    cuponero:   { skin:'#d8b890', hair:{color:'#a0a0a0', style:'longgray'}, shirt:'#ffd54f', shirtAccent:'#c0a020', pants:'#3a3a3a', shoes:'#1a1a1a', face:'tired',     accessory:'clipboard' },
    cantante:   { skin:'#dab098', hair:{color:'#1a1a1a', style:'messy'},    shirt:'#26a69a', shirtAccent:'#0a5048', pants:'#3a3a4a', shoes:'#1a1a1a', face:'shouting',  accessory:'megaphone' },
    turistas:   { skin:'#e8c896', hair:{color:'#a08060', style:'short'},    shirt:'#ff7043', shirtAccent:'#c04020', pants:'#3a4070', shoes:'#5a4a3a', face:'cheerful',  accessory:'pointing' }
};

const PLAYER_SKIN = {
    skin:'#d8a878',
    hair:{color:'#3a2a1a', style:'shortBack'},
    shirt:'#3a5d8c', shirtAccent:'#2a4670',
    pants:'#1a1a2a', shoes:'#0a0a0a',
    face:'back',
    accessory:'backpack'
};

function _shade(hex, amount) {
    let h = hex.replace('#', '');
    if (h.length === 3) h = h.split('').map((c) => c + c).join('');
    let r = parseInt(h.substr(0, 2), 16);
    let g = parseInt(h.substr(2, 2), 16);
    let b = parseInt(h.substr(4, 2), 16);
    if (amount < 0) {
        const t = -amount;
        r = Math.round(r * (1 - t)); g = Math.round(g * (1 - t)); b = Math.round(b * (1 - t));
    } else {
        r = Math.round(r + (255 - r) * amount); g = Math.round(g + (255 - g) * amount); b = Math.round(b + (255 - b) * amount);
    }
    return `rgb(${r},${g},${b})`;
}

function _px(ctx, x, y, w, h, color) {
    ctx.fillStyle = color;
    ctx.fillRect(x * SPRITE_CELL, y * SPRITE_CELL, w * SPRITE_CELL, h * SPRITE_CELL);
}

function _drawShadow(ctx) {
    ctx.fillStyle = 'rgba(0,0,0,0.4)';
    ctx.beginPath();
    ctx.ellipse(32 * SPRITE_CELL, 76 * SPRITE_CELL, 14 * SPRITE_CELL, 2.5 * SPRITE_CELL, 0, 0, Math.PI * 2);
    ctx.fill();
}

function _drawLegs(ctx, skin) {
    const pantShade = _shade(skin.pants, -0.25);
    _px(ctx, 24, 52, 7, 20, skin.pants);
    _px(ctx, 33, 52, 7, 20, skin.pants);
    _px(ctx, 29, 52, 2, 20, pantShade);
    _px(ctx, 38, 52, 2, 20, pantShade);
    _px(ctx, 31, 52, 2, 20, '#000');
    _px(ctx, 23, 72, 9, 4, skin.shoes);
    _px(ctx, 32, 72, 9, 4, skin.shoes);
    _px(ctx, 23, 75, 9, 1, _shade(skin.shoes, -0.4));
    _px(ctx, 32, 75, 9, 1, _shade(skin.shoes, -0.4));
}

function _drawTorso(ctx, skin, isBack) {
    const shade = _shade(skin.shirt, -0.18);
    _px(ctx, 18, 29, 28, 4, skin.shirt);
    _px(ctx, 20, 33, 24, 19, skin.shirt);
    _px(ctx, 40, 33, 4, 19, shade);
    _px(ctx, 40, 29, 6, 4, shade);
    _px(ctx, 20, 50, 24, 2, skin.shirtAccent);
    if (!isBack) {
        _px(ctx, 31, 33, 2, 17, _shade(skin.shirt, -0.1));
    } else {
        _px(ctx, 31, 33, 2, 17, _shade(skin.shirt, -0.1));
    }

    _px(ctx, 14, 29, 4, 16, skin.shirt);
    _px(ctx, 46, 29, 4, 16, skin.shirt);
    _px(ctx, 17, 29, 1, 16, shade);
    _px(ctx, 46, 29, 1, 16, _shade(skin.shirt, 0.15));

    _px(ctx, 14, 45, 4, 4, skin.skin);
    _px(ctx, 46, 45, 4, 4, skin.skin);
    _px(ctx, 14, 48, 4, 1, _shade(skin.skin, -0.2));
    _px(ctx, 46, 48, 4, 1, _shade(skin.skin, -0.2));
}

function _drawNeckHead(ctx, skin) {
    _px(ctx, 30, 26, 4, 4, _shade(skin.skin, -0.22));
    _px(ctx, 24, 12, 16, 14, skin.skin);
    _px(ctx, 22, 17, 2, 4, skin.skin);
    _px(ctx, 40, 17, 2, 4, skin.skin);
    _px(ctx, 22, 19, 2, 1, _shade(skin.skin, -0.25));
    _px(ctx, 40, 19, 2, 1, _shade(skin.skin, -0.25));
    _px(ctx, 24, 24, 16, 2, _shade(skin.skin, -0.18));
    _px(ctx, 24, 12, 16, 1, _shade(skin.skin, -0.12));
}

function _drawHair(ctx, skin) {
    const hair = skin.hair;
    if (!hair) return;
    const c = hair.color;
    const shade = _shade(c, -0.3);
    if (hair.style === 'short') {
        _px(ctx, 24, 8, 16, 5, c);
        _px(ctx, 23, 10, 1, 6, c);
        _px(ctx, 40, 10, 1, 6, c);
        _px(ctx, 24, 8, 16, 1, shade);
    } else if (hair.style === 'shortBack') {
        _px(ctx, 24, 8, 16, 14, c);
        _px(ctx, 23, 10, 1, 10, c);
        _px(ctx, 40, 10, 1, 10, c);
        _px(ctx, 24, 8, 16, 1, shade);
    } else if (hair.style === 'cap') {
        _px(ctx, 24, 7, 16, 5, c);
        _px(ctx, 22, 11, 18, 2, c);
        _px(ctx, 38, 11, 10, 2, shade);
        _px(ctx, 24, 7, 16, 1, _shade(c, 0.2));
        _px(ctx, 30, 9, 4, 2, '#c02020');
    } else if (hair.style === 'hood') {
        _px(ctx, 22, 6, 20, 8, c);
        _px(ctx, 21, 12, 2, 10, c);
        _px(ctx, 41, 12, 2, 10, c);
        _px(ctx, 24, 14, 16, 4, _shade(skin.skin, -0.55));
        _px(ctx, 22, 6, 20, 1, shade);
    } else if (hair.style === 'ponytail') {
        _px(ctx, 24, 8, 16, 5, c);
        _px(ctx, 23, 10, 1, 8, c);
        _px(ctx, 40, 10, 1, 8, c);
        _px(ctx, 38, 14, 4, 12, c);
        _px(ctx, 24, 8, 16, 1, shade);
        _px(ctx, 38, 24, 4, 2, shade);
    } else if (hair.style === 'longgray') {
        _px(ctx, 22, 8, 20, 6, c);
        _px(ctx, 21, 12, 2, 14, c);
        _px(ctx, 41, 12, 2, 14, c);
        _px(ctx, 22, 8, 20, 1, shade);
        _px(ctx, 21, 24, 22, 2, shade);
    } else if (hair.style === 'messy') {
        _px(ctx, 24, 8, 16, 5, c);
        _px(ctx, 23, 9, 2, 4, c);
        _px(ctx, 39, 9, 2, 4, c);
        _px(ctx, 27, 6, 3, 3, c);
        _px(ctx, 33, 6, 4, 3, c);
        _px(ctx, 22, 11, 1, 6, c);
        _px(ctx, 41, 11, 1, 6, c);
        _px(ctx, 25, 6, 1, 1, shade);
        _px(ctx, 35, 6, 1, 1, shade);
    } else if (hair.style === 'helmet') {
        _px(ctx, 22, 7, 20, 11, c);
        _px(ctx, 23, 18, 18, 1, shade);
        _px(ctx, 22, 7, 20, 2, _shade(c, 0.2));
        _px(ctx, 25, 14, 14, 4, '#5a8aaa');
        _px(ctx, 25, 14, 14, 1, '#88bbdd');
        _px(ctx, 22, 18, 1, 4, c);
        _px(ctx, 41, 18, 1, 4, c);
    }
}

function _drawFace(ctx, skin) {
    if (skin.face === 'back') return;
    const f = skin.face;
    const eyeY = 17;
    const drawEyes = (style) => {
        if (style === 'slit') {
            _px(ctx, 28, eyeY, 3, 1, '#000');
            _px(ctx, 33, eyeY, 3, 1, '#000');
        } else if (style === 'droopy') {
            _px(ctx, 28, eyeY, 2, 1, '#000');
            _px(ctx, 34, 18, 2, 1, '#000');
        } else if (style === 'glasses') {
            _px(ctx, 26, 16, 5, 4, '#000');
            _px(ctx, 33, 16, 5, 4, '#000');
            _px(ctx, 27, 17, 3, 2, '#88ccee');
            _px(ctx, 34, 17, 3, 2, '#88ccee');
            _px(ctx, 31, 18, 2, 1, '#000');
        } else {
            _px(ctx, 28, eyeY, 2, 2, '#000');
            _px(ctx, 34, eyeY, 2, 2, '#000');
            if (style === 'wide') {
                _px(ctx, 28, eyeY, 1, 1, '#fff');
                _px(ctx, 34, eyeY, 1, 1, '#fff');
            }
        }
    };

    if (f === 'sneaky') {
        drawEyes('slit');
        _px(ctx, 30, 22, 4, 1, '#000');
        _px(ctx, 34, 21, 1, 1, '#000');
    } else if (f === 'tired') {
        drawEyes('slit');
        _px(ctx, 27, 16, 1, 1, '#000');
        _px(ctx, 36, 16, 1, 1, '#000');
        _px(ctx, 30, 23, 4, 1, '#000');
        _px(ctx, 30, 22, 1, 1, '#000');
    } else if (f === 'cheerful') {
        drawEyes('default');
        _px(ctx, 29, 22, 6, 2, '#000');
        _px(ctx, 30, 22, 4, 1, '#fff');
    } else if (f === 'eager') {
        drawEyes('wide');
        _px(ctx, 29, 22, 6, 2, '#000');
        _px(ctx, 30, 22, 4, 1, '#fff');
        _px(ctx, 29, 16, 2, 1, '#000');
        _px(ctx, 33, 16, 2, 1, '#000');
    } else if (f === 'shifty') {
        _px(ctx, 28, 17, 2, 2, '#fff');
        _px(ctx, 34, 17, 2, 2, '#fff');
        _px(ctx, 28, 17, 2, 1, '#000');
        _px(ctx, 34, 17, 2, 1, '#000');
        _px(ctx, 29, 18, 1, 1, '#000');
        _px(ctx, 35, 18, 1, 1, '#000');
    } else if (f === 'angry') {
        _px(ctx, 26, 15, 5, 1, '#000');
        _px(ctx, 33, 15, 5, 1, '#000');
        _px(ctx, 27, 16, 3, 1, '#000');
        _px(ctx, 34, 16, 3, 1, '#000');
        drawEyes('default');
        _px(ctx, 29, 22, 6, 1, '#000');
    } else if (f === 'drunk') {
        drawEyes('droopy');
        _px(ctx, 30, 22, 4, 1, '#000');
        _px(ctx, 33, 23, 2, 1, '#aa3030');
        _px(ctx, 23, 21, 2, 2, '#cc6060');
        _px(ctx, 39, 21, 2, 2, '#cc6060');
    } else if (f === 'focused') {
        drawEyes('glasses');
        _px(ctx, 30, 22, 4, 1, '#000');
    } else if (f === 'shouting') {
        drawEyes('wide');
        _px(ctx, 28, 21, 8, 4, '#000');
        _px(ctx, 29, 22, 6, 2, '#882020');
        _px(ctx, 31, 22, 2, 1, '#fff');
        _px(ctx, 33, 22, 2, 1, '#fff');
    } else if (f === 'desperate') {
        drawEyes('default');
        _px(ctx, 27, 15, 1, 1, '#000');
        _px(ctx, 36, 15, 1, 1, '#000');
        _px(ctx, 29, 23, 6, 1, '#000');
        _px(ctx, 29, 22, 1, 1, '#000');
        _px(ctx, 34, 22, 1, 1, '#000');
    } else if (f === 'pidiendo') {
        drawEyes('default');
        _px(ctx, 30, 22, 5, 1, '#000');
        _px(ctx, 30, 21, 1, 1, '#000');
        _px(ctx, 36, 22, 6, 1, '#fafafa');
        _px(ctx, 41, 22, 1, 1, '#cc4020');
        _px(ctx, 36, 22, 1, 1, '#888');
        _px(ctx, 41, 20, 1, 1, '#aaa');
        _px(ctx, 42, 19, 1, 1, '#bbb');
        _px(ctx, 41, 17, 1, 1, '#ccc');
    }
}

function _drawAccessory(ctx, skin) {
    const a = skin.accessory;
    if (!a) return;
    if (a === 'cards') {
        _px(ctx, 26, 36, 4, 7, '#fff');
        _px(ctx, 30, 35, 4, 7, '#fff');
        _px(ctx, 34, 36, 4, 7, '#fff');
        _px(ctx, 26, 36, 4, 1, '#000');
        _px(ctx, 30, 35, 4, 1, '#000');
        _px(ctx, 34, 36, 4, 1, '#000');
        _px(ctx, 26, 42, 4, 1, '#000');
        _px(ctx, 30, 41, 4, 1, '#000');
        _px(ctx, 34, 42, 4, 1, '#000');
        _px(ctx, 27, 38, 1, 1, '#c02020');
        _px(ctx, 31, 37, 1, 1, '#c02020');
        _px(ctx, 35, 38, 1, 1, '#c02020');
    } else if (a === 'flyer') {
        _px(ctx, 8, 38, 8, 9, '#f8f8f8');
        _px(ctx, 8, 38, 8, 1, '#aaa');
        _px(ctx, 9, 40, 6, 1, '#444');
        _px(ctx, 9, 42, 6, 1, '#444');
        _px(ctx, 9, 44, 4, 1, '#444');
        _px(ctx, 8, 38, 1, 9, '#aaa');
        _px(ctx, 15, 38, 1, 9, '#888');
    } else if (a === 'clipboard') {
        _px(ctx, 47, 37, 9, 12, '#a07050');
        _px(ctx, 48, 39, 7, 9, '#fff');
        _px(ctx, 50, 37, 3, 2, '#222');
        _px(ctx, 49, 41, 5, 1, '#333');
        _px(ctx, 49, 43, 5, 1, '#333');
        _px(ctx, 49, 45, 3, 1, '#333');
    } else if (a === 'selfiestick') {
        ctx.strokeStyle = '#222';
        ctx.lineWidth = 3;
        ctx.beginPath();
        ctx.moveTo(48 * SPRITE_CELL, 44 * SPRITE_CELL);
        ctx.lineTo(60 * SPRITE_CELL, 14 * SPRITE_CELL);
        ctx.stroke();
        _px(ctx, 56, 8, 7, 9, '#222');
        _px(ctx, 57, 9, 5, 7, '#5a8acc');
        _px(ctx, 58, 10, 1, 1, '#fff');
        _px(ctx, 60, 12, 1, 1, '#fff');
    } else if (a === 'sneakyhand') {
        _px(ctx, 46, 44, 10, 4, skin.skin);
        _px(ctx, 50, 44, 6, 8, skin.skin);
        _px(ctx, 50, 52, 1, 2, _shade(skin.skin, -0.25));
        _px(ctx, 52, 52, 1, 2, _shade(skin.skin, -0.25));
        _px(ctx, 54, 52, 1, 2, _shade(skin.skin, -0.25));
        _px(ctx, 50, 47, 6, 1, _shade(skin.skin, -0.2));
    } else if (a === 'megaphone') {
        _px(ctx, 50, 12, 8, 2, '#bb2020');
        _px(ctx, 51, 14, 7, 2, '#dd3030');
        _px(ctx, 51, 16, 5, 2, '#dd3030');
        _px(ctx, 50, 18, 3, 2, '#dd3030');
        _px(ctx, 47, 20, 3, 4, '#aa2020');
        _px(ctx, 45, 24, 4, 4, '#222');
        _px(ctx, 50, 12, 8, 1, _shade('#bb2020', 0.3));
    } else if (a === 'beer') {
        _px(ctx, 48, 38, 4, 10, '#5a3a1a');
        _px(ctx, 49, 35, 2, 3, '#5a3a1a');
        _px(ctx, 49, 33, 2, 2, '#444');
        _px(ctx, 48, 41, 4, 3, '#f0d090');
        _px(ctx, 49, 42, 2, 1, '#aa3030');
        _px(ctx, 48, 38, 1, 10, '#3a2010');
        _px(ctx, 51, 38, 1, 10, _shade('#5a3a1a', 0.2));
    } else if (a === 'scooter') {
        _px(ctx, 16, 76, 32, 2, '#1a1a1a');
        _px(ctx, 14, 74, 4, 4, '#0a0a0a');
        _px(ctx, 46, 74, 4, 4, '#0a0a0a');
        _px(ctx, 14, 75, 4, 1, '#444');
        _px(ctx, 46, 75, 4, 1, '#444');
        _px(ctx, 30, 36, 4, 38, '#444');
        _px(ctx, 30, 36, 1, 38, '#666');
        _px(ctx, 18, 36, 28, 2, '#444');
        _px(ctx, 12, 36, 6, 4, '#222');
        _px(ctx, 46, 36, 6, 4, '#222');
        _px(ctx, 18, 36, 28, 1, '#666');
    } else if (a === 'pointing') {
        _px(ctx, 46, 36, 10, 3, skin.skin);
        _px(ctx, 52, 38, 4, 1, skin.skin);
        _px(ctx, 46, 38, 10, 1, _shade(skin.skin, -0.2));
    } else if (a === 'cup') {
        _px(ctx, 46, 40, 8, 7, '#a09080');
        _px(ctx, 47, 39, 6, 1, '#cabba0');
        _px(ctx, 48, 41, 4, 1, '#222');
        _px(ctx, 50, 38, 1, 2, '#daa520');
        _px(ctx, 46, 40, 1, 7, _shade('#a09080', -0.25));
        _px(ctx, 53, 40, 1, 7, _shade('#a09080', 0.15));
    } else if (a === 'backpack') {
        _px(ctx, 19, 30, 3, 22, '#2a1810');
        _px(ctx, 42, 30, 3, 22, '#2a1810');
        _px(ctx, 22, 32, 20, 18, '#3a2a20');
        _px(ctx, 22, 32, 20, 2, _shade('#3a2a20', 0.25));
        _px(ctx, 24, 36, 16, 2, '#5a4030');
        _px(ctx, 30, 38, 4, 6, '#1a0a08');
        _px(ctx, 22, 49, 20, 1, '#0a0a0a');
        _px(ctx, 41, 32, 1, 18, '#1a0a08');
    }
}

function _drawCharacter(ctx, skin) {
    const isBack = skin.face === 'back';
    _drawShadow(ctx);
    _drawLegs(ctx, skin);
    _drawTorso(ctx, skin, isBack);
    _drawNeckHead(ctx, skin);
    _drawHair(ctx, skin);
    if (!isBack) _drawFace(ctx, skin);
    _drawAccessory(ctx, skin);
}

function _drawLabel(ctx, w, h, name) {
    if (!name) return;
    const labelH = 30;
    const labelY = h - labelH - 6;
    ctx.fillStyle = 'rgba(0, 0, 0, 0.78)';
    ctx.fillRect(8, labelY, w - 16, labelH);
    ctx.fillStyle = '#fff';
    let fontSize = 20;
    ctx.font = `bold ${fontSize}px sans-serif`;
    while (ctx.measureText(name).width > (w - 24) && fontSize > 11) {
        fontSize -= 1;
        ctx.font = `bold ${fontSize}px sans-serif`;
    }
    ctx.textAlign = 'center';
    ctx.textBaseline = 'middle';
    ctx.fillText(name, w / 2, labelY + labelH / 2);
}

function _renderSpriteCanvas(skin, name) {
    const canvas = document.createElement('canvas');
    canvas.width = SPRITE_CANVAS_W;
    canvas.height = SPRITE_CANVAS_H;
    const ctx = canvas.getContext('2d');
    ctx.imageSmoothingEnabled = false;
    ctx.clearRect(0, 0, canvas.width, canvas.height);
    _drawCharacter(ctx, skin);
    _drawLabel(ctx, canvas.width, canvas.height, name);
    return canvas;
}

function getSpriteTexture(npcId) {
    if (_spriteCache[npcId]) return _spriteCache[npcId];
    const npc = NPCS[npcId];
    const skin = SPRITE_SKINS[npcId];
    if (!npc || !skin) return null;

    const canvas = _renderSpriteCanvas(skin, npc.nombre);
    const texture = new THREE.CanvasTexture(canvas);
    texture.magFilter = THREE.NearestFilter;
    texture.minFilter = THREE.LinearMipMapLinearFilter;
    texture.colorSpace = THREE.SRGBColorSpace;
    texture.generateMipmaps = true;
    _spriteCache[npcId] = texture;
    return texture;
}

function getPlayerTexture() {
    if (_spriteCache._player) return _spriteCache._player;
    const canvas = _renderSpriteCanvas(PLAYER_SKIN, '');
    const texture = new THREE.CanvasTexture(canvas);
    texture.magFilter = THREE.NearestFilter;
    texture.minFilter = THREE.LinearMipMapLinearFilter;
    texture.colorSpace = THREE.SRGBColorSpace;
    texture.generateMipmaps = true;
    _spriteCache._player = texture;
    return texture;
}
