feat(vida): ritmo também define a sensibilidade dos sensores

O servidor manda "ritmo calmo|normal|agitado" ao celular junto com o
humor, e o rosto ajusta limiares e quantos estímulos precisa:

- calmo: limiares 50% mais altos, 3 sustos seguidos para reagir, o dobro
  de sons e 4 movimentos para acordar, 3 disparos do PIR em 2 min;
- normal: reage a barulho alto e movimento forte, 2 movimentos ou os
  sons configurados para acordar;
- agitado: limiares 40% mais baixos, reage a qualquer som ou movimento
  (com reações variadas) e acorda no primeiro estímulo.

A madrugada silenciosa continua valendo por cima. Com o modo vida
desligado o celular volta ao "normal" (só os Ajustes).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
julio
2026-10-06 17:39:14 -03:00
co-authored by Claude Opus 5.5
parent dd40c730be
commit 9e09df40e2
6 changed files with 141 additions and 52 deletions
+50 -23
View File
@@ -592,6 +592,8 @@ function trata(ordem) {
}
} else if (ordem.indexOf("humor ") === 0) {
HUMOR = ordem.slice(6).trim();
} else if (ordem.indexOf("ritmo ") === 0) {
PERFIL = PERFIS[ordem.slice(6).trim()] || PERFIS.normal;
} else if (ordem === "dormir" || ordem === "sono") {
r.dormindo = true;
} else if (ordem === "acordar") {
@@ -743,24 +745,45 @@ const MIC = {
/* Acordar com som: precisa de N barulhos em 1 minuto (ajuste "Sons para acordar"). Na madrugada
silenciosa só barulho ALTO conta, e movimento/câmera não acordam (alguém se mexendo na cama). */
const DESPERTAR = { sons: 3, janela: 60, noite: true, ini: 23, fim: 7, eventos: [], ultimo: -Infinity, estava: false };
const DESPERTAR = { sons: 3, noite: true, ini: 23, fim: 7 };
function madrugada() {
if (!DESPERTAR.noite || DESPERTAR.ini === DESPERTAR.fim) return false;
const h = new Date().getHours();
return DESPERTAR.ini < DESPERTAR.fim ? h >= DESPERTAR.ini && h < DESPERTAR.fim : h >= DESPERTAR.ini || h < DESPERTAR.fim;
}
/* Conta o INÍCIO de cada barulho (o mesmo barulho comprido vale um) e diz se já dá para acordar. */
function barulhoAcorda(ouviu, agora) {
const D = DESPERTAR;
if (ouviu && !D.estava && agora - D.ultimo >= 1.5) {
D.ultimo = agora;
D.eventos = D.eventos.filter((t) => agora - t < D.janela).concat(agora);
console.log("ouvido: barulho " + D.eventos.length + "/" + D.sons + (madrugada() ? " (madrugada)" : ""));
/* Perfil de sensibilidade = ritmo do modo vida (o servidor manda "ritmo calmo|normal|agitado").
Calmo precisa de vários estímulos para reagir e limiares mais altos; agitado reage a qualquer
som ou movimento. Multiplica os limiares dos Ajustes, sem mudar os Ajustes. */
const PERFIS = {
calmo: { lim: 1.5, reagir: 3, acordarSom: 2, acordarMov: 4, espera: 2, qualquer: false },
normal: { lim: 1.0, reagir: 1, acordarSom: 1, acordarMov: 2, espera: 1, qualquer: false },
agitado: { lim: 0.6, reagir: 1, acordarSom: 0.34, acordarMov: 1, espera: 0.5, qualquer: true },
};
let PERFIL = PERFIS.normal;
const limiar = (x) => Math.min(0.95, x * PERFIL.lim);
/* Conta o INÍCIO de cada estímulo (o mesmo som/movimento comprido vale um) numa janela de tempo
e diz quando chegou a `precisa`. */
function novoContador(janela) { return { janela, eventos: [], ultimo: -Infinity, estava: false, visto: -Infinity }; }
const CONTA = { somAcordar: novoContador(60), movAcordar: novoContador(30), somReagir: novoContador(20), movReagir: novoContador(20) };
function contar(c, ativo, agora, precisa, nome) {
// contador parado um tempo (ex.: o de "acordar" enquanto estava acordado): começa do zero
if (agora - c.visto > 2) c.estava = false;
c.visto = agora;
if (ativo && !c.estava && agora - c.ultimo >= 1.0) {
c.ultimo = agora;
c.eventos = c.eventos.filter((t) => agora - t < c.janela).concat(agora);
if (precisa > 1) console.log(nome + " " + c.eventos.length + "/" + precisa + (madrugada() ? " (madrugada)" : ""));
}
D.estava = ouviu;
if (D.eventos.length >= D.sons && agora - D.eventos[D.eventos.length - 1] < 1) { D.eventos = []; return true; }
c.estava = ativo;
if (c.eventos.length >= precisa && agora - c.eventos[c.eventos.length - 1] < 1) { c.eventos = []; return true; }
return false;
}
/* Reação acordado: agitado varia; os outros se assustam. */
function reacaoEstimulo() {
if (!PERFIL.qualquer) return "surpreso";
const l = ["surpreso", "feliz", "alegre", "piscadinha", "surpreso"];
return l[Math.floor(Math.random() * l.length)];
}
function som(nivel) {
const n = Math.max(0, Math.min(1, Number(nivel) || 0));
@@ -776,8 +799,8 @@ function som(nivel) {
if (MIC.piso === null) MIC.piso = n;
else MIC.piso += (n - MIC.piso) * (n < MIC.piso ? 0.05 : 0.0015);
const temSom = n >= MIC.limiarSom; // ruido audivel (absoluto)
const alto = n >= MIC.limiarAlto && (n - MIC.piso) >= MIC.ganho;
const temSom = n >= limiar(MIC.limiarSom); // ruido audivel (absoluto)
const alto = n >= limiar(MIC.limiarAlto) && (n - MIC.piso) >= limiar(MIC.ganho);
if (temSom) MIC.ultimoSom = agora;
// registro periodico (aparece no logcat do app: prova que o ouvido chegou)
@@ -787,16 +810,18 @@ function som(nivel) {
}
if (r.dormindo) {
if (barulhoAcorda(madrugada() ? alto : temSom, agora)) { // acordou: barulho repetido
const precisa = Math.max(1, Math.round(DESPERTAR.sons * PERFIL.acordarSom));
if (contar(CONTA.somAcordar, madrugada() ? alto : temSom, agora, precisa, "ouvido: barulho")) { // acordou
r.dormindo = false;
animar("surpreso");
MIC.proxSurpresa = agora + MIC.cooldown;
console.log("ouvido: acordou nivel=" + n.toFixed(2));
relatarSom("acordou", n);
}
} else if (alto && !MIC.estavaAlto && agora >= MIC.proxSurpresa) {
MIC.proxSurpresa = agora + MIC.cooldown; // susto: barulho alto e repentino
animar("surpreso");
} else if (contar(CONTA.somReagir, PERFIL.qualquer ? temSom : alto, agora, PERFIL.reagir, "ouvido: estimulo") &&
agora >= MIC.proxSurpresa) {
MIC.proxSurpresa = agora + MIC.cooldown * PERFIL.espera; // susto: barulho alto e repentino
animar(reacaoEstimulo());
console.log("ouvido: surpreso nivel=" + n.toFixed(2));
relatarSom("surpreso", n);
}
@@ -880,7 +905,7 @@ function rastrear(t) {
MOV.olharY+=((t.y-.5)*2*MOV.olharMax*.6-MOV.olharY)*.65;
if(t.atividade===true) {
MOV.ultimoMov=agora;
if(r.dormindo && !madrugada()) { r.dormindo=false;animar('surpreso'); }
if(r.dormindo && !madrugada() && PERFIL !== PERFIS.calmo) { r.dormindo=false;animar('surpreso'); }
}
}
function movimento(vel, cx, cy) {
@@ -894,7 +919,7 @@ function movimento(vel, cx, cy) {
if (!COMPORTAMENTO.reagir) { MOV.olharX = MOV.olharY = 0; return; }
if (TRACK.alvo && agora-TRACK.visto<.35) return;
const mexeu = v >= MOV.limiar;
const mexeu = v >= limiar(MOV.limiar);
if (mexeu) MOV.ultimoMov = agora;
// olhar: desliza na direcao de quem se mexeu; sem movimento, volta ao centro
@@ -909,19 +934,21 @@ function movimento(vel, cx, cy) {
MOV.cx.toFixed(2) + "," + MOV.cy.toFixed(2));
}
const forte = v >= MOV.limiarForte;
const forte = v >= limiar(MOV.limiarForte);
if (r.dormindo) {
if (mexeu && !madrugada()) { // acordou: algo se mexeu na frente (de dia)
// acordou: algo se mexeu na frente (de dia; calmo precisa de vários movimentos)
if (!madrugada() && contar(CONTA.movAcordar, mexeu, agora, PERFIL.acordarMov, "presenca: movimento")) {
r.dormindo = false;
animar("surpreso");
MOV.proxSurpresa = agora + MOV.cooldown;
console.log("presenca: acordou nivel=" + v.toFixed(2));
relatarMov("acordou", v);
}
} else if (forte && !MOV.estavaForte && agora >= MOV.proxSurpresa) {
} else if (contar(CONTA.movReagir, PERFIL.qualquer ? mexeu : forte, agora, PERFIL.reagir, "presenca: estimulo") &&
agora >= MOV.proxSurpresa) {
// susto so no INICIO do movimento forte: se continuar se mexendo, nao repete
MOV.proxSurpresa = agora + MOV.cooldown;
animar("surpreso");
MOV.proxSurpresa = agora + MOV.cooldown * PERFIL.espera;
animar(reacaoEstimulo());
console.log("presenca: surpreso nivel=" + v.toFixed(2));
relatarMov("surpreso", v);
}