Phase 4: character-profile request/response
BridgeProfile builds the read-models the website consumes; BridgeRequests
registers the inbound handlers. The sidecar asks, the shard answers on the Core
thread (inbound lines are marshaled through Timer.DelayCall before a handler
runs), so all of these read live world state safely.
- char.request: resolve by serial, or by account + slot, and reply with a full
profile (stats, all trained skills, worn equipment with flattened AOS mods,
resists). Works for offline characters since a logged-off mobile stays
resident until Delete.
- account.roster: light per-character summary, offline chars included.
- vendor.snapshot: every player vendor owned by an account, with held gold and
priced listings.
Each request may carry a reqId the reply echoes so the sidecar can correlate.
An unresolvable request gets a bridge.error reply rather than silence, so the
website can show a real failure instead of hanging.
Verified against the real world with a sending stub: all five requests answered,
both char lookup paths (account+slot and serial) returning the identical profile,
vendor.snapshot returning seed_000's two vendors and 80 listings, and the bad
account returning bridge.error. Two real-data findings noted in docs/PLAN.md §14:
a GM character can have skill base > cap (the website must not assume otherwise),
and the mod-flattening path still wants a genuinely kitted character to exercise
against real suffix gear.
Adds tools/stub_sidecar_request.ps1 (sends requests) and a hardened
tools/stub_sidecar.ps1 (survives reaping/rebind).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -1,31 +1,45 @@
|
||||
param(
|
||||
[int] $Port = 7788,
|
||||
[string] $Log = "$PSScriptRoot\sidecar_loop.log"
|
||||
[string] $Log = "$PSScriptRoot\sc_robust.log"
|
||||
)
|
||||
|
||||
$ErrorActionPreference = 'Stop'
|
||||
"[sidecar] listening on 127.0.0.1:$Port" | Out-File $Log -Encoding utf8
|
||||
# Robust stub sidecar: survives port-in-use from a just-killed instance, and never
|
||||
# dies on a transient error. Test scaffolding only.
|
||||
|
||||
function Say($msg) {
|
||||
for ($i = 0; $i -lt 5; $i++) {
|
||||
try { "$msg" | Out-File -FilePath $Log -Append -Encoding utf8; return }
|
||||
catch { Start-Sleep -Milliseconds 100 }
|
||||
}
|
||||
}
|
||||
|
||||
"" | Out-File -FilePath $Log -Encoding utf8
|
||||
Say "[sidecar] starting on 127.0.0.1:$Port"
|
||||
|
||||
$listener = New-Object System.Net.Sockets.TcpListener([System.Net.IPAddress]::Loopback, $Port)
|
||||
$listener.Start()
|
||||
$listener.Server.SetSocketOption('Socket', 'ReuseAddress', $true)
|
||||
|
||||
# Wait for the port to become bindable if a prior instance is still lingering.
|
||||
$bound = $false
|
||||
for ($i = 0; $i -lt 30 -and -not $bound; $i++) {
|
||||
try { $listener.Start(); $bound = $true }
|
||||
catch { Say "[sidecar] bind retry: $($_.Exception.Message)"; Start-Sleep -Seconds 1 }
|
||||
}
|
||||
if (-not $bound) { Say "[sidecar] could not bind $Port; giving up"; exit 1 }
|
||||
|
||||
Say "[sidecar] listening"
|
||||
|
||||
while ($true) {
|
||||
try {
|
||||
$client = $listener.AcceptTcpClient()
|
||||
"[sidecar] === shard connected ===" | Add-Content $Log
|
||||
|
||||
$stream = $client.GetStream()
|
||||
$reader = New-Object System.IO.StreamReader($stream)
|
||||
|
||||
while ($null -ne ($line = $reader.ReadLine())) {
|
||||
"[sidecar] <- $line" | Add-Content $Log
|
||||
}
|
||||
|
||||
"[sidecar] === shard disconnected ===" | Add-Content $Log
|
||||
Say "[sidecar] === shard connected ==="
|
||||
$reader = New-Object System.IO.StreamReader($client.GetStream())
|
||||
while ($null -ne ($line = $reader.ReadLine())) { Say "[sidecar] <- $line" }
|
||||
Say "[sidecar] === shard disconnected ==="
|
||||
$client.Close()
|
||||
}
|
||||
catch {
|
||||
"[sidecar] error: $_" | Add-Content $Log
|
||||
Start-Sleep -Milliseconds 200
|
||||
Say "[sidecar] loop error: $($_.Exception.Message)"
|
||||
Start-Sleep -Milliseconds 300
|
||||
}
|
||||
}
|
||||
|
||||
65
tools/stub_sidecar_request.ps1
Normal file
65
tools/stub_sidecar_request.ps1
Normal file
@@ -0,0 +1,65 @@
|
||||
param(
|
||||
[int] $Port = 7788,
|
||||
[string] $Log = "$PSScriptRoot\sc_request.log"
|
||||
)
|
||||
|
||||
function Say($msg) {
|
||||
for ($i = 0; $i -lt 5; $i++) {
|
||||
try { "$msg" | Out-File -FilePath $Log -Append -Encoding utf8; return }
|
||||
catch { Start-Sleep -Milliseconds 100 }
|
||||
}
|
||||
}
|
||||
|
||||
"" | Out-File -FilePath $Log -Encoding utf8
|
||||
Say "[req] starting on 127.0.0.1:$Port"
|
||||
|
||||
$listener = New-Object System.Net.Sockets.TcpListener([System.Net.IPAddress]::Loopback, $Port)
|
||||
$listener.Server.SetSocketOption('Socket', 'ReuseAddress', $true)
|
||||
|
||||
$bound = $false
|
||||
for ($i = 0; $i -lt 30 -and -not $bound; $i++) {
|
||||
try { $listener.Start(); $bound = $true }
|
||||
catch { Start-Sleep -Seconds 1 }
|
||||
}
|
||||
if (-not $bound) { Say "[req] could not bind"; exit 1 }
|
||||
|
||||
Say "[req] listening"
|
||||
$client = $listener.AcceptTcpClient()
|
||||
Say "[req] === shard connected ==="
|
||||
|
||||
$stream = $client.GetStream()
|
||||
$reader = New-Object System.IO.StreamReader($stream)
|
||||
$writer = New-Object System.IO.StreamWriter($stream)
|
||||
$writer.AutoFlush = $true
|
||||
|
||||
# Give the shard a beat to send its hello, then fire the requests.
|
||||
Start-Sleep -Milliseconds 500
|
||||
|
||||
$requests = @(
|
||||
'{"kind":"account.roster","reqId":"r-roster","account":"whitlocktech"}',
|
||||
'{"kind":"char.request","reqId":"r-darrow","account":"whitlocktech","slot":0}',
|
||||
'{"kind":"vendor.snapshot","reqId":"r-vendor","account":"seed_000"}',
|
||||
'{"kind":"char.request","reqId":"r-bad","account":"does_not_exist","slot":0}',
|
||||
'{"kind":"char.request","reqId":"r-serial","serial":"0x24C"}'
|
||||
)
|
||||
|
||||
foreach ($r in $requests) {
|
||||
$writer.WriteLine($r)
|
||||
Say "[req] -> $r"
|
||||
Start-Sleep -Milliseconds 400
|
||||
}
|
||||
|
||||
# Read replies for a few seconds.
|
||||
$deadline = (Get-Date).AddSeconds(8)
|
||||
while ((Get-Date) -lt $deadline) {
|
||||
if ($stream.DataAvailable) {
|
||||
$line = $reader.ReadLine()
|
||||
if ($null -ne $line) { Say "[req] <- $line" }
|
||||
} else {
|
||||
Start-Sleep -Milliseconds 100
|
||||
}
|
||||
}
|
||||
|
||||
Say "[req] done"
|
||||
$client.Close()
|
||||
$listener.Stop()
|
||||
Reference in New Issue
Block a user