Skip to main content

Welcome to a Grasp lesson

Create a free account to keep studying and create your own study path.

Lesson 44

Implementing Relationship Decay

Recommended resources

Hello and welcome back!

In our last lesson, we built a memory system that gives characters a sense of history, logging significant interactions that change their relationship status. This was a crucial step in making relationships feel earned. But what happens when characters drift apart? Relationships that aren't maintained tend to fade, and a believable simulation should reflect that.

Today, we'll bring this dynamic element into our game. The learning outcome for this lesson is to implement a gradual decay of relationship scores over time if there is no interaction. This will make the social world feel alive and create a natural incentive for characters (and the player) to stay connected.


1. The Case for Decay: Why Relationships Fade

In game design, especially for social simulations, every mechanic should serve a purpose. Relationship decay isn't just about making things harder; it's about making the world more believable and the player's actions more meaningful.

Prosocial economics for game design

To understand the design philosophy behind this, let's look at an article on prosocial game design. It discusses the core components of friendship and why maintaining connections is important.

Please read these two short sections: Under 'Pattern: Friendship formula', review the 'Key friendship factors'. Notice that 'Repeat encounters' is a fundamental requirement for forming and maintaining friendships. Under 'Pattern: Measuring trust', read the paragraph below 'Analysis and tuning' that starts with 'Rate of trust accumulation.' It points out that 'Large gaps in the accumulation of trust suggest a pause in the relationship' and that it's natural for relationships to fade. This provides the design justification for our decay mechanic: an absence of interaction should naturally weaken a relationship.

By implementing decay, we are simulating the natural need for "repeat encounters." A relationship that is ignored will slowly return to a neutral state, while a relationship that is actively nurtured will be sustained.

Relationship Score Decay Visualization
This graph illustrates the concept perfectly. The red line shows a relationship score that increases and then stays high forever. The green line shows a more realistic model where the score begins to decay after a period of no interaction.


2. Modeling Decay: The Asymptotic Curve

How should a score decay? A simple approach would be to subtract a fixed amount each day (e.g., -0.5 points). This is called linear decay. However, it feels unnatural—a score of 90 would decay at the same rate as a score of 10.

A more realistic model is asymptotic decay, where the rate of decay is proportional to the current score. The score drops faster when it's high and slows down as it approaches a baseline (like zero). This is achieved with an exponential decay function.

While the formal equation is often written as , where is time, a much simpler way to implement this in a game loop is with a daily multiplicative factor.

Each day, we can calculate the new score like this:

For example, with a decay_rate of 0.01 (or 1%), a score of 80 would become:

  • Day 1:
  • Day 2:
  • Day 3:

Notice how the amount subtracted decreases each day. This produces the smooth, natural curve we're looking for.

Logarithmic scaling difficulty based on game time

This forum post about scaling game difficulty provides a great breakdown of the math behind these curves and includes interactive graphs to help you visualize them. You don't need to read the code, but playing with the graphs is highly recommended.

First, read the section that introduces the equation 'f(t) = c + ae^kt' to understand the formal mathematics. Then, scroll to the end and click on the Desmos links (e.g., 'unreasonably interactive version'). Experiment with the sliders to see how changing the parameters affects the shape of the decay curve. This will give you an intuitive feel for the mechanic we're building.



3. Implementing Decay in Rust

Now, let's translate this model into code.

Step 1: Track the Last Interaction Time

First, we need to know when the last interaction occurred for each relationship. Let's add a timestamp to our RelationshipData struct.

// In lib.rs, update your RelationshipData struct




#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct RelationshipData {
    pub scores: RelationshipScores,
    pub memories: Vec<MemoryEntry>,
    pub last_interaction_day: u64, // Add this line!
}

Next, update this timestamp whenever a social interaction happens. In the process_social_interaction function you created in the last lesson, add one line.

// In lib.rs, inside `impl GameState`

pub fn process_social_interaction(
    &mut self,
    actor_id: &CharacterId,
    target_id: &CharacterId,
    interaction_id: &str,
    friendship_delta: f32,
    romance_delta: f32,
) {
    let actor = self.characters.get_mut(actor_id).unwrap();
    // Use .or_default() to get or insert a default RelationshipData
    let relationship = actor.relationship_data.entry(target_id.clone()).or_default();

    // ... (your existing logic for capturing status and applying score changes)

    // Update the last interaction timestamp
    relationship.last_interaction_day = self.game_day; // Add this line!

    // ... (your existing logic for logging memories)
}

Step 2: Create the Decay Function

Now for the core logic. We'll create a new function in GameState that will be called once per game day. This function will iterate through all relationships and apply decay if necessary.

// In lib.rs, add these constants near the top for easy tuning
const FRIENDSHIP_DECAY_RATE: f32 = 0.005; // 0.5% per day
const ROMANCE_DECAY_RATE: f32 = 0.01;    // 1% per day

// In lib.rs, add this new function to `impl GameState`
pub fn update_relationship_decay(&mut self) {
    let current_day = self.game_day;

    for character in self.characters.values_mut() {
        for relationship in character.relationship_data.values_mut() {
            // Only apply decay if at least one day has passed since the last interaction
            if current_day > relationship.last_interaction_day {
                // Decay friendship score if it is positive
                if relationship.scores.friendship > 0.0 {
                    relationship.scores.friendship *= 1.0 - FRIENDSHIP_DECAY_RATE;
                }
                
                // Decay romance score if it is positive
                if relationship.scores.romance > 0.0 {
                    relationship.scores.romance *= 1.0 - ROMANCE_DECAY_RATE;
                }
                
                // We could also decay negative scores towards 0, making grudges fade
                // if relationship.scores.friendship < 0.0 {
                //     relationship.scores.friendship *= 1.0 - FRIENDSHIP_DECAY_RATE;
                // }
            }
        }
    }
}

This function should be called from your main game loop, once per simulated day. For example, if you have a function that advances the game clock, you would call it there.



4. Adding Nuance: Decay Floors

Our current system works, but it has a slight problem: over a long period, even the strongest friendships will decay to zero. In many simulation games, like The Sims, certain relationship milestones provide a "floor," preventing the score from dropping below a certain level. This makes achieving high-level relationships more meaningful.

The Sims 2 Relationship Decay Thresholds
This screenshot from The Sims 2 shows how different relationship statuses have thresholds. The note 'STR never decays below' implies a decay floor, a common and effective design pattern we can adopt.

Let's use the RelationshipType enum from the previous lesson to implement this. A "Best Friend" might never decay below the "Good Friend" threshold, for example.

Here is the refined update_relationship_decay function:

// In lib.rs, replace the previous version of update_relationship_decay

pub fn update_relationship_decay(&mut self) {
    let current_day = self.game_day;

    for character in self.characters.values_mut() {
        for relationship in character.relationship_data.values_mut() {
            if current_day > relationship.last_interaction_day {
                let status = relationship.scores.get_status();

                // Determine the decay floor based on the current friendship status
                let friendship_floor = match status.friendship {
                    // Best Friends decay, but never below the 'Good Friend' threshold
                    RelationshipType::BestFriend => 70.0,
                    // Good Friends decay, but never below the 'Friend' threshold
                    RelationshipType::GoodFriend => 35.0, 
                    // Other positive relationships decay towards 0
                    _ => 0.0, 
                };

                // Apply decay only if the score is above its floor
                if relationship.scores.friendship > friendship_floor {
                    relationship.scores.friendship *= 1.0 - FRIENDSHIP_DECAY_RATE;
                    // Ensure the score doesn't dip below the floor after decay
                    relationship.scores.friendship = relationship.scores.friendship.max(friendship_floor);
                }
                
                // You could apply a similar logic for romance floors
                let romance_floor = match status.romance {
                    RelationshipType::Love => 75.0,
                    RelationshipType::Crush => 40.0,
                    _ => 0.0,
                };

                if relationship.scores.romance > romance_floor {
                    relationship.scores.romance *= 1.0 - ROMANCE_DECAY_RATE;
                    relationship.scores.romance = relationship.scores.romance.max(romance_floor);
                }
            }
        }
    }
}

This enhanced version makes your social system much more robust. It rewards players for reaching high relationship tiers by making them more stable, while still requiring some maintenance.



Conclusion

Fantastic work! You have now implemented a dynamic and natural-feeling relationship decay system. This makes your game's social world feel less static and more alive, as relationships now require active maintenance to be preserved.

Key Takeaways:

  • Relationship decay is a core mechanic in social simulations that models the need for repeated interactions to maintain friendships.
  • Asymptotic decay, implemented via a daily multiplicative factor, creates a more natural curve than linear decay.
  • Implementing decay requires tracking the time since the last interaction and applying the decay logic periodically as part of the game loop.
  • Adding "decay floors" based on relationship status adds depth and makes high-level relationships more stable and rewarding.

In our next lesson, we will create a relationship panel UI in Unity to display a character's relationships with others. You'll finally be able to see these scores changing in real-time as characters interact or drift apart, providing crucial feedback to the player.