A high-contrast, tactical naval radar combat game (Grid Strike) designed for the LilyGo T-Deck Plus (ESP32-S3), LilyGo T-Display P4 (AMOLED/LCD), and Elecrow CrowPanel Advanced (9-inch & 10.1-inch touchscreen-only) using the on-device WADAMESH Lua App SDK (wada.* API v1).
Grid Strike implements a full 1v1 multiplayer loop over LoRa mesh featuring strict lockstep turn-based combat with explicit ACK handshakes, a salted commit-reveal anti-cheat audit, crash recovery persistence, and tactile screen-shake / audio feedback. It also includes a smart offline Hunt/Target AI mode for solo play.
| Main Menu (T-Deck) | Fleet Deployment (T-Deck) | Tactical Combat (T-Deck) |
|---|---|---|
![]() |
![]() |
![]() |
| Fleet Deployment (CrowPanel Advanced 9"/10.1") | Tactical Combat (CrowPanel Advanced 9"/10.1") |
|---|---|
![]() |
![]() |
├── gridstrike.lua # Standalone Lua app ready for SD card sideloading
├── gridstrike.json # App manifest (v1.0)
├── deploy/
│ └── apps/
│ └── gridstrike/
│ └── 1.0/
│ ├── gridstrike.json # WADAMESH Store manifest bundle
│ └── gridstrike.lua # Production release script
├── pictures/
│ ├── grid_strike_banner.jpg # GitHub banner artwork
│ ├── grid_strike_background.jpeg # In-game backdrop
│ ├── PXL_20261101_040742023.jpg # Hardware photo: Main Menu (T-Deck Plus)
│ ├── PXL_20261101_040747266.jpg # Hardware photo: Fleet Setup (T-Deck Plus)
│ └── PXL_20261101_040803116.jpg # Hardware photo: Tactical Combat Radar (T-Deck Plus)
└── README.md # Documentation & reference guide
| Device | Display Resolution | Layout Mode | Cell Size | Radar Dimensions | Input Support |
|---|---|---|---|---|---|
| LilyGo T-Deck Plus | 320 × 240 (Native) |
Split Landscape | 14 × 14 px |
Two 144 × 144 px panels |
Physical Keyboard, Trackball, Touchscreen |
| LilyGo T-Display P4 (LCD) | 1168 × 474 |
Wide Landscape (Forced) | 36 × 36 px |
Two 364 × 364 px panels + Side Panel |
Touchscreen, Touch Buttons |
| LilyGo T-Display P4 (AMOLED) | 1232 × 502 |
Wide Landscape (Forced) | 36 × 36 px |
Two 364 × 364 px panels + Side Panel |
Touchscreen, Touch Buttons |
| Elecrow CrowPanel Advanced 9" & 10.1" | 1024 × 600 (1024 × 578 body) |
Wide Landscape (Forced) | 34 × 34 px |
Two 344 × 344 px panels + 260px Side Panel |
Touchscreen-Only (GT911), On-Screen D-Pad & Touch Buttons |
- Host Match: Generates a random 4-character hex match ID (e.g.,
A3F1) and broadcastsBS|SEEKover the mesh channel. - Join Match: Discovers active host beacons, parses the match ID, and connects via direct message (DM).
- VS AI (Offline Mode): Instant play against an on-device bot running a Hunt/Target algorithm with checkerboard parity and orthogonal target locking.
- Persistent Career Record: Tracks lifetime wins, losses, matches played, and hit accuracy in
wada.store("career_stats").
Deploy the standard 5 naval warships:
- Carrier (Length 5,
CV) - Battleship (Length 4,
BB) - Cruiser (Length 3,
CA) - Submarine (Length 3,
SS) - Destroyer (Length 2,
DD)
Controls: Tap any cell on the 10 × 10 Deployment Grid (or use WASD / Arrow keys + Enter / Space), tap DIR: HORIZ/VERT (ROTATE) (or press R) to rotate between Horizontal and Vertical, tap RESET FLEET (or press C) to reset, and tap LAUNCH BATTLE to deploy. Tapping near the right or bottom grid edge automatically clamps the ship inside the 10 × 10 board.
Airtime is scarce; packets are formatted as terse pipe-delimited strings prefixed with BS| and <match_id>:
Phase A: Match Handshake (Seq 0)
Host Broadcast: BS|SEEK|<match_id>|<callsign>
Challenger Join: BS|JOIN|<match_id>|0|<seed>|<board_hash>
Host Ready + ACK: BS|RDY|<match_id>|0|<seed>|<board_hash>
Challenger Start: BS|ACK|<match_id>|0
Phase B: Lockstep Combat Loop (Monotonic Seq = 1, 2, 3...)
Attacker Shot: BS|FIRE|<match_id>|<seq>|<x>,<y>
Defender Result: BS|RES|<match_id>|<seq>|<x>,<y>|<status>|<ship_id>
Attacker Confirm: BS|ACK|<match_id>|<seq>
Phase C: Post-Game Commit-Reveal Audit (Seq = final_seq + 1)
Both Players: BS|REV|<match_id>|<seq>|<salt>|<fleet_str>
Confirmations: BS|ACK|<match_id>|<seq>
Crash Recovery: BS|SYNC|<match_id>|<local_seq>
- Implicit ACK & Piggybacking: Receiving a matching
BS|RESserves as an implicit ACK forBS|FIRE. The next turn'sBS|FIREalso confirms receipt of the previous turn's result. - Dropped ACK Fix (Idempotency): If the Defender is on
seq = N + 1and receives duplicateBS|FIRE|<N>due to RF packet loss, the Defender does not re-apply damage or advance the turn. It immediately re-transmits its cachedlast_sent_res.payloadto unblock the attacker. - Exponential Backoff Retry Engine: Monitored by
"lora_tx_retry"(intervals: 6s, 8s, 10s, 12s, 14s). Displays[TX SEQ:N | RETRY X/5]in the top bar. After 5 failed attempts, provides a modal dialog for manual resend or forfeit.
Prevents modified clients from lying about hit results or moving ships mid-game:
- Canonical Fleet String: Sorted 1 to 5:
<id>:<x>,<y>,<dir>;(e.g.1:0,0,H;2:2,4,V;3:5,1,H;4:7,3,V;5:8,8,H— 39 bytes). - Commitment: During
Seq 0, both players exchange an 8-char hex DJB2 hash of<fleet_str>|<salt>where<salt>is a 6-character random alphanumeric string. - Three-Point Verification: When a player wins (
status == "W"), both reveal their salt and fleet string:- Commitment Check:
djb2_hex(fleet_str .. "|" .. salt) == enemy_board_hash. - Geometry Check: Validates all 5 ships fit
0..9bounds with zero overlapping cells. - Combat Honesty Check: Reconstructs the opponent's grid and cross-references all shots fired. Any shot reported as
Mthat contains a ship, or reported asH/Son empty water, triggers immediate disqualification.
- Commitment Check:
- Ghost Fleet Overlay: Verified matches reveal unhit enemy ships on the Target Radar in dim purple (
#7755AA).
- Live state is packed into
wada.store("active_match")(~304 bytes):<match_id>|<opponent_id>|<state_enum>|<local_seq>|<my_salt>|<my_board_hash>|<enemy_board_hash>|<my_fleet_str>|<my_shots_100>|<enemy_shots_100>|<tx_pending>|<last_res> - Upon device reboot or app launch,
app.on_load()detects unfinished matches and presents a hot-resume modal ([RESUME MATCH]or[ABANDON]).
- Screen Shake (
fx_shake): Struck panels shake across 6 non-blocking 35ms ticks ({-4, +4, -3, +3, -1, 0}px offset) while flashing crimson (#FF3333) on Hit or amber (#FFAA00) on Sunk before snapping cleanly back to origin. - Audio Feedback (
sfx_play):"TURN_ALERT": Dual sonar ping (1200Hz, then 1600Hz)"FIRE": Torpedo launch blip (900Hz)"MISS": Low muted splash thud (220Hz)"HIT": Double-pulse klaxon (600Hz, then 450Hz)"SUNK": Descending 3-tone cadence (800Hz -> 500Hz -> 250Hz)"CHEAT_WARN": Alternating siren (1000Hz / 500Hz)
| Input | Context | Action |
|---|---|---|
| Touchscreen | Lobby | Tap Host, Join, or VS AI metallic button |
| Touchscreen | Setup | Tap Deployment Grid to place current ship (auto-clamps at board edges); tap ROTATE, RESET FLEET, or LAUNCH BATTLE on Side Panel |
| Touchscreen | Combat | Tap any cell on Target Radar to aim/fire (or pre-aim during enemy turn) or tap FIRE button on Side Panel |
| Touchscreen | Global | Tap top-right corner ([VOL] / [MUTE]) or Side Panel SOUND button to toggle audio |
| Menu Button | Bottom-Right (All non-Lobby screens) | Return immediately to Main Menu / Lobby |
WASD / Arrows |
Combat / Setup | Move aiming reticle / position ship preview |
Enter / Space |
Combat | Fire torpedo at reticle coordinates |
Enter / Space |
Setup | Confirm placement of current ship |
R |
Setup | Rotate warship orientation (Horizontal |
C |
Setup | Clear placed ships and restart deployment |
M |
Global | Toggle sound effects on/off |
1 / 2 / 3 |
Lobby | Host Match (1), Join Match (2), Play vs Offline AI (3) |
Q / Esc |
Any active screen | Abandon active match / return to Main Menu |
- Copy
gridstrike.luaandgridstrike.jsonto your microSD card:/meshcomod/apps/gridstrike.lua /meshcomod/apps/gridstrike.json - Insert the card into your T-Deck Plus or T-Display P4 and open the Apps drawer. Grid Strike will appear with the game icon.
Using the WADAMESH repository's sideload script:
python scripts/sideload_app.py --port COM3 --reboot deploy/apps/gridstrike/1.0




