Skip to content

feat(game): choose a game mode before entering the yard#31

Merged
ralyodio merged 1 commit into
mainfrom
feat/game-modes
Jul 26, 2026
Merged

feat(game): choose a game mode before entering the yard#31
ralyodio merged 1 commit into
mainfrom
feat/game-modes

Conversation

@ralyodio

Copy link
Copy Markdown
Contributor

/play only ever opened one scene — Team Deathmatch against bots — with no way to see or change that. It now asks first.

Mode What's in the yard
Team Deathmatch Four Directorate against you and three Nightcell. They flank, shoot back and throw.
Firing Range Stationary targets. Nothing returns fire — for learning the rifle and the grenade arc.
Free Roam The yard, empty.

Two of the three already existed and had no entry point. TrainingTargets and the empty greybox are the "training" and "greybox" technical scenes PRD §23.1 already lists under the free sandbox. This exposes built work rather than adding scope.

Not new competitive modes

The locked V1 decision is one multiplayer mode — 6v6 Team Deathmatch — and inventing a second would contradict it. Everything here is single-player against NPCs and sits on the free side of the access line, so /play stays openable without an account. Worth saying plainly since the request started from "can we have a multiplayer demo": this is NPCs only, no networked play.

Notes

  • Opponents now takes a roster size, and an empty roster is a supported configuration, not a degenerate one: it owns the MatchSimulation, which is where the player's own grenade count, cooldown and blast are resolved, so it is built in every mode including the ones with no bots.
  • The picker is a real fieldset/radio group, so arrow-key navigation and screen-reader announcement come for free (accessibility is P0). The native input is visually hidden but keeps focus, so the focus ring is drawn on the label — without that the group is keyboard-navigable and invisible to anyone using it that way.
  • Changing mode reloads rather than re-dressing the yard live: roster and targets are decided at boot, and swapping them in place would be a second, subtly different code path for something that happens once.
  • The choice is remembered on change, not on start, so picking a mode and then reloading does not silently lose it. ?mode= beats the remembered value so a shared link opens what it says.
  • ?mode= is also read by access.ts for demo/campaign/multiplayer. The two never collide — access values are not scene ids and fall through to the default — and there is a test pinning exactly that.

Verification

Typecheck, lint, 245 tests (10 new) and the build pass. All three modes were booted in a browser and screenshotted: the picker renders with the selected mode highlighted and its blurb below, Firing Range spawns stationary targets with no roaming bots, and Free Roam is empty. No console errors in any of the three.

🤖 Generated with Claude Code

`/play` only ever opened one scene — Team Deathmatch against bots — with no way
to see or change that. It now asks first, offering three modes on the start
gate.

Two of the three already existed and had no entry point. `TrainingTargets`
(stationary targets, no return fire) and the empty greybox are the "training"
and "greybox" technical scenes PRD §23.1 already lists under the free sandbox;
this exposes built work rather than adding scope.

These are deliberately not new competitive modes. The locked V1 decision is one
multiplayer mode, 6v6 Team Deathmatch, and inventing a second would contradict
it. Everything here is single-player against NPCs and sits on the free side of
the access line, so `/play` stays openable without an account.

`Opponents` takes a roster size, and an empty roster is a supported
configuration rather than a degenerate one: it owns the `MatchSimulation`, which
is where the player's own grenade count, cooldown and blast are resolved, so it
is built in every mode including the ones with no bots.

The picker is a real fieldset and radio group, so arrow-key navigation and
screen-reader announcement come for free (accessibility is P0). The native
input is visually hidden but keeps focus, so the focus ring is drawn on the
label instead — without that the group is keyboard-navigable and invisible to
anyone using it that way.

Changing mode reloads rather than re-dressing the yard live: the roster and the
targets are decided at boot, and swapping them in place would be a second,
subtly different code path for something that happens once. The choice is
remembered on change rather than on start, so picking a mode and then reloading
does not silently lose it, and `?mode=` beats the remembered value so a link
opens what it says.

Note `?mode=` is also read by `access.ts` for demo/campaign/multiplayer. The two
never collide — access values are not scene ids and fall through to the default
— and there is a test pinning that.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown

vu1nz Security Review

0 finding(s) in PR #?

No security issues found.

@ralyodio
ralyodio merged commit 3ba560b into main Jul 26, 2026
6 checks passed
@ralyodio
ralyodio deleted the feat/game-modes branch July 26, 2026 19:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant