feat(rosto): atividades (momentos dele) e seis gestos novos

Atividades: a tela se divide, o rosto vai para a esquerda e encolhe, e
à direita um bonequinho do Carinha (silhueta na cor dos olhos) faz algo:
ler (vira a página), cantar (microfone e notas), jogar bola
(embaixadinha), videogame (TV com joguinho), pintar (cavalete que ganha
pinceladas) e malhar (halteres e suor). Os olhos grandes acompanham:
leem as linhas, seguem a bola, olham a TV. Camada própria atrás dos
olhos, vale para todos os rostos; em pé (retrato) a cena vai para baixo.

- Comando {tipo: "atividade", valor: nome|parar}; toque na tela, dormir
  e E-Stop encerram; o celular informa a atividade na telemetria.
- Modo vida escolhe pelo humor (feliz canta e joga bola, tranquilo lê
  e pinta, entediado joga videogame), nunca de madrugada, e não
  interrompe uma atividade em andamento.
- Gestos novos: rir, comemorar, tímido, confuso, bocejar e tonto; o
  humor e o modo vida também os usam.
- Painel: favoritos vão até 20 e o bravo volta junto com os novos;
  botões de atividade no Ao vivo, no Ctrl+K e tópico no guia.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
julio
2026-10-06 21:12:14 -03:00
co-authored by Claude Opus 5.5
parent 48fd51e377
commit d7a17b5683
13 changed files with 895 additions and 36 deletions
+8 -1
View File
@@ -106,9 +106,16 @@ class ServidorTest(unittest.TestCase):
self.assertEqual(self.pedir('POST', '/comando', {'tipo': 'gesto', 'valor': 'rm -rf'})[0], 400)
self.assertEqual(self.pedir('POST', '/comando', {'tipo': 'rosto', 'valor': 'nada'})[0], 400)
self.assertEqual(kiosk.FILA.ordens(), ['feliz', 'rosto miudo'])
for novo in ('acenar', 'esconder', 'espiar', 'pensar', 'musica'): # gestos com mãos e faixa
for novo in ('acenar', 'esconder', 'espiar', 'pensar', 'musica', # gestos com mãos e faixa
'rir', 'bocejar', 'confuso', 'tonto', 'timido', 'comemorar', 'bravo'):
self.assertEqual(self.pedir('POST', '/comando', {'tipo': 'gesto', 'valor': novo})[0], 200)
kiosk.FILA.limpar()
# atividades (momentos dele)
self.assertEqual(self.pedir('POST', '/comando', {'tipo': 'atividade', 'valor': 'videogame'})[0], 200)
self.assertEqual(self.pedir('POST', '/comando', {'tipo': 'atividade', 'valor': 'parar'})[0], 200)
self.assertEqual(self.pedir('POST', '/comando', {'tipo': 'atividade', 'valor': 'voar'})[0], 400)
self.assertEqual(kiosk.FILA.ordens(), ['atividade videogame', 'atividade parar'])
kiosk.FILA.limpar()
kiosk.FILA.adicionar('feliz'); kiosk.FILA.adicionar('rosto miudo')
self.assertEqual(self.pedir('GET', '/fila?aparelho=1')[1], ['feliz', 'rosto miudo'])
self.assertEqual(self.pedir('GET', '/fila?aparelho=1')[1], [])
+17
View File
@@ -96,6 +96,23 @@ class VidaTest(unittest.TestCase):
self.assertIn('ritmo normal', self.ordens)
self.assertTrue(self.v.pir_acorda(DIA + 600)) # desligado: comportamento de sempre
def test_atividades_pelo_humor(self):
self.v.celular_online = lambda: True
self.v.necessidades.update(energia=80, alegria=70, social=70, diversao=10) # entediado
self.v.rng.random = lambda: 0.0 # sorteio sempre "sim"
ato = self.v.agir(DIA)
self.assertTrue(ato.startswith('atividade '))
self.assertIn(ato.split()[1], ('videogame', 'bola', 'pintar', 'ler'))
self.assertEqual(self.ordens[-1], ato)
self.assertEqual(self.v.diario[0]['tipo'], 'atividade')
self.assertGreater(self.v.necessidades['diversao'], 10)
self.v.celular_atividade = lambda: 'videogame' # ocupado: não interrompe
self.assertIsNone(self.v.agir(DIA + 60))
self.v.celular_atividade = lambda: None
noite = time.mktime((2026, 10, 6, 2, 0, 0, 0, 0, -1))
self.v.config['noite'] = False
self.assertFalse(str(self.v.agir(noite)).startswith('atividade')) # de madrugada, nunca
def test_horario_da_madrugada(self):
from controle_pc import madrugada
h = lambda hora: time.mktime((2026, 10, 6, hora, 10, 0, 0, 0, -1))