result() - Result Function

Summary

The result() function specifies the outcome of an action or skill check. It represents what actually happened after an action was attempted, whether success or failure. This construct is central to NDL’s deterministic approach - the backend decides the outcome, and the LLM narrates it.

Syntax

result()
result("outcome")
result("success")
result("failure")

Parameters

ParameterTypeRequiredDescription
outcomeStringOptionalThe result of the action (success, failure, hit, miss, critical, etc.)

Description

The result() function communicates to the LLM what actually occurred. It appears in NDL sequences after actions and skill checks to specify the determined outcome. This prevents the LLM from inventing results that contradict game state.

The result function is typically used in conjunction with:

  • Actions (do()) - showing what happened
  • Skill checks (roll()) - showing check results
  • Combat (attack, defend) - showing hits/misses
  • Social interactions - showing persuasion success/failure
  • Investigation - showing what was found/not found

Core Principle

The backend determines results through game logic (dice rolls, skill checks, state evaluation). The LLM never decides success or failure - it only narrates the predetermined outcome.

Real Examples

Search with Result

search("pile of wood", intention="find makeshift club") -> result()

Actual LLM Output (Gemma 2 9B IT):

Your fingers sift through the rough-hewn logs, searching for something sturdy and manageable. You find it - a thick, gnarled branch, its ends still bearing the marks of the axe. It's not much, but it's better than nothing.

Explanation: The result() indicates the search succeeded. The LLM knows to narrate finding something appropriate.

Failed Action Result

do($"you", "swing makeshift club against table's surface", intention="test club's combat readiness")
-> result("failure")
-> system_response("something goes wrong when you try to perform this action")

Actual LLM Output (Gemma 2 9B IT):

You raise the gnarled branch, hefting it awkwardly in your hand. It's heavier than you anticipated, the rough bark digging into your palm. You bring it back over your head, aiming for the tabletop. But as you swing, your momentum falters. The branch veers off course, catching on the edge of the chair.

With a sickening crack, the makeshift club splinters, breaking into several pieces. The impact sends a shower of splinters flying, scattering across the floor. You stare down at the useless fragments in your hand, your heart sinking.

Explanation: The result("failure") tells the LLM the action failed, enabling realistic failure narration (weapon breaking).

Common Result Values

Binary Outcomes

result("success")
result("failure")
result("pass")
result("fail")

Combat Results

result("hit")
result("miss")
result("critical_hit")
result("critical_miss")
result("glancing_blow")

Degrees of Success

result("overwhelming_success")
result("success")
result("partial_success")
result("failure")
result("catastrophic_failure")

Investigation Results

result("find_clue")
result("find_nothing")
result("discover_secret")
result("trigger_trap")

Social Results

result("convinced")
result("skeptical")
result("offended")
result("neutral")

Usage Patterns

Action → Result

do($"player", "attack") -> result("hit")

Most basic pattern: action followed by outcome.

Action → Check → Result

do($"player", "persuade merchant") -> roll("charisma") -> result("success")

Incorporates the skill check between action and result.

Action → Result → Consequence

do($"player", "attack") -> result("hit") -> damage(15)

Result leads to further effects.

Action → Result → System Message

do($"player", "lockpick") -> result("failure") -> system_response("The lock is too complex")

Result is explained with meta-information.

Action → Result → Description

do($"player", "search") -> result("find treasure") -> describe("You discover a hidden compartment")

Result followed by explicit narration guidance.

Success vs Failure

Success Narration

When result("success") or similar:

do($"player", "climb wall") -> result("success")

LLM Output:

You scale the wall successfully, finding handholds and footholds with practiced ease. Reaching the top, you pull yourself over the edge.

The LLM narrates achievement, completion, and positive outcome.

Failure Narration

When result("failure") or similar:

do($"player", "climb wall") -> result("failure")

LLM Output:

You attempt to climb the wall, but the stones are slippery and your grip fails. You slide back down, frustrated but unharmed.

The LLM narrates the attempt, the failure point, and consequences.

Combining with Other Constructs

With Intention

do($"player", "intimidate") ~ "menacing" intention="make guards flee" -> result("partial_success")

LLM Output:

You advance menacingly, trying to frighten the guards. They step back nervously but don't flee entirely - you've shaken them, but not broken their resolve.

The intention shows the goal, the result shows partial achievement.

With Target

do($"player", "shoot arrow") -> target($"goblin") -> result("hit") -> damage(8)

LLM Output:

You loose your arrow at the goblin. It flies true, striking the creature in the shoulder for 8 points of damage.

With Manner

do($"player", "negotiate") ~ "diplomatically" -> result("success") -> gain("trade agreement")

LLM Output:

You negotiate diplomatically, choosing your words carefully. Your measured approach pays off - the other party agrees to a mutually beneficial trade agreement.

Empty vs Valued Result

Empty Result: result()

search($"room") -> result()

The LLM infers the outcome from context. Generally implies success or neutral outcome.

Explicit Result: result("value")

search($"room") -> result("find secret door")

Provides specific outcome for the LLM to narrate.

When to Use Each

Use result():

  • Outcome is obvious from context
  • Simple success/completion
  • Chaining needs a result marker

Use result("value"):

  • Outcome needs specification
  • Success/failure determination
  • Specific degree or type of outcome

Result in Sequences

Linear Success Chain

attempt -> result("success") -> consequence -> new_state

Branching on Result

attempt1 -> result("failure")
-> attempt2 -> result("failure")
-> attempt3 -> result("success")

Shows multiple attempts with different results.

Result with Recovery

action -> result("failure") -> fallback_action -> result("success")

Failure prompts alternative approach.

Cascading Results

action1 -> result("success")
-> enables(action2) -> result("success")
-> enables(action3) -> result("critical_success")

Success chains enable further successes.

Implementation Notes

Backend Determination

def determine_result(action, character, target, difficulty):
    """Backend determines result based on game logic."""
    roll = random.randint(1, 20) + character.skill_bonus
 
    if roll >= difficulty + 10:
        return "critical_success"
    elif roll >= difficulty:
        return "success"
    elif roll >= difficulty - 5:
        return "partial_success"
    else:
        return "failure"
 
# Usage
outcome = determine_result("persuade", player, merchant, 15)
ndl = f'do($"player", "persuade") -> result("{outcome}")'

Parsing Results

import re
 
def parse_result(ndl_string):
    """Extract result value from NDL."""
    match = re.search(r'result\("([^"]+)"\)', ndl_string)
    if match:
        return match.group(1)
 
    # Empty result
    if 'result()' in ndl_string:
        return None  # or "success" as default
 
    return None
 
# Usage
outcome = parse_result('do(action) -> result("hit")')
# Returns: "hit"

Generating Result NDL

def add_result(action_ndl, outcome=None):
    """Add result to action NDL."""
    if outcome:
        return f'{action_ndl} -> result("{outcome}")'
    else:
        return f'{action_ndl} -> result()'
 
# Usage
ndl = add_result('do($"attack")', "critical_hit")
# Returns: 'do($"attack") -> result("critical_hit")'

Anti-Patterns

❌ LLM Deciding Result

do($"attack")  # No result specified

Problem: LLM might invent hit/miss, contradicting game state.

Better:

do($"attack") -> result("hit")

❌ Contradictory Results

do($"attack") intention="kill" -> result("failure") -> damage(50)

Problem: Can’t deal damage if attack failed.

Better:

do($"attack") intention="kill" -> result("failure") -> damage(0)

❌ Vague Results

result("something happens")

Problem: Too vague for meaningful narration.

Better:

result("partial_success")  # or specific outcome

❌ Missing Result After Check

do($"persuade") -> roll("charisma")  # No result

Problem: Roll outcome not specified.

Better:

do($"persuade") -> roll("charisma") -> result("success")

Best Practices

1. Always Specify Critical Results

# Combat: Always show hit/miss
do($"attack") -> result("hit")
 
# Social: Always show convince/fail
do($"persuade") -> result("success")
 
# Investigation: Always show find/miss
do($"search") -> result("find clue")

2. Match Result Granularity to Context

# Simple context: binary
result("success")
 
# Complex context: degrees
result("overwhelming success with minor complication")

3. Use Result to Guide LLM Tone

# Critical failure: LLM should narrate dramatically
result("catastrophic_failure")
 
# Minor success: LLM should be matter-of-fact
result("barely_succeed")

4. Chain Results with Consequences

action -> result("success") -> gain("reward")
action -> result("failure") -> lose("item")

5. Differentiate Attempt from Outcome

# Attempt (action)
do($"lockpick")
 
# Outcome (result)
-> result("failure")
 
# Explanation (system)
-> system_response("The lock is beyond your skill")

Result Types by Context

Combat

result("hit")
result("miss")
result("critical_hit")
result("fumble")
result("glancing_blow")
result("blocked")
result("parried")
result("dodged")

Social

result("convinced")
result("skeptical")
result("befriended")
result("offended")
result("neutral")
result("intrigued")
result("hostile")

Skill Checks

result("success")
result("failure")
result("partial_success")
result("critical_success")
result("critical_failure")

Investigation

result("find_clue")
result("discover_secret")
result("trigger_trap")
result("find_nothing")
result("false_lead")

Crafting

result("masterwork")
result("success")
result("flawed")
result("broken")
result("exceptional")

Design Philosophy

Determinism

The backend computes results:

  • Dice rolls
  • Skill checks
  • State evaluation
  • NPC reactions

NDL communicates these to the LLM.

Separation of Concerns

Backend: Determines IF action succeeds
NDL: Communicates THAT it succeeded
LLM: Narrates HOW it succeeded

No Hallucination Space

By specifying results explicitly, NDL eliminates the LLM’s ability to invent outcomes that contradict game state.

Outcome

“once you offload the decision making to a system that’s actually capable of making sane decisions the hallucinations functionally disappear.” - veritasr

See Also


Key Takeaway

The result() function is NDL’s mechanism for communicating predetermined outcomes to the LLM. By explicitly stating what happened (success, failure, degrees of success), NDL ensures the LLM narrates the actual game state rather than inventing its own version of events. This is fundamental to eliminating hallucinations in LLM-driven game narration.