This commit is contained in:
khodowany
2026-10-04 21:15:21 -05:00
parent c6b4ba1b43
commit 2759337d16
33 changed files with 10510 additions and 137 deletions
+62 -49
View File
@@ -1,28 +1,33 @@
# Fantasy Politics
`product-fpol` is a private, noncommercial fantasy-style game for friends around the 2026 US midterm elections. The eventual game will have a candidate draft, trades, and final standings based on real-world candidate performance.
`product-fpol` is a private, noncommercial fantasy politics game for friends around the 2026 US midterms. This first playable development slice lets you browse candidates, build a 12-slot party, check league capacity and replay fictional election-night scoring.
This repository currently contains only the foundation: a basic webpage, a C++ HTTP server, and development tooling. There are no accounts, candidate records, league rules, drafts, trades, scores, or election data yet.
The backend is **C++23**, with **Modulus** for builds, **Ligo** for HTTP, and **Noto/SQLite** for immutable catalog, rating and results snapshots. The frontend is TypeScript/Vite. Accounts, live drafts, cross-team ownership, trades, automatic data ingestion and deployment are later milestones.
## Prerequisites
## Start developing
- Linux with Docker available to your user and Python 3 for the backend tooling.
- Node.js 22.12 or newer and npm for the frontend; CI uses Node 24.
- Network access on the first setup to fetch npm packages, the build image, and public Conan dependencies.
Modulus supplies the shared C++23/GCC 12 build environment, Conan/CMake integration, formatting, and editor configuration. Ligo supplies HTTP networking and routing. Both are consumed from the public khodo Conan registry; sibling source checkouts are not required. Package reads do not need registry credentials.
## Development
From the repository root, configure and build the backend:
Prerequisites: Linux, Docker available to your user, Python 3, and Node.js 22.12+ with npm (CI uses Node 24). Initial setup requires network access for public Conan packages, the build image and npm dependencies. Sibling library checkouts are not required.
```sh
python3 modulus.py configure
python3 modulus.py build
```
Seed the development database with explicitly fictional candidates, ratings and results. Run the built admin executable from the repo root in the build environment, or directly on a compatible Linux host:
```sh
build/build_x86_debug/fpol_admin import \
data/demo/catalog.json data/demo/ratings.json \
data/demo/results-0.json data/demo/results-1.json \
data/demo/results-2.json data/demo/results-3.json data/demo/results-4.json
# Optional: a separate, partial real catalog. It has no real ratings/results.
build/build_x86_debug/fpol_admin import data/real/mi-2026-10-04.json
python3 modulus.py run
```
In another terminal, start the frontend:
In another terminal:
```sh
cd frontend
@@ -30,67 +35,75 @@ npm ci
npm run dev
```
Open **http://127.0.0.1:5173**. Vite reloads frontend edits and proxies `/api` to **http://127.0.0.1:8080**. Rebuild and restart the backend after C++ changes. Both configured ports fail explicitly when occupied.
Open **http://127.0.0.1:5173**. Select the fictional catalog, click **Try an example roster**, then **Play replay**. Search/filter the field, change picks and compare points with/without race multipliers. Vite proxies `/api` to port 8080. Restart the backend after C++ changes; refresh the browser after imports. The roster is an in-page sandbox and is cleared on refresh or catalog change.
Check the backend directly or through Vite:
Data is stored at `var/fpol.sqlite` (ignored by Git), relative to the working directory. Both executables accept `--database PATH`; use the same path when importing and serving. Importing the same document again is harmless. Corrections require new snapshot IDs, preserving the earlier result for replay. `python3 tools/generate_demo.py` deterministically regenerates only fictional fixtures.
The standard Modulus runner launches `fpol_server` inside its container, publishes port 8080 only on host loopback, and mounts the repository so the development database persists. For local direct builds, launch the built server executable instead. There is no hosted environment or authentication yet.
## Scoring to try
Every office — House, Senate, Governor — has a Democrat, Republican, Other and Flex slot. Other includes independent and third-party candidates; Flex takes any affiliation in that office. Each candidate can appear only once in a roster, and opposing candidates may both be picked.
`(vote share percentage + 50 if called/certified winner) × frozen race multiplier`
| Rating | Multiplier | Example |
| --- | --- | --- |
| Safe | 1.00 | 80% winner → 130 points |
| Likely | 1.10 | 60% winner → 121 points |
| Lean | 1.25 | 54% winner → 130 points |
| Toss-up | 1.50 | 51% winner → 151.5; 49% loser → 73.5 |
Candidates are rounded half up to two decimals before summing. Reporting shares score without a win bonus; the current-leader preview is hypothetical and displayed separately. A missing count/rating is unknown, never an invented zero. Only a filled, fully scored roster gets a complete total. Corrections can retract a call. The replay demonstrates unreported votes, early totals, changing leads, calls and corrections. All five frames are synthetic, not a historical backtest.
## Admin and API
```sh
curl http://127.0.0.1:8080/api/health
curl http://127.0.0.1:5173/api/health
build/build_x86_debug/fpol_admin --help
build/build_x86_debug/fpol_admin capacity data/demo/capacity.json
build/build_x86_debug/fpol_admin score-preview data/demo/preview.json
build/build_x86_debug/fpol_admin catalog
```
The only application API is `GET /api/health`, which returns HTTP 200 using Ligo's existing envelope:
The last three commands use the same C++ handlers as `POST /api/capacity`, `POST /api/score-preview` and `GET /api/catalog`. `GET /api/rules` describes the slots and scoring. `GET /api/health` is process liveness. Ligo returns `{ "ok": true, "payload": ... }`, or an error envelope. No admin write endpoint is exposed.
```json
{"ok":true,"payload":{"status":"ok"}}
```
See [the data/API contract](docs/data-contract.md) for schemas, immutable import behavior, input bounds and round handling. The capacity report checks the per-party and total candidate supply for 2–12 players; it does not certify ratings or results coverage.
Unknown routes return HTTP 404 with Ligo's error envelope. Health is a process liveness check; there is no database or external data source to check yet.
## Data readiness
## Built frontend
**The real league is not ready to draft.** The included Michigan snapshot contains 73 candidates in 15 races; the remaining states, real ratings and live result/call coverage are unresolved. Switching to that catalog shows those gaps rather than fictional replacements. An official candidate list may change and must be reconciled before use.
Build the frontend, then serve it with the C++ backend:
Read the dated [source feasibility report](docs/data-coverage.md) and [office/state coverage matrix](data/coverage_2026.csv). Free automatic updates remain a launch requirement. No provider transport or national coverage is claimed verified, and no background scraper/poller has been enabled. Runoff/ranked-choice resolution also requires a settled policy before affected races can launch.
## Serve the built frontend
```sh
npm --prefix frontend run build
python3 modulus.py run -- --web-root frontend/dist
```
Open **http://127.0.0.1:8080**. Node is only needed to build the assets; the C++ server serves the resulting HTML, JavaScript, and CSS. Assets are loaded at startup, so restart the server after rebuilding. `npm --prefix frontend run preview` is also available on port 4173, with the same API proxy, for local frontend previews.
Open **http://127.0.0.1:8080**. Assets load at startup, so restart after rebuilding. Node is not needed to serve built assets. The server accepts `--host`, `--port`, `--web-root`, `--database` and `--help`; defaults are `127.0.0.1:8080`, API-only, and `var/fpol.sqlite`. Port 0 selects an available port. SIGINT/SIGTERM stop it cleanly. Exact registered static paths are served; hidden files, symlinks and `/api` asset collisions are excluded. Use only Vite output as the web root. `npm --prefix frontend run preview` is available on port 4173 with the same API proxy.
The server accepts `--host ADDRESS`, `--port PORT`, `--web-root DIRECTORY`, and `--help`. It defaults to `127.0.0.1:8080` and API-only mode. An explicit web root must contain `index.html`. Only exact registered asset paths are served; there is no SPA fallback or query-string normalization in this scaffold. Hidden files and symlinks are excluded, and `/api` is reserved. Use only the Vite build output as the web root.
The Modulus runner binds the server to `0.0.0.0` **inside its container**, publishing port 8080 only on the host's loopback interface. An installed executable defaults to loopback. Port `0` selects an available port and is useful for tests. SIGINT and SIGTERM stop the server cleanly.
## Checks and editor support
```sh
python3 modulus.py test
python3 modulus.py format --check
npm --prefix frontend run build
```
The frontend build includes strict TypeScript checking. `npm --prefix frontend run typecheck` runs that check alone. The backend CTest smoke suite starts real server processes to check health, routing, binary/static assets, startup errors, port conflicts, and shutdown.
Check both native configurations:
## Verify changes
```sh
python3 modulus.py configure --all
python3 modulus.py build --all
python3 modulus.py test --all
python3 modulus.py format --check
npm --prefix frontend run build
```
Configure generates ignored editor files. Customize `modulus.json`, not those generated files; refresh compile databases with `python3 modulus.py lsp`. C++ and Python use the shared Modulus formatting policy and full names rather than aliases. Run `python3 modulus.py format` before committing.
CTest's `product_fpol` suite runs the domain/persistence tests through the production admin executable, then HTTP process tests for API envelopes, validation, static assets, startup errors, occupied ports and shutdown. The frontend build includes strict TypeScript checking. For socket-restricted environments, core checks can also run independently:
The checked-in `modulus.py` is copied unchanged from Modulus's canonical `modulus/bootstrap.py`. `configure --update` refreshes shared tooling and packages. `--no-remote` on configure uses only the project-local Conan cache, which must already contain dependencies. Keep build outputs and downloaded tooling untracked.
```sh
python3 tests/test_core.py build/build_x86_debug/fpol_admin
```
The Gitea workflow checks the frontend build plus backend formatting, debug/release builds, and tests. It has no publishing or deployment job. Its direct build mode provisions the shared compiler and tools on the CI runner, following the sibling repositories' convention. CMake installation installs the backend executable; distribute built frontend assets separately when deployment is added.
Passing core checks does not replace the HTTP suite. See [the local verification record](docs/verification.md) for the compiler and environmental limitations of this implementation session. CI retains the shared GCC 12 debug/release builds and full test suite.
## Future product work
Configure generates ignored editor support; customize `modulus.json` and refresh with `python3 modulus.py lsp`. C++ and Python follow shared Modulus formatting; run `python3 modulus.py format` before committing. The checked-in bootstrap remains byte-identical to Modulus's canonical bootstrap. `configure --update` refreshes tooling/packages; `--no-remote` needs an already-populated project cache. CMake installs both executables; distribute frontend assets separately.
- Define league rules, eligible races and candidates, drafting, trades, and scoring together before introducing domain models.
- Add persistence when needed; Noto is the existing SQLite library to evaluate then.
- Choose an election data source later. Live feeds, scraping, update schedules, and correction handling remain undecided; this scaffold makes no external election requests.
- Add accounts, invitations, deployment, and operational requirements when the game needs them.
## Next milestone
Keep reusable networking and infrastructure in the shared libraries and product rules in this repository. Luma and Sonus are native UI/audio libraries and are not dependencies of this web application.
Clear the data gates first. Then add the private league and invitations, persist team ownership, freeze the chosen catalog/ratings before a live snake draft, add trades/locks, and connect verified polling to the existing snapshot/import layer. Election-night standings can reuse the deterministic scorer and correction history. Keep product rules here and reusable infrastructure in the shared library repos.