Tune awake decay rates for segment-aligned status model
Baby (4 segments): - hunger -8/hr, happiness -4.5/hr, hygiene -6/hr, energy -9/hr - health base -0.4/hr (was -0.75) - health penalty thresholds aligned to segment boundaries: mild at < 50 (attention), strong at < 25 (urgent) — was < 70/40, which fired penalties in the 'okay' range - health regen threshold lowered to 76 (baby good = 4/4 starts at 76) — was 80 Adult (10 segments): - hunger -5/hr, happiness -2.5/hr, hygiene -4/hr, energy -5.5/hr - health base -0.25/hr (was -0.4) - penalty thresholds unchanged (already align with 10-segment model) - regen threshold unchanged at 80 Pacing: - Baby first 'okay' stat at ~2.7hr, first 'attention' at ~5-6hr - Adult first 'okay' stat at ~5-6hr, first 'attention' at ~7-8hr - Growing up feels like increased resilience, not more annoyance Sleep behaviour, item values, careState mapping, segmented UI rendering, and Nostr persistence are unchanged. Tests updated to cover new rates, penalty alignment, and regen threshold.
This commit is contained in:
@@ -24,6 +24,172 @@ function decay(overrides: {
|
||||
});
|
||||
}
|
||||
|
||||
// ─── Baby awake ───────────────────────────────────────────────────────────────
|
||||
|
||||
describe('baby awake decay', () => {
|
||||
it('1 hour from full stats: decays at tuned rates', () => {
|
||||
const r = decay({ stage: 'baby', state: 'active', elapsedSeconds: 3600 });
|
||||
// hunger: 100 + trunc(-8) = 92
|
||||
// happiness: 100 + trunc(-4.5) = 96
|
||||
// hygiene: 100 + trunc(-6) = 94
|
||||
// energy: 100 + trunc(-9) = 91
|
||||
expect(r.stats.hunger).toBe(92);
|
||||
expect(r.stats.happiness).toBe(96);
|
||||
expect(r.stats.hygiene).toBe(94);
|
||||
expect(r.stats.energy).toBe(91);
|
||||
});
|
||||
|
||||
it('1 hour from full: health regens (all stats after decay ≥ 76)', () => {
|
||||
const r = decay({ stage: 'baby', state: 'active', elapsedSeconds: 3600 });
|
||||
// After deltas: hunger=92, happiness=96, hygiene=94, energy=91 — all ≥ 76.
|
||||
// healthDelta = -0.4 + 1.5 = 1.1, trunc(1.1) = 1. health = 101 → 100.
|
||||
expect(r.stats.health).toBe(100);
|
||||
});
|
||||
});
|
||||
|
||||
// ─── Baby health penalty alignment ───────────────────────────────────────────
|
||||
|
||||
describe('baby health penalties aligned to segment boundaries', () => {
|
||||
it('stats in "okay" range (60): no penalties, health barely decays', () => {
|
||||
const r = decay({
|
||||
stage: 'baby',
|
||||
state: 'active',
|
||||
stats: { hunger: 60, happiness: 100, health: 100, hygiene: 60, energy: 100 },
|
||||
elapsedSeconds: 3600,
|
||||
});
|
||||
// hunger after: 60 + trunc(-8) = 52 (still ≥ 50, no penalty)
|
||||
// hygiene after: 60 + trunc(-6) = 54 (still ≥ 50, no penalty)
|
||||
// happiness after: 96, energy after: 91. Neither < 50.
|
||||
// healthDelta = -0.4 (base only), no penalties, no regen (hunger 52 < 76).
|
||||
// trunc(-0.4) = 0 → health stays 100.
|
||||
expect(r.stats.health).toBe(100);
|
||||
expect(r.stats.hunger).toBe(52);
|
||||
expect(r.stats.hygiene).toBe(54);
|
||||
});
|
||||
|
||||
it('stats in "attention" range (40): mild penalties apply', () => {
|
||||
const r = decay({
|
||||
stage: 'baby',
|
||||
state: 'active',
|
||||
stats: { hunger: 40, happiness: 100, health: 100, hygiene: 40, energy: 100 },
|
||||
elapsedSeconds: 3600,
|
||||
});
|
||||
// hunger after: 40 + trunc(-8) = 32 (≤ 50, mild penalty)
|
||||
// hygiene after: 40 + trunc(-6) = 34 (≤ 50, mild penalty)
|
||||
// healthDelta = -0.4 (base) + -0.5 (hunger≤50) + -0.5 (hygiene≤50) = -1.4
|
||||
// trunc(-1.4) = -1 → health = 99.
|
||||
expect(r.stats.health).toBe(99);
|
||||
});
|
||||
|
||||
it('stats in "urgent" range (15): strong penalties stack', () => {
|
||||
const r = decay({
|
||||
stage: 'baby',
|
||||
state: 'active',
|
||||
stats: { hunger: 15, happiness: 100, health: 100, hygiene: 15, energy: 100 },
|
||||
elapsedSeconds: 3600,
|
||||
});
|
||||
// hunger after: 15 + trunc(-8) = 7 (≤ 50 + ≤ 25)
|
||||
// hygiene after: 15 + trunc(-6) = 9 (≤ 50 + ≤ 25)
|
||||
// healthDelta = -0.4 + -0.5 + -1.0 + -0.5 + -1.0 = -3.4
|
||||
// trunc(-3.4) = -3 → health = 97.
|
||||
expect(r.stats.health).toBe(97);
|
||||
});
|
||||
|
||||
it('penalty fires at exact boundary (hunger decays to exactly 50)', () => {
|
||||
const r = decay({
|
||||
stage: 'baby',
|
||||
state: 'active',
|
||||
// hunger 90 → 90 + trunc(-8*5) = 50. Exactly at attention boundary.
|
||||
stats: { hunger: 90, happiness: 100, health: 100, hygiene: 100, energy: 100 },
|
||||
elapsedSeconds: 3600 * 5,
|
||||
});
|
||||
// hunger after = 50. careState "attention" starts at ≤ 50, penalty must fire.
|
||||
// happiness: 100 + trunc(-4.5*5) = 78. hygiene: 100 + trunc(-6*5) = 70. energy: 100 + trunc(-9*5) = 55.
|
||||
// No other stat ≤ 50 → only hunger penalty.
|
||||
// healthDelta = -0.4*5 + -0.5*5 = -4.5, trunc(-4.5) = -4. health = 96.
|
||||
expect(r.stats.hunger).toBe(50);
|
||||
expect(r.stats.health).toBe(96);
|
||||
});
|
||||
|
||||
it('all stats urgent: maximum penalty pressure', () => {
|
||||
const r = decay({
|
||||
stage: 'baby',
|
||||
state: 'active',
|
||||
stats: { hunger: 10, happiness: 10, health: 80, hygiene: 10, energy: 10 },
|
||||
elapsedSeconds: 3600,
|
||||
});
|
||||
// After deltas: hunger=2, happiness=6, hygiene=4, energy=1.
|
||||
// All four stats ≤ 50 AND ≤ 25 → all mild + strong penalties fire.
|
||||
// healthDelta = -0.4 (base)
|
||||
// + 4 × -0.5 (mild) + 4 × -1.0 (strong) = -0.4 - 2.0 - 4.0 = -6.4
|
||||
// trunc(-6.4) = -6 → health = 80 - 6 = 74.
|
||||
expect(r.stats.health).toBe(74);
|
||||
});
|
||||
});
|
||||
|
||||
// ─── Baby health regen threshold ──────────────────────────────────────────────
|
||||
|
||||
describe('baby health regen threshold (≥ 76)', () => {
|
||||
it('all stats = 85: regens (after decay all ≥ 76)', () => {
|
||||
const r = decay({
|
||||
stage: 'baby',
|
||||
state: 'active',
|
||||
stats: { hunger: 85, happiness: 85, health: 85, hygiene: 85, energy: 85 },
|
||||
elapsedSeconds: 3600,
|
||||
});
|
||||
// After deltas: hunger=77, happiness=81, hygiene=79, energy=76 — all ≥ 76.
|
||||
// healthDelta = -0.4 + 1.5 = 1.1, trunc(1.1) = 1.
|
||||
expect(r.stats.health).toBe(86);
|
||||
});
|
||||
|
||||
it('all stats = 76: does NOT regen (after decay < 76)', () => {
|
||||
const r = decay({
|
||||
stage: 'baby',
|
||||
state: 'active',
|
||||
stats: { hunger: 76, happiness: 76, health: 76, hygiene: 76, energy: 76 },
|
||||
elapsedSeconds: 3600,
|
||||
});
|
||||
// After deltas: hunger=68, happiness=72, hygiene=70, energy=67 — NOT all ≥ 76.
|
||||
// healthDelta = -0.4, no regen. trunc(-0.4) = 0 → health stays 76.
|
||||
expect(r.stats.health).toBe(76);
|
||||
});
|
||||
});
|
||||
|
||||
// ─── Adult awake ──────────────────────────────────────────────────────────────
|
||||
|
||||
describe('adult awake decay', () => {
|
||||
it('1 hour from full stats: decays at tuned rates', () => {
|
||||
const r = decay({ stage: 'adult', state: 'active', elapsedSeconds: 3600 });
|
||||
// hunger: 100 + trunc(-5) = 95
|
||||
// happiness: 100 + trunc(-2.5) = 98
|
||||
// hygiene: 100 + trunc(-4) = 96
|
||||
// energy: 100 + trunc(-5.5) = 95
|
||||
expect(r.stats.hunger).toBe(95);
|
||||
expect(r.stats.happiness).toBe(98);
|
||||
expect(r.stats.hygiene).toBe(96);
|
||||
expect(r.stats.energy).toBe(95);
|
||||
});
|
||||
|
||||
it('1 hour from full: health stays at 100 (regen cancels base)', () => {
|
||||
const r = decay({ stage: 'adult', state: 'active', elapsedSeconds: 3600 });
|
||||
// After deltas all ≥ 80. healthDelta = -0.25 + 1.0 = 0.75. trunc(0.75) = 0.
|
||||
expect(r.stats.health).toBe(100);
|
||||
});
|
||||
|
||||
it('adult penalty thresholds unchanged: hunger < 60 triggers mild', () => {
|
||||
const r = decay({
|
||||
stage: 'adult',
|
||||
state: 'active',
|
||||
stats: { hunger: 55, happiness: 100, health: 100, hygiene: 100, energy: 100 },
|
||||
elapsedSeconds: 3600,
|
||||
});
|
||||
// hunger after: 55 + trunc(-5) = 50 (< 60, mild penalty fires)
|
||||
// healthDelta = -0.25 + -0.5 = -0.75, trunc = 0 → health stays 100.
|
||||
expect(r.stats.health).toBe(100);
|
||||
expect(r.stats.hunger).toBe(50);
|
||||
});
|
||||
});
|
||||
|
||||
// ─── Baby sleeping ────────────────────────────────────────────────────────────
|
||||
|
||||
describe('baby sleeping decay', () => {
|
||||
@@ -33,36 +199,36 @@ describe('baby sleeping decay', () => {
|
||||
});
|
||||
|
||||
it('1 hour: hunger decays only 20% of awake rate', () => {
|
||||
// Awake hunger rate = -7.0/hr → sleeping = -7.0 * 0.2 = -1.4/hr
|
||||
// trunc(-1.4) = -1 → 100 - 1 = 99
|
||||
// Awake hunger rate = -8.0/hr → sleeping = -8.0 * 0.2 = -1.6/hr
|
||||
// trunc(-1.6) = -1 → 100 - 1 = 99
|
||||
const r = decay({ stage: 'baby', state: 'sleeping', elapsedSeconds: 3600 });
|
||||
expect(r.stats.hunger).toBe(99);
|
||||
});
|
||||
|
||||
it('1 hour: happiness does not decay (rate too small to truncate)', () => {
|
||||
// Awake happiness rate = -4.0/hr → sleeping = -0.8/hr → trunc(-0.8) = 0
|
||||
// Awake happiness rate = -4.5/hr → sleeping = -0.9/hr → trunc(-0.9) = 0
|
||||
const r = decay({ stage: 'baby', state: 'sleeping', elapsedSeconds: 3600 });
|
||||
expect(r.stats.happiness).toBe(100);
|
||||
});
|
||||
|
||||
it('1 hour: hygiene decays only 20% of awake rate', () => {
|
||||
// Awake hygiene rate = -5.0/hr → sleeping = -1.0/hr → trunc(-1.0) = -1
|
||||
// Awake hygiene rate = -6.0/hr → sleeping = -1.2/hr → trunc(-1.2) = -1
|
||||
const r = decay({ stage: 'baby', state: 'sleeping', elapsedSeconds: 3600 });
|
||||
expect(r.stats.hygiene).toBe(99);
|
||||
});
|
||||
|
||||
it('1 hour: health base does not decay when stats are healthy', () => {
|
||||
const r = decay({ stage: 'baby', state: 'sleeping', elapsedSeconds: 3600 });
|
||||
// After deltas: hunger=99, happiness=100, hygiene=99, energy=100 — all ≥ 80
|
||||
// After deltas: hunger=99, happiness=100, hygiene=99, energy=100 — all ≥ 76
|
||||
// Base health = 0 (sleeping), regen = trunc(1.5) = 1. 100 + 1 → clamped to 100.
|
||||
expect(r.stats.health).toBe(100);
|
||||
});
|
||||
|
||||
it('30 minutes: stats barely change due to Math.trunc', () => {
|
||||
const r = decay({ stage: 'baby', state: 'sleeping', elapsedSeconds: 1800 });
|
||||
// hunger trunc(-7*0.2*0.5) = trunc(-0.7) = 0 → 100
|
||||
// happiness trunc(-4*0.2*0.5) = trunc(-0.4) = 0 → 100
|
||||
// hygiene trunc(-5*0.2*0.5) = trunc(-0.5) = 0 → 100
|
||||
// hunger trunc(-8*0.2*0.5) = trunc(-0.8) = 0 → 100
|
||||
// happiness trunc(-4.5*0.2*0.5) = trunc(-0.45) = 0 → 100
|
||||
// hygiene trunc(-6*0.2*0.5) = trunc(-0.6) = 0 → 100
|
||||
// energy trunc(40*0.5) = 20 → stays 100
|
||||
expect(r.stats.hunger).toBe(100);
|
||||
expect(r.stats.happiness).toBe(100);
|
||||
@@ -83,20 +249,20 @@ describe('baby sleeping decay', () => {
|
||||
stats: { hunger: 20, happiness: 50, health: 50, hygiene: 20, energy: 50 },
|
||||
elapsedSeconds: 3600,
|
||||
});
|
||||
// hunger after: 20 + trunc(-7*0.2*1) = 20-1 = 19
|
||||
// hygiene after: 20 + trunc(-5*0.2*1) = 20-1 = 19
|
||||
// happiness after: 50 + trunc(-4*0.2*1) = 50
|
||||
// energy after: 50 + trunc(40*1) = 90
|
||||
// hunger after: 20 + trunc(-8*0.2) = 20 + trunc(-1.6) = 19
|
||||
// hygiene after: 20 + trunc(-6*0.2) = 20 + trunc(-1.2) = 19
|
||||
// happiness after: 50 + trunc(-4.5*0.2) = 50 + trunc(-0.9) = 50
|
||||
// energy after: 50 + trunc(40) = 90
|
||||
//
|
||||
// healthDelta = 0 (base sleeping)
|
||||
// hunger 19 < 70: -0.75*0.25 = -0.1875
|
||||
// hunger 19 < 40: -1.25*0.25 = -0.3125
|
||||
// hygiene 19 < 70: -0.75*0.25 = -0.1875
|
||||
// hygiene 19 < 40: -1.25*0.25 = -0.3125
|
||||
// energy 90, not < 50 → 0
|
||||
// happiness 50, not < 50 → 0
|
||||
// total = -1.0, trunc(-1.0) = -1 → health = 50-1 = 49
|
||||
expect(r.stats.health).toBe(49);
|
||||
// hunger 19 < 50: -0.5*0.25 = -0.125
|
||||
// hunger 19 < 25: -1.0*0.25 = -0.25
|
||||
// hygiene 19 < 50: -0.5*0.25 = -0.125
|
||||
// hygiene 19 < 25: -1.0*0.25 = -0.25
|
||||
// energy 90 ≥ 50 → 0
|
||||
// happiness 50 ≥ 50 → 0
|
||||
// total = -0.75, trunc(-0.75) = 0 → health stays 50
|
||||
expect(r.stats.health).toBe(50);
|
||||
expect(r.stats.hunger).toBe(19);
|
||||
expect(r.stats.hygiene).toBe(19);
|
||||
});
|
||||
@@ -110,12 +276,12 @@ describe('adult sleeping decay', () => {
|
||||
expect(r.stats.energy).toBe(100);
|
||||
});
|
||||
|
||||
it('1 hour from full stats: hunger/happiness/hygiene barely change', () => {
|
||||
// hunger: trunc(-4.5*0.2) = trunc(-0.9) = 0 → 100
|
||||
it('1 hour from full stats: hunger decays slightly, happiness/hygiene unchanged', () => {
|
||||
// hunger: trunc(-5.0*0.2) = trunc(-1.0) = -1 → 99
|
||||
// happiness: trunc(-2.5*0.2) = trunc(-0.5) = 0 → 100
|
||||
// hygiene: trunc(-3.5*0.2) = trunc(-0.7) = 0 → 100
|
||||
// hygiene: trunc(-4.0*0.2) = trunc(-0.8) = 0 → 100
|
||||
const r = decay({ stage: 'adult', state: 'sleeping', elapsedSeconds: 3600 });
|
||||
expect(r.stats.hunger).toBe(100);
|
||||
expect(r.stats.hunger).toBe(99);
|
||||
expect(r.stats.happiness).toBe(100);
|
||||
expect(r.stats.hygiene).toBe(100);
|
||||
});
|
||||
@@ -138,75 +304,22 @@ describe('adult sleeping decay', () => {
|
||||
stats: { hunger: 15, happiness: 15, health: 50, hygiene: 15, energy: 10 },
|
||||
elapsedSeconds: 3600,
|
||||
});
|
||||
// hunger after: 15 + trunc(-4.5*0.2) = 15 + 0 = 15
|
||||
// happiness after: 15 + trunc(-2.5*0.2) = 15 + 0 = 15
|
||||
// hygiene after: 15 + trunc(-3.5*0.2) = 15 + 0 = 15
|
||||
// hunger after: 15 + trunc(-5*0.2) = 15 + trunc(-1.0) = 14
|
||||
// happiness after: 15 + trunc(-2.5*0.2) = 15 + trunc(-0.5) = 15
|
||||
// hygiene after: 15 + trunc(-4*0.2) = 15 + trunc(-0.8) = 15
|
||||
// energy after: 10 + trunc(35) = 45
|
||||
//
|
||||
// healthDelta = 0 (base sleeping)
|
||||
// hunger 15 < 60: -0.5*0.25 = -0.125
|
||||
// hunger 15 < 30: -1.0*0.25 = -0.25
|
||||
// hunger 14 < 60: -0.5*0.25 = -0.125
|
||||
// hunger 14 < 30: -1.0*0.25 = -0.25
|
||||
// hygiene 15 < 60: -0.5*0.25 = -0.125
|
||||
// hygiene 15 < 30: -1.0*0.25 = -0.25
|
||||
// energy 45 ≥ 40 → 0
|
||||
// happiness 15 < 40: -0.4*0.25 = -0.1
|
||||
// happiness 15 < 20: -0.8*0.25 = -0.2
|
||||
// total = -(0.125+0.25+0.125+0.25+0.1+0.2) = -1.05, trunc(-1.05) = -1
|
||||
// total = -1.05, trunc(-1.05) = -1
|
||||
expect(r.stats.health).toBe(49);
|
||||
});
|
||||
});
|
||||
|
||||
// ─── Awake decay unchanged ────────────────────────────────────────────────────
|
||||
|
||||
describe('awake decay is unchanged', () => {
|
||||
it('baby awake 1 hour: full original decay rates', () => {
|
||||
const r = decay({ stage: 'baby', state: 'active', elapsedSeconds: 3600 });
|
||||
// hunger: 100 + trunc(-7) = 93
|
||||
// happiness: 100 + trunc(-4) = 96
|
||||
// hygiene: 100 + trunc(-5) = 95
|
||||
// energy: 100 + trunc(-8) = 92
|
||||
expect(r.stats.hunger).toBe(93);
|
||||
expect(r.stats.happiness).toBe(96);
|
||||
expect(r.stats.hygiene).toBe(95);
|
||||
expect(r.stats.energy).toBe(92);
|
||||
});
|
||||
|
||||
it('adult awake 1 hour: full original decay rates', () => {
|
||||
const r = decay({ stage: 'adult', state: 'active', elapsedSeconds: 3600 });
|
||||
// hunger: 100 + trunc(-4.5) = 96 (trunc rounds toward zero → -4)
|
||||
// happiness: 100 + trunc(-2.5) = 98 (trunc → -2)
|
||||
// hygiene: 100 + trunc(-3.5) = 97 (trunc → -3)
|
||||
// energy: 100 + trunc(-5) = 95
|
||||
expect(r.stats.hunger).toBe(96);
|
||||
expect(r.stats.happiness).toBe(98);
|
||||
expect(r.stats.hygiene).toBe(97);
|
||||
expect(r.stats.energy).toBe(95);
|
||||
});
|
||||
|
||||
it('baby awake with low stats: full health penalties', () => {
|
||||
const r = decay({
|
||||
stage: 'baby',
|
||||
state: 'active',
|
||||
stats: { hunger: 20, happiness: 50, health: 50, hygiene: 20, energy: 50 },
|
||||
elapsedSeconds: 3600,
|
||||
});
|
||||
// hunger after: 20 + trunc(-7) = 13
|
||||
// hygiene after: 20 + trunc(-5) = 15
|
||||
// happiness after: 50 + trunc(-4) = 46
|
||||
// energy after: 50 + trunc(-8) = 42
|
||||
//
|
||||
// healthDelta = -0.75 (base)
|
||||
// hunger 13 < 70: -0.75
|
||||
// hunger 13 < 40: -1.25
|
||||
// hygiene 15 < 70: -0.75
|
||||
// hygiene 15 < 40: -1.25
|
||||
// energy 42 < 50: -0.5
|
||||
// happiness 46 < 50: -0.5
|
||||
// total = -(0.75+0.75+1.25+0.75+1.25+0.5+0.5) = -5.75
|
||||
// trunc(-5.75) = -5 → health = 50 - 5 = 45
|
||||
expect(r.stats.health).toBe(45);
|
||||
expect(r.stats.hunger).toBe(13);
|
||||
expect(r.stats.hygiene).toBe(15);
|
||||
expect(r.stats.hunger).toBe(14);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -215,8 +328,8 @@ describe('awake decay is unchanged', () => {
|
||||
describe('hibernating is not treated as sleeping', () => {
|
||||
it('baby hibernating uses awake decay rates', () => {
|
||||
const r = decay({ stage: 'baby', state: 'hibernating', elapsedSeconds: 3600 });
|
||||
// Same as awake — energy uses awake rate (-8), not sleep regen
|
||||
expect(r.stats.energy).toBe(92);
|
||||
expect(r.stats.hunger).toBe(93);
|
||||
// Same as awake — energy uses awake rate (-9), not sleep regen
|
||||
expect(r.stats.energy).toBe(91);
|
||||
expect(r.stats.hunger).toBe(92);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -52,48 +52,62 @@ export interface DecayInput {
|
||||
|
||||
/**
|
||||
* Baby stage decay rates (per hour).
|
||||
*
|
||||
* Design goal: Needs attention every 3-5 hours.
|
||||
*
|
||||
* Design goal: First stat (energy) drops to "okay" (3/4) around 2.7 hours,
|
||||
* first "attention" (2/4) around 5-6 hours. Simpler than adult, needs care
|
||||
* sooner but not punitively.
|
||||
*
|
||||
* Health penalty thresholds are aligned to baby segment boundaries:
|
||||
* attention = value ≤ 50, urgent = value ≤ 25.
|
||||
* Penalties only begin at "attention" — no silent health drain while UI
|
||||
* still shows "okay".
|
||||
*/
|
||||
const BABY_DECAY = {
|
||||
hunger: -7.0,
|
||||
happiness: -4.0,
|
||||
hygiene: -5.0,
|
||||
hunger: -8.0,
|
||||
happiness: -4.5,
|
||||
hygiene: -6.0,
|
||||
energy: {
|
||||
awake: -8.0,
|
||||
sleeping: 6.0, // Regeneration
|
||||
awake: -9.0,
|
||||
sleeping: 6.0, // Legacy value — overridden by BABY_SLEEP_ENERGY_REGEN
|
||||
},
|
||||
health: {
|
||||
base: -0.75,
|
||||
hungerBelow70: -0.75,
|
||||
hungerBelow40: -1.25,
|
||||
hygieneBelow70: -0.75,
|
||||
hygieneBelow40: -1.25,
|
||||
base: -0.4,
|
||||
// Tier 1: mild — stat in attention range (≤ 50)
|
||||
hungerBelow50: -0.5,
|
||||
hygieneBelow50: -0.5,
|
||||
energyBelow50: -0.5,
|
||||
energyBelow25: -1.0,
|
||||
happinessBelow50: -0.5,
|
||||
// Tier 2: strong — stat in urgent range (≤ 25)
|
||||
hungerBelow25: -1.0,
|
||||
hygieneBelow25: -1.0,
|
||||
energyBelow25: -1.0,
|
||||
happinessBelow25: -1.0,
|
||||
// Regeneration when all stats are >= 80
|
||||
regenThreshold: 80,
|
||||
// Regeneration when all stats are in "good" range (4/4 = value ≥ 76)
|
||||
regenThreshold: 76,
|
||||
regenRate: 1.5,
|
||||
},
|
||||
} as const;
|
||||
|
||||
/**
|
||||
* Adult stage decay rates (per hour).
|
||||
*
|
||||
* Design goal: Needs attention every 5-7 hours.
|
||||
*
|
||||
* Design goal: First stat (energy) drops to "okay" (7/10) around 5-6 hours,
|
||||
* first "attention" (6/10) around 7-8 hours. More resilient than baby — growing
|
||||
* up should feel like a reward, not more annoyance.
|
||||
*
|
||||
* Adult penalty thresholds were already close to segment boundaries and
|
||||
* are left unchanged.
|
||||
*/
|
||||
const ADULT_DECAY = {
|
||||
hunger: -4.5,
|
||||
hunger: -5.0,
|
||||
happiness: -2.5,
|
||||
hygiene: -3.5,
|
||||
hygiene: -4.0,
|
||||
energy: {
|
||||
awake: -5.0,
|
||||
sleeping: 5.0, // Regeneration
|
||||
awake: -5.5,
|
||||
sleeping: 5.0, // Legacy value — overridden by ADULT_SLEEP_ENERGY_REGEN
|
||||
},
|
||||
health: {
|
||||
base: -0.4,
|
||||
base: -0.25,
|
||||
hungerBelow60: -0.5,
|
||||
hungerBelow30: -1.0,
|
||||
hygieneBelow60: -0.5,
|
||||
@@ -285,23 +299,23 @@ function calculateBabyDecay(
|
||||
// Base health decay is 0 while sleeping.
|
||||
let healthDelta = isSleeping ? 0 : BABY_DECAY.health.base * elapsedHours;
|
||||
|
||||
// Hunger penalties
|
||||
if (hunger < 70) healthDelta += BABY_DECAY.health.hungerBelow70 * penaltyMul * elapsedHours;
|
||||
if (hunger < 40) healthDelta += BABY_DECAY.health.hungerBelow40 * penaltyMul * elapsedHours;
|
||||
// Hunger penalties (aligned to baby segment boundaries: attention ≤ 50, urgent ≤ 25)
|
||||
if (hunger <= 50) healthDelta += BABY_DECAY.health.hungerBelow50 * penaltyMul * elapsedHours;
|
||||
if (hunger <= 25) healthDelta += BABY_DECAY.health.hungerBelow25 * penaltyMul * elapsedHours;
|
||||
|
||||
// Hygiene penalties
|
||||
if (hygiene < 70) healthDelta += BABY_DECAY.health.hygieneBelow70 * penaltyMul * elapsedHours;
|
||||
if (hygiene < 40) healthDelta += BABY_DECAY.health.hygieneBelow40 * penaltyMul * elapsedHours;
|
||||
if (hygiene <= 50) healthDelta += BABY_DECAY.health.hygieneBelow50 * penaltyMul * elapsedHours;
|
||||
if (hygiene <= 25) healthDelta += BABY_DECAY.health.hygieneBelow25 * penaltyMul * elapsedHours;
|
||||
|
||||
// Energy penalties
|
||||
if (energy < 50) healthDelta += BABY_DECAY.health.energyBelow50 * penaltyMul * elapsedHours;
|
||||
if (energy < 25) healthDelta += BABY_DECAY.health.energyBelow25 * penaltyMul * elapsedHours;
|
||||
if (energy <= 50) healthDelta += BABY_DECAY.health.energyBelow50 * penaltyMul * elapsedHours;
|
||||
if (energy <= 25) healthDelta += BABY_DECAY.health.energyBelow25 * penaltyMul * elapsedHours;
|
||||
|
||||
// Happiness penalties
|
||||
if (happiness < 50) healthDelta += BABY_DECAY.health.happinessBelow50 * penaltyMul * elapsedHours;
|
||||
if (happiness < 25) healthDelta += BABY_DECAY.health.happinessBelow25 * penaltyMul * elapsedHours;
|
||||
if (happiness <= 50) healthDelta += BABY_DECAY.health.happinessBelow50 * penaltyMul * elapsedHours;
|
||||
if (happiness <= 25) healthDelta += BABY_DECAY.health.happinessBelow25 * penaltyMul * elapsedHours;
|
||||
|
||||
// Health regeneration (all stats >= 80)
|
||||
// Health regeneration (all stats in "good" range: 4/4 = value ≥ 76)
|
||||
const threshold = BABY_DECAY.health.regenThreshold;
|
||||
if (hunger >= threshold && happiness >= threshold && hygiene >= threshold && energy >= threshold) {
|
||||
healthDelta += BABY_DECAY.health.regenRate * elapsedHours;
|
||||
|
||||
Reference in New Issue
Block a user