# Junkstep

Junkstep is a C++ rhythm game early alpha about turning timing into pressure.
It reads StepMania, Etterna, and 4K osu!mania charts, then adds a battle layer where accurate play sends junk to the other side.

The game is still early, but the core loop is there: pick a chart, read falling notes, hit cleanly, and manage the junk that hides the next notes.

## What works now

- Standalone single-player Dance mode for imported charts.
- Human vs. bot battle mode.
- Local same-keyboard multiplayer.
- LAN host/join practice over TCP.
- A main menu with song select, settings, key rebinding, a tutorial, and a top-right account/social dropdown.
- A multiplayer menu for online match queueing, local play, LAN practice, and LAN port settings.
- LAN and online matches use song select as a map-pick phase, so the connected player can still adjust song-select mods and settings before play.
- LAN and online map handoff can transfer a missing chart from the selecting client and import it into `data/network_maps/` as a `.jsc` snapshot.
- Online map handoff packages sibling difficulties plus available song, banner, and background assets so transferred maps are playable and browsable after import.
- A local song database. Drop chart files or folders into the window and the game indexes them.
- Search, sorting, and per-song difficulty selection in song select.
- Local leaderboards for single-player scores.
- React/Vite online website with chart rankings, public profiles, user browsing, account settings pages, friends, and chat on `https://junkstep.com`.
- Versioned local replay capture for single-player clears, with input checksums and replay playback from song select.
- Local profile save data for play totals, best score/combo/health, active display name, and last-played chart.
- Manifest-backed asset pack import/export for noteskins, themes, board skins, music, and SFX.
- Song select banner/background previews and music samples from imported chart metadata.
- Pause menu with resume, restart, song select, and main menu actions.
- An early-alpha C++ backend server for website-only registration, game login, presence, friends, leaderboards, score upload, Etterna-style single-player skill ratings, and multiplayer Elo.
- Server-owned online match sessions with backend slot assignment, chart locking, ordered input/burst events, match finish reporting, spectator reads, replay logs, input-delay config, and desync reports.
- Online backend calls run in background tasks so slow HTTPS requests do not freeze rendering or gameplay.
- An in-game F9 online hub with friends, global chat, rankings, users, and song-select online leaderboard reads.
- StepMania/Etterna `.sm` and `.ssc` import.
- 4K osu!mania `.osu` import.
- Hold-note import, tail rendering, completion scoring, and lenient early-release handling for StepMania/Etterna and osu!mania charts.
- Chart music playback when raylib can decode the referenced audio file.
- Early-alpha SFX for menus, judgements, match start, misses, bursts, and an optional metronome.
- Separate hit-sound volume, because judgement taps get annoying fast.
- Music, SFX, and hit-sound mute toggles.
- Rebindable menu, dance, attack, defense, burst, and restart keys.
- Default controller support for menus and P1 play.
- Controller layout presets for split, inverted, dance-only, and off behavior.
- Pattern Density mod for thinning charts or adding deterministic extra chord notes.
- No Fail keeps practice and multiplayer battle rounds from ending just because the local player's health hit zero.
- Battle Junk toggle for bot and local multiplayer matches that keeps attack/defense charts without garbage pressure.
- Selectable bot styles: steady, aggressive, defensive, and chaotic.
- Battle round timer, best-of-three sets, defense counter-clears, and saved results summaries.
- Etterna-style scroll options: XMod, CMod, MMod, downscroll/upscroll, mini, hidden, sudden, dark, and hit feedback.
- A HUD layout editor with a live gameplay preview and draggable HUD pieces.
- Theme, note-shape, lane-palette, glow, board opacity, judgement size, HUD scale, HUD position, and HUD visibility settings.
- Toggleable performance overlay with FPS, frame timing, frame history, window size, and display refresh diagnostics.

In battle modes, the same falling rows can be played with the attack or defense key bank. Attack hits queue named incoming junk patterns on the opponent. Defense hits clear incoming junk first, prioritizing their own lane before spilling into other queued pressure, then existing junk. Queued junk uses a longer telegraph above each lane before it lands, while misses add junk to your side and reduce health. Notes fall from the top into bottom receptors, and junk piles down from the top of each lane so the readable area gets tighter.

Battle rounds have a round clock and play as first-to-two sets. Defense notes can counter-clear pressure back onto the opponent when the clear is clean. If neither side is dead when time expires, the round resolves by a tiebreak value based on score, health, max combo, and remaining pressure. The results screen keeps a match summary with the finish reason, elapsed time, set score, value, and pressure totals, and battle summaries are saved locally for the song-select panel.

`Burst` builds from accurate hits. At full charge, press your bound burst key to clear some junk and pressure the other side.

## Controls

| Action | Keys |
| --- | --- |
| Menu select | `Enter` or mouse click |
| Menu navigation | `Up` / `Down` or mouse wheel |
| Menu back | `Escape` |
| Pause gameplay | `Escape` |
| Dance single left/down/up/right | arrow keys |
| P1 attack left/down/up/right | `A` / `S` / `W` / `D` |
| P1 defend left/down/up/right | `Left` / `Down` / `Up` / `Right` |
| P1 Burst | `Space` |
| P2 attack left/down/up/right | `J` / `K` / `I` / `L` |
| P2 defend left/down/up/right | `Numpad 4` / `Numpad 2` / `Numpad 8` / `Numpad 6` |
| P2 Burst | `Right Shift` |
| Restart match | `R` |
| Replay selected song | `P` from song select |
| Toggle performance overlay | `F3` |
| Import maps | Drop `.sm`, `.ssc`, `.osu`, `.jsc`, or a song folder onto the window |

All bindings can be changed from `Settings -> Rebind Keys`.

Controller defaults use the D-pad for menus, dance notes, and defense notes. The south/east face buttons select, the west face button backs out, face buttons play P1 attack lanes, right shoulder/trigger spends Burst, Start restarts, and left shoulder/trigger replays the selected song. Controller Layout changes this between split, inverted, dance-only, and off presets.

Battle input resolves one action per lane press. If attack and defense share a key, the game uses defense while your board has pressure, otherwise it uses the nearest hittable battle note instead of firing both actions. P1 dance bindings also work as a context-sensitive fallback in battle when no attack or defense key was pressed for that lane.

## Notefield settings

The settings menu separates scroll feel into familiar speed mods:

- `XMod`: note spacing follows chart beat spacing and BPM changes.
- `CMod`: notes scroll at a constant configured BPM.
- `MMod`: chart beat spacing is scaled so the chart's max BPM lands on the configured value.
- `Downscroll`: receptors stay at the bottom and notes fall from the top.
- `Upscroll`: receptors move to the top and notes rise from the bottom.
- `Mini`, `Hidden`, `Sudden`, and `Dark`: early appearance modifiers.
- `Pattern Density`: lowers chart density for practice or adds deterministic extra chord notes above 100%.
- `No Fail`: prevents health depletion from ending the local player's run in single-player and battle practice paths.
- `Battle Junk`: turns garbage pressure on or off for bot and local multiplayer battles.
- `Hit Feedback`: master switch for gameplay feedback.
- `Metronome`: optional beat click while playing.
- `Mute Music`, `Mute SFX`, and `Mute Hit Sounds`: quick audio cuts for practice and sync work.
- `Hit Sound Volume`: lowers judgement tap sounds without changing the other audio groups.
- `HUD Layout Editor`: opens a gameplay preview where HUD pieces can be dragged into place.
- `Theme`, `Note Shape`, `Lane Palette`, `Lane Glow`, `Board Opacity`, `Judgement Size`, and `Background Grid`: visual tuning for menus and gameplay.
- `Performance Overlay`: an FPS/frame-time diagnostic panel for tuning visuals, sync feel, and platform performance.
- `Controller Layout`: chooses the gamepad battle mapping preset.

Judgement feedback is split into separate pieces for judge text, combo, accuracy, timing text, timing bar, and judge counts. Players can keep the HUD sparse or make it dense.

## Build

The project uses CMake and fetches raylib automatically.

```powershell
cmake -S . -B build -G "Visual Studio 18 2026" -A x64
cmake --build build --config Debug
ctest --test-dir build -C Debug --output-on-failure
.\build\Debug\junkstep.exe
```

For Visual Studio 2022:

```powershell
cmake -S . -B build -G "Visual Studio 17 2022" -A x64
```

## Backend

The game client's online endpoint is fixed to `https://online.junkstep.com`. There is no in-game host or port setting.

For backend development and smoke testing, the standalone server can still run locally with:

```powershell
.\build\Debug\junkstep_server.exe 27016 server_data
```

In the app, open the top-right account dropdown and choose `Login`. New accounts are registered on `https://junkstep.com`, then used from the game. The same dropdown can fetch online players, friends, and rankings from `https://online.junkstep.com`.

The website at `https://junkstep.com` is the account and community surface. It supports registration, login, account password changes, public user/profile browsing, global and chart leaderboards, friend management, and the same global chat feed exposed in-game. In-game, press `F9` to open the online hub overlay for friends, chat, rankings, and users without leaving play.

The website source lives under `web/` as a Vite/React app. Use `npm install` once, `npm run dev` for local work, and `npm run build` to produce `web/dist/` for deployment.

Production should set `JUNKSTEP_DATABASE_URL` or `DATABASE_URL` to a PostgreSQL connection string. When the server is built with libpq available it stores accounts, issued session tokens, friends, scores, ratings, and chat messages in PostgreSQL. If the binary is built without PostgreSQL support or no database URL is set, it falls back to TSV files for local smoke testing.

The backend exposes:

- `POST /register` (requires the website registration source and a `junkstep.com` origin/referer)
- `POST /login`
- `POST /presence`
- `GET /deployment`
- `GET /account`
- `POST /account/password`
- `GET /online`
- `GET /users`
- `POST /friends/add`
- `GET /friends`
- `POST /scores`
- `GET /leaderboard`
- `GET /charts`
- `GET /profile`
- `GET /chat`
- `POST /chat/send`
- `POST /match/report`
- `POST /matches/queue`
- `GET /matches/state`
- `POST /matches/input`
- `POST /matches/burst`
- `POST /matches/leave`
- `POST /matches/finish`
- `GET /matches/spectate`
- `GET /matches/replay`
- `POST /matches/desync`
- `GET /rankings`

Online Match uses the backend server to assign slots, own the match id, lock the shared chart, and order input/burst events. Once both players are in the lobby, either side can pick a map from song select. The selected chart is serialized into a transfer package containing related difficulties and available media assets; if the other client does not already have that chart id, it downloads the package, imports it into `data/network_maps/`, and starts the same battle chart. Autoplay is disabled for multiplayer charts even if the player has it enabled for solo practice. The client polls the server event stream during play, reports the finish result back for multiplayer Elo updates, and keeps spectator/replay/desync endpoints available for debugging. LAN host/join uses the same song-select map-pick idea for same-network practice and quick testing. The backend now stores production data in PostgreSQL when configured and exposes deployment config, but it is still early-alpha grade; do not treat it as finished security.

## Importing charts

You can import charts by dropping them into the window or passing paths on the command line:

```powershell
.\build\Debug\junkstep.exe "C:\Etterna\Songs\Pack\Song Folder"
.\build\Debug\junkstep.exe "C:\Etterna\Songs\Pack\Song Folder\chart.sm"
.\build\Debug\junkstep.exe "C:\osu!\Songs\Beatmap Folder\chart.osu"
.\build\Debug\junkstep.exe "C:\Junkstep\Network Maps\shared-chart.jsc"
```

Imported StepMania and osu!mania notes stay as dance notes in single-player Dance mode. Battle modes convert the same imported chart into attack and defense notes so Junkstep rules still work.

Hold-note starts and tails are imported for StepMania/Etterna and osu!mania charts. A hold head gets the timing judgement, the tail stays visible while active, holding through the tail resolves quietly without adding a fake Perfect, and releasing early simply stops the tail from counting. The miss only happens if the hold head itself is never pressed.

If the chart has a `#MUSIC` file that raylib can decode, gameplay music starts after a short lead-in so the first notes can scroll into view. Song select also reads StepMania `#BANNER`, `#BACKGROUND`, `#SAMPLESTART`, and `#SAMPLELENGTH` when present. osu!mania imports use the background event and `PreviewTime`.

Supported StepMania timing fields:

- `#OFFSET`
- `#BPMS`
- `#STOPS` / `#FREEZES`
- `#DELAYS`
- `#WARPS`
- `#FAKES`

SSC chart-level timing tags override song-level timing tags when present.

Imported map, score, replay, battle summary, profile, asset, and settings data is stored locally under `data/maps.tsv`, `data/network_maps/`, `data/scores.tsv`, `data/replays.tsv`, `data/replays/`, `data/match_summaries.tsv`, `data/profiles.tsv`, `data/assets/`, and `data/settings.ini`. Replay files use the versioned `.jsr` debug format and include deterministic input counts/checksums. Network map transfer currently shares the playable chart data, not the original audio or banner files.

Asset packs are folder-based. Drop a folder containing `manifest.ini` into the app to import it under `data/assets/`, or copy/export it back out for sharing. The manifest fields are simple `key=value` lines:

```ini
type=noteskin
id=my-pack
name=My Pack
author=Ari
preview=preview.png
arrowLeft=left.png
arrowDown=down.png
arrowUp=up.png
arrowRight=right.png
```

Supported asset `type` values are `noteskin`, `theme`, `board`, `sfx`, and `music`.

## Project layout

- `src/game`: engine-agnostic gameplay simulation.
- `src/app`: raylib rendering, audio, input, settings, scenes, profiles, asset packs, replays, and local song data.
- `src/net`: LAN and online client networking.
- `src/server`: standalone C++ backend server.
- `tests`: smoke tests for timing, junk, import, and health behavior.
- `docs/PROJECT_PLAN.md`: design notes and production roadmap.
