Skip to content
Back to home
Featured

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
WebSocket (Reverb) over LAN

Arena server

  • Laravel 13 · FrankenPHP
  • Append-only event log
  • SQLite per arena
Queue + retry

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 .env do not represent production.
  • Small details can be expensive. On Windows, localhost instead of 127.0.0.1 added about 230 ms per broadcast.
  • Simple for operators is a feature. A backup is a file copy; starting up is one button.