@@ -1274,6 +1274,73 @@ public void CheckStandardInputStatus_WithBreakEnabled_DetectsCtrlCAndInvokesInt2
12741274 testHandler . Results . Should ( ) . NotContain ( ( byte ) TestResult . Failure ) ;
12751275 }
12761276
1277+ // ──────────────────────────────────────────────────────────────────────
1278+ // Phase 1 — Full hardware keyboard stack → AH=08h polling loop
1279+ // Reproduces the keyboard bug: INT 21h clears IF, so hardware IRQ1
1280+ // (keyboard) can never fire while the WriteAssemblyInRam polling loop
1281+ // is running. Keys injected through the full hardware stack never reach
1282+ // the BiosKeyboardBuffer.
1283+ // ──────────────────────────────────────────────────────────────────────
1284+
1285+ /// <summary>
1286+ /// Reproduces the live-emulation keyboard bug: a key pressed DURING the
1287+ /// AH=08h WriteAssemblyInRam polling loop never reaches BiosKeyboardBuffer
1288+ /// because INT 21h clears IF and the loop never re-enables interrupts (STI).
1289+ /// Full keyboard stack: HeadlessGui → InputEventHub → PS2Keyboard
1290+ /// → Intel8042Controller → IRQ1 (blocked by IF=0) → BiosKeyboardInt9Handler
1291+ /// → BiosKeyboardBuffer → ConsoleDevice → AH=08h.
1292+ /// </summary>
1293+ [ Fact ]
1294+ public void AH08h_ReadsKeyFromHardwareStack_WhenKeyInjectedDuringPolling ( ) {
1295+ // Arrange: COM calls AH=08h immediately. The key arrives while the
1296+ // WriteAssemblyInRam polling loop is running with IF=0.
1297+ byte [ ] program = new byte [ ] {
1298+ // Call AH=08h (Character Input Without Echo) — enters polling loop
1299+ 0xB4 , 0x08 , // mov ah, 08h
1300+ 0xCD , 0x21 , // int 21h
1301+
1302+ // Write AL to details port for diagnostics
1303+ 0xBA , 0x98 , 0x09 , // mov dx, DetailsPort (0x998)
1304+ 0xEE , // out dx, al
1305+
1306+ // Check AL == 0x31 ('1')
1307+ 0x3C , 0x31 , // cmp al, 31h
1308+ 0x75 , 0x04 , // jne failed
1309+
1310+ // Success
1311+ 0xB0 , 0x00 , // mov al, TestResult.Success
1312+ 0xEB , 0x02 , // jmp writeResult
1313+
1314+ // failed:
1315+ 0xB0 , 0xFF , // mov al, TestResult.Failure
1316+
1317+ // writeResult:
1318+ 0xBA , 0x99 , 0x09 , // mov dx, ResultPort
1319+ 0xEE , // out dx, al
1320+ 0xF4 // hlt
1321+ } ;
1322+
1323+ // Act — inject '1' through the full hardware keyboard stack at cycle 50.
1324+ // The key arrives while AH=08h is in its polling loop with IF=0.
1325+ DosTestHandler testHandler = RunDosTest ( program , keyInjectionAction : SimulateDigit1 ) ;
1326+
1327+ // Assert
1328+ testHandler . Details . Should ( ) . NotBeEmpty ( "AH=08h should write AL to details port" ) ;
1329+ byte actualAl = testHandler . Details [ 0 ] ;
1330+ actualAl . Should ( ) . Be ( 0x31 ,
1331+ "AH=08h should read '1' (0x31) from the hardware keyboard stack during polling" ) ;
1332+ testHandler . Results . Should ( ) . Contain ( ( byte ) TestResult . Success ) ;
1333+ testHandler . Results . Should ( ) . NotContain ( ( byte ) TestResult . Failure ) ;
1334+ }
1335+
1336+ /// <summary>
1337+ /// Simulates pressing the '1' key through the full UI → hardware keyboard stack.
1338+ /// </summary>
1339+ private static void SimulateDigit1 ( HeadlessGui gui ) {
1340+ gui . SimulateKeyPress ( PhysicalKey . Digit1 ) ;
1341+ gui . SimulateKeyRelease ( PhysicalKey . Digit1 ) ;
1342+ }
1343+
12771344 // ──────────────────────────────────────────────────────────────────────
12781345 // Phase 0 — STDIN/STDOUT handle routing tests
12791346 // These tests redirect STDIN (handle 0) to a file stream and verify that
0 commit comments