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

Light detection ties how far enemies notice the player to light. In Silent Hill the flashlight switches which environment preset’s flags are live once it is half lit1, and enemy code reads those flags. In a dark map a larval stalker targets Harry from 17 units with the flashlight on and from 2.5 with it off2. A walking creeper’s range also depends on which way it faces: with the flashlight on it runs from 12 units behind to 20 ahead, and in the dark from 0.8 to 1.23.

  • Own lightSilent Hill (1999). In dark maps the flashlight lengthens the range at which enemies notice Harry, from 2.5 to 17 units for a larval stalker.1,2,3

Games

GameYearHowNote
Silent Hill1999Own lightin dark maps the flashlight lengthens the range at which enemies notice Harry, from 2.5 to 17 units for a larval stalker
Every claim about this mechanic, by game (3)
Silent Hill 1999In the US build, the flashlight swaps which environment preset's flags are live, and enemy code reads those flags. Gfx_EffectsUpdate moves flashlightIntensity toward 1 while the flashlight is on and toward 0 while it's off, by 4 × g_DeltaTime per update, and func_8003F838 copies the live flags (field_154) from the flashlight-off preset (field_1C[0]) while the intensity is under 0.5 and from the flashlight-on preset (field_1C[1]) from 0.5 up. WorldEnv_MapPresetSet gives a map whose header's unnamed field_16 is 2 the preset pair 6 and 3, or 7 and 4 in low-visibility interiors (off, then on). Presets 6 and 7 set SpecialEnvEventFlags_DarkEnvironment and SpecialEnvEventFlags_FlashlightAllowed; presets 3 and 4 set FlashlightAllowed without DarkEnvironment. A map whose field_16 is 1 gets preset 1 or 2 for both, which set neither flag, and Gfx_MapEnvStepUpdate marks the flashlight unavailable whenever the two presets are the same. So in those dark maps, the flags read as dark with the flashlight off and as lit with it on; map events can load other pairs.Code
In the US build, LarvalStalker_ControlUpdate sets the larval stalker's target position to Harry's whenever his distance on the ground plane is under 2 × base + step, with base and step picked by the live light flags (silent-hill/flashlight-presets): 2.5 and 8 when neither DarkEnvironment nor FlashlightAllowed is set, 3.5 and 10 when only FlashlightAllowed is, and 0.5 and 1.5 otherwise. Computed, that's 13 world units in fog presets; in the dark map presets, 17 units with the flashlight on and 2.5 with it off. This check reads no facing and no line of sight.Code
In the US build, a walking creeper (Creeper_ControlWalkForward) retargets Harry's position when func_8006FD90 passes, with arguments picked by the live light flags (silent-hill/flashlight-presets). func_8006FD90 scales a range by how directly the creeper faces Harry, f = 1 − |angle off its heading|/180° (1 straight ahead, 0 behind), raised to the power count + 1; it fails beyond base + step × that, and otherwise a ray test toward Harry (Ray_CharaTraceQuery) decides. With only FlashlightAllowed set, the creeper calls it with count 0, base 12, and step 8: 12 units behind to 20 ahead. With FlashlightAllowed and DarkEnvironment, count 2, base 0.8, step 0.4: 0.8 to 1.2 units. With neither flag, count 1, base 4, step 12: 4 to 16 units (all computed). A separate check (func_80070360 with 0.5) or an alert flag also retargets it.Code

References

  1. ^ abIn the US build, the flashlight swaps which environment preset's flags are live, and enemy code reads those flags. Gfx_EffectsUpdate moves flashlightIntensity toward 1 while the flashlight is on and toward 0 while it's off, by 4 × g_DeltaTime per update, and func_8003F838 copies the live flags (field_154) from the flashlight-off preset (field_1C[0]) while the intensity is under 0.5 and from the flashlight-on preset (field_1C[1]) from 0.5 up. WorldEnv_MapPresetSet gives a map whose header's unnamed field_16 is 2 the preset pair 6 and 3, or 7 and 4 in low-visibility interiors (off, then on). Presets 6 and 7 set SpecialEnvEventFlags_DarkEnvironment and SpecialEnvEventFlags_FlashlightAllowed; presets 3 and 4 set FlashlightAllowed without DarkEnvironment. A map whose field_16 is 1 gets preset 1 or 2 for both, which set neither flag, and Gfx_MapEnvStepUpdate marks the flashlight unavailable whenever the two presets are the same. So in those dark maps, the flags read as dark with the flashlight off and as lit with it on; map events can load other pairs.Codeshdecompilations/silent-hill-decomp
    How we know
    393    if (g_SysWork.field_2388.isFlashlightOn)
    ⋮
    395        g_SysWork.field_2388.flashlightIntensity += Q12_MULT_FLOAT_PRECISE(g_DeltaTime, 4.0f);
    ⋮
    397    else
    ⋮
    399        g_SysWork.field_2388.flashlightIntensity -= Q12_MULT_FLOAT_PRECISE(g_DeltaTime, 4.0f);
    ⋮
    402    g_SysWork.field_2388.flashlightIntensity = CLAMP(g_SysWork.field_2388.flashlightIntensity, Q12(0.0f), Q12(1.0f));
    
    src/bodyprog/world/world_effects.c lines 393–403 at 405a621 · upstream ↗
    412    if (ptr->primitiveType == PrimitiveType_None)
    ⋮
    414        ptr->field_1C[0] = ptr->field_84[0];
    415        ptr->field_1C[1] = ptr->field_84[1];
    
    src/bodyprog/world/world_effects.c lines 412–415 at 405a621 · upstream ↗
    430    func_8003F838(&ptr->field_154, &ptr->field_1C[0], &ptr->field_1C[1], ptr->flashlightIntensity);
    
    src/bodyprog/world/world_effects.c line 430 at 405a621 · upstream ↗
    636static void func_8003F838(s_StructUnk3* target, s_StructUnk3* envSettings0, s_StructUnk3* envSettings1, q19_12 weight) // 0x8003F838
    ⋮
    649    // Copy enviroment flags.
    650    if (weight < Q12(0.5f))
    ⋮
    652        target->effectsInfo.flags.field_00[0] = envSettings0->effectsInfo.flags.field_00[0];
    ⋮
    654    else
    ⋮
    656        target->effectsInfo.flags.field_00[0] = envSettings1->effectsInfo.flags.field_00[0];
    
    src/bodyprog/world/world_effects.c lines 636–657 at 405a621 · upstream ↗
    319    if (preset0 == preset1)
    ⋮
    321        g_SysWork.field_2388.isFlashlightUnavailable = true;
    ⋮
    336    Gfx_FogParametersSet(&g_SysWork.field_2388.field_84[0], preset0);
    337    Gfx_FogParametersSet(&g_SysWork.field_2388.field_84[1], preset1);
    
    src/bodyprog/world/world_effects.c lines 316–337 at 405a621 · upstream ↗
    22static s_MapEnvPresetIdxs D_800A9F80 = { 1, 1  };
    23static s_MapEnvPresetIdxs D_800A9F84 = { 2, 2  };
    24static s_MapEnvPresetIdxs D_800A9F88 = { 6, 3  };
    25static s_MapEnvPresetIdxs D_800A9F8C = { 7, 4  };
    
    src/bodyprog/world/world_effects.c lines 22–25 at 405a621 · upstream ↗
    210    switch (mapHdr->field_16)
    ⋮
    212        case 1:
    213            if (hasActiveChunk)
    ⋮
    215                presetIdxPtr = &D_800A9F84;
    ⋮
    217            else
    ⋮
    219                presetIdxPtr = &D_800A9F80;
    ⋮
    221            break;
    ⋮
    223        case 2:
    224            if (hasActiveChunk)
    ⋮
    226                presetIdxPtr = &D_800A9F8C;
    ⋮
    228            else
    ⋮
    230                presetIdxPtr = &D_800A9F88;
    ⋮
    
    src/bodyprog/world/world_effects.c lines 210–232 at 405a621 · upstream ↗
    5s_MapEffectsInfo MAP_EFFECTS_INFOS[21] = {
    ⋮
    7        .flags                  = { .field_00 = { SpecialEnvEventFlags_None, 0xFF, UnkGfxEnum_0, 0 }},
    ⋮
    23        .flags                  = { .field_00 = { SpecialEnvEventFlags_None, 0xFF, UnkGfxEnum_0, 0 }},
    ⋮
    39        .flags                  = { .field_00 = { SpecialEnvEventFlags_None, 0xFF, UnkGfxEnum_2, 0 }},
    ⋮
    55        .flags                  = { .field_00 = { (SpecialEnvEventFlags_FlashlightAllowed | SpecialEnvEventFlags_EnableBrightness | SpecialEnvEventFlags_EnableLensflare), 0xFF, UnkGfxEnum_1, 0 }},
    ⋮
    71        .flags                  = { .field_00 = { (SpecialEnvEventFlags_FlashlightAllowed | SpecialEnvEventFlags_EnableBrightness | SpecialEnvEventFlags_EnableLensflare), 0xFF, UnkGfxEnum_1, 0 }},
    ⋮
    87        .flags                  = { .field_00 = { (SpecialEnvEventFlags_FlashlightAllowed | SpecialEnvEventFlags_EnableBrightness | SpecialEnvEventFlags_EnableLensflare), 0xFF, UnkGfxEnum_1, 0 }},
    ⋮
    103        .flags                  = { .field_00 = { (SpecialEnvEventFlags_DarkEnvironment | SpecialEnvEventFlags_FlashlightAllowed | SpecialEnvEventFlags_EnableBrightness), 0xFF, UnkGfxEnum_0, 0 }},
    ⋮
    119        .flags                  = { .field_00 = { (SpecialEnvEventFlags_DarkEnvironment | SpecialEnvEventFlags_FlashlightAllowed | SpecialEnvEventFlags_EnableBrightness), 0xFF, UnkGfxEnum_0, 0 }},
    
    src/bodyprog/sys/map_info.c lines 5–119 at 405a621 · upstream ↗

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

    Checked by a second AI model, 2026-10-22.

    In the claim ledger

  2. ^ abIn the US build, LarvalStalker_ControlUpdate sets the larval stalker's target position to Harry's whenever his distance on the ground plane is under 2 × base + step, with base and step picked by the live light flags (silent-hill/flashlight-presets): 2.5 and 8 when neither DarkEnvironment nor FlashlightAllowed is set, 3.5 and 10 when only FlashlightAllowed is, and 0.5 and 1.5 otherwise. Computed, that's 13 world units in fog presets; in the dark map presets, 17 units with the flashlight on and 2.5 with it off. This check reads no facing and no line of sight.Codeshdecompilations/silent-hill-decomp
    How we know
    84void LarvalStalker_ControlUpdate(s_SubCharacter* larvalStalker)
    
    src/maps/characters/larval_stalker.c line 84 at 405a621 · upstream ↗
    111    idxInfo = g_SysWork.field_2388.field_154.effectsInfo.flags.field_0 & (SpecialEnvEventFlags_DarkEnvironment | SpecialEnvEventFlags_FlashlightAllowed);
    112    if (idxInfo == 0)
    ⋮
    114        baseDistMax = Q12(2.5f);
    115        distStep = Q12(8.0f);
    ⋮
    117    else if (idxInfo == 2)
    ⋮
    119        baseDistMax = Q12(3.5f);
    120        distStep = Q12(10.0f);
    ⋮
    122    else
    ⋮
    124        baseDistMax = Q12(0.5f);
    125        distStep = Q12(1.5f);
    ⋮
    128    if (Math_Vector2MagCalcSafeQ6(g_SysWork.playerWork.player.position.vx - larvalStalker->position.vx,
    129                            g_SysWork.playerWork.player.position.vz - larvalStalker->position.vz) < ((baseDistMax * 2) + distStep))
    ⋮
    
    src/maps/characters/larval_stalker.c lines 111–133 at 405a621 · upstream ↗
    298    SpecialEnvEventFlags_DarkEnvironment   = 1 << 0, // Double check.
    299    SpecialEnvEventFlags_FlashlightAllowed = 1 << 1,
    
    include/game.h lines 298–299 at 405a621 · upstream ↗

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

    Checked by a second AI model, 2026-10-22.

    In the claim ledger

  3. ^ abIn the US build, a walking creeper (Creeper_ControlWalkForward) retargets Harry's position when func_8006FD90 passes, with arguments picked by the live light flags (silent-hill/flashlight-presets). func_8006FD90 scales a range by how directly the creeper faces Harry, f = 1 − |angle off its heading|/180° (1 straight ahead, 0 behind), raised to the power count + 1; it fails beyond base + step × that, and otherwise a ray test toward Harry (Ray_CharaTraceQuery) decides. With only FlashlightAllowed set, the creeper calls it with count 0, base 12, and step 8: 12 units behind to 20 ahead. With FlashlightAllowed and DarkEnvironment, count 2, base 0.8, step 0.4: 0.8 to 1.2 units. With neither flag, count 1, base 4, step 12: 4 to 16 units (all computed). A separate check (func_80070360 with 0.5) or an alert flag also retargets it.Codeshdecompilations/silent-hill-decomp
    How we know
    372void Creeper_ControlWalkForward(s_SubCharacter* creeper)
    ⋮
    384    if (((g_SysWork.field_2388.field_154.effectsInfo.flags.field_0 & (SpecialEnvEventFlags_DarkEnvironment | SpecialEnvEventFlags_FlashlightAllowed)) == SpecialEnvEventFlags_FlashlightAllowed && func_8006FD90(creeper, 0, Q12(12.0f), Q12(8.0f))) ||
    385        ((g_SysWork.field_2388.field_154.effectsInfo.flags.field_00[0] & SpecialEnvEventFlags_FlashlightAllowed) &&
    386         (g_SysWork.field_2388.field_154.effectsInfo.flags.field_00[0] & SpecialEnvEventFlags_DarkEnvironment) && func_8006FD90(creeper, 2, Q12(0.8f), Q12(0.4f))) ||
    387        (!(g_SysWork.field_2388.field_154.effectsInfo.flags.field_0 & (SpecialEnvEventFlags_DarkEnvironment | SpecialEnvEventFlags_FlashlightAllowed)) && func_8006FD90(creeper, 1, Q12(4.0f), Q12(12.0f))) ||
    388        (func_80070360(creeper, 0, Q12(0.5f)) || creeperProps.flags & CreeperFlag_Alerted))
    ⋮
    390        creeperProps.targetPositionX = playerChara.position.vx;
    391        creeperProps.targetPositionZ = playerChara.position.vz;
    
    src/maps/characters/creeper.c lines 372–392 at 405a621 · upstream ↗
    137bool func_8006FD90(s_SubCharacter* chara, s32 count, q19_12 baseDistMax, q19_12 distStep) // 0x8006FD90
    ⋮
    149                                         g_SysWork.playerWork.player.position.vz - chara->position.vz) -
    ⋮
    152        distMult = (Math_AngleNormalizeSigned(ratan2(g_SysWork.playerWork.player.position.vx - chara->position.vx,
    153                                                     g_SysWork.playerWork.player.position.vz - chara->position.vz) -
    154                                              chara->rotation.vy) * 2) + Q12_ANGLE(360.0f);
    ⋮
    158        distMult = (Q12_ANGLE(180.0f) - Math_AngleNormalizeSigned((ratan2(g_SysWork.playerWork.player.position.vx - chara->position.vx,
    159                                                                          g_SysWork.playerWork.player.position.vz - chara->position.vz) -
    160                                                                   chara->rotation.vy))) * 2;
    ⋮
    163    for (i = distMult; count > 0; count--)
    ⋮
    165        distMult = Q12_MULT_PRECISE(distMult, i);
    ⋮
    168    dist = Math_Vector2MagCalcSafeQ6(g_SysWork.playerWork.player.position.vx - chara->position.vx,
    169                                     g_SysWork.playerWork.player.position.vz - chara->position.vz);
    ⋮
    
    src/bodyprog/collision/los.c lines 137–171 at 405a621 · upstream ↗
    173        return false;
    ⋮
    195    return !Ray_CharaTraceQuery(&sp10, &pos, &offset, chara) || sp20.vx != Q12(0.0f);
    
    src/bodyprog/collision/los.c lines 172–195 at 405a621 · upstream ↗

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

    Checked by a second AI model, 2026-10-22.

    In the claim ledger

Sources

Code