Skip to content

feat(affinity): Bond/Chemistry lines, tiered labels, sparser eval distribution#132

Merged
enriquephl merged 17 commits into
devfrom
feat/affinity-bond-chemistry-tiers
Jun 29, 2026
Merged

feat(affinity): Bond/Chemistry lines, tiered labels, sparser eval distribution#132
enriquephl merged 17 commits into
devfrom
feat/affinity-bond-chemistry-tiers

Conversation

@enriquephl

Copy link
Copy Markdown
Member

Summary

Splits the relationship state into two engine-owned derived lines — Bond (friendship) and Chemistry (romance) — folded from the unchanged 6-axis affinity vector. Each line gets 4 value-derived tier labels; the legacy relationship_label is preserved for back-compat. The six-axis evaluator is made sparser and asymmetric so progress feels earned. The 6-axis base mechanics (EMA, time-decay) are unchanged; only the new-row default seed is lowered.

Spec: docs/superpowers/specs/2026-06-30-affinity-bond-chemistry-tiers-design.md

What changed

  • Folding (core): bond = (max(warmth,0)+trust+intrigue)/3, chemistry = (max(warmth,0)+intimacy+tension)/3. warmth floored at 0 (no 0.5 baseline); patience excluded (rule-owned).
  • Tiers + bar curve: 4 tiers per line with widening raw gaps (0.15 / 0.35 / 0.62), mapped to even 25% bands → fast early, grind near 100%.
  • Labels: bond_label (acquaintance→confidant), chemistry_label (spark→lover). Legacy relationship_label now = the higher line mapped to an old name (stranger when both tier 1; frenemy retired from emission, kept parseable) — replaces the tangled infer_label heuristic.
  • Eval distribution: asymmetric per-axis cap −0.6 / +0.4 (losses bigger/easier than gains); prompt rewritten so ordinary turns score 0, gains are rare-but-large, losses fire more readily. EMA + decay untouched.
  • Per-turn label transitions: deterministically computed over the delta-only span, stored on companion_affinity_events.label_changes (NULL when no tier moved), and served by the BFF + debug /event endpoints alongside a folded effective_deltas_computed.
  • Persistence (migration 0029): bond/chemistry as Postgres GENERATED ALWAYS … STORED columns (auto-populate existing rows, can't drift); lowered new-row default seed (warmth 0.1, trust/intrigue/tension 0) so a fresh session reads as stranger with a neutral opening tone; label_changes JSONB column.
  • API: AffinitySnapshot gains bond/chemistry bar fills + labels; new shared DTOs; OpenAPI snapshot refreshed.
  • Docs: full rewrite of affinity-model.md / .zh.md; api-reference updated.

Out of scope

filter_prompt override for affinity_evaluation — deferred, tracked in #130.

Testing

  • Full workspace suite green: 687 tests (core 63 / server-unit 168 / server-integration 350 / store 106).
  • cargo fmt --all + clippy --workspace --all-targets -D warnings clean.
  • OpenAPI snapshot regenerated and in sync (CI parity check passes).
  • Migration is backward-compatible: generated columns auto-populate, SET DEFAULT affects new rows only, label_changes is nullable — existing rows untouched.

🤖 Generated with Claude Code

enriquephl and others added 17 commits June 30, 2026 03:20
Design spec for splitting the relationship label into engine-owned Bond
(friendship) and Chemistry (romance) lines, each with 4 tiered labels
(+ stranger via the legacy field), an exponential-feel bar curve, per-turn
label transitions stored on companion_affinity_events and served by the BFF,
and an asymmetric/sparser six-axis eval distribution. 6-axis base unchanged.

Defers the filter_prompt override (#130).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: enriquephl <70942788+enriquephl@users.noreply.github.com>
Refine the persistence approach: bond/chemistry are Postgres
GENERATED ALWAYS ... STORED columns kept in lockstep with the 6 axes,
instead of engine-written values. Removes the load_or_create/record_ghost
special-casing and the default-mismatch risk. relationship_label and the
per-turn label_changes stay engine-written.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: enriquephl <70942788+enriquephl@users.noreply.github.com>
Composite uses warmth.max(0) (no 0.5 baseline); migration 0029 lowers the
new-row default seed (warmth 0.1, trust/intrigue/tension 0) so a fresh
session reads as stranger with near-empty bars while keeping a neutral
opening tone. Existing rows and axis mechanics unchanged.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: enriquephl <70942788+enriquephl@users.noreply.github.com>
…ef 1)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: enriquephl <70942788+enriquephl@users.noreply.github.com>
Signed-off-by: enriquephl <70942788+enriquephl@users.noreply.github.com>
Signed-off-by: enriquephl <70942788+enriquephl@users.noreply.github.com>
Signed-off-by: enriquephl <70942788+enriquephl@users.noreply.github.com>
Signed-off-by: enriquephl <70942788+enriquephl@users.noreply.github.com>
Signed-off-by: enriquephl <70942788+enriquephl@users.noreply.github.com>
…bel_changes

Signed-off-by: enriquephl <70942788+enriquephl@users.noreply.github.com>
…transition DTOs

Signed-off-by: enriquephl <70942788+enriquephl@users.noreply.github.com>
Signed-off-by: enriquephl <70942788+enriquephl@users.noreply.github.com>
Signed-off-by: enriquephl <70942788+enriquephl@users.noreply.github.com>
Full rewrite of docs/affinity-model.md + .zh.md covering 6-axis base,
bond/chemistry folding formulas (warmth floored), tiers (0.15/0.35/0.62),
even-25%-band bar curve, 8 tier keys, legacy relationship_label mapping
(stranger/frenemy retired logic), eval distribution (most-turns-0, +0.4/-0.6
asymmetric cap), persistence (generated cols + lowered seed + label_changes),
and API surfaces (AffinitySnapshot bars+labels, BFF effective_deltas_computed
+ label_changes).

Light touch on docs/api-reference.md + .zh.md: updated affinity snapshot JSON
examples and BFF event response to show new fields.

Regenerated openapi.json: adds BondChemistryDeltas, LabelTransitionDto,
TurnLabelChangesDto schemas; AffinitySnapshot bond/chemistry/bond_label/
chemistry_label fields; BffAffinityDelta/AffinityEventEntry computed+changes.

Also includes cargo fmt reflows from earlier tasks and a one-line fix to the
debug_affinity_returns_vector_for_owner test whose expected warmth/intrigue
defaults still referenced the old seed (0.3/0.5) instead of migration-0029
values (0.1/0.0).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: enriquephl <70942788+enriquephl@users.noreply.github.com>
Signed-off-by: enriquephl <70942788+enriquephl@users.noreply.github.com>
Codex review (PR #132, P2): the snapshot read relationship_label from the
stored DB column while bond/chemistry + their labels are computed fresh from
the axes. A freshly-created row (column still NULL) returned null, and a
pre-migration row returned a stale old-heuristic value (even frenemy) — both
contradicting the new lines and defeating the "fresh session = stranger" goal.
Derive it via legacy_relationship_label() on read, like the other label fields.
Field stays Option<String> (no OpenAPI change).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: enriquephl <70942788+enriquephl@users.noreply.github.com>
Codex review (PR #132, P2): effective_deltas_computed linearly folded the
per-axis delta and ignored the max(warmth,0) floor, reporting phantom
bond/chemistry gains when warmth<0. Now computed at persist time from the
floored before/after scores, stored on companion_affinity_events
.effective_line_deltas, and read by the BFF/debug /event endpoints (like
label_changes). Drops BondChemistryDeltas::from_axis_deltas.

Signed-off-by: enriquephl <70942788+enriquephl@users.noreply.github.com>
@enriquephl enriquephl merged commit 9903c69 into dev Jun 29, 2026
6 checks passed
@enriquephl enriquephl deleted the feat/affinity-bond-chemistry-tiers branch June 29, 2026 23:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant