From cdfc35d5dafd5b5f3ec62a82dd85c687bbe24d95 Mon Sep 17 00:00:00 2001 From: root Date: Sat, 7 Mar 2026 15:30:22 +0000 Subject: [PATCH] feat: add relationship classification helpers with tests Co-Authored-By: Claude Opus 4.6 --- .../__tests__/relationshipHelpers.test.ts | 84 +++++++++++++++++++ server/src/systems/relationshipHelpers.ts | 57 +++++++++++++ 2 files changed, 141 insertions(+) create mode 100644 server/src/systems/__tests__/relationshipHelpers.test.ts create mode 100644 server/src/systems/relationshipHelpers.ts diff --git a/server/src/systems/__tests__/relationshipHelpers.test.ts b/server/src/systems/__tests__/relationshipHelpers.test.ts new file mode 100644 index 0000000..719a340 --- /dev/null +++ b/server/src/systems/__tests__/relationshipHelpers.test.ts @@ -0,0 +1,84 @@ +import { describe, it, expect } from 'vitest'; +import { classify, getPartnerCap, getFriendCap } from '../relationshipHelpers.js'; + +describe('relationshipHelpers', () => { + describe('classify', () => { + it('returns Partner for value >= 80', () => { + expect(classify(80)).toBe('Partner'); + expect(classify(100)).toBe('Partner'); + }); + + it('returns Close Friend for value 50-79', () => { + expect(classify(50)).toBe('Close Friend'); + expect(classify(79)).toBe('Close Friend'); + }); + + it('returns Friend for value 20-49', () => { + expect(classify(20)).toBe('Friend'); + expect(classify(49)).toBe('Friend'); + }); + + it('returns Acquaintance for value 5-19', () => { + expect(classify(5)).toBe('Acquaintance'); + expect(classify(19)).toBe('Acquaintance'); + }); + + it('returns Stranger for value -4 to 4', () => { + expect(classify(0)).toBe('Stranger'); + expect(classify(4)).toBe('Stranger'); + expect(classify(-4)).toBe('Stranger'); + }); + + it('returns Wary for value -19 to -5', () => { + expect(classify(-5)).toBe('Wary'); + expect(classify(-19)).toBe('Wary'); + }); + + it('returns Rival for value -49 to -20', () => { + expect(classify(-20)).toBe('Rival'); + expect(classify(-49)).toBe('Rival'); + }); + + it('returns Enemy for value -79 to -50', () => { + expect(classify(-50)).toBe('Enemy'); + expect(classify(-79)).toBe('Enemy'); + }); + + it('returns Nemesis for value <= -80', () => { + expect(classify(-80)).toBe('Nemesis'); + expect(classify(-100)).toBe('Nemesis'); + }); + }); + + describe('getPartnerCap', () => { + it('returns 1 for average stats', () => { + expect(getPartnerCap(10, 10)).toBe(1); + }); + + it('returns 2 for high sociability and empathy', () => { + expect(getPartnerCap(15, 14)).toBe(2); + }); + + it('returns 1 if only sociability is high', () => { + expect(getPartnerCap(15, 10)).toBe(1); + }); + + it('returns 1 if only empathy is high', () => { + expect(getPartnerCap(10, 14)).toBe(1); + }); + }); + + describe('getFriendCap', () => { + it('returns baseCap for sociability 10', () => { + expect(getFriendCap(10)).toBe(5); + }); + + it('increases with high sociability', () => { + expect(getFriendCap(16)).toBe(8); + }); + + it('decreases with low sociability', () => { + expect(getFriendCap(6)).toBe(3); + }); + }); +}); diff --git a/server/src/systems/relationshipHelpers.ts b/server/src/systems/relationshipHelpers.ts new file mode 100644 index 0000000..c1694e7 --- /dev/null +++ b/server/src/systems/relationshipHelpers.ts @@ -0,0 +1,57 @@ +import { relationshipConfig } from '../config/relationshipConfig.js'; + +export function classify(value: number): string { + for (const tier of relationshipConfig.tiers) { + if (value >= tier.min) return tier.label; + } + return 'Nemesis'; +} + +export function getPartnerCap(sociability: number, empathy: number): number { + if ( + sociability >= relationshipConfig.polySociabilityThreshold && + empathy >= relationshipConfig.polyEmpathyThreshold + ) { + return relationshipConfig.polyPartnerCap; + } + return relationshipConfig.defaultPartnerCap; +} + +export function getFriendCap(sociability: number): number { + return relationshipConfig.baseFriendCap + + Math.floor((sociability - 10) * relationshipConfig.friendCapPerSociability); +} + +export function getEffectiveClassification( + entityId: number, + value: number, + allRelationships: Map, + sociability: number, + empathy: number, +): string { + const raw = classify(value); + + if (raw === 'Partner') { + const cap = getPartnerCap(sociability, empathy); + let partnerCount = 0; + for (const [otherId, rel] of allRelationships) { + if (otherId !== entityId && rel.value >= 80 && rel.value > value) { + partnerCount++; + } + } + if (partnerCount >= cap) return 'Close Friend'; + } + + if (raw === 'Friend') { + const cap = getFriendCap(sociability); + let friendCount = 0; + for (const [otherId, rel] of allRelationships) { + if (otherId !== entityId && rel.value >= 20 && rel.value < 80 && rel.value > value) { + friendCount++; + } + } + if (friendCount >= cap) return 'Acquaintance'; + } + + return raw; +}