Spaces:
Sleeping
Sleeping
Upload folder using huggingface_hub
Browse files- .pytest_cache/v/cache/nodeids +2 -0
- app/main.py +21 -3
- docs/ROADMAP.md +16 -11
- static/app.js +11 -0
- static/index.html +8 -8
- static/styles.css +8 -0
- tests/test_api.py +20 -0
.pytest_cache/v/cache/nodeids
CHANGED
|
@@ -336,6 +336,7 @@
|
|
| 336 |
"engine/tests/test_word_classifier.py::TestPositionOverrides::test_still_is_temporal",
|
| 337 |
"tests/test_api.py::test_care_bad_action_422",
|
| 338 |
"tests/test_api.py::test_care_feed_moves_mood_and_returns_needs",
|
|
|
|
| 339 |
"tests/test_api.py::test_cruel_message_records_a_moment",
|
| 340 |
"tests/test_api.py::test_health_ok",
|
| 341 |
"tests/test_api.py::test_history_endpoint",
|
|
@@ -361,6 +362,7 @@
|
|
| 361 |
"tests/test_api.py::test_toy_bad_name_422",
|
| 362 |
"tests/test_api.py::test_toy_balloon_moves_mood",
|
| 363 |
"tests/test_api.py::test_toy_logs",
|
|
|
|
| 364 |
"tests/test_api.py::test_toy_triggers_snapshot",
|
| 365 |
"tests/test_appearance.py::test_angry_is_reddish",
|
| 366 |
"tests/test_appearance.py::test_arousal_opens_eyes_and_saturates",
|
|
|
|
| 336 |
"engine/tests/test_word_classifier.py::TestPositionOverrides::test_still_is_temporal",
|
| 337 |
"tests/test_api.py::test_care_bad_action_422",
|
| 338 |
"tests/test_api.py::test_care_feed_moves_mood_and_returns_needs",
|
| 339 |
+
"tests/test_api.py::test_care_refreshes_panels_not_stale",
|
| 340 |
"tests/test_api.py::test_cruel_message_records_a_moment",
|
| 341 |
"tests/test_api.py::test_health_ok",
|
| 342 |
"tests/test_api.py::test_history_endpoint",
|
|
|
|
| 362 |
"tests/test_api.py::test_toy_bad_name_422",
|
| 363 |
"tests/test_api.py::test_toy_balloon_moves_mood",
|
| 364 |
"tests/test_api.py::test_toy_logs",
|
| 365 |
+
"tests/test_api.py::test_toy_refreshes_panels_not_stale",
|
| 366 |
"tests/test_api.py::test_toy_triggers_snapshot",
|
| 367 |
"tests/test_appearance.py::test_angry_is_reddish",
|
| 368 |
"tests/test_appearance.py::test_arousal_opens_eyes_and_saturates",
|
app/main.py
CHANGED
|
@@ -82,14 +82,33 @@ def snapshot():
|
|
| 82 |
return _state()
|
| 83 |
|
| 84 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 85 |
@app.post("/care")
|
| 86 |
def care(body: CareIn):
|
| 87 |
if body.action not in CARE_ACTIONS:
|
| 88 |
raise HTTPException(status_code=422, detail=f"unknown action: {body.action!r}")
|
| 89 |
_care.do(body.action)
|
| 90 |
-
|
|
|
|
|
|
|
| 91 |
_snap.maybe_snapshot()
|
|
|
|
|
|
|
|
|
|
| 92 |
state = _state()
|
|
|
|
| 93 |
state["line"] = f"you {body.action} it"
|
| 94 |
return state
|
| 95 |
|
|
@@ -106,8 +125,7 @@ def toy(body: ToyIn):
|
|
| 106 |
read = [s.v, s.a, s.d, s.u, s.g, s.w, s.i]
|
| 107 |
deltas = [after[i] - before[i] for i in range(7)]
|
| 108 |
state = _state()
|
| 109 |
-
state
|
| 110 |
-
state["deltas"] = deltas
|
| 111 |
state["line"] = body.toy
|
| 112 |
_audit.append(build_row(
|
| 113 |
session=_SESSION,
|
|
|
|
| 82 |
return _state()
|
| 83 |
|
| 84 |
|
| 85 |
+
def _enrich_physical(state: dict, label: str, read: list[int], deltas: list[int]) -> None:
|
| 86 |
+
"""Toy/care interactions have no language to parse, but they DO push a Score
|
| 87 |
+
through the soul. Surface the real read, deltas, armor and dominant move so the
|
| 88 |
+
panels track the action instead of showing the previous chat message, and mark
|
| 89 |
+
the trace as a physical (non-linguistic) interaction."""
|
| 90 |
+
state["read"] = read
|
| 91 |
+
state["deltas"] = deltas
|
| 92 |
+
state["acceptance"] = _bridge.last_acceptance()
|
| 93 |
+
state["trace"] = {"words": [], "structures": [], "contributors": [],
|
| 94 |
+
"kind": "physical", "label": label}
|
| 95 |
+
state["why"] = f"{label}: " + _why(read, deltas)
|
| 96 |
+
|
| 97 |
+
|
| 98 |
@app.post("/care")
|
| 99 |
def care(body: CareIn):
|
| 100 |
if body.action not in CARE_ACTIONS:
|
| 101 |
raise HTTPException(status_code=422, detail=f"unknown action: {body.action!r}")
|
| 102 |
_care.do(body.action)
|
| 103 |
+
s = _care.score_for(body.action)
|
| 104 |
+
before = _bridge.mood()
|
| 105 |
+
_bridge.ingest(s)
|
| 106 |
_snap.maybe_snapshot()
|
| 107 |
+
after = _bridge.mood()
|
| 108 |
+
read = [s.v, s.a, s.d, s.u, s.g, s.w, s.i]
|
| 109 |
+
deltas = [after[i] - before[i] for i in range(7)]
|
| 110 |
state = _state()
|
| 111 |
+
_enrich_physical(state, f"you {body.action} it", read, deltas)
|
| 112 |
state["line"] = f"you {body.action} it"
|
| 113 |
return state
|
| 114 |
|
|
|
|
| 125 |
read = [s.v, s.a, s.d, s.u, s.g, s.w, s.i]
|
| 126 |
deltas = [after[i] - before[i] for i in range(7)]
|
| 127 |
state = _state()
|
| 128 |
+
_enrich_physical(state, body.toy, read, deltas)
|
|
|
|
| 129 |
state["line"] = body.toy
|
| 130 |
_audit.append(build_row(
|
| 131 |
session=_SESSION,
|
docs/ROADMAP.md
CHANGED
|
@@ -114,17 +114,22 @@ The current Space's defect and the showcase, together.
|
|
| 114 |
- All in the soul SQLite db → persisted by M2's snapshot.
|
| 115 |
- *Community-shared "who cared for it" surfacing deferred to M5 (economy/governance).*
|
| 116 |
|
| 117 |
-
## M4 — Toys, world, physics, fluid animation, full GFX
|
| 118 |
-
- **Toys
|
| 119 |
-
|
| 120 |
-
|
| 121 |
-
|
| 122 |
-
|
| 123 |
-
|
| 124 |
-
|
| 125 |
-
-
|
| 126 |
-
- *
|
| 127 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 128 |
|
| 129 |
## M5 — Community economy & governance *(exploratory / eventual)*
|
| 130 |
- Caring actions (cleaning up after it, feeding, play, attention) **earn a soft care
|
|
|
|
| 114 |
- All in the soul SQLite db → persisted by M2's snapshot.
|
| 115 |
- *Community-shared "who cared for it" surfacing deferred to M5 (economy/governance).*
|
| 116 |
|
| 117 |
+
## M4 — Toys, world, physics, fluid animation, full GFX ✅ DONE (2026-06-15)
|
| 118 |
+
- **Toys** (`app/toys.py`, `POST /toy`): 5 momentary VADUGWI stimuli — balloon (high-arousal
|
| 119 |
+
delight), musicbox (positive valence at LOW arousal+urgency), mirror (self-Worth), teddy
|
| 120 |
+
(comfort/grounding), **rattle (Urgency spike — the dimension chat/care never reach)**. Each
|
| 121 |
+
ingests an absolute-target `Score` into the one shared soul; audit-logged like chat.
|
| 122 |
+
- **In-browser physics + fluid animation** (`static/physics.js` pulse engine + `app.js` loop):
|
| 123 |
+
a `requestAnimationFrame` loop drives spring-damped **squish** (impulse on every response,
|
| 124 |
+
sized by |VADUGWI deltas|) and continuous **breathing** (rate+depth from arousal). Writes to
|
| 125 |
+
a `#blob-anim` wrapper so it never fights `renderBlob`'s per-message transform.
|
| 126 |
+
- **Mascot behaviors** — idle FSM `behaviorFor(mood)`: **sleep** (arousal+urgency both low →
|
| 127 |
+
eyes close, Zzz, slow deep breath), calm, idle, **roam** (drifts across the floor at moderate
|
| 128 |
+
arousal), **excited** (fast bounce at high arousal). All derived purely from the live mood.
|
| 129 |
+
- **GFX**: 5 codex-generated transparent toy sprites adopted into the tray.
|
| 130 |
+
- Shipped & verified live on `deucebucket/clanker` (`/toy` returns moving deltas; Playwright
|
| 131 |
+
video confirms breathing/squish/sleep/roam; rattle shows U=210 in the read panel).
|
| 132 |
+
- *Community-shared world decor / care economy deferred to M5.*
|
| 133 |
|
| 134 |
## M5 — Community economy & governance *(exploratory / eventual)*
|
| 135 |
- Caring actions (cleaning up after it, feeding, play, attention) **earn a soft care
|
static/app.js
CHANGED
|
@@ -192,6 +192,17 @@ const ROLE_CLASS = {
|
|
| 192 |
function renderTrace(trace) {
|
| 193 |
if (!trace) return;
|
| 194 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 195 |
// words as role-colored chips
|
| 196 |
const wordsEl = $('#trace-words');
|
| 197 |
wordsEl.innerHTML = '';
|
|
|
|
| 192 |
function renderTrace(trace) {
|
| 193 |
if (!trace) return;
|
| 194 |
|
| 195 |
+
// physical interaction (toy/care): no language to parse — show the action,
|
| 196 |
+
// not the previous chat message's words.
|
| 197 |
+
if (trace.kind === 'physical') {
|
| 198 |
+
$('#trace-words').innerHTML =
|
| 199 |
+
`<span class="trace-physical">${trace.label || 'physical interaction'} — physical interaction, no language to parse</span>`;
|
| 200 |
+
$('#trace-structs').innerHTML = '';
|
| 201 |
+
$('#trace-contribs').innerHTML = '';
|
| 202 |
+
$('#trace-unknown').classList.add('hidden');
|
| 203 |
+
return;
|
| 204 |
+
}
|
| 205 |
+
|
| 206 |
// words as role-colored chips
|
| 207 |
const wordsEl = $('#trace-words');
|
| 208 |
wordsEl.innerHTML = '';
|
static/index.html
CHANGED
|
@@ -6,7 +6,7 @@
|
|
| 6 |
<link rel="preconnect" href="https://fonts.googleapis.com">
|
| 7 |
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
| 8 |
<link href="https://fonts.googleapis.com/css2?family=Fredoka:wght@500;600&family=JetBrains+Mono:wght@400;600&display=swap" rel="stylesheet">
|
| 9 |
-
<link rel="stylesheet" href="/static/styles.css?v=
|
| 10 |
</head>
|
| 11 |
<body>
|
| 12 |
<div id="layout">
|
|
@@ -131,11 +131,11 @@
|
|
| 131 |
<div id="bottom-band">
|
| 132 |
<!-- Toy tray: each toy pushes the soul toward a distinct VADUGWI region -->
|
| 133 |
<div id="toy-tray">
|
| 134 |
-
<button class="toy-btn" data-toy="balloon" title="bright bouncy delight"><img src="/static/assets/toy-balloon.png?v=
|
| 135 |
-
<button class="toy-btn" data-toy="musicbox" title="a slow soothing tune"><img src="/static/assets/toy-musicbox.png?v=
|
| 136 |
-
<button class="toy-btn" data-toy="mirror" title="sees itself, stands tall"><img src="/static/assets/toy-mirror.png?v=
|
| 137 |
-
<button class="toy-btn" data-toy="teddy" title="safe and held"><img src="/static/assets/toy-teddy.png?v=
|
| 138 |
-
<button class="toy-btn" data-toy="rattle" title="a sudden startling jolt"><img src="/static/assets/toy-rattle.png?v=
|
| 139 |
</div>
|
| 140 |
<!-- Care buttons row -->
|
| 141 |
<div id="care-row">
|
|
@@ -174,7 +174,7 @@
|
|
| 174 |
</div>
|
| 175 |
|
| 176 |
</div>
|
| 177 |
-
<script src="/static/physics.js?v=
|
| 178 |
-
<script src="/static/app.js?v=
|
| 179 |
</body>
|
| 180 |
</html>
|
|
|
|
| 6 |
<link rel="preconnect" href="https://fonts.googleapis.com">
|
| 7 |
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
| 8 |
<link href="https://fonts.googleapis.com/css2?family=Fredoka:wght@500;600&family=JetBrains+Mono:wght@400;600&display=swap" rel="stylesheet">
|
| 9 |
+
<link rel="stylesheet" href="/static/styles.css?v=10">
|
| 10 |
</head>
|
| 11 |
<body>
|
| 12 |
<div id="layout">
|
|
|
|
| 131 |
<div id="bottom-band">
|
| 132 |
<!-- Toy tray: each toy pushes the soul toward a distinct VADUGWI region -->
|
| 133 |
<div id="toy-tray">
|
| 134 |
+
<button class="toy-btn" data-toy="balloon" title="bright bouncy delight"><img src="/static/assets/toy-balloon.png?v=10" alt="balloon"></button>
|
| 135 |
+
<button class="toy-btn" data-toy="musicbox" title="a slow soothing tune"><img src="/static/assets/toy-musicbox.png?v=10" alt="musicbox"></button>
|
| 136 |
+
<button class="toy-btn" data-toy="mirror" title="sees itself, stands tall"><img src="/static/assets/toy-mirror.png?v=10" alt="mirror"></button>
|
| 137 |
+
<button class="toy-btn" data-toy="teddy" title="safe and held"><img src="/static/assets/toy-teddy.png?v=10" alt="teddy"></button>
|
| 138 |
+
<button class="toy-btn" data-toy="rattle" title="a sudden startling jolt"><img src="/static/assets/toy-rattle.png?v=10" alt="rattle"></button>
|
| 139 |
</div>
|
| 140 |
<!-- Care buttons row -->
|
| 141 |
<div id="care-row">
|
|
|
|
| 174 |
</div>
|
| 175 |
|
| 176 |
</div>
|
| 177 |
+
<script src="/static/physics.js?v=10"></script>
|
| 178 |
+
<script src="/static/app.js?v=10"></script>
|
| 179 |
</body>
|
| 180 |
</html>
|
static/styles.css
CHANGED
|
@@ -580,3 +580,11 @@ html, body { width: 100%; height: 100%; overflow: hidden; font-size: 12px; }
|
|
| 580 |
#toy-tray .toy-btn:active { transform: scale(0.94); }
|
| 581 |
#toy-tray .toy-btn img { width: 28px; height: 28px; display: block; }
|
| 582 |
#toy-tray .toy-btn:has(img) { padding: 6px 8px; }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 580 |
#toy-tray .toy-btn:active { transform: scale(0.94); }
|
| 581 |
#toy-tray .toy-btn img { width: 28px; height: 28px; display: block; }
|
| 582 |
#toy-tray .toy-btn:has(img) { padding: 6px 8px; }
|
| 583 |
+
.trace-physical {
|
| 584 |
+
display: block;
|
| 585 |
+
color: #c9b89a;
|
| 586 |
+
font-size: 10px;
|
| 587 |
+
font-style: italic;
|
| 588 |
+
padding: 6px 2px;
|
| 589 |
+
opacity: 0.85;
|
| 590 |
+
}
|
tests/test_api.py
CHANGED
|
@@ -193,3 +193,23 @@ def test_index_has_toy_tray():
|
|
| 193 |
assert 'id="toy-tray"' in html
|
| 194 |
for toy in ("balloon", "musicbox", "mirror", "teddy", "rattle"):
|
| 195 |
assert f'data-toy="{toy}"' in html
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 193 |
assert 'id="toy-tray"' in html
|
| 194 |
for toy in ("balloon", "musicbox", "mirror", "teddy", "rattle"):
|
| 195 |
assert f'data-toy="{toy}"' in html
|
| 196 |
+
|
| 197 |
+
|
| 198 |
+
# Stale-panel fix: toy/care must refresh trace/why/acceptance, not show last chat
|
| 199 |
+
def test_toy_refreshes_panels_not_stale():
|
| 200 |
+
client.post("/say", json={"text": "you are kind and wonderful"}) # prime chat trace
|
| 201 |
+
b = client.post("/toy", json={"toy": "musicbox"}).json()
|
| 202 |
+
assert b["trace"]["kind"] == "physical"
|
| 203 |
+
assert b["trace"]["words"] == [] and b["trace"]["label"] == "musicbox"
|
| 204 |
+
assert "acceptance" in b and "absorbed_pct" in b["acceptance"]
|
| 205 |
+
assert "deltas" in b and len(b["deltas"]) == 7
|
| 206 |
+
assert b["why"] and "musicbox" in b["why"]
|
| 207 |
+
|
| 208 |
+
|
| 209 |
+
def test_care_refreshes_panels_not_stale():
|
| 210 |
+
client.post("/say", json={"text": "you are kind and wonderful"}) # prime chat trace
|
| 211 |
+
b = client.post("/care", json={"action": "pet"}).json()
|
| 212 |
+
assert b["trace"]["kind"] == "physical"
|
| 213 |
+
assert "acceptance" in b and "absorbed_pct" in b["acceptance"]
|
| 214 |
+
assert "deltas" in b and len(b["read"]) == 7
|
| 215 |
+
assert b["why"] and "pet" in b["why"]
|