skip to content
tlam://snippet
cd ..

Limerence

A model of the first 72 hours after meeting someone you like. Every mechanism is from real research, which somehow makes it worse.

limerence.ts · 157 lines · typescript
/** * limerence.ts * A model of the first 72 hours after meeting someone you like. * Every mechanism below is from published research, which somehow makes it worse. */type Chemical = 'dopamine' | 'serotonin' | 'norepinephrine'interface Signal {  what: string  /** 0 = unambiguous, 1 = could mean literally anything */  ambiguity: number  /** what a calm, reasonable friend would say it means, -1..1 */  actualMeaning: number}const clamp = (n: number, lo: number, hi: number) => Math.min(hi, Math.max(lo, n))class LimerentBrain {  readonly chemistry: Record<Chemical, number> = { dopamine: 1, serotonin: 1, norepinephrine: 1 }  thoughtsPerHour = 0  confidence = 0.8                      // you are, usually, fine at talking to people  private expectedReplyMinutes = 30  private replays = new Map<string, number>()  constructor(readonly them: string) {}  /** Serotonin in new love drops to levels seen in OCD. One thought, on a loop. */  meet(): void {    this.chemistry.serotonin = 0.6    this.chemistry.dopamine = 2.0    this.chemistry.norepinephrine = 1.8 // the palms    this.thoughtsPerHour = 12  }  /**   * "Acute sensitivity to any act that can be interpreted favourably" — and an   * equal and opposite fear of rejection. (Tennov, 1979)   * Ambiguity isn't noise. It's room for hope, and room for dread. You get both.   */  decode(signal: Signal): { hope: number; dread: number } {    const hope = clamp(signal.actualMeaning + signal.ambiguity * (this.chemistry.dopamine - this.chemistry.serotonin), -1, 1)    const dread = clamp(signal.actualMeaning - signal.ambiguity * (1 - this.confidence) * 2, -1, 1)    this.replay(signal.what)    // Not knowing if they like you makes you think about them more,    // which makes you like them more. (Whitchurch, Wilson & Gilbert, 2011)    this.thoughtsPerHour += Math.round(signal.ambiguity * 10)    return { hope, dread } // held simultaneously. this is normal. apparently.  }  /**   * Dopamine fires on the gap between expected and actual. Surprise is the drug,   * which is why a slow reply makes the next fast one feel incredible.   */  receiveReply(afterMinutes: number): number {    const surprise = clamp((this.expectedReplyMinutes - afterMinutes) / this.expectedReplyMinutes, -1, 1)    this.chemistry.dopamine += surprise    this.expectedReplyMinutes = afterMinutes // that's the baseline now. forever.    return surprise  }  /** Crystallisation: you see the flaw. You keep it anyway. (Stendhal, via Tennov) */  crystallise(flaw: string): string {    return `${flaw}, which is honestly kind of cute`  }  /** Normally confident people go suddenly shy around them. */  standNear(): string {    this.confidence *= 0.25    return this.confidence < 0.3 ? 'said "you too" when they said happy birthday to someone else' : 'fine'  }  /** Arousal gets misattributed to whoever's nearby. (Dutton & Aron, 1974) */  explainHeartRate(_actualCause: 'the third espresso' | 'the wobbly bridge'): string {    return this.them  }  replay(moment: string): number {    const n = (this.replays.get(moment) ?? 0) + 1    this.replays.set(moment, n)    return n  }  mostReplayed(): [string, number] {    return [...this.replays].sort((a, b) => b[1] - a[1])[0]  }}// ─── Texting ────────────────────────────────────────────────────────────────const keenness = (text: string) =>  ((text.match(/y/g)?.length ?? 1) - 1) * 0.25 +   // every extra y is a confession  (text.match(/!/g)?.length ?? 0) * 0.2 +  (text.includes(':)') ? 0.1 : 0) +  (/^[A-Z]/.test(text) ? -0.3 : 0)                 // capital letter = your dentist/** Warm enough to be nice. Cold enough to look like you weren't waiting. */const TARGET_KEENNESS = 0.1function pickOpener(drafts: string[]): string {  return [...drafts].sort(    (a, b) => Math.abs(keenness(a) - TARGET_KEENNESS) - Math.abs(keenness(b) - TARGET_KEENNESS),  )[0]}/** Nobody wrote these rules. Everyone follows them. */function replyDelay(theirMinutes: number, now: Date): number {  const delay = theirMinutes * 2 + Math.random() * 7 // jitter, so it doesn't look calculated  const bedtime = new Date(now)  bedtime.setHours(23, 30)  const latest = (bedtime.getTime() - now.getTime()) / 60_000  return Math.round(Math.min(delay, latest)) // but never risk them falling asleep}// ─── Research (Instagram) ───────────────────────────────────────────────────function scrollBackTo(year: number, from = 2026): { postsSeen: number; accidentalLikeRisk: number } {  const postsSeen = (from - year) * 41  const tremor = postsSeen / 600                // the further back, the shakier the thumb  return { postsSeen, accidentalLikeRisk: clamp(tremor, 0, 1) }}// ─── Simulation ─────────────────────────────────────────────────────────────const me = new LimerentBrain('Sam')const log = (when: string, what: string) => console.log(`${when.padEnd(14)} ${what}`)log('Sat 21:40', 'Sam laughs at the hummus joke.')me.meet()log('Sat 21:41', `Sam is standing next to you. Result: ${me.standNear()}.`)log('Sat 23:47', `Home. Coat still on. Heart rate explained by: ${me.explainHeartRate('the third espresso')}.`)for (let i = 0; i < 37; i++) me.replay('the bit where Sam touched my arm')const firstText = pickOpener(['Hey!', 'hey', 'heyyy', 'hey!', 'hey :)', 'hi', 'heyy!!'])log('Sun 11:02', `Seven drafts later, sends: "${firstText}"`)log('Sun 11:03', 'Phone unlocked 14 times. Read receipts: off. Regretting that.')log('Sun 13:30', `Reply after 148 min. Surprise: ${me.receiveReply(148).toFixed(2)}. Dopamine: ${me.chemistry.dopamine.toFixed(1)}.`)const wait = replyDelay(148, new Date('2026-11-08T13:30:00'))log('Sun 13:30', `Waits ${wait} min before replying, per the rules.`)log('Sun 22:15', `Reply after 4 min. Surprise: +${me.receiveReply(4).toFixed(2)}. Dopamine: ${me.chemistry.dopamine.toFixed(1)}. Slot machine pays out.`)const ig = scrollBackTo(2016)log('Mon 00:50', `${ig.postsSeen} posts deep (Crete, 2016). Accidental-like risk: ${(ig.accidentalLikeRisk * 100).toFixed(0)}%.`)log('Mon 00:51', `Finds: they still follow their ex. Filed as: "${me.crystallise('they still follow their ex')}".`)const { hope, dread } = me.decode({ what: 'haha yeah', ambiguity: 0.9, actualMeaning: 0.2 })log('Mon 14:02', 'Sam: "haha yeah". Friends consulted: 3. Screenshots sent: 4.')log('Mon 14:02', `Read as: ${hope.toFixed(2)} (they love me) and ${dread.toFixed(2)} (they hate me). At the same time.`)const [moment, times] = me.mostReplayed()log('Mon 14:03', `Thoughts per hour: ${me.thoughtsPerHour}. Most replayed: "${moment}" (${times}x).`)log('Mon 14:06', 'Sam: "want to get a drink thursday?"')// @ts-expect-error feelings are not type-safeme.chemistry.serotonin = 'who cares'

replies (0)

login to reply.

// no replies yet