Skip to content

feat(ngs2): report custom sampler voice state - #5470

Merged
adriadam10 merged 1 commit into
boykopovar:mainfrom
p3n1cillin:claude/ngs2-custom-sampler-state
Oct 14, 2026
Merged

adriadam10 merged 1 commit into
boykopovar:mainfrom
p3n1cillin:claude/ngs2-custom-sampler-state

Conversation

@p3n1cillin

Copy link
Copy Markdown

What

With #4417 merged, Dead Cells (PPSA15552) creates custom sampler voices (rack 0x4001) and polls them with sceNgs2VoiceGetState. That call threw for every custom sampler voice:

terminate called after throwing an instance of 'std::runtime_error'
  what():  NGS2: voice state of rack 0x4001 is not implemented

The title passes a 48-byte buffer at two call sites, and both read only the 64-bit value at offset 16:

0x1740f83: mov rdi, qword ptr [rbx + 0x28]   ; voice handle
0x1740f87: lea rsi, [rbp - 0x48]             ; state
0x1740f8b: mov edx, 0x30                     ; 48 bytes
0x1740f90: call sceNgs2VoiceGetState
0x1740f95: mov rax, qword ptr [rbp - 0x38]   ; state + 0x10
0x1740f99: mov qword ptr [rbx + 8], rax      ; kept

0x1741009: call sceNgs2VoiceGetState         ; same buffer and size
0x174100e: mov eax, dword ptr [rbp - 0x38]   ; state + 0x10
0x1741011: sub eax, dword ptr [rbx + 8]      ; minus the kept value

So offset 16 is a running counter that the title measures progress with. The custom sampler state now:

  • reports the voice's state flags in the common voice state;
  • reports the decoded sample count at offset 16;
  • zeroes the other fields;
  • returns INVALID_OUT_SIZE for any other size, as the sampler, submixer and reverb racks do.

shadPS4's OrbisNgs2CustomSamplerVoiceState is about 80 bytes, so it doesn't match what this title passes. The size and that one field come only from the title, and the other field names and the console's values are unverified. This is recorded under Unknown function info in docs/dev/debt/libSceNgs2.native.md.

Tested

  • Dead Cells on Windows 11 (MinGW GCC 15), on main a0f5916a2 with fix(ngs2): resolve voice patches to rack handles #4777. Dead Cells patches a sampler voice to a rack handle, which needs fix(ngs2): resolve voice patches to rack handles #4777 first. It also needs two local, unsubmitted stubs:

    • the custom filter param it sends, which is 48 bytes rather than the 40 main reads (see my comment on #4417);
    • its rotated sceNgs2GeomCalcListener listener, front (1,0,0) and up (0,0,1).

    Before this change, it stops on the error above about 80 s in. After it, it runs for the whole 120 s run with no library stop. With the same stack plus a local stand-in for this call, it reaches its title screen with audio.

  • Unit test: ngs2_custom now creates a custom sampler voice, renders one grain and checks that:

    • a 56-byte buffer returns INVALID_OUT_SIZE;
    • a 48-byte buffer returns INUSE|PLAYING with 8 decoded samples, then 16 after a second render.

    With main's Voice.cpp the test aborts on the throw. All 10 NGS2 tests pass on Windows and on Linux (WSL Ubuntu, GCC).

  • python tools/check_conventions.py --base origin/main and tools/pr_overlap.py pass, with no overlap with open PRs.

Checklist

  • Based on current main; no other open PR implements the same functions
  • One topic per PR; follow-ups go in a new PR
  • No comments in code except technical debt
  • Unimplemented paths throw (NotImplemented_nid_no_patch); silent stubs are listed under Silent stubs in their library's technical debt file
  • No notes, investigation .md files or images added to the repository (attach them to this PR)
  • New third-party code is a submodule built from source
  • Generic behaviour, not specific to one title
  • Depends on: none to build; Dead Cells reaches it with fix(ngs2): resolve voice patches to rack handles #4777 and the custom filter param it sends
  • Unblocks: Dead Cells (PPSA15552) audio, Dead Cells (PPSA15552): NGS2 rack id 0x4001 is not implemented #3553
  • AI-assisted: yes

🤖 Generated with Claude Code

Dead Cells (PPSA15552) calls sceNgs2VoiceGetState on custom sampler
voices with a 48-byte buffer and reads the 64-bit value at offset 16 as
a running counter. Fill it with the decoded sample count, keep the
common state flags, and return INVALID_OUT_SIZE for other sizes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@github-actions

github-actions Bot commented Oct 13, 2026 •

Copy link
Copy Markdown

Other open pull requests touch the same code. Merging one will break the other, or both implement the same thing. If this one builds on them, list them in Depends on.

Pull request Conflicting files (lines on main) Same exports Same new files
#5548 docs/dev/debt/libSceNgs2.native.md L27

@adriadam10

Copy link
Copy Markdown
Collaborator

Checked the new Ngs2CustomSamplerVoiceState (48 bytes, decoded sample count at offset 16), the INVALID_OUT_SIZE path for other sizes, and the ngs2_custom test (fails to build without the change, passes with it); the 9 libSceNgs2.native ctest targets pass on Linux. The debt line records what only the title establishes. Merging.

@adriadam10
adriadam10 merged commit 956269a into boykopovar:main Oct 14, 2026
9 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants