Game Features
This commit is contained in:
@@ -1,109 +1,97 @@
|
||||
# Fantasy Politics
|
||||
|
||||
`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.
|
||||
A private fantasy election game for friends, built in C++23 with the shared **Modulus**, **Ligo**, and **Noto/SQLite** libraries. The frontend uses TypeScript and Vite.
|
||||
|
||||
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.
|
||||
The app now has username/password accounts, invitation-based registration, private leagues, timed snake drafts, private queues, rosters, and provisional standings. The main navigation is **Leagues / Candidates / Results**. The earlier scoring simulator lives separately at `/lab.html`, with its APIs enabled only by `--allow-demo`.
|
||||
|
||||
## Start developing
|
||||
**Real drafts remain locked.** The included official catalog has 115 candidates in 30 Michigan/North Carolina races. National candidate coverage, sourced ratings, and live count/outcome verification are still incomplete. League creation and invitations work before those data gates clear. There is no deployed site yet.
|
||||
|
||||
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:
|
||||
|
||||
```sh
|
||||
cd frontend
|
||||
npm ci
|
||||
npm run dev
|
||||
```
|
||||
|
||||
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.
|
||||
|
||||
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
|
||||
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 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.
|
||||
|
||||
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.
|
||||
|
||||
## Data readiness
|
||||
|
||||
**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.
|
||||
|
||||
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
|
||||
## Start locally
|
||||
|
||||
Use the sibling `modulus`, `noto`, and **updated `ligo`** checkouts in this workspace. This change extends Ligo's HTTP API; an older published `ligo/latest` package cannot build this app. The workspace helper exports the local recipes into your Conan cache and builds against those revisions, without publishing anything.
|
||||
|
||||
Prerequisites: a C++23 compiler (the configured target is GCC 12), CMake 3.25+, Ninja, Python 3, Conan 2.26.2, Node 22.12+, and network access for initial dependencies. CI uses Node 24.
|
||||
|
||||
```sh
|
||||
conan profile detect # once, if you do not already have a profile
|
||||
python3 tools/build_workspace.py
|
||||
npm --prefix frontend ci
|
||||
npm --prefix frontend run build
|
||||
python3 modulus.py run -- --web-root frontend/dist
|
||||
|
||||
build/workspace_debug/fpol_admin import data/real/us-2026-10-04-partial.json
|
||||
build/workspace_debug/fpol_admin create-user YOUR_USERNAME
|
||||
# Password is prompted privately; use at least 12 characters.
|
||||
|
||||
build/workspace_debug/fpol_server --web-root frontend/dist
|
||||
```
|
||||
|
||||
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.
|
||||
Open **http://127.0.0.1:8080**. The first account created through the local CLI is an administrator. Friends create accounts through private league invitations. All commands default to `var/fpol.sqlite`; use `--database PATH` consistently to select another database.
|
||||
|
||||
## Verify changes
|
||||
For Vite development, start the backend with `--origin http://127.0.0.1:5173`, then run `npm --prefix frontend run dev`. Use the exact configured origin, including hostname and port. For Vite preview use origin `http://127.0.0.1:4173`. Cookie security defaults suit loopback development; production uses HTTPS and `--secure-cookies`.
|
||||
|
||||
The existing Modulus workflow remains available once the matching Ligo package is in its Conan cache/remote:
|
||||
|
||||
```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
|
||||
```
|
||||
|
||||
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:
|
||||
`modulus.py` remains byte-identical to the canonical bootstrap. Do not use `configure --update` to replace the local Ligo revision with an older published API. The [deployment guide](docs/deployment.md) describes a source-based Compose build that includes all four checkouts.
|
||||
|
||||
## Try a complete practice draft
|
||||
|
||||
```sh
|
||||
python3 tests/test_core.py build/build_x86_debug/fpol_admin
|
||||
build/workspace_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
|
||||
build/workspace_debug/fpol_server --web-root frontend/dist --allow-demo
|
||||
```
|
||||
|
||||
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.
|
||||
Create a league and choose the **Practice** candidate pool. Invite a second player, then start from the draft room. Demo data stays explicitly separate from real data. The legacy simulator replays its five synthetic snapshots; those old snapshots are not automatically published as live league results.
|
||||
|
||||
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.
|
||||
## Rules
|
||||
|
||||
## Next milestone
|
||||
Leagues have 2–12 teams and 12 snake-draft rounds. Every team has Democrat, Republican, Other, and Flex slots for each of House, Senate, and Governor. A pick fills its party slot before Flex. Candidates are exclusive within a league; legal-pick checks preserve enough candidates for every remaining roster slot.
|
||||
|
||||
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.
|
||||
The server owns the 120-second clock. A timeout takes the first legal private-queue candidate, otherwise a uniformly random legal candidate. Commissioners can pause/resume. A process restart pauses active drafts; resuming gives the current picker a fresh 120 seconds. Browser draft views poll every 2 seconds; results and standings poll every 15 seconds.
|
||||
|
||||
`(November first-round / first-choice vote share percentage + 50 for the eventual called/certified winner) × frozen race rating`
|
||||
|
||||
Safe ×1, Likely ×1.1, Lean ×1.25, Toss-up ×1.5. Each candidate is rounded half up to two decimals before summing. Counts and outcomes are independent: a runoff changes the winner bonus while November share stays intact. Call retractions remove the bonus; corrections can lower points. Missing counts/ratings are unknown, not zero. Provisional rankings show reporting coverage beside known subtotals. Catalog, ratings, order, and rules freeze at draft start. Trades and waivers remain a later milestone.
|
||||
|
||||
## Election data and operations
|
||||
|
||||
The C++ `fpol_ingest` worker supports reviewed Open America count mappings, a normalized official-source JSON contract, candidate-source change capture, and a civicAPI capture/probe path. **civicAPI normalization is not implemented until a real response/schema has been validated.** No feed is configured or enabled automatically.
|
||||
|
||||
Changed raw payloads and normalized observations are immutable. Failed/ambiguous responses are quarantined; the last good counts remain visible with stale provenance. Replaying a file cannot verify a source or publish to a real catalog. Operators must obtain a successful live capture, review mappings, and explicitly activate coverage.
|
||||
|
||||
See [ingestion and source contracts](docs/ingestion.md), [source coverage](docs/data-coverage.md), the [506-contest inventory](data/contests_2026.json), and the [state/office coverage matrix](data/coverage_2026.csv). Official lists can change; review again before freezing. `tools/generate_real_catalog.py` reproduces the reviewed NC import and combined catalog; it does not fetch fresh evidence or claim a new review date.
|
||||
|
||||
Password recovery is operator assisted:
|
||||
|
||||
```sh
|
||||
build/workspace_debug/fpol_admin reset-link USERNAME
|
||||
```
|
||||
|
||||
Give that user `https://YOUR_HOST/#reset/TOKEN` using the returned token. It expires in 30 minutes, works once, and revokes all existing sessions on use. There is no email service. See [the API contract](docs/application-api.md) for session, CSRF, and request details.
|
||||
|
||||
## Verify
|
||||
|
||||
```sh
|
||||
python3 tools/build_workspace.py --test
|
||||
npm --prefix frontend run build
|
||||
node tests/browser.mjs build/workspace_debug/fpol_server
|
||||
python3 modulus.py format --check
|
||||
```
|
||||
|
||||
The browser smoke test uses installed Chromium (`FPOL_CHROMIUM` can select a binary), Node's standard library, two isolated browser sessions, and the real C++ server. Screenshots go to ignored `build/browser-artifacts/`.
|
||||
|
||||
Socket-independent checks can be run separately:
|
||||
|
||||
```sh
|
||||
python3 tests/test_core.py build/workspace_debug/fpol_admin
|
||||
python3 tests/test_application.py build/workspace_debug/fpol_admin build/workspace_debug/fpol_ingest
|
||||
```
|
||||
|
||||
They do not replace HTTP/browser checks. [The verification record](docs/verification.md) states exactly what ran here and what remains blocked. [Deployment and backup instructions](docs/deployment.md) cover persistent storage, Caddy HTTPS, bootstrap accounts, online backups, and restore.
|
||||
|
||||
Reference in New Issue
Block a user