Spaces:
Paused
Paused
| """Data model (SPEC.md §2). | |
| Everything here is a plain dataclass so it can live in ``gr.State`` (Gradio | |
| deep-copies session state; dataclasses, lambdas and module-level functions are | |
| all deepcopy-safe). Per-user game state is carried in :class:`GameSession`; | |
| static content (seed primitives, challenge definitions) lives module-level in | |
| ``ledger.py`` / ``challenges.py`` and is never mutated. | |
| """ | |
| from __future__ import annotations | |
| from dataclasses import dataclass, field | |
| from typing import Callable, Optional | |
| # --------------------------------------------------------------------------- # | |
| # 2.1 Concept — an entry in the ledger | |
| # --------------------------------------------------------------------------- # | |
| class Concept: | |
| id: str # stable slug, e.g. "hidden_info" | |
| label: str # short human label, e.g. "hiding information" | |
| player_phrase: str # how the player expressed it when teaching | |
| understanding: str # the alien's internal gloss, 1 sentence, alien framing | |
| taught_on_turn: int | |
| times_applied: int = 0 | |
| # Concept ids this one was *composed from* (generalization beat). Lets the UI | |
| # draw the constellation edge from e.g. "surprise" back to "hidden_info". | |
| built_from: tuple[str, ...] = () | |
| # True if reached during a generalization challenge (the alien composed it) | |
| # rather than taught directly — drives the finale's "taught vs understood alone" | |
| # split. (built_from can't be used for that split: a generalization with no | |
| # prior taught concepts has empty built_from but is still self-reached.) | |
| via_generalization: bool = False | |
| # --------------------------------------------------------------------------- # | |
| # 2.2 WorldState — the deterministic sandbox | |
| # --------------------------------------------------------------------------- # | |
| class Obj: | |
| id: str | |
| name: str # "blue stone", "red stone", "basket" | |
| location: str # an Agent id, a Container id, or "ground" | |
| hidden: bool = False # concealed from other agents? | |
| class Agent: | |
| id: str # "alien", "other" | |
| name: str | |
| holding: list[str] = field(default_factory=list) # obj ids | |
| # NOTE: not in the SPEC's Agent sketch. Added so the `move_to` verb (which the | |
| # SPEC lists in the closed action set) has somewhere to record its effect. | |
| # None of the §8 win-predicates depend on it; it is a clean superset. | |
| location: str = "ground" | |
| class WorldState: | |
| objects: dict[str, Obj] | |
| agents: dict[str, Agent] | |
| containers: list[str] # e.g. ["basket"] — things that can conceal | |
| log: list[str] = field(default_factory=list) # human-readable action record | |
| # --------------------------------------------------------------------------- # | |
| # 2.3 Action — the closed, enumerable verb set | |
| # --------------------------------------------------------------------------- # | |
| class Action: | |
| verb: str # one of world.ALLOWED_VERBS | |
| args: dict = field(default_factory=dict) | |
| # --------------------------------------------------------------------------- # | |
| # 2.4 Challenge — the current goal, with a MECHANICAL win condition | |
| # --------------------------------------------------------------------------- # | |
| class Challenge: | |
| id: str | |
| title: str # "Teach the alien to hide the stone" | |
| setup_blurb: str # shown to the player (NOT to the model) | |
| teaches: Optional[str] # concept id this challenge introduces, or None | |
| win_predicate: Callable[[WorldState], bool] # checked after each action | |
| # For generalization challenges (teaches=None): concept ids a *previously | |
| # learned* concept is expected to be applied through. Not in the SPEC's | |
| # Challenge sketch; added to power the §5 "times_applied / concept lights up | |
| # on reuse" beat without the model judging semantics. | |
| relies_on: tuple[str, ...] = () | |
| # A concrete, player-facing objective, shown as a live goal strip in the UI so | |
| # "what counts as winning" is legible (not in the SPEC sketch; UX clarity). | |
| goal: str = "" | |
| # --------------------------------------------------------------------------- # | |
| # Per-session container (everything that goes into a single gr.State) | |
| # --------------------------------------------------------------------------- # | |
| class GameSession: | |
| """All per-user mutable state. One of these lives in one ``gr.State``. | |
| ``challenge_index`` indexes the module-level ``challenges.CHALLENGES`` list | |
| rather than carrying a ``Challenge`` (with its callable predicate) through | |
| state — keeps the session pure data and the static arc in one place. | |
| """ | |
| ledger: list[Concept] | |
| world: WorldState | |
| challenge_index: int = 0 | |
| turn: int = 0 | |
| # Conversation transcript for rendering. Each entry: | |
| # {"who": "player"|"alien"|"system", "text": str, | |
| # "gap": str|None, "kind": str|None} | |
| history: list[dict] = field(default_factory=list) | |
| # A candidate_concept awaiting the player's "Yes, it learned that" (§5). | |
| pending_candidate: Optional[dict] = None | |
| # Set when the current challenge's win_predicate just became true. | |
| won_current: bool = False | |