buff

📁 simhacker/moollm 📅 Jan 26, 2026
1
总安装量
1
周安装量
#47105
全站排名
安装命令
npx skills add https://github.com/simhacker/moollm --skill buff

Agent 安装分布

mcpjam 1
claude-code 1
windsurf 1
zencoder 1
cline 1

Skill 文档

Buff

“All effects are buffs. Some are just shitty.”

Buffs modify stats, abilities, or behavior. They have durations, can stack, and come from various sources. Curses are just negative buffs — no separate system.

Characters Only

Buffs only target characters. This is a design constraint, not a limitation.

  • Single closure signature: (world, subject, verb, object)
  • subject is always a character — no type checking needed
  • Rooms that need buffs get a “room spirit” character
# Room needs to be "haunted"? Create its spirit.
character:
  id: dark-cave-spirit
  name: "Spirit of the Dark Cave"
  location: room/dark-cave
  buffs:
    - ref: buff/haunted

Structure

buff:
  name: "Caffeinated"
  source: "Espresso"
  effect: { energy: +2, focus: +1 }
  duration: 5  # simulation turns
  stacks: false
Field Purpose
name Display name
source What granted this buff
effect Stat mods OR semantic prompt
duration How long it lasts
stacks Can multiple instances exist?
max_stacks If stacking, limit
decay How it ends (time, action, condition)

Buff Types

Numeric

Traditional stat modifiers:

buff:
  name: "Caffeinated"
  effect: { energy: +2, focus: +1 }
  duration: 5

Semantic

Arbitrary effect prompts interpreted by the LLM — not predefined stats, just vibes:

  • “feeling lucky”
  • “cats seem to like you today”
  • “slightly cursed”
  • “radiating calm energy”
  • “shadows feel watchful”

How it works:

Buff: "cats seem to like you today"
Action: PAT TERPIE
LLM: Gives bonus, narrates extra warmth

Mixed

Combine numeric and semantic:

buff:
  name: "Terpie's Blessing"
  effect:
    calm: +2
    vibe: "cats trust you more"
  duration: "a while"

Standard Properties Buffs Affect

Player/NPC Stats (Sims-Style Needs)

# Numeric needs — decay over time, restored by actions
needs:
  hunger: 80      # 0=starving, 100=full
  energy: 65      # 0=exhausted, 100=rested
  social: 45      # 0=lonely, 100=connected
  hygiene: 90     # 0=filthy, 100=clean
  bladder: 30     # 0=desperate, 100=empty
  fun: 55         # 0=bored, 100=entertained
  comfort: 70     # 0=miserable, 100=cozy

Mind-Mirror Stats (Cognitive/Emotional)

# Mental state — affects decision-making and narration
mind:
  focus: 75       # Concentration (0-100)
  mood: 20        # Emotional valence (-100 to +100)
  stress: 35      # Anxiety level (0-100)
  creativity: 60  # Creative capacity (0-100)
  confidence: 50  # Self-assurance (0-100)
  patience: 40    # Frustration tolerance (0-100)
  curiosity: 80   # Exploration drive (0-100)

Room Spirit Stats

Room spirits are characters whose stats affect the room they haunt:

character:
  id: forge-spirit
  name: "Spirit of the Forge"
  location: room/blacksmith-forge
  
  # These stats affect everyone in the room
  production_speed: 120   # +20% crafting speed
  error_rate: 8           # 8% chance of mistakes
  mood_influence: +5      # Slight pride boost
  comfort_bonus: -10      # Hot and uncomfortable
  discovery_chance: 15    # Sometimes find rare materials
  danger_level: 25        # Burns, sparks, accidents
  
  buffs:
    - id: master-craftsman-blessing
      source: "Pleased the forge spirit"
      effect: { production_speed: +30, error_rate: -5 }
      duration: "until you leave"
Spirit Stat What It Does Example Buff Effect
production_speed Work/craft rate Blessing: +30% faster
error_rate Mistake probability Curse: +20% more errors
mood_influence Mood granted to visitors Haunting: -15 mood
comfort_bonus Comfort modifier Cozy: +20 comfort
discovery_chance Finding hidden things Mysterious: +25%
danger_level Hazard intensity Cursed: traps more deadly

Sources

Source Example
Interactions Petting a cat grants joy
Consumables Coffee grants energy
Locations Being in pub grants comfort
Items Lit lamp grants grue immunity
Relationships High friendship grants trust
Personas Wearing persona grants themed buffs

Lifecycle Hooks

Three hooks control buff behavior, written as natural language and compiled to JS:

Hook → Compiles To Purpose
start start_js Runs when buff activates
simulate simulate_js Runs each tick while active
is_finished is_finished_js Returns true → buff ends

Example: Poison Buff

buff:
  id: poison
  name: "Poisoned"
  tags: [curse, damage-over-time, dispellable]
  
  # Natural language prompts (author writes these)
  start: "Mark character as poisoned, turn them slightly green"
  simulate: "Reduce HP by 1, chance of groaning sound"
  is_finished: "Return true after 5 ticks OR if HP drops below 10"
  
  # Compiled by buff compiler (generated)
  start_js: |
    subject.poisoned = true;
    subject.tint = 'green';
  simulate_js: |
    subject.hp -= 1;
    if (Math.random() < 0.3) world.emit('*groan*');
  is_finished_js: |
    return subject.poisonTicks >= 5 || subject.hp < 10;

Closure Signature

All compiled hooks use the same signature:

(world, subject, verb, object) => { ... }
  • world — shared game state (never null)
  • subject — the character with the buff (never null for buffs)
  • verb — context-dependent (may be null)
  • object — context-dependent (may be null)

Body-only in YAML: Write just the code body, engine wraps it.

Buff Interactions

Buffs can look up and modify other buffs by tag:

Interaction Effect Example
cancels Remove buffs with these tags Antidote cancels [poison]
boosts Multiply/extend buffs with tags Fire spell boosts [fire] x2
replaces Remove old, add this Drunk replaces [tipsy]
merges_with Combine into new buff Rage + Focus → Battle Trance
blocked_by Can’t apply if these exist Poison blocked by [immunity-poison]
counters Weaken/shorten these buffs OJ counters [hangover]
countered_by These weaken/shorten this Couch-lock countered by [citrus]

Cancel Example

buff:
  id: cleanse
  name: "Cleanse"
  tags: [holy, dispel]
  cancels: [curse, poison, disease]  # Remove all matching
  start: "Holy light purges dark afflictions"

Boost Example

buff:
  id: fire-attunement
  name: "Fire Attunement"
  boosts:
    tags: [fire]
    multiplier: 2.0
    extend_duration: 5
  start: "Fire spells burn twice as hot"

Merge Example

buff:
  id: rage
  name: "Rage"
  tags: [combat, aggression]
  merges_with:
    tags: [focus, discipline]
    result: battle-trance  # Creates new combined buff
    
buff:
  id: battle-trance
  name: "Battle Trance"
  tags: [combat, legendary]
  effect: { damage: +50%, focus: +30, pain_immunity: true }
  start: "Fury and focus unite — you become a weapon"

Blocked By Example

buff:
  id: poison
  name: "Poisoned"
  tags: [poison, damage-over-time]
  blocked_by: [immunity-poison, divine-protection]
  # Won't apply if target has these tags

Weight Trees (ML-Style Mixtures)

Buffs can form hierarchical weighted mixtures, like neural network layers:

Blend → Strains → Terpenes → Effects
  ↓        ↓          ↓          ↓
weights  weights    weights    final
  ↑        ↑          ↑          ↑
 BUFFS   BUFFS      BUFFS     BUFFS   ← Each stage can be modified by buffs!

Meta-buffs can modify the weight tree itself:

Buff Affects Example
tolerance Strain weights Regular use → diminishing returns
sensitivity Terpene weights First time → effects amplified
synergy-boost Effect weights Entourage → all effects +20%
citrus-clarity Specific terpenes Limonene effects doubled
indica-affinity Strain category Indica strains hit harder

Tolerance Relationships

Tolerances use the character relationship map — same system as NPC friendships:

character:
  id: player
  name: "Don"
  
  # Relationships include people AND substances
  # Terpenes are unidirectional — they don't have feelings back
  relationships:
    # NPCs (bidirectional)
    bob: { trust: 45, friendship: 60 }
    alice: { trust: 80, friendship: 75 }
    
    # Terpene tolerances (unidirectional — no reciprocal)
    terpene/myrcene: { tolerance: 45 }      # Couch-lock less effective
    terpene/limonene: { tolerance: 12 }     # Citrus hits hard
    terpene/pinene: { tolerance: 30 }
    terpene/linalool: { tolerance: 5 }      # Lavender knocks you out
    terpene/caryophyllene: { tolerance: 60 } # Need more for pain relief
    terpene/humulene: { tolerance: 20 }
    terpene/terpinolene: { tolerance: 8 }   # Full creative boost
    terpene/ocimene: { tolerance: 3 }       # Maximum effect

Key difference from NPC relationships:

Aspect NPC Relationship Terpene Relationship
Direction Bidirectional Unidirectional
Reciprocal Bob likes you back Myrcene has no feelings
Tracked on Both characters Player only
Decay Neglect hurts both Time heals tolerance

Tolerance mechanics:

Tolerance Multiplier Experience
0 (virgin) 1.5x “Whoa, this is intense”
25 (light) 1.2x “Nice, I feel it”
50 (moderate) 1.0x “Standard effect”
75 (heavy) 0.7x “Need more than usual”
100 (maxed) 0.4x “Barely feel anything”

Tolerance changes:

# Each use increases tolerance
on_use:
  tolerance_gain: 2-5 points per use
  
# Tolerance decays over time (T-break!)
on_rest:
  tolerance_decay: 1 point per day of abstinence
  
# Full reset after extended break
t_break:
  duration: 2 weeks
  effect: "Reset to 50% of current tolerance"

Effective weight calculation:

def get_effective_terpene_weight(character, terpene, base_weight):
    tolerance = character.tolerances.get(terpene, 0)
    
    # Convert tolerance to multiplier
    if tolerance < 25:
        multiplier = 1.5 - (tolerance / 50)  # 1.5x → 1.0x
    elif tolerance < 75:
        multiplier = 1.0 - ((tolerance - 50) / 100)  # 1.0x → 0.75x
    else:
        multiplier = 0.75 - ((tolerance - 75) / 100)  # 0.75x → 0.5x
    
    return base_weight * multiplier

Layer 1: Terpenes → Effects

Each terpene has weighted effects:

myrcene-blessing:
  effects_weighted:
    relaxation: { value: +30, weight: 1.0 }   # Full effect
    pain_relief: { value: +20, weight: 0.8 }  # 80%
    sedation: { value: +25, weight: 0.9 }     # 90%

Layer 2: Strains → Terpenes

Each strain is a weighted mixture of terpenes:

strain-og-kush:
  terpene_profile:
    myrcene: 0.35      # 35% of profile
    limonene: 0.25     # 25%
    caryophyllene: 0.20
    linalool: 0.10
    humulene: 0.10

Layer 3: Blends → Strains

Blends mix multiple strains:

blend-wake-and-bake:
  strain_mixture:
    sour-diesel: 0.50     # Half the blend
    jack-herer: 0.30      # 30%
    pineapple-express: 0.20

Computing Final Effects

# Blend → Strain → Terpene → Effect propagation
def compute_blend_effects(blend):
    final_terpenes = {}
    
    # Layer 3→2: Blend weights × Strain terpene profiles
    for strain_id, strain_weight in blend.strain_mixture.items():
        strain = get_strain(strain_id)
        for terpene, terpene_weight in strain.terpene_profile.items():
            final_terpenes[terpene] += strain_weight * terpene_weight
    
    # Layer 2→1: Terpene amounts × Effect weights
    final_effects = {}
    for terpene, amount in final_terpenes.items():
        terpene_buff = get_terpene_buff(terpene)
        for effect, config in terpene_buff.effects_weighted.items():
            final_effects[effect] += amount * config.weight * config.value
    
    return final_effects

Example Calculation

Wake & Bake Blend:
├── Sour Diesel (50%)
│   ├── limonene: 0.30 × 0.50 = 0.15
│   └── pinene: 0.15 × 0.50 = 0.075
├── Jack Herer (30%)
│   ├── limonene: 0.20 × 0.30 = 0.06
│   └── pinene: 0.25 × 0.30 = 0.075
└── Pineapple Express (20%)
    ├── limonene: 0.30 × 0.20 = 0.06
    └── pinene: 0.25 × 0.20 = 0.05

Final limonene: 0.15 + 0.06 + 0.06 = 0.27
Final pinene: 0.075 + 0.075 + 0.05 = 0.20

Then: limonene × mood_boost, pinene × focus → final character effects

This is essentially a mini neural network where:

  • Weights are terpene profiles and strain mixtures
  • Activations are effect values
  • Forward pass computes final buff effects

Buff Orchestration (Simulation Loop)

The orchestrator runs buff rounds during simulation ticks:

1. Scan Phase

Orchestrator collects all active buffs across all characters:

# Orchestrator builds active-buff manifest
active_buffs:
  - character: player
    buff_ref: "skills/buff/buffs/INDEX.yml#caffeinated"
    remaining: 6
    stacks: 2
    
  - character: player
    buff_ref: "skills/buff/buffs/INDEX.yml#high"
    remaining: 8
    stacks: 1
    
  - character: bob-npc
    buff_ref: "skills/buff/buffs/INDEX.yml#drunk"
    remaining: 4
    stacks: 1

2. Event Generation

Create buff-tick events with pointers:

buff_round:
  tick: 42
  events:
    - type: buff-simulate
      character: player
      buff: caffeinated
      simulate_js: "subject.energy_effective += 20; subject.focus_effective += 15;"
      
    - type: buff-simulate  
      character: player
      buff: high
      simulate: "Deep thoughts about random topics, food cravings"
      simulate_js: "if (Math.random() < 0.3) world.emit('*ponders existence*');"
      
    - type: buff-simulate
      character: bob-npc
      buff: drunk
      simulate: "Occasional slurred speech, may say embarrassing things"

3. LLM Simulation Prompt

Orchestrator instructs LLM to enumerate and simulate:

prompt: |
  BUFF ROUND — Tick 42
  
  Enumerate and simulate each active buff:
  
  1. PLAYER — Caffeinated (6 ticks remaining, 2 stacks)
     Effect: +20 energy, +15 focus per stack
     Simulate: Apply effects, note jitteriness if 2+ stacks
     
  2. PLAYER — High (8 ticks remaining)
     Effect: -25 stress, +20 creativity, +30 hunger
     Simulate: "Deep thoughts about random topics, food cravings"
     → Narrate any random musings or munchie urges
     
  3. BOB — Drunk (4 ticks remaining)
     Effect: +30 confidence, -25 focus, -30 judgement
     Simulate: "Occasional slurred speech, may say embarrassing things"
     → Decide if Bob says something regrettable this tick
  
  For each buff:
  - Apply stat modifications to _effective values
  - Run simulate behavior (chance-based events)
  - Check is_finished conditions
  - Decrement remaining duration
  - Remove expired buffs, trigger spawns_after
  
  Return updated character states and any narration.

4. Buff Lifecycle Per Tick

┌─────────────────────────────────────────────────────────────────┐
│                        BUFF TICK                                 │
├─────────────────────────────────────────────────────────────────┤
│                                                                  │
│  For each character:                                             │
│    For each active buff:                                         │
│                                                                  │
│      1. APPLY EFFECTS                                            │
│         stat_effective += buff.effect × buff.stacks             │
│                                                                  │
│      2. RUN SIMULATE                                             │
│         Execute simulate_js OR let LLM interpret simulate       │
│         (chance-based events, narration, random behaviors)      │
│                                                                  │
│      3. CHECK IS_FINISHED                                        │
│         If is_finished_js returns true → mark for removal       │
│         If remaining <= 0 → mark for removal                    │
│                                                                  │
│      4. DECREMENT DURATION                                       │
│         remaining -= 1                                           │
│                                                                  │
│      5. HANDLE EXPIRATION                                        │
│         If marked for removal:                                   │
│           - Remove buff from character                           │
│           - Trigger spawns_after buffs (with delay/chance)      │
│           - Emit buff-expired event                              │
│                                                                  │
│      6. HANDLE INTERACTIONS                                      │
│         Check for cancels, boosts, replaces, merges             │
│         Apply buff-on-buff effects                               │
│                                                                  │
└─────────────────────────────────────────────────────────────────┘

5. Compiled vs Interpreted

Mode When How
Compiled simulate_js exists Engine evals cached closure directly
Interpreted Only simulate text LLM reads prompt, narrates behavior
Hybrid Both exist JS runs effects, LLM narrates flavor
buff:
  id: drunk
  # LLM interprets this for narration
  simulate: "Occasional slurred speech, may say embarrassing things"
  # Engine runs this for mechanics
  simulate_js: |
    if (Math.random() < 0.2) {
      world.emit(subject.name + " slurs something incomprehensible");
    }

6. Attention Concentration (Time-Slicing)

The event-based design concentrates LLM attention on specific tasks:

┌──────────────────────────────────────────────────────────────────┐
│              LLM ATTENTION TIME-SLICING                          │
├──────────────────────────────────────────────────────────────────┤
│                                                                   │
│  Instead of: "Simulate everything at once" (diffuse attention)   │
│                                                                   │
│  We do: Series of focused micro-tasks                            │
│                                                                   │
│  ┌─────────┐   ┌─────────┐   ┌─────────┐   ┌─────────┐          │
│  │ Buff 1  │ → │ Buff 2  │ → │ Buff 3  │ → │ Buff 4  │          │
│  │ PLAYER  │   │ PLAYER  │   │ BOB     │   │ ROOM    │          │
│  │ caffein │   │ high    │   │ drunk   │   │ haunted │          │
│  └─────────┘   └─────────┘   └─────────┘   └─────────┘          │
│       ↓             ↓             ↓             ↓                │
│   [focused]    [focused]    [focused]    [focused]               │
│   attention    attention    attention    attention               │
│                                                                   │
└──────────────────────────────────────────────────────────────────┘

Why this works:

Problem Solution
LLM loses track with many buffs One buff at a time, clear context
Effects get confused/merged Each buff isolated in its own slice
Hard to debug Each event is traceable, logged
Inconsistent simulation Same prompt structure every time

Iteration Pattern:

# Orchestrator feeds LLM one task at a time
iteration_1:
  focus: "PLAYER's Caffeinated buff"
  context: [player_state, buff_definition, tick_number]
  task: "Apply effects, check finish condition, narrate if needed"
  output: [updated_state, narration, events]
  
iteration_2:
  focus: "PLAYER's High buff"
  context: [player_state, buff_definition, tick_number]  
  task: "Apply effects, chance of munchies event, narrate thoughts"
  output: [updated_state, narration, events]
  
# ... and so on

Benefits:

  1. Focused attention — LLM only thinks about one buff
  2. Predictable structure — Same input/output format each time
  3. Debuggable — Can trace exactly which buff caused what
  4. Parallelizable — Independent buffs can run in parallel
  5. Interruptible — Can pause/resume between iterations
  6. Cacheable — Compiled _js buffs skip LLM entirely

Speed-of-Light Compatible:

This fits the speed-of-light pattern — many focused micro-operations in a single LLM call, or batched across calls:

# Single call, multiple focused tasks
prompt: |
  Process these buff events in sequence:
  
  [1/4] PLAYER — Caffeinated
  → Apply: energy +40, focus +30 (2 stacks)
  → Check: is_finished? No (6 remaining)
  → Output: state changes only
  
  [2/4] PLAYER — High  
  → Apply: stress -25, creativity +20, hunger +30
  → Simulate: "Deep thoughts" — roll for musing
  → Output: state + optional narration
  
  [3/4] BOB — Drunk
  → Apply: confidence +30, focus -25
  → Simulate: "Slurred speech" — roll for embarrassment
  → Output: state + optional narration
  
  [4/4] LIBRARY-SPIRIT — Haunted
  → Apply: error_rate +15, mood_influence -20
  → Simulate: "Poltergeist activity" — roll for book fall
  → Output: room effects + optional event

Stacking

  • Same source: Doesn’t stack — refresh duration instead
  • Different sources: Stack additively up to category limit

Category Limits

terpene_effects: 3
charm_effects: 5
consumable_effects: 4
negative_effects: 3  # 3+ same negative = LEGENDARY

Synergies

Some buffs COMBINE into stronger effects:

  • Myr + Lily = “Sedation Stack”
  • Lemon + Pine = “Focus Boost”
  • All 8 kittens = “ENTOURAGE EFFECT” (legendary)

Negative Buffs (Curses)

Curses are just shitty buffs. Same structure, negative effects.

buff:
  name: "Scratched"
  source: "Failed BELLY RUB"
  effect: { hp: -1, visible_marks: true }
  duration: "Until healed"

Persistent Curses

Long-term negative buffs with lift conditions:

buff:
  name: "Curse of Darkness"
  effect: { lamp_efficiency: -25% }
  duration: conditional
  lift_condition: "Light 3 dark places"
  reward_on_lift: "LIGHT-BEARER title"

Duration Types

Type Example
Turns duration: 4
Conditional duration: until you eat
While present duration: while in pub
Permanent duration: forever
Natural language duration: a few minutes
Probabilistic duration: 25% fade chance per turn

Natural Language Durations

We’re not tracking real time — the LLM interprets and makes its best guess:

  • “forever”
  • “5 minutes”
  • “a day”
  • “until sunset”
  • “randomly 50%”
  • “a while”
  • “briefly”
  • “until you forget”

See time/ for full natural duration examples.

Decay

When LLM judges turn(s) have passed:

  1. Decrement duration on timed buffs
  2. Remove buffs that hit 0
  3. Apply new buffs from current turn

Effective Derived Values: Flags Edition

This is the effective derived values protocol for booleans.

Type Base Modifiers Effective
Numeric energy: 5 buff +2 effective_energy: 7
Boolean in_darkness: false room.lit=false, has_lamp=false effective_in_darkness: true

Same pattern:

  • Numeric: base + sum(modifiers) = effective
  • Boolean: base OR any(conditions) = effective flag

Push / Pull / Latch

The LLM can handle any combination:

Mode Pattern Example
Pull Compute on demand in_darkness derived from lamp + room state
Push Source sets flag Buff explicitly sets urgent_situation: true
Latch Stays until cleared has_visited_room_a: true persists
# PULL — derived on demand, not stored
in_darkness: (room.lit == false) AND (has_lamp == false)

# PUSH — buff explicitly sets
buff:
  sets_flags: [urgent_situation]

# LATCH — persists in state until cleared
player:
  visited_rooms: [room-a, room-b]  # grows, never shrinks

Traditional reactive systems pick one mode. The LLM does all three simultaneously — it sees the whole context and figures out which pattern applies.

Tweening and Animation

Values don’t have to snap — they can interpolate over time:

Type Instant Tweened
Numeric energy: 5 → 7 energy: 5 → 7 over 3 turns
Boolean lit: false → true lit: fading in over 2 turns
Position room-a → room-b walking through hallway
buff:
  name: "Warming Up"
  effect: { warmth: +3 }
  tween: ease-in    # Gradual increase
  duration: 5

animation:
  entering_room:
    from: hallway
    to: pub
    frames: [approaching, at_door, stepping_in, arrived]

The LLM narrates intermediate states. “You feel yourself warming up…” not just “You are warm now.”

Velocity

Any reactive variable can have a rate of change:

energy:
  value: 5
  velocity: -1      # Draining 1 per turn
  
trust:
  value: 45
  velocity: +3      # Building rapport
  
mood:
  value: "content"
  velocity: "improving"  # Semantic velocity works too
Variable Value Velocity Meaning
energy 5 -1 Tired and getting worse
trust 45 +3 Relationship strengthening
position room-a north Moving northward
mood anxious calming Settling down

The LLM reads velocity to predict and narrate: “You’re running low on energy and fading fast…” vs “Low energy but recovering.”

Physics Simulation

Extend to full 2D/3D cartoon physics:

thrown_ball:
  position: [5, 3]
  velocity: [2, 4]       # Moving up-right
  acceleration: [0, -1]  # Gravity pulling down
  
bouncing:
  elasticity: 0.8        # Loses 20% on bounce
  
cartoon_physics:
  hang_time: true        # Pause at apex
  squash_stretch: true   # Deform on impact
  delayed_fall: true     # Look down first, then fall

The LLM narrates physics with cartoon timing:

The ball arcs gracefully upward… hangs for a moment at the peak… then plummets, SQUASHING flat against the floor before bouncing back slightly less enthusiastically.

Works for:

  • Thrown objects (ball, inventory items)
  • Character movement (jumping, falling, knockback)
  • Environmental effects (swinging doors, rolling boulders)
  • Looney Tunes logic (run off cliff, pause, look down, THEN fall)
  • Temperature cooling or warming (ice cream melting, water freezing)

Commands

Command Effect
BUFFS or STATUS List active buffs with remaining duration
EXAMINE [buff] Full details of buff source, effect, duration