3a0959f55d
Latency: replies are spoken sentence-by-sentence off the desk API's NDJSON endpoint, so the wait is time-to-first-sentence rather than the whole model call, and Deepgram's live websocket transcribes while you're still talking instead of uploading the WAV afterwards. Both fall back invisibly — a stream that fails before anything was said drops to converse(), and a socket that never opens just means the old one-shot path. Speaking lived in four near-copies in the controller (a reply, a holding line, a streamed sentence, a dialogue scene) that had already drifted: one didn't arm barge-in, another skipped the follow-up rule. It's now speech.Speaker plus an Utterance describing the policy differences, with collaborators injected so the whole of it tests without Qt or audio. The mouth follows the audio rather than a timer: tts.level_of reduces each PCM frame to a 0..1 loudness on a sqrt curve (speech sits well below peak, and a linear map leaves the mouth barely open during normal talking) and that indexes the talking frames, which the sprite script now draws as an openness ramp. Offline pyttsx3 has no waveform, so stale levels hand control back to the timed loop instead of freezing the mouth mid-syllable. Also: the pet starts where you left it (ignoring positions on monitors that are no longer connected, since restoring those faithfully is how it ends up somewhere unreachable), and `python -m bolt_pet --doctor` is a preflight that says what to do about each problem rather than only what's wrong. tests/test_pipeline_smoke.py breaks the pure-logic rule on purpose. Every unit test passed all week while notifications sat unspoken for minutes, the pet said things twice and [laughing] got read aloud — each an interaction between two individually-correct units. It drives whole turns against a real HTTP server on a loopback port, faking only the mic and the speakers. It found a NameError in the paint path that would have fired on every repaint while talking. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
85 lines
2.8 KiB
Python
85 lines
2.8 KiB
Python
"""Remembering where the pet was left — and refusing to when that's a trap."""
|
|
|
|
import json
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
|
|
|
|
from bolt_pet import window_state
|
|
|
|
|
|
def test_a_saved_position_comes_back(tmp_path):
|
|
path = tmp_path / "window.json"
|
|
window_state.save(1200, 640, path)
|
|
assert window_state.load(path) == (1200, 640)
|
|
|
|
|
|
def test_no_file_yet_is_not_an_error(tmp_path):
|
|
assert window_state.load(tmp_path / "nope.json") is None
|
|
|
|
|
|
def test_a_corrupt_file_falls_back_to_the_default_corner(tmp_path):
|
|
"""Whatever is in there, the pet still has to start."""
|
|
path = tmp_path / "window.json"
|
|
for junk in ("", "{", "null", "[]", '{"x": "left"}', '{"y": 3}'):
|
|
path.write_text(junk)
|
|
assert window_state.load(path) is None
|
|
|
|
|
|
def test_saving_creates_the_cache_directory(tmp_path):
|
|
path = tmp_path / "deep" / "nested" / "window.json"
|
|
window_state.save(10, 20, path)
|
|
assert window_state.load(path) == (10, 20)
|
|
|
|
|
|
def test_an_unwritable_location_is_swallowed(tmp_path):
|
|
"""A read-only cache dir is a reason to forget the position, not to crash
|
|
on every drag."""
|
|
window_state.save(1, 2, tmp_path / "no" / "\0bad" / "window.json")
|
|
|
|
|
|
def test_the_write_is_atomic_and_leaves_no_litter(tmp_path):
|
|
path = tmp_path / "window.json"
|
|
window_state.save(5, 5, path)
|
|
window_state.save(7, 7, path)
|
|
assert json.loads(path.read_text()) == {"x": 7, "y": 7}
|
|
assert [p.name for p in tmp_path.iterdir()] == ["window.json"]
|
|
|
|
|
|
# ── validating against the screens that exist *now* ─────────────────────────
|
|
|
|
LAPTOP = (0, 0, 1920, 1080)
|
|
EXTERNAL = (1920, 0, 4480, 1440)
|
|
|
|
|
|
def test_a_position_on_a_connected_screen_is_kept():
|
|
assert window_state.is_visible_on(1700, 900, 128, [LAPTOP])
|
|
|
|
|
|
def test_a_position_on_an_unplugged_monitor_is_refused():
|
|
"""The dangerous case: restoring it faithfully puts the pet somewhere you
|
|
can't see or reach."""
|
|
assert not window_state.is_visible_on(3000, 700, 128, [LAPTOP])
|
|
assert window_state.is_visible_on(3000, 700, 128, [LAPTOP, EXTERNAL])
|
|
|
|
|
|
def test_mostly_off_screen_counts_as_gone():
|
|
"""A few pixels of ear poking onto the desktop is not "reachable"."""
|
|
assert not window_state.is_visible_on(1910, 500, 128, [LAPTOP])
|
|
assert window_state.is_visible_on(1830, 500, 128, [LAPTOP])
|
|
|
|
|
|
def test_touching_an_edge_is_not_overlapping():
|
|
assert not window_state.is_visible_on(1920, 0, 128, [LAPTOP])
|
|
|
|
|
|
def test_negative_coordinates_are_fine_when_a_screen_is_there():
|
|
"""Monitors left of or above the primary have negative origins."""
|
|
left_of_primary = (-1920, 0, 0, 1080)
|
|
assert window_state.is_visible_on(-900, 400, 128, [left_of_primary, LAPTOP])
|
|
|
|
|
|
def test_no_screens_at_all_is_not_visible():
|
|
assert not window_state.is_visible_on(100, 100, 128, [])
|