Commit Graph
32 Commits
Author SHA1 Message Date
FrankandClaude Sonnet 5 6b1436dfe7 Randomize decoy usernames and which rapports carry coded messages
Decoy names in /var/home and verifyCodes.txt were a hardcoded 4-name
list (Luke, Charles, William, Peter), so a real player registering
under one of those names would collide with it. Now each session
picks 6 decoys from a pool of 10, excluding any name already taken by
a real player, and stores the pick as a session setting.

Likewise the 3 "special report" rapports that get coded messages were
always Doyle, Vega and Lennox. Each session now randomly assigns 3 of
the 20 rapports to that role, and the win screen / mainframe-help
message reference whichever agents were actually picked instead of
the hardcoded names.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-22 17:09:52 +02:00
Frank 367ded6441 Only regenerate verify codes on broadcast, not any targeted /chat
Any /chat {agent-id} {code} was previously exempted only if aimed at
the code's rightful owner; sending to the wrong player still burned
it. Now only an untargeted, open-chat broadcast counts as a leak -
mistargeting via /chat is harmless.
2026-08-18 21:49:45 +02:00
Frank 1f35784c52 Don't regenerate a verify code when sent privately to its rightful owner
checkAndRegenerateVerifyCodes() regenerated a code on any message
containing it, regardless of who the message was sent to - so sharing
a code exactly as intended, via /chat {agent-id} {code} to the
correct recipient, still burned it immediately, making the puzzle
unsolvable. Now only broadcasts and messages sent to the wrong player
count as a leak.
2026-08-18 21:47:21 +02:00
Frank b0b357f99b Strip spaces from generated decode report codes
generateSpecialCode() reused generateRandomString(), whose charset
includes a space. Across a 75-100 character code that made a space
almost certain, and /decode splits its argument on spaces, silently
truncating the code the player typed back in - breaking decoding.

Fixed at generation time rather than at display time, since the
stored value itself was the problem, not just how it's shown.
generateRandomString() is left untouched for its other use (the
"garbage" filler text shown to players who fail to decode), where
spaces are harmless.
2026-08-18 21:32:06 +02:00
FrankandClaude Sonnet 5 a00899e444 Route every Mercure publish through an event, log pushes per-player
Every $hub->publish() call site (chat, hints, security alerts, virus
alerts, game_finished, and the pre-game lobby events) now dispatches
a PushMercureMessageEvent instead of publishing directly. Two
listeners handle it: PublishMercureMessageListener does the actual
Mercure publish exactly as before (same wire format, no client
changes needed), and LogMercureMessageListener appends it to the
activity log of whichever player(s) it was actually delivered to.

A private /chat to one agent only gets logged for that agent, never
broadcast into everyone's transcript - the event carries an explicit
targetScreen (null = everyone) rather than leaving listeners to guess
from the payload shape.

The per-player log format moved from flat text to JSON Lines (one
timestamped, structured entry per line) via a new shared
SessionActivityLogger service, since a flat string can't carry an
event's type or exact payload - both needed for a future feature to
replay a player's full session history, not just their own commands,
on page reload. The admin session log viewer now parses and
formats these entries back into readable lines.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-18 19:47:53 +02:00
FrankandClaude Sonnet 5 2951a39204 Add mainframe hint system and cron-driven timeout handling
The cron (app:hints:check, every minute) now walks each playing
player through a strict dependency chain - help, communication,
verification, navigation, rapports/decode, endgame urgency - and
sends at most one hint per player per run: the first step that's
both unresolved and old enough to nudge about. Later steps genuinely
depend on earlier ones (e.g. /verify isn't usable until a player has
finished contacting everyone), so a player never gets a hint for
something they can't act on yet.

Two bits of new tracking were needed:
- Help command usage wasn't recorded anywhere, so it's now saved to
  a new HelpUsedForPlayer{N} session setting.
- "All messages decoded" needed a reliable session-wide signal, so
  the rm right (previously granted alongside sudo when player 1
  decodes) now comes from player 3's decode instead, making
  sudo+scan+rm together mean "the whole team has decoded".

The cron also now finishes sessions whose countdown has run out:
sets the session to LOST and broadcasts the same game_finished
Mercure event the win path already uses, so every connected player
gets pushed to the lost page within a minute of the game actually
ending - not just whichever player's own client-side timer happens
to notice first.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-16 17:56:16 +02:00
FrankandClaude Sonnet 5 daa37390d0 Fix path traversal via username in session log file paths
Registration only validated username with NotBlank, so a username
like "../../../../public/x" got concatenated directly into a
filesystem path for both writing (game activity logs) and reading
(admin log viewer) - reachable from the public webroot since public/
is a few directories up from where those logs are stored.

Adds a character-set validator (letters, numbers, underscore, hyphen)
to registration and admin user editing going forward, and sanitizes
at the point of use (Player::getLogFileBasename()) so any
already-stored unsafe username can't escape the log directory either.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-10 21:41:25 +02:00
FrankandClaude Sonnet 5 2bfc2aca1a Keep pregame lobby chat open for an hour after the game ends
The chat used to disappear the moment a session left CREATED status.
Sessions now record a finishedAt timestamp when they're won or lost,
and the lobby (with chat) stays reachable via /game/{session} for an
hour afterward instead of immediately redirecting to the win/lose
feedback page. The lobby template shows a distinct "game finished"
header with a link to that feedback page during this window.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-10 16:47:57 +02:00
FrankandClaude Sonnet 5 98066118a6 Expand terminal filesystem, add mainframe hint cron, redirect PHP logs
- Game1 terminal: flesh out the virtual filesystem with a realistic
  spread of Linux directories/files (~70 dirs, ~140 files) so it no
  longer reads as an obviously small puzzle set, without touching any
  win-condition or rapport files.
- Add app:hints:check command + a php-cron container (BusyBox crond)
  that nudges players who haven't contacted every teammate 5 minutes
  into a session, via a new 'hint' Mercure message type.
- Log the cron command's output to var/log/cron/cron.log and rotate
  it (25MB / 90 days) via logrotate, run daily from the same crontab.
- Redirect PHP's error_log and Symfony's prod app/deprecation logs
  from stderr-only into var/log/php/*.log (kept alongside stderr),
  with the same rotation policy.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-12 23:34:36 +02:00
Frank 3984a33282 Add win/lose game-ending flow
Removing all 3 locked files while the timer is still running now marks
the session WON immediately (checked right after every successful rm)
and broadcasts a "game_finished" signal over Mercure so every
connected player gets redirected together, not just the one who
removed the last file. A new /won/{session} route + won.html.twig
mirrors the existing lost flow (victory narrative + the same feedback
form).

The existing timer-expiry path already set LOST but always redirected
to lostUrl regardless of actual status; it now picks won/lost based on
the status the server reports.

Also fixes a pre-existing bug on the lost page (and would-be bug on
the new won page): PlayerService::GetCurrentlyActiveAsPlayer() only
matches players in READY/PLAYING sessions, so by the time a session
has ended it always returned null there, silently breaking the
feedback form. Both pages now look the player up directly via
PlayerRepository instead.

Added a navigatingAway flag so the page's "confirm before leaving"
prompt doesn't block our own win/lose redirects.
2026-07-11 21:41:16 +02:00
Frank eee6c3a369 Auto-trigger locked-files restoration exactly at the 60s deadline
Previously the restore check only ran lazily on a player's next
message, so files could stay wrongly-removed for an arbitrary amount
of time after the window expired. The frontend now schedules a
setTimeout (using the server-provided deadline, mirroring the terminal
lock's reveal timer) that pings the backend right at the deadline so
the check runs promptly regardless of player activity. Restored via
data-files-removal-deadline on page load too, so a refresh mid-window
doesn't lose the timer.
2026-07-11 17:45:08 +02:00
Frank e6ba469ef9 Restore locked files if not all removed within 60 seconds
Removing one of the 3 AI-virus-protected files now starts a 60-second
window (tracked session-wide via LockedFilesRemovalDeadline). If the
other locked files aren't also removed before it expires, the virus
restores whichever ones were deleted and broadcasts a red warning to
the whole session, forcing players to coordinate the removal instead
of picking them off one at a time.

Also extends the Mercure broadcast payload with an optional 3rd
"messageType" element so pushed messages can render red (virus) or
green (mainframe) instead of always defaulting to green.
2026-07-11 17:26:59 +02:00
Frank 6fd0b5d993 Grant rm right alongside sudo when player 1 decodes their message
rm was checked everywhere but never actually added to any player's
rights, so the command was unreachable until now.
2026-07-11 17:08:44 +02:00
Frank 6db9d42852 Wire up decode puzzle chain and lock terminal on file removal
Connects the previously-disconnected /decode command to per-player
messages: player 1 unlocks sudo for everyone, player 2 unlocks a scan
command that reveals which files the AI virus has locked, player 3
learns those files should be removed.

Also adds a retaliation mechanic: successfully removing a file locks
the player's terminal. The AI virus locks it out in red immediately;
the mainframe reveals a 12-character recovery code in green after 30
seconds, which can be submitted via /unlock to restore access early,
otherwise the terminal auto-recovers after 45 seconds.
2026-07-11 16:48:15 +02:00
FrankandClaude Sonnet 5 05f4f2c32f Fix EveryoneVerified setting being scoped to a specific player
checkIfAllPlayersVerified() read and wrote the EveryoneVerified
setting scoped to whichever player happened to trigger the check
(getSetting(..., $player) / setPlayer($player)), but it's meant to be
a single session-wide flag. getFileContent() correctly reads it as
session-global (no player, filters player IS NULL), so the two never
matched: the "everyone verified" Mercure message still fired (that
code path only checks its own player-scoped copy), but the special
code injection into the Doyle/Vega/Lennox report files never ran
since getFileContent() never found the setting. Store and read it
consistently as session-global.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-04 18:19:14 +02:00
FrankandClaude Sonnet 5 ddd2726687 Fix cat command using relative path for physical file lookup
getFileContent() built the game1 filesystem path as a relative
string with no leading slash. That only resolves correctly when
PHP's cwd happens to be the project root (e.g. CLI), but under
PHP-FPM/nginx the cwd is nginx's document root (public/), so
file_exists() always failed for real requests even though the file
existed and the in-memory virtual file list (used by ls) said it
should. Use the already-injected $projectDir (%kernel.project_dir%),
matching how the class already builds the session log path.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-04 18:12:55 +02:00
Frank van den BergandClaude Sonnet 4.6 aa56617e50 hub location mercure adjustment
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-28 23:09:35 +02:00
Frank 928ab3cbfe Added mercure to update when everyone is ready 2026-01-08 20:11:14 +01:00
Frank 4d021e7cf1 Trying to add waiting pages 2026-01-08 19:32:13 +01:00
Frank 9d9de0fd0d Logfiles for sessions 2026-01-08 18:14:56 +01:00
Frank 2984a6acb2 Most of the cat command 2026-01-08 17:02:16 +01:00
Frank cdb469456f Dynamic number of players 2026-01-08 15:49:47 +01:00
Frank b6c09c267f Message when everyone is verified 2026-01-08 15:34:43 +01:00
Frank 5eff66c24d Post return messages 2026-01-08 11:41:46 +01:00
Frank cdd5bc3fd8 Verification done 2026-01-07 20:06:28 +01:00
Frank 173407cd26 ls 2026-01-07 17:45:17 +01:00
Frank 90e5fd904f Sudo, rm and setup for ls 2026-01-07 15:00:28 +01:00
Frank 225c14124a Hint for first part 2026-01-07 14:33:10 +01:00
Frank 812dc06988 Toevoeging van meer responses op messages 2026-01-05 23:37:36 +01:00
Frank 376ed3f592 Updated rechten voor speler. Settings toegevoegd en onderdelen voor game1 toegevoegd. 2026-01-05 17:07:32 +01:00
Frank 0543ed43b9 Messages handling voor spel 1 2026-01-05 15:27:37 +01:00
Frank 654b4036b4 Quite some work done here. 2026-01-03 22:12:51 +01:00