Punakawan Digital Scoring
Real-time pencak silat scoring that keeps running without the internet.
A per-arena judging system that carries every judge's tap to the referee board and audience screen within milliseconds over the venue's local network, plus a championship registration website for team managers and organizers.
- Role
- Co-founder · architecture & development
- Period
- 2026 — present
- Stack
- Laravel 13
- Vue 3 + Inertia
- Laravel Reverb
- FrankenPHP / Octane
- SQLite (WAL)
- Livewire 3
- Tailwind CSS
- Links
- punakawanscoring.com
At a glance
- 32 msreal-time message between devices on the venue LAN
- 0 errors857 taps from 10 parallel arenas in 20 seconds
- 1,095 → 6queries per championship page after optimization
- 427automated tests in the arena app
Figures from the project’s internal measurement documents.
Arena architecture
Officials’ devices
- Judges ×3
- Referee board
- Timer
- Secretary
- Audience display
Arena server
- Laravel 13 · FrankenPHP
- Append-only event log
- SQLite per arena
Championship management system
Schedules in, results out
The problem
In pencak silat, every point is decided by several judges pressing buttons at almost the same moment. When a score is late to appear on the referee board or the audience screen, team officials start questioning the result, and arguments at the edge of the arena are a real risk.
The previous generation of scoring relied on polling: every screen kept asking the server whether anything had changed. As championships grew, that load showed. The target is now events of up to 3,000 athletes, 10 parallel arenas, and roughly 100–150 active devices at once.
Key decisions
Instead of patching the old system, the arena app was rebuilt from scratch with a single job: run the match that is happening right now, and nothing more.
- Offline-first. Once a session’s schedule is pulled from the championship management system, the arena runs fully without internet. Results are sent back through a queue and retried automatically.
- One SQLite database per arena. A problem in arena A never touches arena B, and a backup is just a copy of one file.
- WebSockets, not polling. Laravel Reverb pushes every change to the judges’ tablets, the referee board, the timer, and the audience display.
- The server decides. The timer and point validity are computed on the server; device clocks are not trusted. Tablets still feel instant thanks to an optimistic UI.
How scores are computed
Every judge tap is stored as an event that is never modified (append-only). The score is recomputed from that list of events, so every number can be audited, and corrections or protests are recorded as new events instead of overwriting data.
For fight categories, a point only counts when at least 2 of 3 judges press the same button within a time window. The engine is validated with full scenarios: 2/3 valid, 3/3 instantly valid, 1/3 discarded, all the way to a retried tap that arrives after the window has closed.
Each regulation’s rules live in their own module: IPSI 2022, IPSI 2012, and Tapak Suci, each for fight (tanding) and form (seni) categories. Adding a new regulation means adding a module, not changing existing ones.
Evidence, not promises
| Test | Result |
|---|---|
| Device-to-device latency over WebSocket | 32 ms from a secretary session to a judge session on another device |
| Load test, 10 arenas × 3 judges | 857 taps in 20 seconds (~4× field rate), 0 errors, p95 91 ms |
| Bad network | Retries never double-count a point; reconnecting screens reload the full history |
| Automated tests | 427 tests in the arena app |
The load test ran on a Linux development machine; official numbers will be repeated on venue hardware.
Built for the field crew
Venue staff are not developers. So there is a “Panel Lapangan” desktop launcher that starts and stops the server, checks service health, and shows a QR code so tablets can connect right away. Every role gets its own screen: secretary, judge, referee board, timer, audience display, and an overlay for live streaming.
Website & registration
Punakawan’s public side lives at punakawanscoring.com: championship listings, team and athlete registration by team managers, payment proof uploads, and a review panel for organizers.
In August 2026 I audited its performance and fixed the findings directly in production:
| Metric | Before | After |
|---|---|---|
| Championship page TTFB | 601 ms | 115 ms |
| Championship page queries | 1,095 | 6 |
| Athlete table queries (201 athletes) | 604 | 4 |
| Homepage posters (10 cards) | 5.4 MB | 484 KB |
Homepage loadEventEnd |
1,017 ms | 298 ms |
One finding: Laravel’s route cache could never be enabled, because three route names were registered twice. After the fix, deploys run automatically through GitHub Actions with tests as the gate.
What I learned
- Measure where it actually runs. Part of my initial website analysis was wrong until I had access to the production server. Numbers from a local
.envdo not represent production. - Small details can be expensive. On Windows,
localhostinstead of127.0.0.1added about 230 ms per broadcast. - Simple for operators is a feature. A backup is a file copy; starting up is one button.