Mechanic · enemies & ai · stealth
Vision cone
The region in front of an enemy in which it can see the player, set by a distance and an angle and blocked by walls.
On this page
A vision cone is the space in which an enemy can see the player. In Metal Gear Solid it reaches 45° either side of a guard’s facing and out to its eye length, and a guard also sees anything within 500 units. Walls block the line of sight, and each frame of full sight raises the guard’s alert level1.
- ConeMetal Gear Solid (1998). A 90° cone out to an eye length of 4,000 units by default, a 500-unit ring all round, and walls block it; a sighting raises the guard's alert level.1,2
Games
| Game | Year | How | Note |
|---|---|---|---|
| Metal Gear Solid | 1998 | Cone | a 90° cone out to an eye length of 4,000 units by default, a 500-unit ring all round, and walls block it; a sighting raises the guard's alert level |
Every claim about this mechanic, by game (2)
References
- ^ abIn Metal Gear Solid Integral (SLPM-86247, the Japanese re-release the decompilation builds), a guard's sight check
s00a_command_800C9E68updates its resultvision.field_B92for one guard per commander update, the guard whosefield_B78equals the commander's update countfield_0x0Cmodulofield_0x08, the number of guard slots it loops over; between turns the result stands. Snake can't be seen off the guard's map or with the player status PLAYER_INTRUDE or PLAYER_HIDDEN set. Otherwise he is seen when he is within COM_EYE_LENGTH + 2,000 units (a 3D distance) and within 2,000 units of the guard's height, and either closer than 500 units or less thanvision.anglefrom the guard's facing, with no wall or floor (HZD_OnlineHazardCheck, HZD_CHK_ALL) and no target flagged TARGET_SEEK (GM_OnlineTargetCheckAny) on the line between them. The guard's setup setsvision.angleto 512 of the 4,096 units in a turn, a cone of 45° either side of the facing, 90° across (computed), andvision.lengthto COM_EYE_LENGTH, 4,000 units unless a stage script'sloption sets it; some guard actions set the length to 3,000 or 0. The result is 1 for a sighting beyondvision.lengthand 2 within it, and 0 whenever Snake has the Stealth item equipped, unless the guard'sparam_bloodis 83. Every frame,s00a_command_800C9D7Cmoves the guard'salert_level(0 to 255): up 1 at a result of 2, down 1 at 1, and down COM_ALERT_DECREMENT at 0 (4 by default; a stage script'stoption of 1 or 2 makes it 3 or 2). On the result-0 branch one exception holds the level where it is instead of lowering it: when the guard's last sighting was a 2 and Snake has PLAYER_INTRUDE or PLAYER_NOHIDDEN set, isn't hidden, and the guard'svision.lengthisn't 0.CodeFoxdieTeam/mgs_reversingrevisedHow we know
3This project aims to completely reverse engineer *Metal Gear Solid Integral* for PlayStation back to C source code which when compiled produces the same assembly code.README.md lines 3–5 at bf9189e · upstream ↗ 189 <td><b>s00a</b></td> 190 <td>120 KB</td> 191 <td>Loading Dock</td> 192 <td><b>✔️ 100% decompiled!</b></td>
README.md lines 189–192 at bf9189e · upstream ↗ 454 include "{{OBJ_DIR}}\enemy\watcher.obj", overlay 455 include "{{OBJ_DIR}}\enemy\check.obj", overlay 456 include "{{OBJ_DIR}}\enemy\action.obj", overlay 457 include "{{OBJ_DIR}}\enemy\enemy.obj", overlay
build/linker_command_file.txt lines 454–457 at bf9189e · upstream ↗ 269 flag = &work->vision.field_B92; ⋮ 274 dir = GV_VecDir2( &svec ); ⋮ 276 dis = GV_VecLen3( &svec ); ⋮ 284 diff = pos->vy - work->control.mov.vy; ⋮ 290 if ( !( work->control.map->index & GM_PlayerMap ) || GM_PlayerStatus & ( PLAYER_INTRUDE | PLAYER_HIDDEN ) ) ⋮ 292 work->vision.field_B92 = 0; 293 return; ⋮ 296 if ( EnemyCommand.field_0x0C % EnemyCommand.field_0x08 == work->field_B78 ) ⋮ 298 x = 2000; ⋮ 300 if ( COM_EYE_LENGTH + x >= dis ) ⋮ 302 if ( x >= diff ) ⋮ 304 if ( dis < 500 || GV_DiffDirAbs( work->vision.facedir, dir ) < work->vision.angle ) ⋮
source/enemy/enemy.c lines 269–349 at bf9189e · upstream ↗ 172int GV_DiffDirAbs( int from, int to ) ⋮ 174 int diff; ⋮ 176 diff = 4095 & ( to - from ); 177 return ( diff <= 2048 ) ? diff : 4096 - diff;
source/libgv/util.c lines 172–178 at bf9189e · upstream ↗ 667 if ((iter->flag & 0x1) && (iter->class & TARGET_SEEK)) ⋮ 669 if (TargetConflict(iter, &tmp) && OnlineTarget(from, to, iter, hit))
source/game/target.c lines 667–669 at bf9189e · upstream ↗ 235 work->vision.angle = 512; 236 work->vision.length = COM_EYE_LENGTH;
source/enemy/check.c lines 235–236 at bf9189e · upstream ↗ 1179 EnemyCommand.field_0x88.vx = 0xFA0; 1180 EnemyCommand.field_0x88.vy = 0xFA0; 1181 EnemyCommand.field_0x88.vz = 0xFA0; ⋮ 1183 ops = GCL_GetOption( 'l' ); 1184 if ( ops ) ⋮ 1186 GCL_StrToSV( ops, (short *)&EnemyCommand.field_0x88 );
source/enemy/command.c lines 1179–1187 at bf9189e · upstream ↗ 1201 ops = GCL_GetOption( 't' ); 1202 if ( ops ) { 1203 EnemyCommand.field_0x88.pad = GCL_StrToInt( ops ); ⋮ 1206 switch ( EnemyCommand.field_0x88.pad ) ⋮ 1208 case 0: 1209 COM_ALERT_DECREMENT = 4; 1210 break; 1211 case 1: 1212 COM_ALERT_DECREMENT = 3; 1213 break; 1214 case 2: 1215 COM_ALERT_DECREMENT = EnemyCommand.field_0x88.pad; ⋮
source/enemy/command.c lines 1201–1219 at bf9189e · upstream ↗ 1046 EnemyCommand.field_0x0C++;source/enemy/command.c line 1046 at bf9189e · upstream ↗ 766 for ( i = 0; i < EnemyCommand.field_0x08; i++ )source/enemy/command.c line 766 at bf9189e · upstream ↗ 1455void ActOverScoutD_800C85DC( WatcherWork* work, int time ) ⋮ 1457 work->vision.length = 3000;
source/enemy/action.c lines 1455–1457 at bf9189e · upstream ↗ 610void s00a_command_800C6B24( WatcherWork* work, int time ) ⋮ 612 work->vision.length = 0;
source/enemy/action.c lines 610–612 at bf9189e · upstream ↗ 224void s00a_command_800C9D7C( WatcherWork* work ) ⋮ 226 int val; 227 val = work->vision.field_B92; 228 switch ( val ) ⋮ 230 case 0: 231 if ( work->field_B94 != 2 || GM_PlayerStatus & PLAYER_HIDDEN || !( GM_PlayerStatus & ( PLAYER_INTRUDE | PLAYER_NOHIDDEN ) ) || work->vision.length == 0 ) ⋮ 233 work->field_B94 = 0; 234 work->alert_level -= COM_ALERT_DECREMENT; ⋮ 236 break; 237 case 1: 238 work->field_B94 = val; 239 work->alert_level -= 1; ⋮
source/enemy/enemy.c lines 224–255 at bf9189e · upstream ↗ 352void EnemyActionMain( WatcherWork *work ) ⋮ 356 s00a_command_800C9878( work ); 357 s00a_command_800C9E68(work); 358 s00a_command_800C9D28(work); 359 s00a_command_800C9D7C(work); 360 Enemy_Think(work);
source/enemy/enemy.c lines 352–360 at bf9189e · 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 Metal Gear Solid Integral (SLPM-86247), a guard looks for footprints only if its stage script gives it the
coption, which setsfield_BA3bit 0x10, and not in the commander's mode 1, TOP_COMM_ALERT (s00a_command_800C9D28skips the check there, though not in evasion). The check (s00a_command_800C9C7C) runs when AsiatoCheck passes and the guard isn't already in think2 5, and takes the nearest of the 48 footprint slots (AsiatoPositions) whosepadis 1 that lies withinvision.length(a horizontal distance) and less thanvision.anglefrom the guard's facing, with no wall between (SearchNearAsiato): the sight cone, without its 500-unit ring or its outer band. A patrolling guard that finds one goes to think2 5 if none offield_BA1bits 4, 2, and 1 (the last a heard noise) ranks above it.CodeFoxdieTeam/mgs_reversingHow we know
681 opt = GCL_GetOption( 'c' ); 682 if ( opt ) ⋮ 684 work->field_BA3 |= 0x10;
source/enemy/watcher.c lines 681–685 at bf9189e · upstream ↗ 179void s00a_command_800C9C7C( WatcherWork* work ) ⋮ 185 if ( !( work->field_BA3 & 16 ) ) ⋮ 187 return; ⋮ 191 if ( work->think2 == 5 ) ⋮ 193 return; ⋮ 198 if ( !( AsiatoCheck( hzd, &ctrl->mov ) ) ) ⋮ 200 return; ⋮ 203 if ( SearchNearAsiato( hzd, &ctrl->mov, vision->facedir, vision->angle, vision->length ) < 0 ) ⋮ 205 return; ⋮ 208 work->field_BA1 |= 16; ⋮ 211void s00a_command_800C9D28( WatcherWork* work ) ⋮ 213 work->field_BA1 = 0; ⋮
source/enemy/enemy.c lines 179–222 at bf9189e · upstream ↗ 205int SearchNearAsiato( HZD_HDL *hzd, SVECTOR *pos, int facedir, int range, int length ) ⋮ 216 for ( i = 0; i < 48 ; i++ ) ⋮ 218 if (AsiatoPositions[i].pad == 1 ) ⋮ 220 GV_SubVec3( &AsiatoPositions[i], pos, &svec ); 221 svec.vy = 0; 222 len = GV_VecLen3( &svec ); ⋮ 224 if ( len < max_len && len < length && GV_DiffDirAbs( facedir, GV_VecDir2(&svec) ) < range && 225 !HZD_OnlineHazardCheck(hzd, pos, &AsiatoPositions[i], HZD_CHK_ALL, SEGMENT_ATR) )
source/enemy/asiato.c lines 205–231 at bf9189e · upstream ↗ 1951 else if ( work->field_BA1 & 16 ) ⋮ 1953 s00a_command_800CB628( work );
source/enemy/think.c lines 1951–1954 at bf9189e · upstream ↗ 590void s00a_command_800CB628( WatcherWork *work ) ⋮ 592 work->think2 = 5;
source/enemy/think.c lines 590–593 at bf9189e · upstream ↗ Checked against the evidence by its author, an AI model, 2026-10-22.
Checked by a second AI model, 2026-10-22.
Sources
Code
- FoxdieTeam and mgs_reversing contributors. 2026. “mgs_reversing: a matching decompilation of Metal Gear Solid Integral for the PlayStation (FoxdieTeam/mgs_reversing).” Commit bf9189e. FoxdieTeam/mgs_reversing @ bf9189e · licence, all uses