On this page
  1. Related
  2. Games
  3. References
  4. Sources

A proximity warning tells the player an enemy is close before it comes into view. Silent Hill’s pocket radio plays static for the two nearest enemies within 32 world units, chosen by distance alone with no line-of-sight test, and the static grows louder as each comes closer1.

  • Audio cueSilent Hill (1999). The pocket radio plays static for the two nearest enemies within 32 units, by distance alone, louder as they close in.1

Games

GameYearHowNote
Silent Hill1999Audio cuethe pocket radio plays static for the two nearest enemies within 32 units, by distance alone, louder as they close in
Every claim about this mechanic, by game (1)

References

  1. ^ abIn the US build (SLUS-00707), Game_NpcUpdate picks the enemies the pocket radio reacts to by distance alone, with no line-of-sight test. An enemy (a character id up to Chara_MonsterCybil, the code's last common enemy) counts while its health is above 0, its CharaFlag_NoRadioStatic flag is clear (Split Head's init sets it; other setters weren't read), and its squared distance from Harry on the ground plane, x and z with height ignored, is under 32² world units (a unit is Q12 1.0). In maps flagged MapFlag_Interior with one or two active chunks, which the code calls low-visibility interiors, the enemy must also share Harry's 40-unit chunk cell on both axes. The code keeps the three nearest, and the radio's two static channels (g_RadioNoise[2]) each follow one of the nearest two, only while ItemToggleFlag_RadioOn is set. Each channel is panned toward its enemy, and its volume falls with distance: the attenuation is the distance converted by Q12_TO_Q4 (sixteenths of a unit, by the decompilers' reading, which their comment "TODO: Is it Q4 here?" doubts), halved outside low-visibility interiors, capped at 255, and Sd_SfxAttributesUpdate takes that share out of 255 from the volume. Computed under that reading, the static falls silent at about 16 units in a low-visibility interior and about 32 units elsewhere.Codeshdecompilations/silent-hill-decomprevised
    How we know
    218    posXShift6 = Q12_TO_Q6(g_SysWork.playerWork.player.position.vx);
    219    posZShift6 = Q12_TO_Q6(g_SysWork.playerWork.player.position.vz);
    ⋮
    227        closestNpcInfos[j].distanceToNpc = SQUARE(32);
    
    src/bodyprog/events/npc_main.c lines 218–229 at 405a621 · upstream ↗
    243        // Only process enemy NPC for radio interference.
    244        if (curNpc->model.charaId <= CHARA_LAST_ENEMY_ID)
    ⋮
    246            // Compute square integer distance from player to NPC.
    247            curDistToNpc = Q12_SQUARE_PRECISE(Q12_TO_Q6(curNpc->position.vx) - posXShift6) +
    248                           Q12_SQUARE_PRECISE(Q12_TO_Q6(curNpc->position.vz) - posZShift6);
    ⋮
    250            // Check if map is low-visibility interior.
    251            isLowVisInterior = false;
    252            if (g_MapOverlayHdr.mapInfo->flags & MapFlag_Interior)
    ⋮
    254                isLowVisInterior = (g_MapOverlayHdr.mapInfo->flags & (MapFlag_OneActiveChunk |
    255                                                                      MapFlag_TwoActiveChunks)) > 0;
    ⋮
    262                if (curNpc->health <= Q12(0.0f)             ||
    263                    curNpc->flags & CharaFlag_NoRadioStatic ||
    ⋮
    
    src/bodyprog/events/npc_main.c lines 243–287 at 405a621 · upstream ↗
    7#define CHUNK_SIZE                        40.0f
    
    include/bodyprog/map/terrain.h line 7 at 405a621 · upstream ↗
    #define CHARA_LAST_ENEMY_ID Chara_MonsterCybil
    include/bodyprog/chara/chara.h#L15 · upstream ↗
    62    splitHead->flags    |= CharaFlag_NoRadioStatic | CharaFlag_Hit;
    
    src/maps/characters/split_head.c line 62 at 405a621 · upstream ↗
    155    s_CloseNpcInfo  closestNpcInfos[3];
    
    src/bodyprog/events/npc_main.c line 155 at 405a621 · upstream ↗
    181    s32 func_800382B0(s32 bitIdx)
    ⋮
    185        for (i = 0; i < (ARRAY_SIZE(closestNpcInfos) - 1); i++)
    ⋮
    197    static s32 func_800382EC()
    ⋮
    201        for (i = 0; i < (ARRAY_SIZE(closestNpcInfos) - 1); i++)
    
    src/bodyprog/events/npc_main.c lines 181–201 at 405a621 · upstream ↗
    68s_RadioNoise g_RadioNoise[2];
    
    src/bodyprog/events/game_sys_states.c line 68 at 405a621 · upstream ↗
    362    if (!(g_SavegamePtr->itemToggleFlags & ItemToggleFlag_RadioOn))
    ⋮
    364        return;
    
    src/bodyprog/events/npc_main.c lines 362–365 at 405a621 · upstream ↗
    423            isExterior = false;
    424            if (!(g_MapOverlayHdr.mapInfo->flags & MapFlag_Interior) ||
    425                !(g_MapOverlayHdr.mapInfo->flags & (MapFlag_OneActiveChunk | MapFlag_TwoActiveChunks)))
    ⋮
    427                isExterior = true;
    ⋮
    433                balance      = Vc_StereoBalanceGet(&closeNpcInfo->position);
    ⋮
    435                // Compute volume from distance.
    436                distToNpc = Q12_TO_Q4(SquareRoot12(Q12(closeNpcInfo->distanceToNpc)));
    437                if (isExterior)
    ⋮
    439                    distToNpc = DIV_FAST(distToNpc, 2);
    ⋮
    442                // Compute clamped volume from Q4 distance. TODO: Is it Q4 here?
    443                vol = CLAMP(distToNpc, Q8(0.0f), Q8_CLAMPED(1.0f));
    ⋮
    445                Sd_SfxAttributesUpdate(Sfx_RadioInterferenceLoop + l, balance, vol, 0);
    
    src/bodyprog/events/npc_main.c lines 423–445 at 405a621 · upstream ↗
    560    convertedVol                = vol;
    561    convertedVol                = g_Sd_VabPlayingInfo.volumeLeft - ((g_Sd_VabPlayingInfo.volumeLeft * (convertedVol)) / 255);
    
    src/bodyprog/sound/sd_call.c lines 560–561 at 405a621 · upstream ↗

    Checked against the evidence by its author, an AI model, 2026-10-22.

    Revised after a check by a second AI model, 2026-10-22.

    In the claim ledger

Sources

Code