name: display-rng-debug description: Use this skill to debug display PRNG divergences — hallucinated monster/object names, glyph randomization differences between C and JS during hallucination.
Display RNG Debugging
When To Use
Use this when hallucination-related screen mismatches occur — wrong hallucinated monster/object names, shifted display RNG streams. These show up as screen-only failures with RNG and events passing:
✗ session ✓ 818 ✓ 818 ✗ 723/818 screen row 0, 'bust vortex' instead of 'schroedinbug'
Background
NetHack uses a separate ISAAC64 RNG stream (rn2_on_display_rng) for
hallucination randomization. During hallucination, newsym() calls
random_monster() / random_object() which consume this display RNG
to pick randomized glyphs. C and JS must consume the same number of
display RNG calls at the same points for the hallucinated names to match.
Common divergence causes:
- Extra
newsym()calls — JS'sdocrt()inrenderAndAutosaveordisplay_sync()→renderMapre-renders all cells, consuming display RNG that C'sflush_screendoesn't (C uses buffered glyphs). - Missing
see_objects()/see_traps()— C'sallmain.c:456-458calls all three display refresh functions when hallucinating. JS may only callsee_monsters. - Per-cell cache misses —
getCachedMapCell()returns null when_displayCellStepIndexdoesn't match the current step, causing an extranewsym()call.
Display RNG Logging
Both C and JS can log display RNG calls interleaved with game RNG.
JS Side
Set environment variables before running the session:
RNG_LOG_DISP=1 node test/comparison/session_test_runner.js ...
Optional: RNG_LOG_DISP_CALLERS=1 adds caller stack tags.
Display RNG entries appear in the RNG log as:
~drn2(383)=157 @ randomMonsterGlyph
The ~ prefix distinguishes them from game RNG entries (rn2(...)).
C Side
Set NETHACK_RNGLOG_DISP=1 when recording sessions:
NETHACK_RNGLOG_DISP=1 python3 test/comparison/c-harness/rerecord.py <session.json>
C entries use the same format: ~drn2(383) = 157 @ newsym(display.c:305)
Re-recording with Display RNG
To get a C session with display RNG data for comparison:
# Re-record a session with display RNG logging enabled
NETHACK_RNGLOG_DISP=1 python3 test/comparison/c-harness/rerecord.py \
test/comparison/sessions/coverage/.../session.json
Debugging Workflow
Identify the first screen divergence step from the PES report or session test output (e.g., step 723).
Enable display RNG logging and replay just the failing session:
RNG_LOG_DISP=1 node test/comparison/rng_step_diff.js <session> --step 723 --window 5Compare display RNG entries between C session data and JS replay. Look for the first
~drn2(...)entry that differs — this tells you whichnewsym()call consumed an extra or missing display RNG value.Trace the caller using the
@ tagto identify which code path made the extra/missing call. Common culprits:docrt_flags→newsymon every cell (inrenderAndAutosave)renderMap→newsymon every cell (indisplay_sync)see_monsters→newsymon each monster positionsee_objects→newsymon each object positionsee_traps→newsymon each trap position
Fix by either:
- Removing the extra
newsymcall - Adding a per-step cache to prevent double consumption
- Skipping
docrt/renderMapduring hallucination when display is already up-to-date
- Removing the extra
Key Code Locations
js/rng.js:384—rn2_on_display_rng()with loggingjs/display_rng.js:13—randomMonsterGlyph()consumes display RNGjs/display_rng.js:25—randomObjectGlyph()consumes display RNGjs/display.js:96—getCachedMapCell()per-step cell cachejs/display.js:2143—docrt_flags()calls newsym on every celljs/display.js:1445—renderMap()calls newsym on every celljs/origin_awaits.js:32—display_sync()calls renderMapjs/allmain.js—advanceTimedTurncalls see_monsters/see_objects/see_traps
Relevant Comparator Behavior
The session comparator (comparators.js) does NOT filter ~drn2 entries.
If present in both C and JS logs, they will be compared directly. The
comparator currently only compares the main game RNG channel; adding a
display_rng channel would require extending comparators.js and the
session_test_runner.js to track display RNG separately.
Current Status
- potionmix2 and potprayspell sessions fail with hallucination display RNG divergence (screen-only; RNG and events pass).
- Root cause: JS's
renderMap/docrtmakes extranewsymcalls that consume display RNG. C'sflush_screenoutputs buffered glyphs with no additional RNG calls. - The per-step cell cache (
getCachedMapCell) prevents double consumption within a single step but doesn't cover cross-step docrt calls.