aboutsummaryrefslogtreecommitdiff
path: root/engine/entities/relationship.ts
blob: 2a7710bd01a0b03244677532fe225aec05eb3d14 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
import type { GameTime } from '@hollowdark/time/gameTime'
import type { BaseEntity, PersonId, RelationshipId } from '@hollowdark/engine/entities/base'

export type RelationType =
  | 'family_parent'
  | 'family_child'
  | 'family_sibling'
  | 'family_cousin'
  | 'family_grandparent'
  | 'family_grandchild'
  | 'family_inlaw'
  | 'romantic_dating'
  | 'romantic_married'
  | 'romantic_divorced'
  | 'romantic_ex'
  | 'romantic_affair'
  | 'friend_close'
  | 'friend_casual'
  | 'friend_drifted'
  | 'professional_colleague'
  | 'professional_boss'
  | 'professional_report'
  | 'professional_client'
  | 'professional_mentor'
  | 'professional_mentee'
  | 'acquaintance'
  | 'stranger_known'
  | 'enemy'

export type RelationshipState = 'warm' | 'neutral' | 'strained' | 'ruptured' | 'dormant'

/**
 * The four axes of intimacy. Each 0–1. Tracked separately because a
 * marriage can be high on practical and low on emotional — that's a
 * specific life texture, not a compatibility score.
 */
export interface IntimacyAxes {
  readonly emotional: number
  readonly intellectual: number
  readonly physical: number
  readonly practical: number
}

export interface TrustEvent {
  readonly at: GameTime
  readonly delta: number
  readonly eventRef: string
}

export interface ConflictEvent {
  readonly at: GameTime
  readonly topic: string
  readonly intensity: number
  readonly resolved: boolean
  readonly residue: number
}

export interface SharedExperience {
  readonly at: GameTime
  readonly kind: string
  readonly emotionalWeight: number
  readonly summary: string
}

export interface Tension {
  readonly id: string
  readonly topic: string
  readonly since: GameTime
  readonly intensity: number
}

/** The five love languages, matched per side — what each person gives vs.
 *  what they need from the other. Misalignment is a real relationship shape. */
export interface LoveLanguageProfile {
  readonly wordsOfAffirmation: number
  readonly actsOfService: number
  readonly receivingGifts: number
  readonly qualityTime: number
  readonly physicalTouch: number
}

export interface LoveLanguageMatrix {
  readonly aGives: LoveLanguageProfile
  readonly aNeeds: LoveLanguageProfile
  readonly bGives: LoveLanguageProfile
  readonly bNeeds: LoveLanguageProfile
}

/**
 * Asymmetric perception: A and B each carry their own mental model of the
 * relationship. Large asymmetries fire scenes — confession, rebuff,
 * shocked realisation.
 */
export interface RelationshipPerception {
  readonly perceivedType: RelationType
  readonly perceivedIntimacy: IntimacyAxes
  readonly perceivedTrust: number
  readonly lastUpdated: GameTime
}

export type RomanticPhase =
  | 'attraction'
  | 'infatuation'
  | 'deepening'
  | 'committed'
  | 'power_struggle'
  | 'stability'
  | 'mature_love'
  | 'decline'
  | 'rupture'

export interface SexualActivityState {
  readonly frequencyPerMonth: number
  readonly mutualSatisfaction: number
  readonly openness: number
}

export interface InfidelityEvent {
  readonly at: GameTime
  readonly withPersonId: PersonId | null
  readonly kind: 'emotional' | 'physical' | 'both'
  readonly discoveredAt: GameTime | null
}

export type FamilyRelation =
  | 'parent'
  | 'child'
  | 'sibling'
  | 'cousin'
  | 'grandparent'
  | 'grandchild'
  | 'inlaw'

export type WorkRelation = 'colleague' | 'boss' | 'report' | 'client' | 'mentor' | 'mentee'

export interface Relationship extends BaseEntity<RelationshipId, 'relationship'> {
  readonly personAId: PersonId
  readonly personBId: PersonId

  readonly type: RelationType
  readonly typeHistory: readonly { readonly type: RelationType; readonly from: GameTime }[]

  readonly startedAt: GameTime

  readonly intimacy: IntimacyAxes
  readonly trust: number
  readonly trustHistory: readonly TrustEvent[]
  readonly powerBalance: number

  readonly conflicts: readonly ConflictEvent[]
  readonly sharedExperiences: readonly SharedExperience[]
  readonly unresolvedTensions: readonly Tension[]

  readonly loveLanguages: LoveLanguageMatrix

  readonly currentState: RelationshipState

  readonly aPerception: RelationshipPerception
  readonly bPerception: RelationshipPerception

  readonly lastInteractionAt: GameTime
  readonly interactionFrequency: number

  readonly romanticPhase: RomanticPhase | null
  readonly sexualActivity: SexualActivityState | null
  readonly infidelityHistory: readonly InfidelityEvent[]
  readonly commitmentLevel: number

  readonly familyRelationType: FamilyRelation | null

  readonly workRelationType: WorkRelation | null
}