191 lines
5.9 KiB
Markdown
191 lines
5.9 KiB
Markdown
|
|
# Recommended kickoff plan
|
|||
|
|
|
|||
|
|
## 0) Toolchain (lock these down)
|
|||
|
|
|
|||
|
|
* **Python** 3.11 (for wheels availability + perf)
|
|||
|
|
* **Node** 20.19+ and **npm** 10+
|
|||
|
|
* **UV** for deps
|
|||
|
|
* **Playwright** (later for UI smoke tests)
|
|||
|
|
* **Git** repo from day 1
|
|||
|
|
|
|||
|
|
## 1) Make a tiny, opinionated repo scaffold
|
|||
|
|
|
|||
|
|
Create a new repo (you can copy/paste this tree into your README so the LLM follows it):
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
gspro-remote/
|
|||
|
|
backend/
|
|||
|
|
app/
|
|||
|
|
__init__.py
|
|||
|
|
main.py # FastAPI app factory + router mounts
|
|||
|
|
api/
|
|||
|
|
__init__.py
|
|||
|
|
actions.py # /api/actions/*
|
|||
|
|
vision.py # /api/vision/* (keep but V2-gated)
|
|||
|
|
core/
|
|||
|
|
config.py # AppConfig + load/save
|
|||
|
|
input_ctrl.py # pydirectinput helpers
|
|||
|
|
screen.py # mss capture utils (non-vision bits)
|
|||
|
|
pkg.json # (optional) for uvicorn dev script via npm - or just use Makefile
|
|||
|
|
pyproject.toml
|
|||
|
|
README.md
|
|||
|
|
frontend/
|
|||
|
|
src/
|
|||
|
|
main.tsx
|
|||
|
|
App.tsx
|
|||
|
|
pages/DynamicGolfUI.tsx
|
|||
|
|
components/
|
|||
|
|
AimPad.tsx
|
|||
|
|
StatBar.tsx
|
|||
|
|
MapPanel.tsx
|
|||
|
|
styles/
|
|||
|
|
index.html
|
|||
|
|
package.json
|
|||
|
|
vite.config.ts
|
|||
|
|
README.md
|
|||
|
|
scripts/
|
|||
|
|
dev.ps1 # start both servers on Windows
|
|||
|
|
.editorconfig
|
|||
|
|
.gitignore
|
|||
|
|
README.md
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
|
|||
|
|
## 2) Bootstrap bare bones projects
|
|||
|
|
|
|||
|
|
### Backend (FastAPI)
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
cd backend
|
|||
|
|
uv venv && uv pip install fastapi uvicorn pydantic-settings pydirectinput pywin32 mss opencv-python pillow zeroconf
|
|||
|
|
# Or with pip:
|
|||
|
|
# python -m venv .venv && .venv\Scripts\activate && pip install fastapi uvicorn pydirectinput pywin32 mss opencv-python pillow zeroconf
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
`app/main.py` (minimal)
|
|||
|
|
|
|||
|
|
```python
|
|||
|
|
from fastapi import FastAPI
|
|||
|
|
from .api.actions import router as actions_router
|
|||
|
|
|
|||
|
|
def create_app() -> FastAPI:
|
|||
|
|
app = FastAPI(title="GSPro Remote", version="0.1.0")
|
|||
|
|
app.include_router(actions_router, prefix="/api/actions", tags=["actions"])
|
|||
|
|
return app
|
|||
|
|
|
|||
|
|
app = create_app()
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
`app/api/actions.py` (just enough to prove the vertical slice)
|
|||
|
|
|
|||
|
|
```python
|
|||
|
|
from fastapi import APIRouter, HTTPException
|
|||
|
|
from pydantic import BaseModel
|
|||
|
|
from ..core.input_ctrl import press_keys, focus_window
|
|||
|
|
|
|||
|
|
router = APIRouter()
|
|||
|
|
|
|||
|
|
class KeyReq(BaseModel):
|
|||
|
|
keys: str
|
|||
|
|
|
|||
|
|
@router.post("/key")
|
|||
|
|
def post_key(req: KeyReq):
|
|||
|
|
if not focus_window("GSPro"):
|
|||
|
|
raise HTTPException(409, "GSPro window not found/active")
|
|||
|
|
press_keys(req.keys)
|
|||
|
|
return {"ok": True}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Run:
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
uvicorn app.main:app --reload --host 0.0.0.0 --port 5005
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### Frontend (Vite + React + TS)
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
cd ../frontend
|
|||
|
|
npm create vite@latest . -- --template react-ts
|
|||
|
|
npm i
|
|||
|
|
npm run dev
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Add a `.env` in frontend later if you want a configurable API base URL.
|
|||
|
|
|
|||
|
|
## 3) Migrate your existing code **incrementally**
|
|||
|
|
|
|||
|
|
You already have solid pieces. Bring them in slice by slice:
|
|||
|
|
|
|||
|
|
* Move `config.py` → `backend/app/core/config.py` (keep AppConfig + persistence).
|
|||
|
|
* Move `input_ctrl.py` → `backend/app/core/input_ctrl.py` (unchanged).
|
|||
|
|
* Create `backend/app/api/vision.py` and paste **only** the streaming endpoint you’ll use first (WebSocket or SSE). Keep OCR endpoints behind a `VISION_ENABLED` flag for V2.
|
|||
|
|
* Defer `screen_watch.py`, `capture.py`, `streaming.py` until the “MapPanel” slice (below). It’s okay if V1 ships with **no** OCR.
|
|||
|
|
|
|||
|
|
## 4) Implement one **vertical slice** end-to-end (MVP proof)
|
|||
|
|
|
|||
|
|
Start with the **Aim Pad + Mulligan** because it touches everything:
|
|||
|
|
|
|||
|
|
* Frontend:
|
|||
|
|
|
|||
|
|
* `components/AimPad.tsx`: buttons call `POST /api/actions/key` with `"left"`, `"right"`, `"up"`, `"down"`, and `"a"` (Reset).
|
|||
|
|
* Add Mulligan button calling `"ctrl+m"`.
|
|||
|
|
|
|||
|
|
* Backend:
|
|||
|
|
|
|||
|
|
* The `post_key` route already exists.
|
|||
|
|
* Make sure `focus_window("GSPro")` works on your machine.
|
|||
|
|
|
|||
|
|
* Test on Windows: you should see GSPro react from the tablet/phone.
|
|||
|
|
|
|||
|
|
Now you have a working remote!
|
|||
|
|
|
|||
|
|
## 5) Add the **MapPanel** as the second slice
|
|||
|
|
|
|||
|
|
* Backend:
|
|||
|
|
|
|||
|
|
* Introduce a simple `/api/vision/ws/stream` that returns a **downscaled JPEG** buffer of a fixed region; reuse your `mss` capture and JPEG base64 helpers. Keep the code minimal.
|
|||
|
|
* Frontend:
|
|||
|
|
|
|||
|
|
* `components/MapPanel.tsx` opens a WS to `/api/vision/ws/stream`, paints frames onto a `<canvas>`, supports expand/collapse (no click mapping yet).
|
|||
|
|
* Aim for **720p @ 30fps, \~75–85 JPEG quality**. Measure latency, then optimize resize **before** JPEG encode.
|
|||
|
|
|
|||
|
|
## 6) Wire the rest of the “Essentials”
|
|||
|
|
|
|||
|
|
* Tee left/right (`c`/`v`)
|
|||
|
|
* Scorecard (`t`) and Range finder (`r`) as secondary buttons under a kebab menu
|
|||
|
|
* Stat row at bottom (hard-coded 0.0° up/right for now—wire later)
|
|||
|
|
|
|||
|
|
## 7) Keep OCR as **V2** but leave hooks
|
|||
|
|
|
|||
|
|
* Add a feature flag `VISION_ENABLED = False` in `app/core/config.py`.
|
|||
|
|
* Keep `vision.py` imported but routes gated: if disabled, return 404 with a friendly message.
|
|||
|
|
* This lets you merge OCR work later without reshaping the app.
|
|||
|
|
|
|||
|
|
## 8) Dev ergonomics on Windows
|
|||
|
|
|
|||
|
|
Create `scripts/dev.ps1` to run both servers:
|
|||
|
|
|
|||
|
|
```powershell
|
|||
|
|
Start-Job { Set-Location backend; uvicorn app.main:app --reload --port 5005 }
|
|||
|
|
Start-Sleep -Seconds 1
|
|||
|
|
Start-Process powershell -ArgumentList 'cd frontend; npm run dev'
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## 9) Packaging (when MVP is stable)
|
|||
|
|
|
|||
|
|
* **Backend**: PyInstaller or Nuitka into a single EXE, then wrap with **Inno Setup** to produce an installer that:
|
|||
|
|
|
|||
|
|
* Installs the EXE + config files to `C:\ProgramData\GSPro Remote\`
|
|||
|
|
* Creates a Start Menu entry and an **auto-start shortcut** for the Windows user
|
|||
|
|
* Opens firewall rule for your port
|
|||
|
|
* **Frontend**: `npm run build` → copy `dist/` into `backend/ui/` and serve using FastAPI `StaticFiles`. (You already had `build-ui.py`; keep that idea.)
|
|||
|
|
|
|||
|
|
## 10) How to use the LLM effectively (so it helps, not hurts)
|
|||
|
|
|
|||
|
|
* **Give it the scaffold** and the **PRD** (and the Section-12 matrix).
|
|||
|
|
* Ask for **one file at a time**: “Implement `components/AimPad.tsx` against `/api/actions/key`” with explicit props and return types.
|
|||
|
|
* Paste back **real compiler/server errors** verbatim; ask it to fix **only those**.
|
|||
|
|
* Freeze the public contracts early (API request/response shapes). LLMs drift if the interface is fuzzy.
|
|||
|
|
|
|||
|
|
---
|