@c80/avatar
v1.1.1
Published
Avatar anfitrion animado "Aura" del ecosistema barman: core de comportamiento framework-agnostico + renderer SVG/CSS de fallback + renderer Rive opcional + adaptador Angular. API por intenciones de negocio.
Readme
@c80/avatar
Avatar anfitrion animado "Aura" del ecosistema barman: una estrella luminosa que acompania al cliente en chillbox (telefono) y en la pantalla de kiosco del box, orientando y comunicando estados sin ser invasiva. El nombre de negocio (PUNTOS ESTELARES) elige la mascota: una estrella.
Principio rector: las apps emiten intenciones de negocio; la libreria decide el comportamiento (que estado, que emocion, que animacion, duracion, prioridad, interrupciones, a que estado volver). Ninguna pantalla manipula animaciones ni conoce Rive.
Libreria hermana e independiente de @c80/ui/@c80/core/@c80/types: es autocontenida.
Los consumidores traducen su dominio a los eventos del avatar.
Estado de implementacion
- CORE framework-agnostico — tipos, contrato de renderer, defaults, behavior-map, cola de eventos, autonomia y runtime. TS puro, sin Angular/Rive/DOM. Importable en Node y determinista (reloj y RNG inyectables).
- Renderer fallback SVG/CSS — Aura dibujada y animada (el entregable visual v1).
- Renderer Rive opcional — dynamic import, wasm self-host, contrato del asset documentado.
- Adaptador Angular —
provideAvatar,AvatarService,<c80-avatar>(Angular 21 zoneless).
Pendiente (fuera de la lib): el .riv real (trabajo artistico), la app kiosk, la integracion en
chillbox tras el flag CLIENT_AVATAR y la extension aditiva de vulcan.
Arquitectura
core/ TS PURO (sin Angular, sin Rive, sin DOM) — el "cerebro"
types.ts eventos, estados, emociones, atencion, acciones, config, snapshot
renderer.contract.ts contrato AvatarRenderer (frontera hacia el dibujo)
defaults.ts config por defecto + presets por modo + resolveConfig()
behavior-map.ts guion evento -> reaccion + resolveBehavior + reglas de coherencia
event-queue.ts cola con prioridad + TTL + dedupe (reloj inyectable)
autonomy.ts scheduler de microacciones en ocio (RNG inyectable, sin timers propios)
runtime.ts orquesta cola/estado/accion/visibilidad/autonomia; publica snapshots
renderers/
fallback/ Aura en SVG (aura-svg.ts) + FallbackRenderer (SVG/CSS + Web Animations API)
rive/ RiveRenderer (dynamic import) + rive-contract.ts (spec del .riv)
angular/ provideAvatar + AvatarService (signals, rAF zoneless) + C80AvatarComponentEl runtime NUNCA toca el DOM: publica AvatarSnapshot y el adaptador conecta
snapshot -> renderer.render(). Un renderer es intercambiable (fallback hoy, Rive manana) sin
que cambie nada aguas arriba.
Modelo de estado (dimensiones separadas)
- Visibilidad:
hidden | entering | visible | leaving. - Estado principal:
idle | processing | awaiting-qr-scan | awaiting-payment | preparing-drink | success | warning | error | offline | maintenance | disabled. - Emocion (complementa, no reemplaza):
neutral | friendly | happy | curious | focused | excited | surprised | confused | empathetic | concerned | relieved. - Atencion:
passive | attentive | active | priority. - Acciones temporales (se reproducen sobre el estado base y regresan solas):
greet | blink | nod | shake-head | look-at-qr | invite-to-scan | celebrate | wait-expectantly | reassure | wake-up | sleep | indicate-pickup.
El runtime publica un AvatarSnapshot inmutable cada vez que algo cambia. El renderer aplica
el snapshot completo con render(snapshot) (idempotente) y dispara la accion temporal cuando
detecta que snapshot.action cambio a una accion nueva.
Prioridades, cola y coherencia
- Prioridades
critical > high > normal > ambient. Un error critico interrumpe cualquier decoracion; una microanimacion (ambient) jamas interrumpe una accion high/critical en curso; una accion no-interrumpible solo cede ante prioridad estrictamente mayor. - La cola descarta eventos caducados (
ttlMs), colapsa repetidos (dedupeKey) y ordena por prioridad con desempate FIFO. - Reglas de coherencia (
applyCoherence): sin emocion alegre en estadoerror; sin celebracion conerror/warningactivo.
Autonomia
Microacciones cuando el avatar esta ocioso y sin cola (parpadeo, dormitar tras inactividad,
mirar/invitar al QR en kiosk). Activada por defecto y configurable por modo. No usa timers del
navegador: el adaptador llama runtime.tick(now) y el core decide que microaccion y cuando.
Instalacion / consumo
@c80/avatar se publica a npm como sus hermanas. En apps del workspace ngx-c80 se resuelve por el
path @c80/avatar. Peers: @angular/core, @angular/common, rxjs. @rive-app/canvas es peer
opcional (solo si se activa el renderer rive).
Config minima (Angular)
// app.config.ts
import { provideAvatar } from '@c80/avatar';
export const appConfig: ApplicationConfig = {
providers: [
provideAvatar({ mode: 'mobile', skin: 'aura' }), // o { mode: 'kiosk' } en el kiosco
],
};<!-- en el shell/layout global, una sola vez -->
<c80-avatar></c80-avatar>// desde cualquier servicio/componente
import { AvatarService } from '@c80/avatar';
private readonly avatar = inject(AvatarService);
this.avatar.show();
this.avatar.emit({ type: 'serve.preparing' });
this.avatar.drinkReady();Uso del core (sin Angular)
import { AvatarRuntime, resolveConfig } from '@c80/avatar';
const runtime = new AvatarRuntime(resolveConfig({ mode: 'kiosk' }));
const off = runtime.subscribe((s) => console.log(s.state, s.emotion, s.action));
runtime.emit({ type: 'avatar.show' });
runtime.emit({ type: 'drink.ready' });
runtime.tick(performance.now()); // el adaptador avanza el tiempo
off();
runtime.destroy();API publica
- Core:
AvatarRuntime,AvatarEventQueue,AutonomyScheduler,resolveConfig,DEFAULT_AVATAR_CONFIG,MODE_PRESETS,BEHAVIOR_MAP,resolveBehavior,applyCoherence,PRIORITY_RANK,comparePriority, y todos los tipos (AvatarEvent,AvatarSnapshot,AvatarConfig,AvatarRenderer, ...). - Angular:
provideAvatar(config?),AvatarService,C80AvatarComponent(<c80-avatar>),AVATAR_CONFIG. - Renderers:
FallbackRenderer,RiveRenderer,buildAuraSvgMarkup,AURA_STYLE,AURA_VIEWBOX,AURA_CSS_VARS, y el contrato Rive (AVATAR_MACHINE,AVATAR_INPUTS,AVATAR_TRIGGERS,ACTION_TO_TRIGGER,STATE_INPUT_VALUES, ...).
AvatarService
emit(event)+ helpers:show(),hide(),sessionStarted(),sessionExpired(),rechargeApproved(),rechargeRejected(),drinkReady(),serveError(recoverable, refunded?),networkOffline(),networkOnline().- Signals de solo lectura:
snapshot,ready,state,emotion,visible. attachRenderer(host)/detachRenderer()los usa el componente; degrada rive -> fallback.
Catalogo de eventos (API por intenciones)
Union discriminada AvatarEvent por type:
- Ciclo de vida:
avatar.show,avatar.hide. - Sesion de box:
box.session.started,box.session.expired. - Recarga / puntos:
recharge.started,recharge.pending,recharge.approved,recharge.rejected,points.insufficient. - Canje y servido:
redeem.confirmed,serve.queued(position?),serve.preparing,serve.dispensing,serve.completed,drink.ready,serve.error(recoverable,refunded?). - Estado del box:
box.idle,box.busy,box.maintenance,box.out-of-service,box.estop. - Conectividad:
network.offline,network.online.
El mapeo evento -> reaccion (estado, emocion, accion, prioridad) vive en BEHAVIOR_MAP.
Modos
mobile (sutil, telefono), web (equilibrado), kiosk (mas expresivo, invita al QR),
maintenance (sobrio, autonomia apagada). resolveConfig(partial) aplica el preset del modo y
luego el override del usuario. Reduced motion es transversal: reducedMotion: 'system'
(default, sigue prefers-reduced-motion con matchMedia) | 'enabled' | 'disabled'.
Renderer fallback (Aura SVG/CSS)
Es el entregable visual v1 y el default. Dibuja una estrella luminosa de 5 puntas con nucleo
brillante y rostro minimo (ojos + boca), animada solo con transform/opacity (barato en CPU,
apto para dias en la Raspberry). Anima con:
- CSS continuo: flotacion, halo, pulso del nucleo, destellos (se pausan solos cuando la
pestania se oculta y con
pause()). - Web Animations API: los gestos temporales (greet, celebrate, mirada, nod, ...).
- Expresiones por estado (tinte del halo) y emocion (forma de boca/ojos).
- Reduced motion: sustituye desplazamientos/rotaciones por cambios suaves de opacidad/escala.
Retematizar sin tocar la lib: redefinir en el host las CSS custom properties
--c80-avatar-core|body|body-edge|glow|rim|face|sparkle (ver AURA_CSS_VARS).
Contrato del asset Rive (para el trabajo artistico)
El renderer rive esta implementado detras del contrato pero sin un .riv real no se valida
end-to-end (honestidad). El asset lo produce un artista en el editor de Rive siguiendo
rive-contract.ts:
- State machine:
AvatarMachine. - Inputs (numericos salvo
reducedMotionbooleano):state,emotion,attention,intensity(0..2),lookX/lookY(-1..1),reducedMotion. Los enumerados usan los indices EXACTOS deSTATE_INPUT_VALUES/EMOTION_INPUT_VALUES/ATTENTION_INPUT_VALUES(son contrato: no reordenar). - Triggers:
greet,blink,nod,shakeHead,lookAtQr,inviteToScan,celebrate,reassure,wakeUp,sleep,indicatePickup(mapeo desde acciones enACTION_TO_TRIGGER).
Como enchufar el .riv
- Instalar el peer opcional en la app:
pnpm add @rive-app/canvas. - Self-hostear el wasm del runtime junto al
.riv(misma carpeta de assets, servido comorive.wasm). Nunca CDN (el kiosco debe funcionar sin internet). provideAvatar({ renderer: 'rive', assetUrl: '/assets/avatar/aura.riv' }).- Si el import o el asset fallan, el adaptador degrada al fallback automaticamente.
Como extender
- Agregar un evento: sumar la variante a
AvatarEvent(types.ts) y su fila enBEHAVIOR_MAP(behavior-map.ts). Si necesita logica (leer campos del evento), ampliarresolveBehavior. - Agregar una emocion: sumarla a
AvatarEmotion, mapear su expresion en el CSS de Aura (AURA_STYLE, selector[data-emotion="..."]) y su indice enEMOTION_INPUT_VALUES(Rive). - Agregar una accion: sumarla a
AvatarAction, su keyframe enFallbackRenderery su trigger enAVATAR_TRIGGERS+ACTION_TO_TRIGGER. - Agregar una skin: por ahora hay una (
aura). Un renderer nuevo puede leerconfig.skinpara elegir markup/paleta; el contratoAvatarRendererno cambia. - Agregar un renderer: implementar
AvatarRenderery seleccionarlo enAvatarService.createRenderer(dynamic import para mantenerlo en chunk aparte).
Limitaciones actuales
- Sin un
.rivreal, el renderer Rive no se valida end-to-end: queda detras del contrato con tipos locales acotados; su rama probada es la de error (asset/paquete ausente -> fallback). - Una sola personalidad (
friendly-host) y una sola skin (aura). - El fallback usa Web Animations API y
transform-box: fill-box(navegadores modernos; el target es Chromium del kiosco y los moviles actuales). - Sin sonido y sin PII (decision de producto v1).
