Fix None directions and duplicate exits in start location generation #109

Merged
Copilot merged 5 commits from copilot/fix-directions-start-location into update-v0.39 2025-10-26 07:44:51 -04:00
Copilot commented 2025-10-26 03:26:09 -04:00 (Migrated from github.com)

During "anything" story start location generation, exits were created with direction: null, producing descriptions like "To the None you see Winding Way" and causing TypeError: '<' not supported between instances of 'NoneType' and 'str' when sorting exits in Location.look(). Additionally, LLM-generated JSON sometimes contained duplicate exit entries, resulting in multiple identical exits in the location.

Root Causes

Four related issues in tale/parse_utils.py:

  1. _select_non_occupied_direction() only checked 4 cardinal directions, returning None implicitly when all occupied
  2. opposite_direction() didn't handle diagonal directions, returning None for unknown inputs
  3. parse_generated_exits() appended opposite directions without None checks
  4. parse_generated_exits() processed all exits without deduplication, allowing duplicate entries

Changes

Extended direction handling

  • _select_non_occupied_direction(): Check 10 directions (north/south/east/west/northeast/northwest/southeast/southwest/up/down) with fallback to 'north'
  • opposite_direction(): Added diagonal direction mappings (northeast↔southwest, northwest↔southeast)

Added None safety in exit generation

# Before: Could append None to directions list
directions_from.append(opposite_direction(direction))

# After: Validate before appending
opposite = opposite_direction(direction)
if opposite:
    directions_from.append(opposite)

# Extra safety for descriptions
has_return_direction = len(directions_from) > 1 and directions_from[1]
from_description = f'To the {directions_from[1]} you see {location.name}.' if has_return_direction else f'You see {location.name}.'

Added duplicate exit filtering

seen_exits = set()  # Track seen exit names to prevent duplicates

for exit in exits:
    # Skip duplicate exits (same name and direction)
    exit_name = exit.get('name', '')
    exit_direction = exit.get('direction', '')
    exit_key = (exit_name.lower().replace('the ', '').replace('The ', ''), exit_direction)
    if exit_key in seen_exits:
        continue  # Skip duplicate exit
    seen_exits.add(exit_key)

Tests

Added test_parse_utils_none_direction.py with 7 test cases covering:

  • Exits without/with null direction fields
  • All directions occupied scenario
  • Diagonal direction handling
  • Location.look() compatibility
  • Duplicate exit filtering

Files Changed

tale/parse_utils.py                      |  35 +++++++++
tests/test_parse_utils_none_direction.py | 164 ++++++++++
2 files changed, 195 insertions(+), 4 deletions(-)
Original prompt

This section details on the original issue you should resolve

<issue_title>Issue with directions and location during start location generation</issue_title>
<issue_description>There is a long running issue with directions being None when start location is generated during anything stories. This may cause issues with returning to the location, and also exceptions in "look" commands. This is what a result might look like:

{
                    "name": "Willowdale",
                    "descr": "",
                    "short_descr": "",
                    "exits": [
                        {
                            "name": "Winding way",
                            "short_descr": "To the None you see Winding Way.",
                            "long_descr": "To the None you see Winding Way.",
                            "direction": null
                        },
                        {
                            "name": "Winding way",
                            "short_descr": "To the None you see Winding Way.",
                            "long_descr": "To the None you see Winding Way.",
                            "direction": null
                        }
                    ],
                    "world_location": [
                        0,
                        0,
                        0
                    ],
                    "built": false,
                    "items": []
                },

note the None where there should be North, South, East or West

Exception which I think is related:

  File “/LlamaTale/tale/base.py”, line 791, in Location.look
  Source code:
      for exit_name in sorted(self.exits):
  Local values:
      exclude_living = <Player 'arto' neph1/LlamaTale#15 @ 0x7fdd236d6170, privs:->
      exit_paragraph = []
      exits_seen = set()
      paragraphs = [’[Willowdale]’, ’A picturesque meadow filled with vibrant wildflowers in the heart of Willowdale. Delicate lavender sways in the gentle breeze, releasing a soothing, sweet fragrance.’]
      self = <Location 'Willowdale Meadow' neph1/LlamaTale#20 @ 0x7fdd23235b10>
      short = False
   EXCEPTION HERE: TypeError: ’<' not supported between instances of 'NoneType' and 'str'

This is likely not caused by the LLM's response, and is more likely a parsing issue. Seems to only occur when generating start locations for anything stories</issue_description>

<agent_instructions>This might be tricky for you to fix, but give it a try. I appreciate any information as to what might be causing it.</agent_instructions>

Comments on the Issue (you are @copilot in this section)


💡 You can make Copilot smarter by setting up custom instructions, customizing its development environment and configuring Model Context Protocol (MCP) servers. Learn more Copilot coding agent tips in the docs.

During "anything" story start location generation, exits were created with `direction: null`, producing descriptions like "To the None you see Winding Way" and causing `TypeError: '<' not supported between instances of 'NoneType' and 'str'` when sorting exits in `Location.look()`. Additionally, LLM-generated JSON sometimes contained duplicate exit entries, resulting in multiple identical exits in the location. ## Root Causes Four related issues in `tale/parse_utils.py`: 1. `_select_non_occupied_direction()` only checked 4 cardinal directions, returning `None` implicitly when all occupied 2. `opposite_direction()` didn't handle diagonal directions, returning `None` for unknown inputs 3. `parse_generated_exits()` appended opposite directions without `None` checks 4. `parse_generated_exits()` processed all exits without deduplication, allowing duplicate entries ## Changes ### Extended direction handling - `_select_non_occupied_direction()`: Check 10 directions (north/south/east/west/northeast/northwest/southeast/southwest/up/down) with fallback to 'north' - `opposite_direction()`: Added diagonal direction mappings (northeast↔southwest, northwest↔southeast) ### Added None safety in exit generation ```python # Before: Could append None to directions list directions_from.append(opposite_direction(direction)) # After: Validate before appending opposite = opposite_direction(direction) if opposite: directions_from.append(opposite) # Extra safety for descriptions has_return_direction = len(directions_from) > 1 and directions_from[1] from_description = f'To the {directions_from[1]} you see {location.name}.' if has_return_direction else f'You see {location.name}.' ``` ### Added duplicate exit filtering ```python seen_exits = set() # Track seen exit names to prevent duplicates for exit in exits: # Skip duplicate exits (same name and direction) exit_name = exit.get('name', '') exit_direction = exit.get('direction', '') exit_key = (exit_name.lower().replace('the ', '').replace('The ', ''), exit_direction) if exit_key in seen_exits: continue # Skip duplicate exit seen_exits.add(exit_key) ``` ### Tests Added `test_parse_utils_none_direction.py` with 7 test cases covering: - Exits without/with null direction fields - All directions occupied scenario - Diagonal direction handling - Location.look() compatibility - Duplicate exit filtering ## Files Changed ``` tale/parse_utils.py | 35 +++++++++ tests/test_parse_utils_none_direction.py | 164 ++++++++++ 2 files changed, 195 insertions(+), 4 deletions(-) ``` - Fixes neph1/LlamaTale#107 <!-- START COPILOT CODING AGENT SUFFIX --> <details> <summary>Original prompt</summary> > > ---- > > *This section details on the original issue you should resolve* > > <issue_title>Issue with directions and location during start location generation</issue_title> > <issue_description>There is a long running issue with directions being None when start location is generated during anything stories. This may cause issues with returning to the location, and also exceptions in "look" commands. This is what a result might look like: > > ``` > { > "name": "Willowdale", > "descr": "", > "short_descr": "", > "exits": [ > { > "name": "Winding way", > "short_descr": "To the None you see Winding Way.", > "long_descr": "To the None you see Winding Way.", > "direction": null > }, > { > "name": "Winding way", > "short_descr": "To the None you see Winding Way.", > "long_descr": "To the None you see Winding Way.", > "direction": null > } > ], > "world_location": [ > 0, > 0, > 0 > ], > "built": false, > "items": [] > }, > ``` > > note the `None` where there should be North, South, East or West > > Exception which I think is related: > > ``` > File “/LlamaTale/tale/base.py”, line 791, in Location.look > Source code: > for exit_name in sorted(self.exits): > Local values: > exclude_living = <Player 'arto' neph1/LlamaTale#15 @ 0x7fdd236d6170, privs:-> > exit_paragraph = [] > exits_seen = set() > paragraphs = [’[Willowdale]’, ’A picturesque meadow filled with vibrant wildflowers in the heart of Willowdale. Delicate lavender sways in the gentle breeze, releasing a soothing, sweet fragrance.’] > self = <Location 'Willowdale Meadow' neph1/LlamaTale#20 @ 0x7fdd23235b10> > short = False > EXCEPTION HERE: TypeError: ’<' not supported between instances of 'NoneType' and 'str' > ``` > > This is likely not caused by the LLM's response, and is more likely a parsing issue. Seems to only occur when generating start locations for anything stories</issue_description> > > <agent_instructions>This might be tricky for you to fix, but give it a try. I appreciate any information as to what might be causing it.</agent_instructions> > > ## Comments on the Issue (you are @copilot in this section) > > <comments> > </comments> > </details> - Fixes neph1/LlamaTale#107 <!-- START COPILOT CODING AGENT TIPS --> --- 💡 You can make Copilot smarter by setting up custom instructions, customizing its development environment and configuring Model Context Protocol (MCP) servers. Learn more [Copilot coding agent tips](https://gh.io/copilot-coding-agent-tips) in the docs.
neph1 commented 2025-10-26 03:48:02 -04:00 (Migrated from github.com)

@copilot This seems to work better. I've noticed it might create duplicate items in the exits list, though:


"exits": [
                        {
                            "name": "Willowdale farmstead",
                            "short_descr": "To the north you see the path leads back to your cozy farmhouse.",
                            "long_descr": "To the north you see the path leads back to your cozy farmhouse.",
                            "direction": "north"
                        },
                        {
                            "name": "Willowdale farmstead",
                            "short_descr": "To the north you see the path leads back to your cozy farmhouse.",
                            "long_descr": "To the north you see the path leads back to your cozy farmhouse.",
                            "direction": "north"
                        },

Can you make sure there are no duplicates, possibly by using sets instead of lists?

@copilot This seems to work better. I've noticed it might create duplicate items in the exits list, though: ``` "exits": [ { "name": "Willowdale farmstead", "short_descr": "To the north you see the path leads back to your cozy farmhouse.", "long_descr": "To the north you see the path leads back to your cozy farmhouse.", "direction": "north" }, { "name": "Willowdale farmstead", "short_descr": "To the north you see the path leads back to your cozy farmhouse.", "long_descr": "To the north you see the path leads back to your cozy farmhouse.", "direction": "north" }, ``` Can you make sure there are no duplicates, possibly by using sets instead of lists?
Copilot commented 2025-10-26 03:51:54 -04:00 (Migrated from github.com)

@copilot This seems to work better. I've noticed it might create duplicate items in the exits list, though:


...

Fixed in commit ea035d4. Added set-based deduplication that tracks seen exits using (name, direction) tuples. Duplicate exits with the same name and direction are now skipped during processing. The exit names are normalized (lowercased and "the " is stripped) to ensure proper comparison.

> @copilot This seems to work better. I've noticed it might create duplicate items in the exits list, though: > ``` > ... Fixed in commit ea035d4. Added set-based deduplication that tracks seen exits using (name, direction) tuples. Duplicate exits with the same name and direction are now skipped during processing. The exit names are normalized (lowercased and "the " is stripped) to ensure proper comparison.
neph1 (Migrated from github.com) approved these changes 2025-10-26 07:44:39 -04:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
neph1/LlamaTale!109
No description provided.