From f64d52fe0127f4971dc18ff3f387c359e6b25907 Mon Sep 17 00:00:00 2001 From: Greg Sochanik Date: Sun, 8 Feb 2026 11:03:24 +0000 Subject: [PATCH] Fix game.objective being overwritten by change_grammar() when quests exist PR #240 (fixing #239) added code to preserve a manually-set objective across build() calls. However, the preservation happens before change_grammar() is called, which unconditionally overwrites the objective via describe_event(). This means the fix only worked for games without quests (no winning policy = no describe_event call). The fix: only auto-generate the objective in change_grammar() if one hasn't already been set (self._objective is None). Fixes #373 Co-authored-by: Cursor --- textworld/generator/game.py | 3 ++- textworld/generator/tests/test_maker.py | 26 +++++++++++++++++++++++++ 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/textworld/generator/game.py b/textworld/generator/game.py index 281be003..6cbac814 100644 --- a/textworld/generator/game.py +++ b/textworld/generator/game.py @@ -457,7 +457,8 @@ def change_grammar(self, grammar: Grammar) -> None: mapping = {k: info.name for k, info in self._infos.items()} commands = [a.format_command(mapping) for a in policy] self.metadata["walkthrough"] = commands - self.objective = describe_event(Event(policy), self, self.grammar) + if self._objective is None: + self.objective = describe_event(Event(policy), self, self.grammar) def save(self, filename: str) -> None: """ Saves the serialized data of this game to a file. """ diff --git a/textworld/generator/tests/test_maker.py b/textworld/generator/tests/test_maker.py index 818f63ec..2e1d28fa 100644 --- a/textworld/generator/tests/test_maker.py +++ b/textworld/generator/tests/test_maker.py @@ -198,6 +198,32 @@ def test_manually_defined_objective(): assert state["objective"] == "There's nothing much to do in here." +def test_manually_defined_objective_with_quests(): + # Regression test for https://github.com/microsoft/TextWorld/issues/373 + # When a game has quests, change_grammar() was unconditionally overwriting + # the objective with auto-generated text from describe_event(). + M = GameMaker() + + R1 = M.new_room("bedroom") + M.set_player(R1) + + key = M.new(type="k", name="key", desc="This is a skeleton key.") + R1.add(key) + + M.set_quest_from_commands(["take key"]) + + game = M.build() + game.objective = "Find a valuable object." + + with make_temp_directory(prefix="test_manually_defined_objective_with_quests") as tmpdir: + game_file = M.compile(tmpdir) + + env = textworld.start(game_file, request_infos=textworld.EnvInfos(objective=True)) + state = env.reset() + assert state["objective"] == "Find a valuable object.", \ + f"Expected custom objective, got: {state['objective']}" + + if __name__ == "__main__": # test_making_a_small_game(play_the_game=True) test_record_quest_from_commands(play_the_game=True)