# YSP Web Project Notes

## Runtime flow

Browseren gar till `index.php`.

`index.php` laddar `index.html`, raknar fram ett versionsnummer fran viktiga filer och ersatter `__APP_VERSION__`.

`index.html` ar bara HTML-skalet. Den laddar:
- `assets/css/app.css`
- `assets/js/app.js`

`assets/js/app.js` pratar med backend via `api.php?action=...`.

## Important files

- `index.php` - entrypoint for the web app and cache/version wrapper.
- `index.html` - page markup only.
- `assets/css/app.css` - all app styling.
- `assets/js/app.js` - all frontend logic and screen rendering.
- `api.php` - API router, account/session logic, state handling, teams, manager mode, transfers and match simulation.
- `.lib/app_version.php` - shared app-version helper used by both `index.php` and `api.php`.
- `.lib/http.php` - JSON responses, request body parsing and CSRF/request checks.
- `.lib/manager_progression.php` - promotion/relegation/playoff helper logic.
- `.legacy-player-names.json` - player-name source imported from the old game data.

## Runtime-generated files

These should not be copied into a clean build:

- `.state.json` - users, chosen teams, league state and current app state.
- `.state.json.bak` - backup of state.
- `.state.lock` - write lock for state.
- `.manager-saves/` - manager save snapshots.
- `.manager.log` - runtime log.

The clean `project` copy starts without accounts because these files are absent.

The webserver must be able to write in the project root to create `.state.lock` and `.state.json`.
It must also be able to write in `.manager-saves/`.

## Assets

- `assets/flags/` - team flag images.
- `assets/icons/` - favicon and app icons.
- `sounds/` - crowd, kick, whistle and crit sounds.

## Current split status

Frontend is split into separate HTML, CSS and JS files.

PHP has a first shared lib split for app versioning and HTTP/request helpers. `api.php` is still the large file and should be split later into smaller modules:
- state/session
- users/auth
- team/player data
- manager league
- fixtures/results
- transfers
- match engine/reporting

## Manager form rule

Daily form dots are player-bound, not slot-bound. The backend locks the boost/drop players from the next fixture's starting XI the first time the fixture form is shown or used. After that, swapping a player between starting XI and bench moves the dot with that player until the fixture is played.

Squad players keep a natural role and a separate slot role. `role`/`naturalRole` is the player's real position; `slotRole` is the position he is currently placed in. The formation board shows the slot role in the white role badge, and only marks a player red when the natural role and slot role are incompatible. Best Lineup assigns the whole XI globally, not slot-by-slot greedily, so it does not steal the only RB/LB for an earlier CB/CM slot when a better total lineup exists.

When a club is in league formtop, daily form changes from one +10% player and one -10% player to two different +10% players and no -10% player.

League form is streak-based. Three straight league wins gives the club a green up arrow in the league table and +3% match power. Three straight league losses gives a red down arrow and -3% match power. Draws reset the streak and cup matches do not count.

Human sim results are briefly masked from other users while the local sim playback is running. The fixture is shown as LIVE with a progressive live score, and the table is hidden until the reveal delay has passed, so waiting players do not see the final table/result state early.

The main `SIM MATCH` button targets the shown NEXT fixture, even if CPU-only rounds or a finished-round continue step sits in front of it. The backend can fast-forward those safe blockers, but it must not auto-continue while the viewer's result is still hidden by the reveal delay.

Future NEXT fixture requests may arrive with only the target round. The backend resolves that to the viewer's own pending fixture in that round, so a stale continue blocker does not trap the user before the next match.

Transfer market is topped up, not reset. `ensure_manager_market` preserves existing human listings and CPU/free-agent listings, then fills the market to the configured target count.

CPU clubs do not all use 4-4-2. Their formation is chosen deterministically from the club and division, then existing CPU squads are migrated into that formation on load. CPU season/cup creation uses a fast formation assignment so starting a new manager season does not run the heavier human Best Lineup algorithm hundreds of times.

The CPU formation migration is guarded by `managerCpuFormationVersion` in state, so login/state requests do not repeatedly rescan and rebuild old CPU squads.

CPU bids on human-listed market players are based on the user's asking price. Overpricing lowers the chance of getting a bid and makes CPU clubs haggle harder, but the offer is no longer rounded in a way that effectively sticks to the base sell value.

Starting a new solo manager season is a full active-game reset. Accounts, selected teams and `.manager-saves/` files are preserved, but active manager seasons, live matches, manager market/offers, trophies, squads, finances and manager divisions are cleared before the new solo season is created. This makes the new game start from the base division with a clean club, without destroying older saves that can still be loaded later.

## Manager saves

Manager saves are standalone snapshots in `.manager-saves/`. They are outside the active `.state.json` game flow: starting `NEW GAME` only resets the active manager game, while `LOAD SAVED GAME` restores one of those snapshots.

Autosave must never prune or delete manager saves. Older code limited saves to two per owner/group through `manager_prune_saves`, which could remove a multiplayer save when a new solo autosave was created. That function is now intentionally a no-op. The only normal way to remove a save is the explicit delete button, and the admin-only `RESET EVERYTHING` remains the hard reset that also clears manager save files.

New save filenames include the season id, for example `jdc.s87926a11f8.20260703-063402.json`. The save metadata includes mode (`solo` or `online`), users, saved-by, division, season year, season id, latest match and score.

Save lists and "has any save" checks should use `.manager-saves/.meta/*.meta.json` sidecars via `manager_save_cached_metadata`. This avoids decoding every multi-megabyte save file just to render the load screen or decide if a user can continue.

Team selection is not globally unique across every account. Separate test accounts and separate saves may choose the same club. The uniqueness rule belongs to a manager season: two human managers cannot start or accept the same online league with the same team.

Numeric-looking usernames, for example `111`, must be returned to the frontend as strings in manager user/invite payloads. PHP can turn numeric JSON object keys into integer array keys, and strict JS comparisons then fail if the API does not cast names back to strings.

## Manager performance

`managerSetup` is the heavy manager endpoint because it returns the public season, league table, fixtures, market, squad context, divisions and transfer offers. Keep the response public and slim: do not expose full CPU squads through `managerSeason.participants`; team/player details should be loaded through the dedicated team/squad endpoints when needed.

The manager screen poll interval is intentionally slower than live match polling. Match polling still needs fast updates, but manager home polling should not request the full manager payload every 500 ms.

## Manager trophies

Team trophies carry a visual key: `cup`, `medal`, or `medal_silver`. Public output infers this for older saves from the trophy `kind`, so old trophies still render with a cup or medal icon.

The manager trophy page filters team trophies to the current season chain. Trophies stored in an older save remain in state for that save, but they do not leak into a newly started solo season for the same club.

Finished seasons also award a league Golden Boot trophy to every tied top league scorer. This is separate from cup goals and uses `statTrophiesApplied` so older finished seasons can be backfilled without duplicating team trophies.

The Top Scorers page also shows league Top Assists below the scorers list, so users can scroll down to see both stat tables in one page. Manager Menu no longer has a separate Top Assists action; that slot opens the Divisions browser instead, and the division table has quick buttons for switching between divisions.

## Promotion zones

League Two uses three direct promotion places, playoffs for places 4-7, and two relegation places. Direct promotion rows use the same green as the National League champion, playoff rows use the same playoff color as the National League playoff places, and the bottom two League Two rows are red. Human managers finishing in those red places are moved down one division for the next season.

## Player IDs

Player IDs are stored on each player, not in a separate registry file. This keeps saves, transfers, injuries and reports in one state tree and avoids an extra file that can drift out of sync.

Generated squad players use deterministic IDs based on club and original squad number, for example `p_fc-halifax-town_01`. `shirtNumber` is separate and is only used for the visible number in matches.

Transfers keep the same player ID when the player moves club. A random `T...` ID is only generated if the target squad already has that ID.

## Quick checks

Run from this folder:

```bash
php -l index.php
php -l api.php
php -l .lib/app_version.php
php -l .lib/http.php
php -l .lib/manager_progression.php
node --check assets/js/app.js
```

Open:

`https://auth.jdc.se/project/`

## Saved test reports

- `reports/manager-online-stress-2026-07-02.md` - manager online stress notes for 3-user/3-season and 15-user split testing.
