| """Headless tests for the Game wrapper, using the dummy policy (no GPU/Gradio). |
| |
| Run: python -m pytest tests/ -q |
| """ |
|
|
| import sys |
| from pathlib import Path |
|
|
| sys.path.insert(0, str(Path(__file__).resolve().parents[1])) |
|
|
| from ttw import narrate |
| from ttw.dummy import make_random_policy |
| from ttw.game import Game |
|
|
|
|
| def _game(): |
| return Game(make_random_policy(seed=3), deck_seed=1) |
|
|
|
|
| def test_baseline_recorded_before_any_step(): |
| g = _game() |
| assert not g.price_frame().empty |
| assert g.gini_frame().iloc[0]["turn"] == 0 |
|
|
|
|
| def test_step_advances_and_grows_history(): |
| g = _game() |
| rows_before = len(g.price_frame()) |
| g.step() |
| assert g.world.turn == 1 |
| assert len(g.price_frame()) > rows_before |
| assert g.gini_frame().iloc[-1]["turn"] == 1 |
|
|
|
|
| def test_price_frame_shape_is_long_per_good(): |
| g = _game() |
| g.step() |
| pf = g.price_frame() |
| assert set(pf.columns) == {"turn", "good", "price"} |
| |
| assert (pf["turn"] == 1).sum() == 5 |
|
|
|
|
| def test_town_frame_lists_all_creatures_sorted_by_wealth(): |
| g = _game() |
| g.step() |
| tf = g.town_frame() |
| assert len(tf) == 5 |
| nets = list(tf["net worth"]) |
| assert nets == sorted(nets, reverse=True) |
|
|
|
|
| def test_tempt_fate_draws_legend_and_tickers(): |
| g = _game() |
| title = g.tempt_fate() |
| assert isinstance(title, str) and title |
| assert any("inspired by" in line for line in g.ticker) |
|
|
|
|
| def test_shock_gold_rush_echoes_to_ticker(): |
| g = _game() |
| before = sum(c.pebbles for c in g.world.alive()) |
| g.shock("gold_rush", amount=10) |
| assert sum(c.pebbles for c in g.world.alive()) == before + 10 * 5 |
| assert any("windfall" in line.lower() for line in g.ticker) |
|
|
|
|
| def test_traces_markdown_handles_dummy_policy_gracefully(): |
| """Dummy policy has no raw thoughts; traces view must not crash.""" |
| g = _game() |
| g.step() |
| assert isinstance(g.traces_markdown(), str) |
|
|
|
|
| def test_legend_markdown_before_and_after_tempt(): |
| g = _game() |
| assert "No legend yet" in g.legend_markdown() |
| g.tempt_fate() |
| md = g.legend_markdown() |
| assert "Inspired by" in md and "β¨" in md |
| assert g.current_legend is not None |
|
|
|
|
| def test_avatar_and_mood_emoji(): |
| assert narrate.avatar("Fenn") == "π¦" |
| assert narrate.avatar("Nobody") == "πΎ" |
| assert narrate.mood_emoji(10) == "π" |
| assert narrate.mood_emoji(4) == "π£" |
|
|
|
|
| def test_town_frame_has_avatars_and_mood_faces(): |
| g = _game() |
| g.step() |
| tf = g.town_frame() |
| assert any("π¦" in str(v) for v in tf["creature"]) |
| assert all(any(face in str(m) for face in "ππππ£") for m in tf["mood"]) |
|
|
|
|
| |
| _FIREWALL_BANNED = ("true", "false", "truth", "legend_key") |
|
|
|
|
| def test_phase6_accessors_return_str(): |
| g = _game() |
| assert isinstance(g.heat_bar_html(), str) |
| assert isinstance(g.relationship_map_html(), str) |
| assert isinstance(g.cutscene_html(), str) |
|
|
|
|
| def test_heat_bar_html_well_formed(): |
| from ttw.world import INVESTIGATION_THRESHOLD |
|
|
| g = _game() |
| html = g.heat_bar_html() |
| assert html |
| assert str(INVESTIGATION_THRESHOLD) in html |
| assert "color:#" in html |
| assert not any(b in html for b in _FIREWALL_BANNED) |
|
|
|
|
| def test_relationship_map_html_covers_cast_and_patron(): |
| g = _game() |
| |
| g.queue_player_move("bribe", target="Fenn", amount=40) |
| g.step() |
| g.queue_player_move("short", good="berries", amount=20) |
| g.step() |
| html = g.relationship_map_html() |
| assert "<table" in html |
| assert "Patron" in html |
| for name in g.world.creatures: |
| assert name in html |
| assert "#2e2519" in html |
|
|
|
|
| def test_cutscene_html_never_blank(): |
| g = _game() |
| before = g.cutscene_html() |
| assert before |
| assert "The wood is still." in before |
| g.step() |
| after = g.cutscene_html() |
| assert after |
| assert "#fff8e6" not in after |
| assert "#161b22" in after |
|
|
|
|
| |
| def test_creature_lab_color_maps_engine_and_falls_back(): |
| from ttw.council import creature_lab_color |
|
|
| assert creature_lab_color("Fenn") == "#76b900" |
| assert creature_lab_color("Oona") == "#10a37f" |
| assert creature_lab_color("Nobody") == "#cdbf9a" |
|
|
|
|
| def test_chapter_markdown_is_html_stepper_with_one_active(): |
| g = _game() |
| spine = g.chapter_markdown() |
| |
| for title in ("First Whispers", "The Heat Rises", "Reckoning", "The Long Game"): |
| assert title in spine |
| |
| assert spine.count("border-bottom:3px solid #2f6b3d") == 1 |
|
|
|
|
| def test_traces_markdown_tags_the_model(): |
| g = _game() |
| |
| |
| g.thoughts = {"Oona": "hold the line", "Pip": "buy acorns"} |
| md = g.traces_markdown() |
| assert "[gpt-oss-20B]" in md |
| assert "[ttw-trader-0.5B]" in md |
|
|
|
|
| def test_raw_output_markdown_empty_state(): |
| g = _game() |
| md = g.raw_output_markdown() |
| assert isinstance(md, str) |
| assert "No raw model output" in md |
|
|
|
|
| def test_raw_output_markdown_tags_and_fence_safe(): |
| g = _game() |
| g.policy.state = {"raw": {"Oona": '{"thought":"x"} ``` # inject'}} |
| md = g.raw_output_markdown() |
| assert "gpt-oss-20B" in md |
| assert "```json" in md |
| |
| |
| assert md.count("```") == 2 |
|
|