Compare commits

...
5 Commits
Author SHA1 Message Date
jesse ae5b26b3eb fix: rojo died on SSH exit - launch via Task Scheduler, not Start-Process
CI / quality (push) Failing after 47s
Root cause: Windows OpenSSH kills the whole process tree when the SSH
session exits, so rojo started over SSH died seconds later. Proven:
1 proc inside the session, 0 from a fresh session.

Guard now calls 'schtasks /Run /TN RojoTWR' instead of Start-Process.
Verified unattended: killed rojo, scheduler restored it (Last Result: 0).
2026-08-06 22:05:36 -04:00
jesse 25d1e37750 docs: rojo watchdog (RojoTWR-Guard) — rojo does not stay up on its own
CI / quality (push) Failing after 1m4s
Verified by killing rojo and confirming the guard restored it with 34872
owned by 'rojo'. Guard refuses to start if the port is held by anything
else (sshd tunnel) rather than masking that bug.
2026-08-06 21:59:48 -04:00
jesse 83b3bf80c3 docs: rojo must run natively on windows; ServerPackages gitignored gotcha
CI / quality (push) Failing after 58s
Two real failures diagnosed 2026-08-06:
- a leftover 'ssh -N -R 34872' reverse tunnel made port 34872 answer as sshd
  while serving the LINUX tree; /api/rojo looked healthy the whole time
- ServerPackages is gitignored, so the windows clone had no wally deps and
  rojo exited on startup, which presents as 'rojo will not connect'
2026-08-06 10:33:36 -04:00
jesse 74899a7197 docs: PROJECT-BRIEF.md — game/design/resource orientation for Claude
CI / quality (push) Failing after 51s
CLAUDE.md covers tool operation; this covers what the game IS, where it
sits in the roadmap, and where reference assets actually live.

Also fixes a stale NAS path in README (RX-7 reference dir is
001-original-images/001-1993-Mazda-RX-7, not a top-level spaced name).
2026-08-06 09:35:05 -04:00
jesse dc0df9122e docs: project CLAUDE.md for native-Windows Claude Code handoff
CI / quality (push) Failing after 46s
Pure ASCII (PS 5.1 reads non-ASCII as ANSI and desyncs the parser).
Records the aux-lane suppression invariant, the verified 6e50f97 baseline,
the read/write channel split, and the probe scripts that check each metric.
2026-08-06 08:45:27 -04:00
4 changed files with 536 additions and 1 deletions
+139
View File
@@ -0,0 +1,139 @@
# CLAUDE.md -- Tokyo Wangan Racing
Guidance for Claude Code running **natively on `windows-gpu`**, the same machine
as Roblox Studio. Pure ASCII on purpose (PowerShell 5.1 reads non-ASCII as ANSI
and desyncs the parser).
---
## 1. Read this before touching MapBuilder
`src/server/MapBuilder.luau` generates the map procedurally. The user also edits
geometry **by hand in Studio**. Those two facts collide, and that collision is
the single most important thing about this repo:
> **A rebuild regenerates anything the source still describes.**
> If the user deleted geometry by hand and you rebuild without teaching the
> source about that deletion, you silently put it back.
The user WILL notice, immediately, and will say so ("you just added back the X
that I deleted"). When that happens: **stop editing, run a read-only probe,
report exact numbers.** Do not "fix" it with another rebuild.
### The live invariant: aux-lane suppression
```lua
local AUX_SUPPRESS_FROM = 525 -- samples 525..550 have NO aux lane
local AUX_SUPPRESS_TO = 550 -- because the user deleted that run by hand
local AUX_BLEND = 5 -- smoothstep width ramp, do not hard-cut
```
- **Do not remove or widen this range.** Re-enabling it resurrects deleted geometry.
- `AUX_BLEND = 5` is not cosmetic. A hard cut produced a **26-stud lateral fence
jog**. The smoothstep removes it.
- The parapet is force-closed across the same range (~line 1031). Normally the
parapet OPENS along a speed-change lane because the aux lane re-fences the
outer edge -- with no aux lane there is nothing to re-fence with, leaving the
deck edge bare. That was the measured barrier gap at samples 527..548.
Any change touching aux lanes, parapets, or barriers must re-run
`tools/probe_regression.luau` and match the baseline below.
---
## 2. Verified baseline (commit 6e50f97)
Measured in the live datamodel, not inferred from source:
| Metric | Value |
|---|---|
| total parts | 16788 |
| PAWestSlip | 60 segs, 34.4 deg total bend, worst 1.4, 0 kinks >5 |
| PAEastSlip | 60 segs, 30.7 deg total bend, worst 1.3, 0 kinks >5 |
| AuxLane parts | 30 (samples 525-550 deliberately absent) |
| barrier coverage | 823 samples, 0 unguarded |
| fence jogs >10 studs | 0 |
`AuxLane = 30` is the **user's hand-edited count**, not the source's original 52.
If a probe reports 52, a rebuild has undone the suppression -- that is a
regression, not progress.
---
## 3. Two channels, different jobs
```
[A] MCP bridge (roblox_studio) -> READ run Luau in Studio, measure, screenshot
[B] Rojo -> WRITE push .luau source into Studio (~1 s)
```
**Use [A] to verify what [B] did. Never assume a sync landed.**
Rojo syncs **scripts, not built geometry**. Editing MapBuilder replaces the
ModuleScript instantly, but `workspace.Map` still holds parts from a PREVIOUS
`build()` run. The map looks stale until you re-run the build. Do not tell the
user "you'll see it live" about map geometry.
**Rojo version must match the Studio plugin.** The plugin auto-updates from the
Marketplace without asking. Server older than plugin fails the handshake with a
cryptic `attempt to index number with 'protocolVersion'`. Both are pinned at
**7.7.0** (`rokit.toml`). If the user reports a connect error, check this first.
---
## 4. Workflow
```bash
make check # stylua --check + selene + rojo build
make build # rojo build --output build.rbxl
make serve # rojo serve (Studio: Rojo -> Connect)
make fmt # stylua src/
```
Probe scripts in `tools/` are **read-only** -- they measure the live datamodel
and never build. Run them through the MCP bridge.
| Script | Answers |
|---|---|
| `probe_regression.luau` | full baseline check vs the table above |
| `verify_auxlane.luau` | is the 525-550 suppression intact? |
| `verify_slips.luau` | slip-road bend angles / kink count |
| `probe_barriergap.luau` | any unguarded deck edge? |
| `probe_jog.luau` | lateral fence jogs |
---
## 5. Hard-won rules
- **Measure, then fix.** Do not infer a bug from symptoms. One inverted
convention can produce three unrelated-looking symptoms; fixing each symptom
leaves the root cause intact.
- **A metric derived from a constant cannot fail.** Ask: would this number
differ between the broken and fixed build? If not, it is not a test.
- **An empty result set must FAIL, never pass.** Count what you actually
exercised; `if tested == 0 then return "NOTHING TESTED -- FAILURE" end`.
- **Verification in Play mode proves nothing.** Play is a disposable sandbox
discarded on stop. Verify in Edit mode.
- **require() caches stale bytecode after a Rojo edit.** The cache still holds
old bytecode even though `.Source` is new -- no error, no warning. Clone the
module and require the clone.
- **Enumerate part names, don't just count.** A raw total hides partial removals
(deleted the tall barrier, missed the kerb). Tally what should be gone vs
what should remain, and report both.
- **AutoRecovery places are silently stale.** After a crash Studio reopens
`*_AutoRecovery_1.rbxl` holding pre-crash state. Check the title bar.
- **Creator Store assets: 50+ scripts or 1M+ tris is zero-tolerance.** Get exact
counts before inserting anything.
---
## 6. Shell
Git Bash (`C:\Program Files\Git\bin\bash.exe`) is present -- prefer it.
PowerShell over a non-interactive pipe mangles quoting. `&` is not a statement
separator in PowerShell. When PowerShell is unavoidable non-interactively, use
`-EncodedCommand` with UTF-16LE base64.
`StudioMCP.exe` lives inside the versioned Studio install and **moves on every
update**. Never hardcode it; `~/claude-tools/studio-mcp.bat` resolves it
dynamically.
+357
View File
@@ -0,0 +1,357 @@
# PROJECT-BRIEF.md — What we're building and where everything is
> **Read this first, before `CLAUDE.md`.**
> `CLAUDE.md` tells you *how to operate the tools safely*.
> This file tells you *what the game is, why it's built this way, and where the
> resources live*. You need both.
>
> Verified against the live repo, the NAS, and Gitea on **2026-08-06**. Every
> path and number below was checked, not recalled.
---
## 1. The game in one paragraph
**Tokyo Wangan Racing** is a Roblox night-driving game set on a neon Tokyo
expressway, built around a real 1993 Mazda RX-7 the owner actually owns. It is
modelled on **Tokyo Xtreme Racer**, not on lap-racing games: **Daikoku Futo PA
(the parking area) is the hub.** You park, choose how you want to run, and drive
out onto the Wangan. You never "join a match."
The core bet: **driving feel is the whole game.** Racing, economy, and
progression are all deliberately deferred until the car is genuinely fun to just
drive around. Do not add game layers on top of a car that doesn't feel good yet.
### The three modes
Selected in the PA, armed when you cross the exit gate, ended when you pull back
into the PA. Defined in `src/shared/Modes.luau`.
| Mode | 日本語 | What it is | Challenges? | Timed? |
|---|---|---|---|---|
| **CRUISE** | 流し | Free roam, bank kilometres, meet drivers | yes | no |
| **DUEL** | バトル | Open a gap and *hold* it — who breaks first | yes | no |
| **TIME ATTACK** | タイムアタック | Solo flying lap, sector splits, personal best | **no** | yes |
Cruise and Duel both allow challenges, so a cruise turns into a battle
organically. **Time Attack opts out on purpose** so a hot lap is never ruined —
that's a design decision, not an oversight. Don't "fix" it.
### The circuit
Closed loop, **~49,400 studs (13.8 km)**, samples evenly spaced 60 studs apart so
the loop closes seamlessly. Roughly a 2:46 lap at 300 km/h. Scale is
**1 stud = 0.28 m**. Coordinates: `+X = east`, `+Z = north`.
Landmark zones (themed, **not** geographically accurate): Daikoku Futo PA,
Rainbow Bridge, Odaiba (Unicorn Gundam + ferris wheel), Yamate-style tunnel,
Tokyo Tower replica, Tatsumi PA, bilingual green signage (大黒PA, レインボーブリッジ,
お台場, 辰巳PA).
---
## 2. Where you are in the plan
Source of truth: `docs/ROADMAP.md` and `docs/GDD.md`. Current position:
- **M0 — drive-only MVP: DONE.** Spawn, drive, switch cars, respawn, persistence.
- **M1 — make driving feel GOOD: IN PROGRESS. This is the current work.**
- done: constraint chassis (springs, prismatic suspension, servo steering,
motor wheels, engine braking), chase camera, two-layer engine sound
- **not done: import the real RX-7 mesh** (`docs/MESH_SWAP.md`)
- **not done: real 13B rotary audio** from the owner's actual car
(`docs/ROTARY_SOUND.md`) — current loops are stand-ins
- **not done: tune accel curve + grip** — needs a Studio playtest
- **M2 — more cars + map polish:** map v3 and ambience done; more meshes pending.
- **M3+ — racing, economy, hardening, traffic AI:** deliberately not started.
> **M1's exit test:** *the RX-7 is genuinely fun to just drive around.*
> Ship nothing else until that's true. If asked to build a racing/economy
> feature, flag that it's M3/M4 and confirm before starting.
---
## 3. Repo layout and architecture
Repo: `~/roblox/tokyo-wangan-racing` (Git Bash path on this box).
**Server-authoritative: clients request, the server decides.** The RX-7
allowlist and anti-cheat are server-side. Keep the client dumb.
```
ReplicatedStorage/Shared src/shared GameConfig, Cars, Circuit, Modes,
Chassis, Remotes, ProfileTemplate
ServerScriptService/Server src/server init.server + managers
ServerScriptService/ServerPackages ServerPackages ProfileStore (Wally)
StarterPlayerScripts/Client src/client HUD, car picker, controllers
```
Mapping is declared in `default.project.json` (Rojo).
| Module | Responsibility |
|---|---|
| `src/server/MapBuilder.luau` | **3,479 lines — the big one.** Procedurally builds the entire expressway, PA, ramps, barriers, signage. |
| `src/server/CarManager.luau` | Spawns the equipped car; enforces the RX-7 allowlist |
| `src/server/ModeManager.luau` | Mode selection, exit-gate arming, mode end |
| `src/server/DataManager.luau` | ProfileStore wrapper — session lock, reconcile, Studio mock |
| `src/server/AntiCheat.luau` | Speed/teleport sanity check — a speed bump, not a fortress |
| `src/server/Ambience.luau` | Atmosphere haze, bloom, 22:00→03:00 clock drift |
| `src/shared/Circuit.luau` | The 1,050-line circuit spline — sample positions, PA geometry |
| `src/shared/Chassis.luau` | Shared chassis tuning constants |
| `src/client/ChassisController.luau` | Drives the chassis client-side (driver owns network physics) |
| `src/client/CameraController.luau` | Damped chase cam, flat horizon, speed FOV kick |
| `src/client/SoundController.luau` | Two-layer engine crossfade + tyre squeal |
**Cars:** RX-7 (restricted), City Runabout (public default), Silvia S15, Skyline
GT-R R34, stylized hypercar. The **RX-7 is demo-locked** to the owner + his son
via `GameConfig.RX7AllowedUserIds`, enforced in `CarManager` and the `EquipCar`
remote. Stats only affect feel — no prices, no unlocks in the MVP.
---
## 4. Resources you can reach — and how
### 4a. Live Roblox Studio (this machine, local)
Two channels, different jobs. **Use [A] to verify what [B] did.**
```
[A] MCP bridge -> READ run Luau in the datamodel, screenshot the viewport
[B] Rojo -> WRITE push .luau source into Studio (~1 s)
```
- `mcp__roblox_studio__execute_luau` — `datamodel_type` **must be `"Edit"`**,
never `"Server"` (Server edits a throwaway Play copy that dies on stop).
- `mcp__roblox_studio__screen_capture` — Studio's **3D viewport only**. Not the
desktop, not Explorer, not Blender.
- Rojo: `rojo serve default.project.json --port 34872`, then **somebody must
click Rojo → Connect in Studio.** It never auto-connects, and an ESTABLISHED
socket on 34872 is idle heartbeat, *not* a sync session.
Full operational detail, version pins, and the failure decoder: **`CLAUDE.md`**.
#### Rojo must run natively on Windows — never over an SSH tunnel
**Rojo runs on the Windows box, in the Windows checkout.** Studio, Rojo and the
repo it serves all live on the same machine. Start it with:
```cmd
cd /d %USERPROFILE%\roblox\tokyo-wangan-racing
%USERPROFILE%\.local\bin\rojo.exe serve default.project.json --port 34872
```
A scheduled task `RojoTWR` does exactly this — `schtasks /Run /TN "RojoTWR"`.
(Launching via `start` over SSH does **not** work: the process dies when the SSH
session exits. Use the task.)
**Rojo does not stay up on its own.** The root cause was found on 2026-08-06:
**Windows OpenSSH kills the entire process tree when the SSH session exits.**
Anything started over SSH with `Start-Process` — including Rojo — dies seconds
later. This presents as "Rojo was running a minute ago and now the plugin won't
connect," and it is *not* a plugin fault.
Proof: `rojo procs: 1` inside the SSH session, `rojo procs: 0` from a new
session moments later.
The fix is to hand ownership to **Task Scheduler**, which is not part of the SSH
process tree:
```
schtasks /Run /TN "RojoTWR" start rojo detached (survives SSH exit)
%USERPROFILE%\rojo-guard.ps1 idempotent watchdog
schtasks /Query /TN "RojoTWR-Guard" runs the watchdog every 3 minutes
%USERPROFILE%\rojo-guard.log only logs real events, silent when healthy
```
Repo copy of the watchdog: `tools/win/rojo-guard.ps1`.
> **Never start Rojo with `Start-Process`/`start` over SSH.** Always go through
> `schtasks /Run /TN "RojoTWR"`. The guard itself follows this rule internally.
The guard is deliberately conservative:
- if `rojo` already owns 34872 it exits 0 and does nothing (safe mid-session —
it will not disturb a live Studio connection)
- if the port is held by **anything else** (e.g. an `sshd` tunnel) it refuses to
start and logs `PORT HIJACKED`, rather than masking the bug
- if `ServerPackages` is missing it logs that instead of letting Rojo silently
exit on startup
Verified end-to-end: killed `rojo`, waited, and the scheduler restored it
unattended (`Last Result: 0`, port owned by `rojo`).
**Clicking Rojo → Connect in Studio is still manual and always will be.** The
guard keeps the *server* alive; it cannot press the button. `Rojo_autoReconnect`
is `false` in the plugin settings, so after a drop you must reconnect by hand
(or flip that setting in the plugin).
> ⚠️ **Do not reverse-tunnel a Linux Rojo to port 34872.** It was tried
> (`ssh -N -R 34872:127.0.0.1:34872`) and it is actively harmful: the port
> answers, `/api/rojo` returns valid JSON, and everything *looks* healthy — but
> it is serving the **Linux** working tree. Studio then syncs files that are not
> the ones being edited on Windows, and `netstat` shows the port owned by
> **sshd** rather than `rojo`. If port 34872 is owned by anything other than a
> process literally named `rojo`, that is the bug.
>
> Diagnostic that settles it in one line:
> ```powershell
> $l=(netstat -ano | Select-String ":34872.*LISTENING"); (Get-Process -Id (($l[0] -split "\s+")[-1])).ProcessName
> ```
> Expect `rojo`. If you get `sshd`, kill the tunnel.
#### `ServerPackages` is gitignored — Rojo will not start without it
`/Packages/`, `/ServerPackages/` and `wally.lock` are **gitignored**, so a fresh
clone has no dependencies and Rojo **refuses to start**, exiting immediately:
```
[ERROR rojo] Rojo project referred to a file using $path that could not be
turned into a Roblox Instance by Rojo. File $path: ServerPackages
```
That error means *missing Wally deps*, not a broken project file. There is only
one dependency (`ProfileStore`, 19 files / 316 KB). Fix with `wally install`, or
copy the folder from a machine that already has it. **Rojo exiting on startup is
silent from Studio's side — it just looks like Rojo "won't connect."**
### 4b. Reference material on the NAS (1.5 GB, verified present)
Host `unraid-1` (10.10.10.43), reachable over SSH. Base path:
```
/mnt/user/data/yy-roblox-games/0-tokyo-wangan-racing/
```
| Folder | Contents (verified counts) |
|---|---|
| `001-original-images/001-1993-Mazda-RX-7/` | **12 files** — the real car, for the mesh swap |
| `001-original-images/002-1993-Nissan-Skyline-GT-R/` | **38 files** |
| `003-google-earth-images/` | **12 files** — `daikoku-futo-01..12.png`, aerial geometry |
| `004-video-reference/` | `aerial/`, `contact-sheets/`, `docs/`, `entrance-routes/` |
| `002-completed-models/` | empty — finished models land here |
> ⚠️ **The README currently prints a stale path** for the RX-7 reference
> (`001-1993 Mazda RX-7/` with spaces, at the top level). The real path is
> `001-original-images/001-1993-Mazda-RX-7/` with hyphens. Use the real one.
Copy a file over rather than working blind:
```bash
scp unraid-1:'/mnt/user/data/yy-roblox-games/0-tokyo-wangan-racing/003-google-earth-images/daikoku-futo-01.png' /tmp/
```
### 4c. Reference material committed in-repo
- `reference/ramps/` — 12 integrity-checked photos of Daikoku/Oi/Heiwajima/
Shibaura PAs, plus `INDEX.md`, `BUILD_SPEC.md`, and OSM/satellite scripts.
**All CC BY / CC BY-SA — reference-only for shape study. Do not trace directly
into shipped assets without attribution.**
- `docs/daikoku-reference-spec.md` — measured spec for the PA entrance geometry
- `docs/japanese-road-standards.md` — real road-geometry standards
- `docs/CAR_PHYSICS_REFERENCE.txt`, `docs/MESH_SWAP.md`, `docs/ROTARY_SOUND.md`
- `assets/audio/` — current stand-in RX-7 loops (idle / rev / whistle)
- `audio-work/` — raw recordings from real car meets, for the rotary audio work
- `c1_georeference.json` — georeferencing data behind the circuit
### 4d. Git / CI
Remote: `ssh://[email protected]:2222/jesse/tokyo-wangan-racing.git`
(self-hosted Gitea on UNRAID-1, SSH key auth already working from this box).
CI runs on a **self-hosted Gitea Actions runner** (`.gitea/workflows/ci.yml`),
Ubuntu 24.04 job container — rojo's binary needs glibc 2.39.
### 4e. Existing tooling — check before writing new probes
`tools/` already holds **109 files, 58 of them diagnostic/verification scripts**
(`diag_*.luau`, `verify_*.luau`, `capture_*.py`, `check_*.luau`). There is a
very high chance a probe already exists for what
you need — `verify_auxlane.luau`, `verify_shared_ramps.luau`,
`check_live_source.luau`, `scripts/verify_map.luau`, and capture helpers for
flyovers/ramps/defects. **Look there first.**
---
## 5. Quality gates — CI enforces all three
```bash
stylua --check src/ # formatting
selene src/ # lint
rojo build --output build.rbxl # must compile
```
Conventions:
- Tunables go in `GameConfig`, not scattered magic numbers.
- Add a save key by adding it to `ProfileTemplate` (auto-reconciles). Never
remove a key without a migration.
- Test in Studio after every server change (ProfileStore uses a mock in Studio).
---
## 6. The things that will bite you
These are compressed from hard-won experience. The long versions live in
`CLAUDE.md` — but these three matter most:
### Never regenerate hand-deleted work
**The single most important rule on this project.** If the user deleted
something in Studio, the *source* must honor it or the next rebuild silently
resurrects it — and they will catch it immediately.
Live example, currently in `MapBuilder.luau`:
```lua
AUX_SUPPRESS_FROM = 525 -- user deleted this aux-lane stretch
AUX_SUPPRESS_TO = 550
AUX_BLEND = 5 -- smoothstep; a hard cut caused a 26-stud fence jog
```
**Do not remove that suppression.** Hand-placed assets live in
`workspace.PAProps`, which the builder never touches. Before any rebuild, ask:
*does this regenerate something removed by hand?*
### Measure before reasoning, and re-measure after
Write a **read-only probe**, get real numbers, *then* form a theory. A code
change is not proof — verify against the bug's signature. Enumerate names, not
just totals: a raw count hides a partial removal.
### Beat the `require()` cache
After a Rojo sync the datamodel still holds **old bytecode** — no error, no
warning, and you will measure the previous build and believe it:
```lua
local tmp = game.ServerScriptService.Server.MapBuilder:Clone()
tmp.Parent = game.ServerScriptService
local MapBuilder = require(tmp) -- fresh bytecode, guaranteed
```
### Verified-good baseline (2026-08-06) — regression-check against these
| metric | value |
|---|---|
| total parts | 16,788 |
| PAWestSlip | 60 segs, 34.4° total bend, worst 1.4°, 0 kinks >5° |
| PAEastSlip | 60 segs, 30.7° total bend, worst 1.3°, 0 kinks >5° |
| AuxLane parts | 30 — **samples 525–550 deliberately absent (user-deleted)** |
| barrier coverage | 823 samples, 0 unguarded |
| fence jogs >10 studs | 0 |
### Never fabricate
If Studio is closed, say "Studio is closed." Never invent a screenshot, a part
count, or a measurement you didn't obtain. `{'error': '', 'isError': True}` on
every MCP call means **Studio is closed** — not that your script is broken.
Reporting a blocker honestly always beats a plausible-looking fake.
---
## 7. First five minutes in a new session
```bash
tasklist | findstr /I "Roblox" # 1. is Studio even running?
claude mcp list # 2. bridge registered + connected?
curl -s http://127.0.0.1:34872/api/rojo # 3. rojo serving?
cd ~/roblox/tokyo-wangan-racing && git status --short && git log --oneline -5
```
Then read `docs/ROADMAP.md` to confirm what milestone is active, and check
`tools/` for an existing probe before writing a new one.
**Working state as of 2026-08-06:** clean tree, 0 uncommitted files, HEAD at
`dc0df91`. (An older note in `START-HERE.md` warns about "36 uncommitted files"
on the Linux box — that has since been committed and pushed. Trust `git status`
over that note.)
+2 -1
View File
@@ -102,4 +102,5 @@ the `EquipCar` remote).
One function changes: `CarManager.buildCarModel`. Full step-by-step in
`docs/MESH_SWAP.md`. Reference photos live on the NAS at
`unraid-1:/mnt/user/data/yy-roblox-games/0-tokyo-wangan-racing/001-1993 Mazda RX-7/`.
`unraid-1:/mnt/user/data/yy-roblox-games/0-tokyo-wangan-racing/001-original-images/001-1993-Mazda-RX-7/`
(12 files). See `PROJECT-BRIEF.md` for the full reference-asset inventory.
+38
View File
@@ -0,0 +1,38 @@
# rojo-guard.ps1 -- keep Rojo alive for Tokyo Wangan Racing.
# Idempotent: starts Rojo only if it is not already listening on 34872.
# Safe to run every few minutes from Task Scheduler.
$ErrorActionPreference = "SilentlyContinue"
$repo = Join-Path $env:USERPROFILE "roblox\tokyo-wangan-racing"
$exe = Join-Path $env:USERPROFILE ".local\bin\rojo.exe"
$port = 34872
$log = Join-Path $env:USERPROFILE "rojo-guard.log"
function Log($m) { "$(Get-Date -f 'yyyy-MM-dd HH:mm:ss') $m" | Add-Content $log }
# Is something already LISTENING on the port?
$listening = netstat -ano | Select-String ":$port\s.*LISTENING"
if ($listening) {
$ownerPid = ($listening[0] -split "\s+")[-1]
$owner = (Get-Process -Id $ownerPid).ProcessName
if ($owner -eq "rojo") { exit 0 } # healthy, nothing to do
Log "PORT HIJACKED by '$owner' (pid $ownerPid) -- not starting. Kill it first."
exit 1 # sshd tunnel or similar: do NOT mask it
}
# Dependencies must exist or rojo exits instantly.
if (-not (Test-Path (Join-Path $repo "ServerPackages"))) {
Log "ServerPackages MISSING -- rojo would exit on startup. Run 'wally install'."
exit 1
}
# Launch DETACHED via Task Scheduler.
# Do NOT use Start-Process here: if this script is ever invoked over SSH,
# Windows OpenSSH kills the whole process tree on session exit and rojo dies
# seconds later. schtasks hands ownership to the scheduler, which survives.
schtasks /Run /TN "RojoTWR" | Out-Null
Start-Sleep -Seconds 4
if (Get-Process rojo -EA SilentlyContinue) { Log "started rojo on $port" }
else { Log "FAILED to start rojo" }