User repro: player gunshots specifically go permanently muted mid-firefight
and stay muted after the alarm ends; all other SFX fine. Not generic pool
starvation — a stuck-state bug in the gunfire.c:3178 two-slot double buffer
(audioHandle / field_A48). Leading mechanism: asynchronous sndDeactivate
posts to the 64-slot SFX event queue; under firefight+alarm load the queue
saturates, alEvtqPostEvent drops the deactivate, the state never disposes,
the slot never clears — once both slots stick, no player gunshot can play.
All three pool sizes are byte-match stock GE => port timing bug, not a value
to bump. Next: read the user's audiotrace.log to confirm evtq saturation +
uncleared slots.
Claude-Session: https://claude.ai/code/session_01U7gyYC3pcatXHhD1kPCgsR
D205: CLOSED (M-78). User re-tested explosion->scream on Bunker after the
PR #28 merge — the explosion SFX is correct by ear now. All four D205
symptoms were D206 (the +1 bank-slot alias); the "scream" was a coincident
guard death-yelp (faithful N64 behaviour). M-74's pan audit already ruled
out a presentation-layer bug.
D207: re-add ONLY the [D207-DROP] trace line (not the reverted last-resort
LOOPED-preemption pass) at both sndDisposeSound no-voice sites in snd.c,
gated on GE_AUDIOTRACE, logging state/prio/flags/count=N/max — so a user
capture on the real failing level is decisive between (a) heavy-level 8/8
starvation, (b) a leak on an alarm-stop path, (c) perceptual. Static
re-check noted in findings D207: ALARM3 slot 162 sampleVolume=80 (below
gunfire), keyMax=8 => not RETRIGGER => continuous-loop by design on N64 too.
findings-index.csv regenerated.
Claude-Session: https://claude.ai/code/session_01U7gyYC3pcatXHhD1kPCgsR
User by-ear A/B vs N64 confirms D206: armour pickup, unarmed melee whiff
and silenced PP7 all correct. D202's silenced-PPK 'slap' was D206 (SFX
bank index +1) — CLOSED; M-65/M-66b ownerless-loop mitigations stay.
D205 narrows to symptom (2) explosion->scream only. D207 (alarm klaxon)
acceptable in play per user; voice-starvation hypothesis didn't hold up
(M-77b), no fix shipped, GE_FORCEALARM probe kept for follow-up.
Regenerated findings-index.csv.
Temporary env-gated probe in handle_alarm_gas_timer_calldamage to force
and trace the alarm klaxon while investigating D207 (user report: alarm
SFX overrides other audio). #ifdef PORT + getenv, inert unless set.
Remove once D207 is resolved (see findings.md D207 / HANDOFF).
GE's sndPlaySfx reads the on-disk sound bank through the cast alias
struct ALInstrumentAlt_s { s32 unk0, unk4, unk8; ALSound *soundArray[1]; }.
On N64 (4-byte pointers) soundArray sits at struct offset 12 and
deliberately aliases the on-disk ALInstrument's bendRange/soundCount
words, so soundArray[N] == on-disk table entry [N-1] — GE's SFX_IDs are
1-based into the table by design (ID 0 = NOTHING_SFX, short-circuited).
On PC the 8-byte pointer + 8-byte alignment push soundArray to offset 16,
and the converted bank (romdata.c afFixupInst) is packed to match, so
every SFX_ID resolved one bank slot too high.
Root cause of D202's silenced-PPK 'slap' (id 46 -> PUNCH1 instead of
GUN_SILPPK_A) and D205 (3) armour + (4) melee->Klobb. M-61's rejected
'index 45, +0.999' was correct all along.
Fix: src/snd.c:1001, #ifndef PORT / #else -> soundArray[soundIndex - 1],
N64 line kept verbatim. ABI/layout only (D3x class), no game-logic change.
Also serves the retrigger chain (same 1-based space).
Verified: build clean; BUNKER1 GE_AUDIOTRACE 60s crash-free, all ~90
distinct requested indices resolve to rom_sfx_decode.py slot N-1 (0/90 at
N); -level_09 golden framediff 3/3. User playtest: 'most audio is good now.'
findings.md D206 + D205 (M-73/M-74) + D207; porting-notes.md §E.
- findings.md: D202 §M-67 (static analysis exhausted, GE_VOICEDUMP probe
motivation), §M-68 (decisive runtime A/B — all three user complaint
classes play the exact ROM samples at the exact ROM pitches) and new
§M-69 (320-request full-corpus exact match: 286/320 CC >= 0.85; the
initial 44 'BAD' were tooling artifacts — voicedump stamps are block-END
times, coarse-slide missed sharp onsets, refs unclipped at voice death;
remaining failures = dropped allocations / shared-wavetable wire
attribution, not corruption) + index row.
- GE-ENV-PROBES.md: GE_VOICEDUMP entry (incl. block-END-stamp gotcha);
[WIRE]/[VOICEMAP]/[EVT]/[STOP-EVT] documented under GE_AUDIOTRACE.
- AUDIO-PLAN.md: status banner — Phases 0-2 done (D199/D200/D201/D204),
Phase 3 verification is the active track.
Env-gated diagnostics for the D202 per-voice sample-correctness work
(M-67/M-68/M-69), all inert unless the env var is set:
- GE_VOICEDUMP (mixer.c aEnvMixerImpl): per-voice resampled mono stream
to voicedump.raw, records [stateAddr][nSamples][us][s16 x n]. NB the
us stamp is mix time — records render ahead, samples END at the stamp.
- [VOICEMAP] (synthesizer.c, under GE_AUDIOTRACE): full PVoice ->
decoder/resampler/env-state address map at synth init, tying voicedump
slot addresses to WIRE filter addresses.
- [WIRE] (load.c) now carries a µs timestamp; sndPlaySfx trace gains
t=µs + wavetable pointer.
- [EVT]/[STOP-EVT] (snd.c): every ALSndpEvent the player processes, plus
the release ramp a STOP/DEACTIVATE computes (deltaUs).
- audiodebug.ps1: -VoiceDump switch.
With scratchpad/exact_match.py these prove every allocated voice plays
the exact ROM sample at the exact ROM pitch (286/320 CC >= 0.85 on the
M-69 full-corpus run; remaining failures are dropped allocations + wire
attribution, not corruption). Remove once D202 closes.
All referenced from HANDOFF.md / GRAPHICS-BACKLOG.md but untracked:
- D172-PARTICLE-COLOR-RESEARCH.md: magenta/cyan blood root-cause candidate
(unhandled GBI opcode 0xB9 in ROM particle records + D146 guard)
- D176a-SKY-NOTES.md: RDPHALF sky-tri wire format decoded; Path B draft state
- VIEWMODEL-RESEARCH.md: 1P weapon model static audit, three ranked suspects
- QOL-INVENTORY.md / WIDESCREEN-FOV-PLAN.md / UNLOCKED-FPS-PLAN.md: parked
Phase-4 proposals
Three #ifdef PORT changes in src/snd.c (N64 build zero-diff), all
documented in findings.md D202:
1. M-65 voice-leak guard: the preemption scan reclaims provably-ownerless
SOUND_FLAG_LOOPED SFX voices only, and only under pool pressure.
Sound 203 (METAL_SLIDE_CLOSE_SFX) is played fire-and-forget by
doorPlayCloseSound0/1 with a NULL owner; stock GE never preempts
flag-0x12 voices, so leaked loops accumulate until new SFX are dropped.
2. sndCreatePostEvent un-stub (D138 stub removed): the stub had deleted ALL
distance attenuation from SFX volume/pan. Verified rt=0.996, drop 0.
3. M-66b option-C expiration (user-chosen disposition for D202): new PC-only
AL_SNDP_PORT_EXPIRE_EVT (reuses the unused 1<<13 slot in snd.h). After a
voice starts, if it is provably ownerless (LOOPED + FINAL_IN_SEQUENCE +
not RETRIGGER + state->state == NULL) and its wave carries an ADPCM loop
with count == -1, post the expire at D202_EXPIRE_DELAY_US (2 s); the
handler re-validates the predicate, fades volume to 0 over
D202_EXPIRE_FADE_US (500 ms), and posts END_EVT. Not a reused STOP_EVT:
its ramp uses the envelope releaseTime (~2.7 ms for sound 203) and would
hard-cut/click. Stale-event safe because sndDisposeSound removes ALL
pending events for a state; owned loops and retrigger sounds untouched.
Placed in sndp (not port/src/mixer.c) because ownership is only visible
there. Measured: pre-fix capture had the 256.7 Hz loop line flat at max
t=39->361 s; post-fix run shows transient bursts with full silence
between and ends allocated=0/8.
GE's amMain feedback loop (src/audi.c:531) has a ~3 ms setpoint that PC
scheduling jitter clears, so the SDL queue starves and the device pads
playback with silence -> audio runs permanently ~2 % below real time.
- F5 (the fix): audioGetAiLengthBytes() subtracts a 1024-frame (~46 ms)
cushion before reporting, so the loop tops up while slack remains;
audi.c's control law untouched (rule #2 clean).
- F1: osAiGetLength reports AI_LEN_REG single-buffer semantics (bytes left
in the buffer currently playing), not full SDL queue depth.
- F3: Audio.QueueLimit 8192->2880 + non-silent drops on overflow.
- F4: oversize-block invariant guard.
- GE_D204=1 cheap audio-health monitor (rt/q/drop/max), safe to leave on;
GE_D204_OLD restores pre-fix behaviour for A/B measurement.
Measured A/B in one binary: rt 0.980 -> 1.000, queue min 0 -> 368,
drop 0. Findings: docs/dev/findings.md D204.
Software RSP-audio-ucode mixer (macro-swap aXxx -> Impl functions, PD-port
pattern) makes libaudio output audible on PC. Alongside it, two pointer-width
bugs surfaced by exercising the audio path for the first time:
- D198: alAdpcmPull/_decodeChunk used K0_TO_PHYS (N64 kseg0 mask) on 64-bit
heap pointers; switched to osVirtualToPhysical like every sibling call site.
- D201: bnkf.c bank/inst/sound/wavetable relocation offsets were s32,
truncating the real 64-bit base pointer for ~50% of allocations; widened
to uintptr_t.
- D200 (already committed-pending from a prior session): reverb's ALDelay
u32 negative indices zero-extended into +4GB offsets on 64-bit; (s32)/(s64)
casts match PD ground truth.
- D199: documents the mixer's aSetBuffer persistent-DMEM-context design,
reconstructed from call-site pairs (no RSP disassembly to check against)
since there's no N64 audio ucode source to reference.
- D195/D197 (playtest reports referenced by an earlier HANDOFF note but never
written into findings.md): backfilled as OPEN with next steps.
Verified: ./build-pc.sh ntsc-final links clean; -level_09 runs 600+ frames
crash-free with music + SFX audible.
The D117 findings.md addenda in the prior commit changed D117's status
text without regenerating findings-index.csv, and the new GE_DETERM_TRACE
env var wasn't yet mentioned in GE-ENV-PROBES.md -- both fail their
--check gate in CI's validate job. Caught by a real CI run, not predicted.
Claude-Session: https://claude.ai/code/session_01P3WrUHAFQZUuhrHhiJgTUr
The "worker's shell is cmd.exe, no grep/cat" note was wrong -- verified
directly against the delegate-local harness's own _run_bash: grep, sed,
cat, ls, head, pipes, and grep -rn all work (Git-for-Windows/MSYS2 on
PATH). The stale claim, baked into 4 of 5 local agent .md files, caused a
real dispatch to burn its whole turn budget full-read_file-ing files
instead of grepping them, hit the turn limit, and returned nothing.
Claude-Session: https://claude.ai/code/session_01P3WrUHAFQZUuhrHhiJgTUr
Adds an env-gated (GE_DETERM=1, default OFF), test-only deterministic-clock
mode to port/src/libultra.c per the design in docs/dev/findings.md section D117:
osGetCount() returns a virtual clock advanced synchronously in osRecvMesg()
when __scMain (the sole consumer of the scheduler's retrace queue) asks for
the next message, instead of tracking real elapsed time. Goal: two runs of
the same build take the same simulation path, for exact frame-diff
regression testing and reproducible crash repro.
Four passes this session, each is a real, verified result, not a guess:
1. Fixed the predicted pre-first-frame deadlock (a poll-driven fallback
caused it in the first cut).
2. Replaced the poll-driven fallback with synchronous per-ask generation
in osRecvMesg -- fixed the deadlock cleanly, but two-run divergence was
still 33-38%.
3. Capped the pre-first-task tick backlog to a fixed constant -- divergence
dropped to 17-20%, still not the ~0% target.
4. Added GE_DETERM_TRACE=1 instrumented tracing and diffed two runs' clock
event logs directly. Proved the tick mechanism is now fully
deterministic (byte-identical clock values through the first real task).
Root-caused the actual remaining divergence: every synthesized retrace
increments the scheduler's frameCount, which gates a half-rate client
(the audio client) by parity -- since the pre-task spin count is still
real-time-scheduling-dependent, that parity isn't guaranteed to match
between runs.
GE_DETERM is NOT achieving its determinism goal yet and is not meant to be
used for anything today -- it ships here purely as a well-documented,
zero-risk-when-off checkpoint (verify.sh bunker1 PASS, worst_cell unchanged,
confirmed after every change this session) so the next attempt starts from
proof instead of re-deriving four sessions of dead ends. Full trace numbers,
exact line citations, and the mechanism: docs/dev/findings.md section D117
(four M-52 addenda).
Claude-Session: https://claude.ai/code/session_01P3WrUHAFQZUuhrHhiJgTUr
CMakeLists.txt already opt-in enables CMAKE_{C,CXX}_COMPILER_LAUNCHER=
ccache when found (b49907de); CI just never installed ccache or persisted
its cache dir across runs. Wires actions/cache + ccache install into both
linux-build and windows-build, keyed on CMakeLists.txt's hash. Game
sources are frozen, so this should give a near-total hit rate after the
first run on each job.
YAML-validated; the actual hit-rate payoff needs a real CI run (cold, then
warm) to confirm -- not verifiable locally.
Claude-Session: https://claude.ai/code/session_01P3WrUHAFQZUuhrHhiJgTUr
verify.sh wraps the manual render-verify recipe (build-if-stale, 640x480
ini pin/restore, headless GE_PCDUMP capture, pixcount, framediff, crash
symbolication) into one command with a machine-readable PASS/CRASH/
NO-FRAMES/REGRESSION verdict + --json, for a single level or a sweep
subset. Live-tested on Windows: single-level and subset-sweep runs both
produce correct verdicts and leave the tree clean.
crash_brief.py turns a ge007.crash.log into a symbolicated, dispatch-ready
investigation brief: greps porting-notes.md/findings-index.csv for
sections covering the crashing function/file, and flags known
parked-crash signatures (D188 va_list, D189 stack overrun) so agents stop
re-investigating them. verify.sh calls it automatically on a CRASH
verdict. Live-tested against a synthetic-but-real crash log built from an
actual built symbol; caught and fixed 3 bugs before trusting it (addr2line
unresolvable under native Windows Python's PATH handling, a substring
keyword-match false-positive, a Windows drive-letter path-split bug).
Claude-Session: https://claude.ai/code/session_01P3WrUHAFQZUuhrHhiJgTUr
Static/tooling slice of docs/dev/notes/AGENTIC-SPEEDUPS-PLAN.md — no build or
runtime environment needed to verify any of it.
- Step 6 / N7: add a VERIFY: line to the investigation-brief template
(docs/dev-process.md) and the CLAUDE.md dispatch-preflight checklist — the
brief must now name the exact probe/verify invocation that proves the task
done, or state that runtime verification is impossible here.
- Step 4 / N2: tools_pc/README.md — indexes the ~60 scripts with a
living vs artifact marker so closed-finding artifacts can be archived.
- Step 4 / R8: tools_pc/gen_findings_index.py -> docs/dev/findings-index.csv
(label, bucket, one_liner, status_raw) — grep-before-you-read aid for the
~200 KB finding log. Heuristic parser of the §F/§H tables.
- Step 4 / R9: tools_pc/gen_env_probes.py — re-greps live getenv("GE_*")
sites and reports NEW/GONE drift vs docs/dev/GE-ENV-PROBES.md (keeps the
curated prose, only checks the site map). Doc reconciled: 7 new probes
added, GE_D54 kept as a tombstone.
- CI: validate job now runs both generators with --check.
Step 1 / N1 (HANDOFF preflight) applied in the local-only docs/HANDOFF.md.
Step 2 (verify.sh) and the HANDOFF/HANDOFF-ARCHIVE trim are left for a
session with a build environment.
Update the public-facing docs to the M-49 state (v0.1.0 alpha, first
full-campaign playtest: 19/21 missions completable, D191 crashes Bunker ii
+ Statue, D193 AI pacing is the top flaw).
- README: lead with what it is + status, research angle second; new
Quick start section; Status/Download rewritten for the alpha + dual
platform; "By the numbers" refreshed (~3 weeks, ~320 commits, ~190
findings, ~49 sessions, 69 PORT files).
- .github/release-notes.md: v0.1.0 title + known-issues rewrite.
- .github/social-preview.html: status/stat lines (regen + re-upload the
PNG separately).
- docs/index.md, docs/dev/LEVEL-STATUS.md: M-49 status.
- docs/dev/findings.md, docs/dev/GRAPHICS-BACKLOG.md: D191-D197 +
D148/D170/D173 M-49 escalations.
Hygiene:
- Fix the `&` literal in internals.md H1.
- Repoint two ../README.md links that 404 on the Pages site.
- Standardise British -> American spelling across the public docs
(behaviour->behavior, colour->color, catalogue->catalog,
centre->center, etc.). Deep internal logs left untouched.
- .gitignore: document the local-notes convention (docs/HANDOFF.md,
docs/BACKLOG.md, and a new docs/dev/notes/ drop for roadmaps and
session plans) so it survives a fresh clone.
* fix(linux): survive the decomp's latent stack-buffer overruns (D189)
The decompiled game code has a class of fixed-size stack-buffer over/under-
runs that write a few words into adjacent stack scratch:
* stan.c sub_GAME_7F0B1DDC — `StandTile *tileStack[39]` with a bail check
of `if (cat >= 41)` that only runs once per outer iteration, so `cat`
reaches 40 + pointCount and the append writes several slots past the end.
BUNKER1 (-level_09) prop room-list setup legitimately visits ~40 tiles.
* bondview2.c bondviewCalcIntroSwirlCamera — `f32 pointbuf[10]` indexed
`&pointbuf[i*3]` for i in [-1..2] then written [0..5], i.e. [-3 .. 11].
On the N64 and on the MinGW/Windows build (neither uses a stack protector)
these are harmless in practice — the Windows build is stable over thousands
of frames. Only Ubuntu's gcc default (`-fstack-protector-strong`) turns them
into a fatal `__stack_chk_fail`, at BUNKER1 stage load and again at the intro
swirl camera (~frame 5).
Two-part fix:
* `-fno-stack-protector` globally (CMakeLists) — matches the N64 and MinGW
builds so the same code runs the same way. A hardened Linux build would
need the whole overrun class swept first.
* `StandTile *tileStack[64]` under `#ifdef PORT` (stan.c) — the one case
that is cleanly understood and bounded (`pointCount` is 4-bit, so 40+15
is the true worst case). Pure local scratch, never escapes, no behaviour
change. N64 keeps `[39]` under `#else`.
* stan.c:2225 link-resolve also switched to the D177 PORT_PTRADD macro for
idiom consistency (the blob maps below 4 GiB so the bare cast is
currently lossless; #else is byte-identical).
Stacked on the D188 xprintf fix. Verified: Windows -level_09 framediff 3/3
within golden threshold (all changes are #ifdef PORT or no-op on MinGW);
Linux -O2 -level_09 now runs to frame 2100+ with no crash.
* docs: D189 update (stack-protector / latent overrun class) + porting-notes D8
* docs: D190 — Linux -level_45 objective.c load crash (open); D189 sweep 5/6
* fix(linux): thread a va_list* through _Printf (x86-64 SysV ABI)
On the x86-64 System V ABI `va_list` is an array type, so a by-value
`va_list` parameter decays to a pointer. `_Printf()` was passing `&args`
(the address of that local pointer, not the argument list) to `_Putfld()`,
which does `va_arg(*args, ...)` — so `_Putfld` walked garbage and
segfaulted. Latent at `-Og`, fatal at `-O2` (PR #9), which is why the
Linux release build crashed at boot:
bossInitMainthreadData (boss.c:247, the D121 per-level -m token sprintf)
-> sprintf("-level_%c%c %s", ...) -> _Putfld -> SIGSEGV @ xprintf.c
Same UB class as D187. Windows x64's `va_list` is a scalar (`char*`), so
`&args` happened to work there — the Windows build is unaffected.
Fix: under `#ifdef PORT`, `_Printf` takes `va_list *` and passes it
straight to `_Putfld` (which already expects `va_list *`); `src/sprintf.c`
(the only PC caller — `rmon.c` is excluded from the build) passes `&args`
of a real `va_list` object. N64 build untouched (`#ifdef PORT`, and it
byte-matches the ROM).
Verified:
- Windows: build links, `-level_09` framediff 3/3 within golden threshold.
- Linux -O2: boot crash gone, now advances past bossInitMainthreadData
into stage load (uncovers a separate pre-existing stan.c stack overflow
during BUNKER1 prop setup — tracked separately).
* docs: D188 (xprintf va_list ABI) + D189 (stan.c stack-smash) findings + porting-notes D7
The HUD weapon/ammo-type icon (D187, PR #19) and blank briefing objective
text (D178, cd1ed574) are both fixed and on main. Remove them from the
release-notes and bundled-README known-issues blurbs so a v0.1.0 tag ships
an accurate list. Black sky and the front-end 3D models (Nintendo logo,
MISSION COMPLETE / mode-select) are still open and stay listed.
A stock MSYS2 MINGW64 shell ships neither `zip` nor `unzip`, so
`tools_pc/bundle-win.sh` failed at the packaging step for a local release
build (the M-45 handoff's documented v0.1.0 cut path). CI is unaffected
(it `pacman -S zip unzip`, so the `zip` branch is still taken).
Fallback order: zip -> 7z on PATH -> "C:\Program Files\7-Zip\7z.exe" ->
powershell Compress-Archive. Verified end-to-end: bundle builds, unpacks
to a clean dir, prepare-assets runs, ge007.x86_64.exe boots -level_09 to
frame 2100+ crash-free.
The HUD weapon/ammo-type icon (bottom-right, and bottom-left when
dual-wielding) stopped drawing ~2026-09-02; the ammo *numbers* still
render. Root cause: set_rgba_redirect_generate_microcode (gunfire.c, the
sole icon emitter) is non-void but has no return statement (the decomp
banner flags it). At -Og the tail call's result stayed in $v0 and it
worked; commit 7549570b restored -O2 for playtest builds, where the
undefined return register makes callers resume the DL from the icon's
start offset and overwrite the icon commands with the following
microcode_constructor / gunDrawHudInteger output.
Fix: add the return under AVOID_UB (already -D'd in the PC build;
CMakeLists.txt:207), N64 byte-match path unchanged under #else.
Codegen-correctness only, same class as the existing AVOID_UB edits.
Owed: build (-O2) + -level_09 capture diff vs docs/img/bunker1-2.png.
A/B: Debug build (-Og) shows the icon, -O2 build without this fix does not.
- tools_pc/bundle-linux.sh: package a Linux build (exe + README + licenses +
prepare-assets) as a .tar.gz. Mirrors bundle-win.sh's ROM / size guards and
prepare-assets assembly. No shared libs bundled — the generated README tells
the user to install SDL2 / zlib / libGL from their distro.
- tools_pc/dist/README.md.in: made OS-neutral via @PLATFORM@ / @EXE@ / @DEPS@ /
@LICENSE_EXTRA@ tokens; refreshed the known-issues list (dropped the stale
"ladders don't work" line — D177 fixed M-36; added black sky + missing HUD
weapon icon).
- .github/workflows/ci.yml: linux-build now packages + uploads the Linux
bundle; the release job attaches both platforms to the draft pre-release.
- .github/release-notes.md: rewritten for the two-platform NTSC-U alpha with
the prepare-assets step and current known issues.
Alpha scope: NTSC-U only, one-time prepare-assets step (Part A launcher and
Part B native converter from release-dropin-rom-plan.md remain follow-ups).
Claude-Session: https://claude.ai/code/session_01V4pK3pT83YnPsBp3Ey4RP2
Drop `paths-ignore` from the `pull_request:` trigger. With it, a docs-only
PR never triggered the workflow, so the required `validate` / `windows-build`
checks stayed "expected" forever and the PR could not be merged without an
owner bypass (M-44 hit this on #14/#15).
The workflow now always runs on PRs; the `changes` job diffs the PR against
its base sha (mirroring the existing branch-push logic against
`github.event.before`) and, for a docs-only change, reports the build jobs
as skipped — which branch protection accepts as success.
Claude-Session: https://claude.ai/code/session_01V4pK3pT83YnPsBp3Ey4RP2