Console (console.py + tela Console no painel):
- cada parte registra o que fez e o motivo: modo vida (com necessidades
e humor), sentidos, celular (ordens e o que o rosto fez), IA local,
aprendizado e ideias do dia; últimas 400 linhas, ao vivo pelo SSE;
- tela com o "agora" (humor, necessidades, o que está fazendo, próxima
decisão, última decisão, jeito), filtros, detalhes, pausa e busca.
Jeito de gente:
- saudade: a festa cresce com o tempo fora (aceno, "Oi! Você voltou!",
"Senti sua falta!"); bom dia lembrando de ontem;
- pedidos com motivo ("Brinca comigo?", "Me faz um carinho?", mostrar a
figurinha nova), no máximo 1 a cada 20 min;
- larga atividade no meio quando fica chato; personalidade (curiosidade,
brincadeira, timidez) que pesa nos gestos e amadurece com o convívio;
- no rosto: acordar grogue, sonhar com algo do dia (balão no motor),
hesitar antes de gestos, errar na atividade e ficar sem graça, olhar
nos olhos e desviar, sorrir de volta (FaceLandmarker no rastreador do
painel) e reagir à luz (sensor de luz no app 0.10.0);
- ordem "dizer" com legenda e voz de robô.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
159 lines
7.1 KiB
Python
159 lines
7.1 KiB
Python
"""Jeito de gente do modo vida: reencontro e saudade, bom dia lembrando de ontem, pedidos com motivo,
|
|
largar atividade no meio, personalidade e o console com o porquê de cada decisão."""
|
|
import random
|
|
import sys
|
|
import tempfile
|
|
import time
|
|
import unittest
|
|
from pathlib import Path
|
|
|
|
sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
|
|
from console import Console # noqa: E402
|
|
from vida import Vida # noqa: E402
|
|
|
|
DIA = time.mktime((2026, 10, 12, 9, 0, 0, 0, 0, -1)) # uma segunda, 9h
|
|
|
|
|
|
class HumanoTest(unittest.TestCase):
|
|
def setUp(self):
|
|
self.tmp = tempfile.TemporaryDirectory()
|
|
self.v = Vida(Path(self.tmp.name) / 'vida.json', iniciar=False, rng=random.Random(1))
|
|
self.ordens, self.linhas = [], []
|
|
self.v.enfileirar = self.ordens.append
|
|
self.v.console = lambda origem, texto, motivo='', nivel='info', agora=None: self.linhas.append((origem, texto, motivo, nivel))
|
|
self.v.celular_online = lambda: True
|
|
self.v.celular_dormindo = lambda: False
|
|
self.v.madrugada = lambda agora: False
|
|
self.perto = False
|
|
self.v.alguem_perto = lambda: self.perto
|
|
|
|
def tearDown(self):
|
|
self.tmp.cleanup()
|
|
|
|
def falas(self):
|
|
return [o[6:] for o in self.ordens if o.startswith('dizer ')]
|
|
|
|
def chegar(self, agora, ausencia):
|
|
self.v.ultimo_perto = agora - ausencia
|
|
self.v.perto_antes = False
|
|
self.perto = True
|
|
self.v.tick(agora)
|
|
|
|
def test_reencontro_cresce_com_o_tempo_fora(self):
|
|
self.v.saudou_dia = '2026-10-12' # bom dia já foi dado
|
|
self.chegar(DIA, 5 * 60) # 5 min fora: nada
|
|
self.assertEqual(self.falas(), [])
|
|
self.assertNotIn('acenar', self.ordens)
|
|
self.chegar(DIA + 100, 20 * 60) # 20 min: acena
|
|
self.assertIn('acenar', self.ordens)
|
|
self.chegar(DIA + 200, 2 * 3600) # 2 h: comemora
|
|
self.assertEqual(self.falas()[-1], 'Oi! Você voltou!')
|
|
social = self.v.necessidades['social']
|
|
self.chegar(DIA + 300, 6 * 3600) # 6 h: saudade
|
|
self.assertEqual(self.falas()[-1], 'Senti sua falta!')
|
|
self.assertIn('coracao', self.ordens)
|
|
self.assertGreater(self.v.necessidades['social'], social)
|
|
self.assertTrue(any('ninguém por perto havia 6h00' in m for _, _, m, _ in self.linhas))
|
|
|
|
def test_bom_dia_lembra_de_ontem(self):
|
|
ontem = dict(presenca=[None] * 24, respostas=0, atos=8)
|
|
ontem['presenca'][1] = 0.6 # ficaram acordados até as 2h
|
|
self.v.dia_anterior = lambda: ontem
|
|
self.v.acaso = random.Random(0)
|
|
self.chegar(DIA, 8 * 3600)
|
|
fala = self.falas()[0]
|
|
self.assertTrue(fala.startswith('Bom dia! '), fala)
|
|
self.assertIn(fala[9:], ['Ontem a gente ficou acordado até as 2h, hein!',
|
|
'Ontem ninguém brincou comigo... hoje vai ser diferente?'])
|
|
self.assertEqual(self.v.saudou_dia, '2026-10-12')
|
|
self.chegar(DIA + 3600, 30 * 60) # mesmo dia: não repete o bom dia
|
|
self.assertEqual(len([f for f in self.falas() if f.startswith('Bom dia')]), 1)
|
|
# de madrugada não faz festa
|
|
self.v.madrugada = lambda agora: True
|
|
n = len(self.falas())
|
|
self.chegar(DIA + 7200, 6 * 3600)
|
|
self.assertEqual(len(self.falas()), n)
|
|
|
|
def test_pede_o_que_falta_com_motivo_e_intervalo(self):
|
|
self.v.saudou_dia = '2026-10-12'
|
|
self.perto, self.v.perto_antes, self.v.visto_em = True, True, DIA
|
|
self.v.atividade_em = DIA # sem folga para atividades
|
|
self.v.necessidades.update(social=30, diversao=80, energia=80, alegria=70)
|
|
self.v.acaso = random.Random(3)
|
|
pedido = None
|
|
for i in range(40):
|
|
if self.v.agir(DIA + i) == 'pedido':
|
|
pedido = DIA + i
|
|
break
|
|
self.assertIsNotNone(pedido)
|
|
self.assertEqual(self.falas()[-1], 'Tô carente... me faz um carinho?')
|
|
self.assertIn('social 30', self.v.porque)
|
|
self.assertEqual(self.v.diario[0]['tipo'], 'pedido')
|
|
n = len(self.falas())
|
|
for i in range(40): # menos de 20 min depois: não pede de novo
|
|
self.v.agir(pedido + 60 + i)
|
|
self.assertEqual(len(self.falas()), n)
|
|
|
|
def test_larga_atividade_no_meio_e_conta(self):
|
|
self.v.acaso = random.Random(0)
|
|
largou = None
|
|
for i in range(60):
|
|
self.v.desistencia = None
|
|
self.v.comecar_atividade(DIA + i, 'ler', 200)
|
|
if self.v.desistencia:
|
|
largou = self.v.desistencia
|
|
break
|
|
self.assertIsNotNone(largou)
|
|
segundos = int(self.ordens[-1].split()[-1])
|
|
self.assertLess(segundos, 110) # mandou ao celular a duração encurtada
|
|
self.v.tick(largou[0] + 1)
|
|
self.assertIn('bocejar', self.ordens)
|
|
self.assertIn('Largou no meio: cansou de ler um livro', [d['texto'] for d in self.v.diario[:3]])
|
|
|
|
def test_personalidade_muda_o_jeito_e_amadurece(self):
|
|
self.assertEqual(self.v.jeito('rir'), 1.0) # neutro nos testes
|
|
self.v.personalidade['brincadeira'] = 0.9
|
|
self.assertGreater(self.v.jeito('rir'), 1.4)
|
|
self.v.personalidade['timidez'] = 0.2
|
|
self.assertLess(self.v.jeito('timido'), 0.7)
|
|
antes = self.v.personalidade['timidez']
|
|
for _ in range(10):
|
|
self.v.estimulo('carinho', DIA)
|
|
self.assertLess(self.v.personalidade['timidez'], antes)
|
|
self.v.salvar()
|
|
outra = Vida(Path(self.tmp.name) / 'vida.json', iniciar=False, rng=random.Random(1))
|
|
self.assertEqual(outra.personalidade, self.v.personalidade)
|
|
self.assertIn('personalidade', self.v.status())
|
|
|
|
def test_cada_decisao_vai_para_o_console_com_o_porque(self):
|
|
self.v.necessidades['energia'] = 10
|
|
self.v.proximo_ato = 0
|
|
self.v.ultimo_tick = DIA
|
|
self.v.tick(DIA)
|
|
origem, texto, motivo, _ = self.linhas[-1]
|
|
self.assertEqual((origem, texto), ('vida', 'Ficou exausto e foi dormir'))
|
|
self.assertIn('energia 10: abaixo de 20', motivo)
|
|
self.assertIn('energia 10 · alegria', motivo)
|
|
self.v.estimulo('carinho', DIA)
|
|
self.assertIn(('sentidos', 'Recebeu carinho'), [(o, t) for o, t, _, _ in self.linhas])
|
|
|
|
|
|
class ConsoleTest(unittest.TestCase):
|
|
def test_guarda_as_ultimas_e_publica(self):
|
|
c = Console()
|
|
publicados = []
|
|
c.publicar = lambda tipo, dados: publicados.append((tipo, dados))
|
|
for i in range(450):
|
|
c.registrar('vida', 'ato %d' % i, 'motivo')
|
|
c.registrar('inventada', 'vira sistema', nivel='qualquer')
|
|
st = c.status()
|
|
self.assertEqual(len(st['linhas']), 400)
|
|
self.assertEqual(st['linhas'][-1]['origem'], 'sistema')
|
|
self.assertEqual(st['linhas'][-1]['nivel'], 'info')
|
|
self.assertEqual(publicados[-1][0], 'console')
|
|
self.assertEqual(len(c.status(desde=449)['linhas']), 2)
|
|
|
|
|
|
if __name__ == '__main__':
|
|
unittest.main()
|