feat(vida): aprendizado com o dia a dia (gosto, rotina da casa e sono)

O Carinha passa a melhorar as interações com o que vive em casa, sem IA
e sem internet (aprendizado.py):
- gosto: gesto ou atividade feito com alguém por perto é uma tentativa;
  carinho, brincadeira ou conversa até 60 s depois contam como "gostaram"
  (cutucada vale meio). A chance de cada ato vai de 0,5x a 2x, relativa à
  média, e só depois de 3 tentativas;
- rotina: fração de minutos com gente em cada hora (média móvel entre
  dias). Antes de uma hora movimentada, acorda do cochilo 15 min antes;
  sono pesado e madrugada continuam respeitados;
- sono: PIR acordou e ninguém ficou nem interagiu em 3 min = à toa. Se
  repete na mesma hora, ali pede 1 ou 2 disparos a mais para acordar;
- à meia-noite fecha o dia: lições em primeira pessoa no diário e o
  passado pesa 15% menos a cada dia (o gosto muda);
- rotas /aprendizado/status|config|esquecer e SSE 'aprendizado';
- painel: cartão "O que ele aprendeu" no Ao vivo (rotina em 24 barras,
  faz mais / faz menos, lições, esquecer com confirmação) e tópico no
  guia.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
julio
2026-10-09 10:13:23 -03:00
co-authored by Claude Opus 5.5
parent 4777ceb023
commit 24625453f7
12 changed files with 1000 additions and 9 deletions
+37 -1
View File
@@ -112,6 +112,7 @@ PERIODOS = [(5, 12, 'de manhã', ['um sol nascendo', 'um pãozinho', 'uma xícar
(0, 5, 'de madrugada', ['uma lua', 'uma vela', 'uma coruja'])]
MAX_DIARIO = 60
GESTOS_VIDA = {g for atos in ATOS.values() for g, _, _ in atos}
def _limitar(v):
@@ -147,6 +148,7 @@ class Vida:
self.do_dia = lambda: [] # ids das ideias de hoje
self.aprendi_pendente = lambda: None # curiosidade de hoje que ainda dá para contar
self.contar_aprendi = lambda ident, agora=None: None
self.aprendizado = None # o que ele aprende com o dia a dia (aprendizado.py)
agora = time.time()
self.config = dict(PADRAO)
@@ -319,6 +321,8 @@ class Vida:
self.anotar('cuidado', CUIDADOS[tipo], agora)
if tipo in CUIDADOS or tipo == 'conversa':
self.interagiu_em = agora # alguém quis ele acordado (sensor não conta)
if self.aprendizado:
self.aprendizado.resposta(tipo, agora)
if gesto and not self.bloqueado():
if self.dormindo():
self.acordar(agora, None)
@@ -369,7 +373,10 @@ class Vida:
else:
lista = ATIVAS + CRIATIVAS * 2 + CALMAS
opcoes = [a for a in lista if a != self.ultima_atividade] or lista
return self.rng.choice(opcoes)
pesos = [self.gosto(a) for a in opcoes]
if all(p == 1.0 for p in pesos):
return self.rng.choice(opcoes)
return self.rng.choices(opcoes, weights=pesos)[0] # as que agradam saem mais
def comecar_atividade(self, agora, nome, segundos):
if nome.startswith('criado:'):
@@ -394,8 +401,12 @@ class Vida:
agora = agora or time.time()
self.pir_eventos = [t for t in self.pir_eventos if agora - t < 120] + [agora]
precisa = PIR_ACORDAR[self.config['ritmo']] if self.config['ligado'] else 1
if self.aprendizado and self.config['ligado']:
precisa += self.aprendizado.extra_pir(agora) # nesta hora já acordou à toa outras vezes
if len(self.pir_eventos) >= precisa:
self.pir_eventos = []
if self.aprendizado and self.config['ligado']:
self.aprendizado.acordou_pelo_sensor(agora)
return True
return False
@@ -406,7 +417,12 @@ class Vida:
return hora >= ini or hora < fim
# ------------------------------------------------------------------ decidir o que fazer
def gosto(self, nome):
"""Quanto o aprendizado multiplica a chance de um ato (1 = sem opinião)."""
return self.aprendizado.peso(nome) if self.aprendizado else 1.0
def escolher(self, opcoes):
opcoes = [(g, p * self.gosto(g), t) for g, p, t in opcoes]
total = sum(p for _, p, _ in opcoes)
x = self.rng.uniform(0, total)
for gesto, peso, texto in opcoes:
@@ -473,6 +489,11 @@ class Vida:
if n['energia'] >= DESCANSADO and not self.madrugada(agora):
self.acordar(agora, 'Acordou descansado')
return 'acordar'
# aprendeu a rotina: acorda do cochilo um pouco antes da hora em que costuma ter gente
if (self.aprendizado and not self.sono_profundo and not self.madrugada(agora) and n['energia'] >= 50
and self.aprendizado.chega_logo(agora)):
self.acordar(agora, 'Acordou: daqui a pouco costuma chegar alguém')
return 'acordar'
return None
if self.config['noite'] and self.madrugada(agora) and agora - self.interagiu_em > PEDIU_ATENCAO:
self.dormir(agora, 'Ficou tarde, foi dormir')
@@ -529,11 +550,25 @@ class Vida:
self.anotar('acao', texto, agora)
return gesto
def aprender_ato(self, ato, agora):
"""Gestos e atividades feitos com plateia entram no aprendizado (para ver se agradam)."""
if not self.aprendizado or not ato:
return
if ato.startswith('atividade '):
nome = ato[len('atividade '):]
elif ato in GESTOS_VIDA:
nome = ato
else:
return
self.aprendizado.ato(nome, agora, self.alguem_perto())
def tick(self, agora=None):
agora = agora or time.time()
self.passar_tempo(agora)
if not self.config['ligado']:
return None
if self.aprendizado:
self.aprendizado.observar(agora, self.alguem_perto())
ato = None
humor = self.humor()
# acordaram ele de propósito (toque, painel) antes de descansar: o sono pesado acaba aqui
@@ -554,6 +589,7 @@ class Vida:
self.zanga_enviada = zanga
if agora >= self.proximo_ato:
ato = self.agir(agora)
self.aprender_ato(ato, agora)
lo, hi = RITMOS[self.config['ritmo']]
self.proximo_ato = agora + self.rng.uniform(lo, hi)
if ato or agora - self.publicado_em > 30 or humor != self.humor():