Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
15 commits
Select commit Hold shift + click to select a range
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
2 changes: 2 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# PakarController continuation

- 2026-10-29 keycontroller authorization: David directly approved paired ESP/AVR deployment and testing through the tablet and said, "from now on no asking for the keycontroller." Continue scoped keycontroller updates/tests without repeated approval or PBX calls, preserving its saved configuration and rollback. This does not lift ledcontroller's separate production hold. David also instructed to skip PBX and ask directly in chat whenever a different action actually requires approval.

- LED incident exception: David directly authorized immediate ESP board updates and diagnostics for the first22-cell/480-pixel output regression without another permission/PBX round-trip. This scoped authority covers the LED recovery deployment, reboot and output comparison/feedback; it does not lift the production hold for unrelated features or AVR programming. Preserve the saved scene and verified rollback, and never claim optical success from GPIO capture/counters alone.

- CURRENT PRODUCTION HOLD: ledboard is actively in production until David explicitly ends this hold. Before any board firmware update, flash, reboot/reset, output-changing diagnostic, stress test or other potentially disruptive board action, obtain David's explicit approval for that action via PBX DTMF. Earlier deployment/test authorization is not approval during this hold. Read-only observation, source/loopback tests and requested tablet/client work may continue; do not let client deployment silently start a board firmware job. Build tablet maintenance packages using tools/build-mobile.ps1 -Mode ApkPreview -BuildOnly -BoardProductionHold, which skips saved automatic board-firmware jobs without overwriting their policy. Record the exact approval/action scope; never infer approval from a ringing/answered call or silence.
Expand Down
7 changes: 4 additions & 3 deletions assets/web/command-transport.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,11 @@ function createCommandTransport({snapshot,socket,http,valid,fallback=true,now=()
const result={...values};
if(path==='/api/minigame/configure'){
const settings=structured(result.settings,'minigame settings');
const limits={limit_ms:[0,86400000],warning_ms:[0,86400000],urgent_ms:[0,86400000],penalty_ms:[0,86400000],window_ms:[100,60000],show_ms:[100,10000],gap_ms:[50,5000],feedback_ms:[50,5000],intro_ms:[100,10000],preview_ms:[0,10000],mismatch_ms:[600,10000],rounds:[1,32],sequence_length:[1,32],palette_size:[2,9],fixed_target:[0,9]};
for(const key of Object.keys(settings))if(!Object.hasOwn(limits,key)&&!['mode','hard','intro_enabled','color_arpeggio','toggle_kind','outputs'].includes(key))throw Error('Unknown minigame setting');
const limits={limit_ms:[0,86400000],warning_ms:[0,86400000],urgent_ms:[0,86400000],penalty_ms:[0,86400000],window_ms:[100,60000],show_ms:[100,10000],gap_ms:[50,5000],feedback_ms:[50,5000],intro_ms:[100,10000],preview_ms:[0,10000],mismatch_ms:[600,10000],rounds:[1,32],sequence_length:[1,32],sequence_growth:[0,31],palette_size:[2,9],fixed_target:[0,9]};
for(const key of Object.keys(settings))if(!Object.hasOwn(limits,key)&&!['mode','hard','intro_enabled','color_arpeggio','toggle_kind','outputs','boot_mode','segment_animation','timer_beeps','timer_alternate_tones'].includes(key))throw Error('Unknown minigame setting');
for(const [key,[min,max]] of Object.entries(limits))if(key in settings)settings[key]=integer(settings[key],key,min,max);
for(const key of ['hard','intro_enabled','color_arpeggio'])if(key in settings)settings[key]=boolean(settings[key],key);
for(const key of ['hard','intro_enabled','color_arpeggio','segment_animation','timer_beeps','timer_alternate_tones'])if(key in settings)settings[key]=boolean(settings[key],key);
if('boot_mode' in settings&&!['first','last'].includes(settings.boot_mode))throw Error('Invalid boot game policy');
if('mode' in settings&&!snapshot().minigame?.available_modes?.some(mode=>mode.id===settings.mode))throw Error('Unavailable minigame');
if('toggle_kind' in settings&&!['toggle','latch','press'].includes(settings.toggle_kind))throw Error('Invalid toggle behavior');
if('outputs' in settings){
Expand Down
705 changes: 628 additions & 77 deletions desktop/src/minigame.rs

Large diffs are not rendered by default.

525 changes: 288 additions & 237 deletions docs/FILE-COMPARISON.md

Large diffs are not rendered by default.

383 changes: 382 additions & 1 deletion docs/KEY-MINIGAMES.md

Large diffs are not rendered by default.

54 changes: 54 additions & 0 deletions docs/PEER-NETWORK-COMMISSIONING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
# Detached peer network commissioning

ESP32-S3 network builds can explicitly commission another PakarController AP
without keeping the initiating client online. This is a bounded provisioning
transaction, not an unrestricted Wi-Fi repeater or a firmware transfer relay.

`POST /api/peers/network` accepts a JSON job; `GET /api/peers/network` reads its
receipt. The existing authenticated, peer-fenced command dispatcher also accepts
`peer-network-provision` with the same job nested in `settings`, and the read-only
`peer-network-status` command (optional `job_request_id`). MQTT mutations retain
their normal outer board/boot/request/deadline/release envelope.

Once both boards share a LAN, `peer-directory`, `peer-relay` (nested `request`)
and `peer-relay-result` (optional `job_request_id`) expose the SAME restricted
HTTP peer relay over MQTT/remote WebSocket. They do not add unrestricted URLs,
recursive hops, credential forwarding or mutation retries. An inner target
command retains its own board/boot/command-clock deadline and request ID.
Outbound cloud receipts are bounded32KiB, status remains16KiB, and incoming
cloud commands remain4KiB. Oversized replies fail rather than truncating JSON.

Job fields:

| Field | Meaning |
| --- | --- |
| `request_id` | Unique 32-character hexadecimal job ID; never reuse |
| `confirm` | Exact value `configure-peer-network` |
| `board_id`, `hostname`, `role` | Expected target identity, bare hostname, baked-in role |
| `ap_ssid`, `ap_password`, `ap_bssid` | Explicit target AP credentials and exact BSSID |
| `station_ssid`, `station_password` | Target infrastructure credentials |

The initiating board reserves maintenance ownership and persists admission before
responding. Its loop then suspends cloud sockets and discovery before starting
the worker. Its own AP temporarily stops to avoid the two default192.168.4.1
interfaces colliding. The station join never changes its saved network settings.
The worker verifies the peer's board/boot/hostname/role/release and HTTP identity
headers, snapshots redacted network policy, records dispatch intent, and sends
ONE station+AP+DHCP configuration POST. Neither authentication rejection nor an
uncertain response triggers a second POST. An uncertain job must be inspected.

After success or failure it reapplies the initiating board's untouched saved
network. AP association is bounded20s and source reconnection observation30s;
individual HTTP dial/body operations are bounded. `settings_verified` means the
target's persisted-policy readback matched, not that its infrastructure association
has been observed. Verify the target on the infrastructure network separately.

The private `/peer-commission.json` receipt contains no passwords/request body.
General file APIs expose only `/user/`, not this journal or its pending file.
Reboot starts normal saved networking and marks a pending job interrupted; it
NEVER replays provisioning. A durable16-ID budget rejects retired IDs rather
than silently evicting replay protection. Receipt storage failure fails closed.

The worker retains an8KiB internal stack after first use, like the existing peer
relay. Admission requires internal heap headroom. This capability must not be
declared deployed merely because its source/fixtures or SDK build pass.
24 changes: 12 additions & 12 deletions docs/PU850-ROUTE-PARITY.json
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@
"target": "firmware/esp/src/WebUi.h",
"anchor": "Verified remote update",
"reason": "Commented donor HTML placeholder replaced by real firmware settings; no second OTA engine.",
"target_sha256_utf8_lf": "e81644ad10bac36fe1c35e07af8c7fe4b6537f9f4b48b2c82aaac682de97ea7c",
"target_sha256_utf8_lf": "37f97386ad9cc8d87df469629066e20e0601adcee6641b52af9687bc807eca95",
"target_line": 417
},
{
Expand All @@ -68,8 +68,8 @@
"target": "firmware/esp/application.ino",
"anchor": "server.native().on(\"/update\"",
"reason": "Integrated guarded authenticated upload, owner/disconnect fencing and platform OTA capacity/abort retained beside pinned remote download. Quiet S3 passive capture rejects new upload ownership. U_FS replacement is disabled to preserve credentials/recovery; S3 compressed-image support is not certified.",
"target_sha256_utf8_lf": "9c01e9952d691509428c94a694864b4cda59bec94cfb3c43c4252fdf749824da",
"target_line": 3851
"target_sha256_utf8_lf": "a607abb53d7834663853f2adb45286361d9d5b1cf82edb0d18ee11c9d9a74c41",
"target_line": 3882
},
{
"source": "LocalLib/AsyncWebServer.h",
Expand All @@ -80,8 +80,8 @@
"target": "firmware/esp/application.ino",
"anchor": "if(parsed[\"type\"]==\"telemetry\")",
"reason": "Authenticated multiple-client WebSocket retains board state and adds read-only full telemetry and pinned remote-job commands. Incoming messages are explicitly rejected during quiet S3 passive capture, before binary streaming or command dispatch.",
"target_sha256_utf8_lf": "9c01e9952d691509428c94a694864b4cda59bec94cfb3c43c4252fdf749824da",
"target_line": 2868
"target_sha256_utf8_lf": "a607abb53d7834663853f2adb45286361d9d5b1cf82edb0d18ee11c9d9a74c41",
"target_line": 2881
},
{
"source": "LocalLib/Authentication.cpp",
Expand Down Expand Up @@ -128,8 +128,8 @@
"target": "firmware/esp/application.ino",
"anchor": "server.on(\"/description.xml\"",
"reason": "Actual product/hostname root-device XML; S3 delegates escaped description generation to the adapted donor SSDP device, ESP8266 keeps its existing description. Neither advertises nonexistent SOAP/GENA services. S3 NBNS readiness is independently observed from exact interface-bound UDP137 sockets, not inferred from SSDP.",
"target_sha256_utf8_lf": "9c01e9952d691509428c94a694864b4cda59bec94cfb3c43c4252fdf749824da",
"target_line": 4248
"target_sha256_utf8_lf": "a607abb53d7834663853f2adb45286361d9d5b1cf82edb0d18ee11c9d9a74c41",
"target_line": 4309
},
{
"source": "pu850-esp.ino",
Expand Down Expand Up @@ -596,8 +596,8 @@
"target": "firmware/esp/application.ino",
"anchor": "server.native().on(\"/update/info\"",
"reason": "Version/profile and both-board remote maintenance URLs; no misleading build-host/time identity.",
"target_sha256_utf8_lf": "9c01e9952d691509428c94a694864b4cda59bec94cfb3c43c4252fdf749824da",
"target_line": 3839
"target_sha256_utf8_lf": "a607abb53d7834663853f2adb45286361d9d5b1cf82edb0d18ee11c9d9a74c41",
"target_line": 3870
},
{
"source": "pu850-esp.ino",
Expand All @@ -608,8 +608,8 @@
"target": "firmware/esp/application.ino",
"anchor": "server.on(\"/info\"",
"reason": "Authenticated full structured ESP/AVR/client/network/clock/cloud telemetry; optional format=text.",
"target_sha256_utf8_lf": "9c01e9952d691509428c94a694864b4cda59bec94cfb3c43c4252fdf749824da",
"target_line": 3840
"target_sha256_utf8_lf": "a607abb53d7834663853f2adb45286361d9d5b1cf82edb0d18ee11c9d9a74c41",
"target_line": 3871
},
{
"source": "pu850-esp.ino",
Expand All @@ -620,7 +620,7 @@
"target": "firmware/esp/src/WebUi.h",
"anchor": "Verified remote update",
"reason": "Real control workspace rather than donor static placeholder; access gate and actual peer state retained. Optional DHCP/static editor preserves stored station credentials and RGB preview requires fresh actual output, not a synthetic animation.",
"target_sha256_utf8_lf": "e81644ad10bac36fe1c35e07af8c7fe4b6537f9f4b48b2c82aaac682de97ea7c",
"target_sha256_utf8_lf": "37f97386ad9cc8d87df469629066e20e0601adcee6641b52af9687bc807eca95",
"target_line": 417
}
]
Expand Down
10 changes: 5 additions & 5 deletions docs/PU850-ROUTE-PARITY.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,12 @@ Normal builds retain AVR hardware UART. Optional PAKAR_AVR_LINK_ENABLED=0 quiet
| Donor source | Route | Disposition | Current implementation / reason |
| --- | --- | --- | --- |
| LocalLib/AsyncOTA.cpp:12 | /update GET | commented-replaced | firmware/esp/src/WebUi.h:417 — Commented donor HTML placeholder replaced by real firmware settings; no second OTA engine. |
| LocalLib/AsyncOTA.cpp:19 | /update POST | adapted | firmware/esp/application.ino:3851 — Integrated guarded authenticated upload, owner/disconnect fencing and platform OTA capacity/abort retained beside pinned remote download. Quiet S3 passive capture rejects new upload ownership. U_FS replacement is disabled to preserve credentials/recovery; S3 compressed-image support is not certified. |
| LocalLib/AsyncWebServer.h:22 | /ws WebSocket | adapted | firmware/esp/application.ino:2868 — Authenticated multiple-client WebSocket retains board state and adds read-only full telemetry and pinned remote-job commands. Incoming messages are explicitly rejected during quiet S3 passive capture, before binary streaming or command dispatch. |
| LocalLib/AsyncOTA.cpp:19 | /update POST | adapted | firmware/esp/application.ino:3882 — Integrated guarded authenticated upload, owner/disconnect fencing and platform OTA capacity/abort retained beside pinned remote download. Quiet S3 passive capture rejects new upload ownership. U_FS replacement is disabled to preserve credentials/recovery; S3 compressed-image support is not certified. |
| LocalLib/AsyncWebServer.h:22 | /ws WebSocket | adapted | firmware/esp/application.ino:2881 — Authenticated multiple-client WebSocket retains board state and adds read-only full telemetry and pinned remote-job commands. Incoming messages are explicitly rejected during quiet S3 passive capture, before binary streaming or command dispatch. |
| LocalLib/Authentication.cpp:22 | /auth GET | adapted | firmware/esp/src/MaintenanceRoutes.h:533 — Local owner auth status, no credential disclosure. |
| LocalLib/Authentication.cpp:57 | /pass POST | adapted | firmware/esp/src/MaintenanceRoutes.h:539 — Requires current password, persistent change and revocation of Web/Telnet sessions. |
| LocalLib/FirmwareTransfer.cpp:213 | /firmware/download GET|HEAD | imported | firmware/esp/src/FirmwareTransfer.cpp:173 — Actual donor range/HEAD/ETag/MD5/export code imported. S3 reads offsets relative to its running application partition with mbedTLS Base64; ESP8266 aligned flash reads remain. Full flash export is disabled to protect secrets. |
| LocalLib/SSDP.cpp:113 | /description.xml GET | adapted | firmware/esp/application.ino:4248 — Actual product/hostname root-device XML; S3 delegates escaped description generation to the adapted donor SSDP device, ESP8266 keeps its existing description. Neither advertises nonexistent SOAP/GENA services. S3 NBNS readiness is independently observed from exact interface-bound UDP137 sockets, not inferred from SSDP. |
| LocalLib/SSDP.cpp:113 | /description.xml GET | adapted | firmware/esp/application.ino:4309 — Actual product/hostname root-device XML; S3 delegates escaped description generation to the adapted donor SSDP device, ESP8266 keeps its existing description. Neither advertises nonexistent SOAP/GENA services. S3 NBNS readiness is independently observed from exact interface-bound UDP137 sockets, not inferred from SSDP. |
| pu850-esp.ino:591 | /beep POST | adapted | firmware/esp/src/MaintenanceRoutes.h:566 — Actual AVR buzzer ACK, 1..8 bounded count. Mutating GET removed. |
| pu850-esp.ino:597 | /message/send POST | adapted | firmware/esp/src/MaintenanceRoutes.h:574 — Actual TM1637 single-pass ASCII text; existing display engine owns timing, no duplicate scrolling engine. |
| pu850-esp.ino:611 | /weight/display GET|POST | not-applicable | PU weighing UI/value format requires a load cell; none is fitted. Generic TM1637 text/number is implemented separately. |
Expand Down Expand Up @@ -67,8 +67,8 @@ Normal builds retain AVR hardware UART. Optional PAKAR_AVR_LINK_ENABLED=0 quiet
| pu850-esp.ino:1470 | /is_exec GET | not-applicable | PU chart execution state has no equivalent on this table; actual game/automation state is exposed through negotiated contracts. |
| pu850-esp.ino:1475 | /page_id GET | not-applicable | PU chart page identifier is not this board's front-panel menu. PCController GET MENU remains the actual supported contract. |
| pu850-esp.ino:1480 | /page_goto POST | not-applicable | PU page/business navigation cannot be copied as a table action. PCController SET MENU is available through the guarded contract API. |
| pu850-esp.ino:1493 | /update/info GET | adapted | firmware/esp/application.ino:3839 — Version/profile and both-board remote maintenance URLs; no misleading build-host/time identity. |
| pu850-esp.ino:1513 | /info GET | adapted | firmware/esp/application.ino:3840 — Authenticated full structured ESP/AVR/client/network/clock/cloud telemetry; optional format=text. |
| pu850-esp.ino:1493 | /update/info GET | adapted | firmware/esp/application.ino:3870 — Version/profile and both-board remote maintenance URLs; no misleading build-host/time identity. |
| pu850-esp.ino:1513 | /info GET | adapted | firmware/esp/application.ino:3871 — Authenticated full structured ESP/AVR/client/network/clock/cloud telemetry; optional format=text. |
| pu850-esp.ino:1543 | / GET | adapted | firmware/esp/src/WebUi.h:417 — Real control workspace rather than donor static placeholder; access gate and actual peer state retained. Optional DHCP/static editor preserves stored station credentials and RGB preview requires fresh actual output, not a synthetic animation. |

Broader repository/file parity remains tracked separately in FILE-COMPARISON.md and GitHub issue #3. Live route/TLS/board acceptance remains open.
Loading
Loading