Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 18 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,8 @@ from a phone.
2. **Open Match Setup.** Double-tap the touch bar on Stack-chan's head, or
briefly press Power, to show the setup QR. Scan it with your phone.
3. **Choose what to watch.** Select a match, supported team (or Neutral),
optional pregame position (or No position), and commentary style, then tap
**Start watching**.
optional pregame position (or No position), commentary style, and whether
to use spoiler protection, then tap **Start watching**.
4. **Wait for confirmation.** The watcher validates the ESPN/Kalshi pairing,
updates its configuration, and acknowledges the device. The new selection
takes effect without restarting the watcher or device. Changing only the
Expand Down Expand Up @@ -91,6 +91,8 @@ not the primary QR flow.
facts, with compact on-device balloons.
- Separate support and position perspectives, including explicit conflict and
uncertain-event wording.
- Optional spoiler protection suppresses proactive Kalshi alerts while
confirmed ESPN events and passive probability/ticker updates continue.
- Phone-based match setup with live Chinese/English switching and hot reload.
- Automatic fixture discovery, adaptive polling, quiet hours, and standalone
market tracking when no match is available.
Expand All @@ -102,17 +104,21 @@ not the primary QR flow.

### Daily start

From the repository root of an existing, configured checkout, keep the watcher
computer awake and run:
Update the repository and restart the watcher. Support/position wording,
position-based market selection, and the global player catalog remain
watcher-only changes. The phone spoiler-protection switch requires Matchday
Mod 1.6.0, but the official host can stay in place.

```sh
python3 tools/stackchan_kalshi_watch.py \
--config config/kalshi_watchlist.json --watch
```

Daily startup does not require cloning, pulling, or reflashing. If speech is
enabled, also start the optional LAN TTS service described in
[Getting started](docs/getting-started.md).
Deploy `config/espn_player_catalog.json` with the watcher checkout. For exact
upgrade boundaries, see the bilingual
[Matchday MOD 1.6.0 notes](docs/releases/1.6.0.md),
[1.5.0 notes](docs/releases/1.5.0.md), and
[1.4.0 commentary-style notes](docs/releases/1.4.0.md).

### First installation

Expand Down Expand Up @@ -145,8 +151,8 @@ The currently documented device release is [Matchday Mod 1.5.0](docs/releases/1.
| [Development](docs/development.md) | Repository map, tests, archive builds, and replay tooling |
| [Host preparation](host/README.md) | Partition and optional CJK-font patches |
| [Commentary styles PRD](docs/commentary-styles-prd.md) | Product rules and implementation contract |
| [Release notes](docs/releases/) | Version-specific changes and upgrade boundaries |
| [Troubleshooting Wiki](https://github.com/xymeow/stackchan-matchday/wiki/Troubleshooting) | FAQ, networking checks, and field debugging |
| [Release notes](docs/releases/1.6.0.md) | Version-specific upgrade boundaries |
| [GitHub Wiki](https://github.com/xymeow/stackchan-matchday/wiki) | Human- and agent-friendly navigation, FAQ, and field debugging |

English guides link to their Chinese counterparts at the top of each page.
Repository documentation is the versioned source of truth; the Wiki is the
Expand All @@ -164,6 +170,9 @@ short operational index and troubleshooting layer.
- Commentary style can change during a match without resetting ESPN history,
market baselines, queues, or polling state. API compatibility is documented
in the [Device API guide](docs/device-api.md).
- Spoiler protection can also change live. It silences proactive Kalshi
alerts, not the passive probability bar or ticker, and never suppresses
confirmed ESPN events.

### For maintainers and AI agents

Expand Down
12 changes: 10 additions & 2 deletions README.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,8 @@ watcher,可把 CoreS3 机器人变成世界杯陪看搭子:屏幕持续显
电脑和 Stack-chan 必须位于同一个可信局域网。
2. **唤出设置码。** 双击头顶触摸条或短按 Power 键,屏幕会显示设置二维码;轻点
一次可关闭,90 秒后也会自动关闭。
3. **用手机选择。** 扫码后选择语言、比赛、支持队、可选持仓和播报语气,再点
“开始看球”。页面显示 watcher 已确认后,配置立即生效,无需重启。
3. **用手机选择。** 扫码后选择语言、比赛、支持队、可选持仓、播报语气和是否开启
防剧透,再点“开始看球”。页面显示 watcher 已确认后,配置立即生效,无需重启。
4. **一起看球。** Stack-chan 会持续更新旗帜、概率和 ticker,并响应比分、文字直播
与盘口变化。

Expand Down Expand Up @@ -100,6 +100,8 @@ Kalshi + ESPN ──只读──> Python watcher ──HTTP──> Stack-chan Ma
- 对进球、红黄牌、换人、险情、比赛阶段和赛果做出反应。
- `casual`、`balanced`、`professional` 三档播报语气,可在比赛中即时切换。
- 支持队和持仓队分开建模;重大事件会自然说明持仓利好或利空。
- 可选防剧透模式:关闭 Kalshi 主动提醒,同时保留 ESPN 已确认事件与静默更新的
概率条和 ticker。
- 由设备托管的中英文手机设置页,以及 watcher 的配置热更新与确认链路。
- 自动发现比赛、自适应轮询、免打扰时段和无比赛市场跟踪。
- 可选局域网 TTS;服务不可用时,视觉反馈和提示音仍然工作。
Expand All @@ -116,6 +118,10 @@ python3 tools/stackchan_kalshi_watch.py \
--config config/kalshi_watchlist.json --watch
```

示例 ticker 是占位值;从手机设置页选择真实比赛后,watcher 会校验 ESPN 与 Kalshi
的双方匹配并原子更新配置。旧版升级前请查看
[Matchday Mod 1.6.0 说明](docs/releases/1.6.0.md)及[全部版本说明](docs/releases/);
设备手机页的防剧透开关需要更新 watcher 与 Mod,但无需重刷官方 host。
日常启动不需要 `git pull`、重新克隆或刷机。需要语音时,再启动
[安装与升级](docs/getting-started.zh-CN.md)中说明的可选局域网 TTS 服务。

Expand Down Expand Up @@ -162,6 +168,8 @@ watcher。完整、可复现的命令见[安装与升级](docs/getting-started.z
- ESPN 接口并非正式 API;缺失或含糊的上游细节会被忽略,不会猜测足球事实。
- 比赛中切换语气不会重置 ESPN 历史、盘口基线、队列或轮询状态;API 兼容性见
[设备 API](docs/device-api.zh-CN.md)。
- 防剧透也可在比赛中即时切换:只关闭 Kalshi 主动提醒,不隐藏概率条或 ticker,
也不会屏蔽 ESPN 已确认事件。

### 给维护者和 AI agent

Expand Down
1 change: 1 addition & 0 deletions config/kalshi_watchlist.example.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
"stackchan_serial_port": "/dev/cu.usbmodem101",
"stackchan_serial_baud": 115200,
"language": "zh",
"spoiler_free_mode": false,
"voice_transport": "clip",
"mac_voice": {"zh": "Tingting", "en": "Samantha"},
"mac_say_rate": 185,
Expand Down
24 changes: 21 additions & 3 deletions docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,10 @@

The phone setup page is the normal control surface. It changes all labels
immediately, persists its language on the device, and applies the selected
match, supported team, optional position, and commentary style through the
watcher's pending/ack flow. The watcher validates the ESPN/Kalshi pairing,
atomically updates its local JSON, hot-reloads, and acknowledges Stack-chan.
match, supported team, optional position, commentary style, and spoiler
protection through the watcher's pending/ack flow. The watcher validates the
ESPN/Kalshi pairing, atomically updates its local JSON, hot-reloads, and
acknowledges Stack-chan.

## Match Setup flow

Expand Down Expand Up @@ -48,6 +49,7 @@ legacy string or a localized object:
```json
{
"language": "en",
"spoiler_free_mode": false,
"mac_voice": {"zh": "Tingting", "en": "Samantha"},
"espn": {
"commentary_style": "balanced",
Expand Down Expand Up @@ -122,6 +124,22 @@ effects, celebrations, expressions, lights, priority, and alert switches are
unchanged. Switching during a match affects newly generated alerts without
replaying old ESPN events or resetting market baselines, queues, or polling.

## Spoiler protection

Top-level `spoiler_free_mode` is a persistent global boolean and defaults to
`false`. Both phone setup pages expose Normal and Spoiler protection choices.

When enabled, the watcher still polls Kalshi and silently updates market
baselines, the probability bar, and ticker, but suppresses all proactive
Kalshi-derived alerts, including price/spread or market-status changes,
closing notices, and suspected-goal signals. Confirmed ESPN events, phases,
and results continue normally.

The dedicated hot-update path applies during a match without resetting ESPN
history, market state, polling, or the selected fixture. Enabling protection
also drops queued market alerts and consumes their current baselines, so
turning it off later does not replay accumulated movement.

## Support and position perspectives

The supported team defines everyday co-watching language. Routine shots,
Expand Down
18 changes: 16 additions & 2 deletions docs/configuration.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@

[English](configuration.md) | [简体中文](configuration.zh-CN.md)

本文说明 watcher 的持久配置、手机 Match Setup、语言与三档播报、支持/持仓视角
球员目录和独立市场模式。配置示例位于
本文说明 watcher 的持久配置、手机 Match Setup、语言与三档播报、防剧透模式
支持/持仓视角、球员目录和独立市场模式。配置示例位于
`config/kalshi_watchlist.example.json`;请把本机修改写入未提交的
`config/kalshi_watchlist.json`。

Expand Down Expand Up @@ -33,6 +33,7 @@ IPv4 地址或主机名,不支持 IPv6 字面地址。
"stackchan_host": "stackchan.local",
"stackchan_transport": "http",
"language": "zh",
"spoiler_free_mode": false,
"setup_server": {
"enabled": true,
"host": "127.0.0.1",
Expand Down Expand Up @@ -108,6 +109,19 @@ python3 tools/stackchan_kalshi_watch.py discover --query 关键词
语气只改变文案,不改变 TTS 音色和语速、音效、动作、表情、灯效、优先级或提醒
开关。比赛中切换立即作用于新提醒,但不会重播旧事件。

## 防剧透模式

顶层 `spoiler_free_mode` 是全局持久布尔值,旧配置缺少时默认 `false`。设备和
watcher 的手机设置页都提供“普通 / 防剧透”选项。

开启后 watcher 仍持续轮询 Kalshi,并静默更新盘口基线、概率条和 ticker,但不会
主动播报任何 Kalshi 派生提醒,包括价格/价差、市场状态变化、临近收盘和疑似进球。
ESPN 已确认的事件、比赛阶段与赛果照常播报。

独立热更新链路可在比赛中即时切换,不会重置 ESPN 已读历史、盘口状态、轮询或当前
比赛。开启时还会丢弃队列中尚未播出的盘口提醒并消费当前基线,之后关闭不会补播
累积变化。

## 支持队与持仓队

- `espn.favorite_team` 是支持队,决定普通射门、扑救、角球等日常陪看视角;可中立。
Expand Down
6 changes: 6 additions & 0 deletions docs/development.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,12 @@ The Python suite covers watcher behavior, Match Setup, commentary contracts,
and device-facing integration helpers. The Node suite exercises the mod web
surface without installing it on hardware.

Spoiler-protection changes should prove that Kalshi-derived alerts are
suppressed while market state and passive displays keep updating, confirmed
ESPN alerts remain eligible, queued market alerts are purged on hot enable,
and a later disable does not replay accumulated movement. Cover watcher-local
and device pending/ack paths, including the boolean `false` case.

## Build without installing

Build a mod archive from the upstream Stack-chan `firmware/` directory:
Expand Down
3 changes: 3 additions & 0 deletions docs/development.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,9 @@ Python 测试覆盖 watcher、Match Setup、结构化事实、文案和投递行
- 疑似进球与等待确认在所有语气中保持不确定性。
- 专业细节只来自可可靠解析的 ESPN 字段;缺失值不能触发猜测。
- 只切换语气不能重复旧事件或重置盘口、告警与轮询状态。
- 防剧透开启后应抑制 Kalshi 派生提醒,但继续更新盘口状态与常驻显示;ESPN 已确认
事件仍可投递。热开启需要清空待播盘口提醒,之后关闭不能补播累积变化。
- watcher 本机与设备 pending/ack 两条链路都要覆盖,尤其要测试布尔值 `false`。
- Match Setup 需要覆盖设备 pending/ack 与 watcher 原子更新两端。
- 投递失败测试应验证退避和有限重试,避免设备重启风暴。

Expand Down
19 changes: 19 additions & 0 deletions docs/device-api.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,8 @@ The device-facing setup surface uses these endpoints under `/api/match-setup`:
- `/pending` lets the watcher retrieve that selection.
- `/ack` confirms success or reports validation failure back to the phone.
- `/language` updates the persisted device language.
- `/style` queues a commentary-style-only update.
- `/spoiler` queues a spoiler-protection-only update.

These endpoints form a pending/ack handshake: the phone writes to Stack-chan,
the watcher validates and atomically updates its local configuration, and the
Expand All @@ -94,6 +96,23 @@ Changing only the style must not reset ESPN event history, market baselines,
alert queues, or polling state, and must not replay old commentary. The new
style applies to subsequently generated alerts.

## Spoiler-protection endpoints

The device accepts a strict JSON boolean at `POST /api/match-setup/spoiler`:

```json
{"spoiler_free_mode":true}
```

It relays the preference through pending/ack. The watcher-local service accepts
the same body at `POST /api/setup/spoiler`, and `GET /api/setup/status` reports
the effective `spoiler_free_mode` value.

Changing only this preference must not reload the selected match or reset ESPN
history, market baselines, or polling. Enabling it drops queued Kalshi alerts;
confirmed ESPN events continue, while the probability bar and ticker still
update silently.

## Calling conventions

- Use every endpoint only on a trusted LAN; the device API has no
Expand Down
25 changes: 25 additions & 0 deletions docs/device-api.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ curl --request POST \
| `/api/match-setup/ack` | watcher 确认或拒绝应用结果 |
| `/api/match-setup/language` | 更新界面语言 |
| `/api/match-setup/style` | 更新播报语气 |
| `/api/match-setup/spoiler` | 更新防剧透模式 |

这是一个异步确认流程:设备收到手机选择不代表 watcher 已采用。客户端应显示待处理
状态,直到 watcher 返回 ack;不要在提交后自行假定配置成功。
Expand Down Expand Up @@ -111,6 +112,30 @@ Content-Type: application/json
`GET /api/setup/status` 返回 watcher 当前生效值。切换只影响新生成文案,不应重置
ESPN 已读事件、盘口基线、告警队列或轮询状态。

### 即时切换防剧透模式

设备接收严格的 JSON 布尔值:

```http
POST /api/match-setup/spoiler
Content-Type: application/json

{"spoiler_free_mode":true}
```

它通过 pending/ack 流程转发。watcher 本机设置服务的对应接口为:

```http
POST /api/setup/spoiler
Content-Type: application/json

{"spoiler_free_mode":true}
```

`GET /api/setup/status` 返回当前生效的 `spoiler_free_mode`。只切换该偏好不会重载
比赛或重置 ESPN 历史、盘口基线与轮询;开启后丢弃队列中的 Kalshi 提醒,已确认的
ESPN 事件照常播报,概率条和 ticker 继续静默更新。

## 调用约定

- 所有接口仅用于可信局域网;设备 API 无认证并开放 CORS。
Expand Down
10 changes: 5 additions & 5 deletions docs/getting-started.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,11 @@ building the Matchday mod, starting the watcher, and optionally enabling LAN
speech.

Already running Matchday? Read the version-specific
[1.5.0](releases/1.5.0.md) and [1.4.0](releases/1.4.0.md) notes before rebuilding.
Support/position-aware wording, position-based market selection, and the global
player catalog are watcher-only updates. An installed 1.5.0 mod can stay in
place: deploy the watcher code and `config/espn_player_catalog.json`, then
restart the watcher process.
[1.6.0](releases/1.6.0.md), [1.5.0](releases/1.5.0.md), and
[1.4.0](releases/1.4.0.md) notes before rebuilding. Spoiler protection on the
device phone page requires both the 1.6.0 watcher and mod, but not a host
reflash. The 1.5.0 support/position wording and global player catalog remain
watcher-only updates.

## Requirements

Expand Down
1 change: 1 addition & 0 deletions docs/getting-started.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,7 @@ curl --request POST --data-binary "say 比赛日准备好了" \
| 变更 | 通常需要更新 |
| --- | --- |
| watcher 文案、球员目录、解析和轮询 | watcher 代码与配置;重启 Python 进程 |
| 防剧透手机开关 | watcher 与 Matchday Mod 1.6.0;无需重刷 host |
| Mod 页面、设备动作、命令或资源 | 重新构建并安装 Mod |
| `xs` 分区或 CJK host 字体 | 重新应用补丁并烧录 host |
| macOS TTS 服务实现 | 重启 TTS 服务;不需要刷设备 |
Expand Down
13 changes: 13 additions & 0 deletions docs/releases/1.5.0.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,14 @@ A formal-name source is not treated as permission to invent a nickname.

### Reliability

- Goal and final-result feedback is now explicitly two-stage in the watcher:
the local fanfare and head motion finish before dynamic LAN TTS starts. The
TTS request remains blocking, so the watcher no longer opens four status
connections per second while the WAV stream is active. A TTS-only failure
does not replay an already delivered goal celebration.
- Suspected-goal market alerts restore the punchier "market jumped / possible
goal / don't blink" cadence while still naming the inferred team and
explicitly waiting for commentary confirmation.
- Tone clips are skipped (successfully) while a TTS stream is playing instead
of erroring into the watcher's retry path — one of the two halves of the
mid-match retry-storm fix.
Expand Down Expand Up @@ -109,6 +117,11 @@ A formal-name source is not treated as permission to invent a nickname.

### 可靠性

- 进球和赛果反馈改为 watcher 两段式执行:先完成本地音效与头部动作,再启动局域网
动态 TTS。TTS 请求保持阻塞,WAV 播放期间不再每秒发出 4 次状态查询;如果仅语音
失败,也不会把已经播过的进球庆祝重新来一遍。
- 盘口疑似进球提醒恢复“盘口突变 / 很可能进球 / 先别眨眼”的紧张感,同时继续
明确球队,并保留等待文字直播确认的不确定性。
- TTS 播放中的音效改为成功跳过而不是报错,不再触发 watcher 的失败重试——
这是比赛中"重试风暴"修复的设备侧一半。
- watcher 失败投递改为退避 10 秒后重试,普通实况最多重试 2 次(进球/点球/红牌
Expand Down
Loading
Loading