docs: add mutual bond system design
Separates feelings (asymmetric sentiment values) from formal relationship statuses (mutual bonds in a central registry). Introduces proposal interaction protocol for partnership formation with acceptance/rejection mechanics. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,92 @@
|
||||
# Mutual Bond System Design
|
||||
|
||||
## Problem
|
||||
|
||||
Partnership is currently a one-sided label derived from sentiment value. NPC A can call B their partner while B considers C their partner. The `classify()` function maps value >= 80 to "Partner" independently per entity, with no mutuality enforcement.
|
||||
|
||||
## Solution
|
||||
|
||||
Separate feelings from formal relationship status via three changes:
|
||||
|
||||
### 1. Bond Registry
|
||||
|
||||
World-level singleton component: `BondRegistry = Map<pairKey, Bond[]>`
|
||||
|
||||
```typescript
|
||||
interface Bond {
|
||||
type: 'partner'; // extensible: 'sibling', 'parent', etc.
|
||||
formedAtTick: number;
|
||||
status: 'active' | 'former';
|
||||
}
|
||||
```
|
||||
|
||||
- Keyed by canonical pair key (`"lowerEntityId:higherEntityId"`)
|
||||
- Helper functions: `makePairKey()`, `getBonds()`, `hasBond()`, `addBond()`, `dissolveBond()`
|
||||
- When a partner despawns, bond transitions to `status: 'former'` (consistent with existing memory system)
|
||||
- Single source of truth for all structural relationships
|
||||
|
||||
### 2. Proposal Interaction
|
||||
|
||||
General-purpose mutual agreement protocol, with partnership as the first use case.
|
||||
|
||||
**New fields on `SocialState`:**
|
||||
- `proposalCooldown: number` -- ticks until this entity can propose again
|
||||
- `pendingProposal: { targetId: EntityId, type: 'partner' } | null` -- queued by relationship system
|
||||
|
||||
**Flow:**
|
||||
|
||||
1. **RelationshipSystem** (after updating sentiment): if entity's value toward other crosses >= `proposalThreshold` (default 80), no existing bond, no proposal cooldown, sets `pendingProposal` on the entity
|
||||
2. **SocialSystem** (new interaction detection): when scanning for partners, if entity has `pendingProposal` targeting a nearby NPC, prioritize initiating interaction with that target. Interaction is flagged as proposal type.
|
||||
3. **Proposal phases**: reuses existing `facing -> pausing -> emoting` but emoting phase uses ring emoji
|
||||
4. **Resolution** (emoting->done transition):
|
||||
- Check rejecter's sentiment toward proposer (must be >= `proposalAcceptanceThreshold`)
|
||||
- Stat-influenced acceptance roll: base ~85%, empathy (+), sociability (+), temperament (-)
|
||||
- **Accept**: bond added to registry, sentiment boost, positive emote, positive transient mods
|
||||
- **Reject**: proposer takes amplified negative delta (2x), rejecter takes smaller hit (0.5x), both scaled by temperament. Proposal cooldown set on initiator, reduced by courage.
|
||||
|
||||
### 3. Classification Rework
|
||||
|
||||
- `classify()`: rename >= 80 tier from `'Partner'` to `'Devoted'`
|
||||
- `getEffectiveClassification()`: add bond registry parameter. If `hasBond(partner, active)`, return `'Partner'` overriding the tier label
|
||||
- `stateSerializer.ts`: switch from `classify()` to `getEffectiveClassification()`, pass bond registry
|
||||
- Wire protocol: add `bond: 'partner' | 'former_partner' | null` field to serialized relationships
|
||||
|
||||
### 4. Client Display
|
||||
|
||||
- Ring emoji during proposal interaction (above both NPCs)
|
||||
- NPC info panel: ring icon next to partner names
|
||||
- Acceptance: ring emoji, then positive emote
|
||||
- Rejection: ring emoji transitions to negative emote
|
||||
|
||||
### 5. Config Additions
|
||||
|
||||
All tuning values in `relationshipConfig.ts`:
|
||||
|
||||
```typescript
|
||||
// Proposal system
|
||||
proposalThreshold: 80,
|
||||
proposalAcceptanceThreshold: 80,
|
||||
proposalBaseAcceptanceChance: 0.85,
|
||||
proposalEmpathyWeight: 0.02,
|
||||
proposalSociabilityWeight: 0.02,
|
||||
proposalTemperamentWeight: -0.02,
|
||||
proposalAcceptanceBonus: 5,
|
||||
proposalRejectionMultiplier: 2.0,
|
||||
proposalRejectionRejecterMultiplier: 0.5,
|
||||
baseProposalCooldown: 500,
|
||||
courageCooldownWeight: 0.04,
|
||||
proposalAcceptanceSociabilityMod: 2,
|
||||
proposalAcceptanceEmpathyMod: 1,
|
||||
proposalRejectionSociabilityMod: -1,
|
||||
proposalRejectionEmpathyMod: -1,
|
||||
proposalModDecayTicks: 150,
|
||||
```
|
||||
|
||||
## Key Properties
|
||||
|
||||
- Feelings remain asymmetric and per-entity (existing system unchanged)
|
||||
- Formal relationships are symmetric and centrally stored
|
||||
- System is general enough for future bond types (sibling, parent/child)
|
||||
- All tuning values configurable
|
||||
- Rejection has real consequences, creating emergent drama
|
||||
- System execution order unchanged: social -> relationship
|
||||
Reference in New Issue
Block a user