File size: 4,017 Bytes
1c9c13f
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
"""M4 전체 관톡 β€” 읽기 루프 β†’ κ°œμž… 포켓(μ±„νŒ…) β†’ 볡귀 β†’ μ—”λ”©κΉŒμ§€ ν•œ λ²ˆμ—."""

import json

import pytest

from engine.plugins.logging import TurnLoggingPlugin
from engine.repositories.content import ContentRepository
from engine.repositories.playlog import PlaylogRepository
from engine.services.pocket import PocketService
from engine.services.state import AXIS_DELTA
from engine.services.story import StoryEngine
from tests.fakes import ScriptedLlm, text_response, tool_call, tool_response


@pytest.fixture(scope="module")
def repo():
    return ContentRepository("content")


@pytest.mark.asyncio
async def test_full_playthrough_with_pocket(repo, tmp_path):
    """E06-09κΉŒμ§€ 읽고 β†’ 포켓 μ±„νŒ… 2ν„΄(수렴) β†’ μƒνƒœ 컀밋 β†’ μ΄μ–΄μ„œ μ—”λ”© 도달."""
    playlog = PlaylogRepository(tmp_path / "playlog.jsonl")
    svc = PocketService(
        repo,
        plugins=[TurnLoggingPlugin(playlog)],
        character_model=ScriptedLlm(script=[
            text_response("β€¦κ·Έλž˜. λ„ˆμ™€ λ‚˜ μ‚¬μ΄μ˜ 일이야, 둜라."),
            text_response("μ°©ν•˜μ§€. 우리끼리만 κ°„μ§ν•˜μž."),
            text_response("β€¦μ΄μ œ 됐어. 아무 말도 ν•„μš” μ—†μ–΄."),
        ]),
        director_model=ScriptedLlm(script=[
            tool_response(tool_call("update_trust", direction="trust")),
            text_response("계속"),
            tool_response(tool_call("update_trust", direction="trust"),
                          tool_call("mark_beat", beat="λ‘˜λ§Œμ˜ λΉ„λ°€ 동쑰")),
            text_response("계속"),
            tool_response(tool_call("finish_pocket", reason="goal_reached")),
            text_response("수렴"),
        ]),
    )

    # 1) 읽기 루프: E06-09κΉŒμ§€ μ‹ λ’° μ„±ν–₯으둜 μ§„ν–‰
    engine = StoryEngine(repo)
    step = engine.start()
    while engine.current_id != "E06-09":
        if step.choices:
            idx = next((i for i, c in enumerate(step.choices) if c.axis == "trust"),
                       next((i for i, c in enumerate(step.choices)
                             if c.axis in (None, "neutral")), 0))
            _, step = engine.choose(idx)
        else:
            step = engine.advance()
    trust_before = engine.state.trust_score

    # 2) κ°œμž… 포켓: μžμœ μ±„νŒ… 3ν„΄ β†’ director 수렴 (μ΅œμ†Œ 체λ₯˜ 3ν„΄)
    sid = await svc.open("E06-09", engine.state)
    t1 = await svc.run_turn(sid, "β€¦μš°λ¦¬ λ‘˜λ§Œμ˜ μΌμ΄λΌλŠ” κ±°μ§€?")
    assert not t1.converged and "둜라" in t1.reply
    t2 = await svc.run_turn(sid, "μ•Œμ•˜μ–΄. μ•„λ¬΄μ—κ²Œλ„ λ§ν•˜μ§€ μ•Šμ„κ²Œ.")
    assert not t2.converged  # μ΅œμ†Œ 체λ₯˜ ν„΄ 미달
    t3 = await svc.run_turn(sid, "…응. μ•½μ†ν• κ²Œ.")
    assert t3.converged and t3.reason == "goal_reached"

    # 3) 볡귀: μˆ¨μ€ μƒνƒœ 컀밋 (μ±„νŒ… 2ν„΄ = trust +2Γ—AXIS_DELTA)
    log = await svc.close(sid, engine.state)
    assert engine.state.trust_score == trust_before + 2 * AXIS_DELTA
    assert log.beats_hit == ["λ‘˜λ§Œμ˜ λΉ„λ°€ 동쑰"]

    # 4) μ΄μ–΄μ„œ 읽기 β†’ μ—”λ”© (μ‹ λ’° μ„±ν–₯ μœ μ§€ β†’ μ˜μ›)
    while step.ending is None:
        if step.choices:
            idx = next((i for i, c in enumerate(step.choices) if c.axis == "trust"),
                       next((i for i, c in enumerate(step.choices)
                             if c.axis in (None, "neutral")), 0))
            _, step = engine.choose(idx)
        else:
            step = engine.advance()
    assert step.ending.id == "ending_eternal"
    assert "E06-10" in engine.visited  # 도달점은 μ±„νŒ…κ³Ό λ¬΄κ΄€ν•˜κ²Œ 톡과

    # 5) ν„΄ 둜그: μœ μ € μž…λ ₯/이벀트/ν„΄ μš”μ•½μ΄ 기둝됨 (연ꡬ 데이터)
    records = [json.loads(l) for l in (tmp_path / "playlog.jsonl").read_text().splitlines()]
    kinds = {r["kind"] for r in records}
    assert {"user_input", "event", "turn_summary"} <= kinds
    tool_events = [r for r in records if r.get("tool_calls")]
    assert any(c["tool"] == "finish_pocket" for r in tool_events for c in r["tool_calls"])