diff --git a/deploy-config/hardhat.json b/deploy-config/hardhat.json
index 8b79cf118..b696a1481 100644
--- a/deploy-config/hardhat.json
+++ b/deploy-config/hardhat.json
@@ -84,6 +84,5 @@
   "multiproofGenesisOutputRoot": "0x0000000000000000000000000000000000000000000000000000000000000001",
   "multiproofGenesisBlockNumber": 0,
   "multiproofBlockInterval": 100,
-  "multiproofIntermediateBlockInterval": 10,
-  "multiproofProofThreshold": 1
+  "multiproofIntermediateBlockInterval": 10
 }
diff --git a/deploy-config/sepolia.json b/deploy-config/sepolia.json
index 52c30a043..85731182f 100644
--- a/deploy-config/sepolia.json
+++ b/deploy-config/sepolia.json
@@ -77,7 +77,6 @@
   "multiproofGenesisBlockNumber": 37223829,
   "multiproofBlockInterval": 600,
   "multiproofIntermediateBlockInterval": 30,
-  "multiproofProofThreshold": 1,
   "risc0VerifierRouter": "0xb121b667dd2cf27f95f9f5107137696f56f188f6",
   "risc0SetBuilderImageId": "0x70909b25db0db00f1d4b4016aeb876f53568a3e5a8e6397cb562d79947a02cc9"
 }
diff --git a/interfaces/multiproof/IVerifier.sol b/interfaces/multiproof/IVerifier.sol
index 4f5fbd38b..14510de9f 100644
--- a/interfaces/multiproof/IVerifier.sol
+++ b/interfaces/multiproof/IVerifier.sol
@@ -13,4 +13,7 @@ interface IVerifier {
     /// @dev Should only occur if a soundness issue is found.
     /// @dev Should only be callable by a proper dispute game.
     function nullify() external;
+
+    /// @notice Whether this verifier has been nullified.
+    function nullified() external view returns (bool);
 }
diff --git a/scripts/deploy/Deploy.s.sol b/scripts/deploy/Deploy.s.sol
index cc91cad7a..76f53555a 100644
--- a/scripts/deploy/Deploy.s.sol
+++ b/scripts/deploy/Deploy.s.sol
@@ -290,7 +290,6 @@ contract Deploy is Deployer {
                 l2ChainID: cfg.l2ChainID(),
                 multiproofBlockInterval: cfg.multiproofBlockInterval(),
                 multiproofIntermediateBlockInterval: cfg.multiproofIntermediateBlockInterval(),
-                multiproofProofThreshold: cfg.multiproofProofThreshold(),
                 protocolVersionsProxy: IProtocolVersions(artifacts.mustGetAddress("ProtocolVersionsProxy")),
                 superchainConfigProxy: superchainConfigProxy,
                 superchainProxyAdmin: superchainProxyAdmin,
diff --git a/scripts/deploy/DeployConfig.s.sol b/scripts/deploy/DeployConfig.s.sol
index ddf13fadd..f4522c365 100644
--- a/scripts/deploy/DeployConfig.s.sol
+++ b/scripts/deploy/DeployConfig.s.sol
@@ -106,7 +106,6 @@ contract DeployConfig is Script {
     uint256 public multiproofGenesisBlockNumber;
     uint256 public multiproofBlockInterval;
     uint256 public multiproofIntermediateBlockInterval;
-    uint256 public multiproofProofThreshold;
 
     // RISC Zero / NitroEnclaveVerifier Configuration (reference values; DeployRiscZeroStack takes these as CLI args)
     bytes32 public risc0SetBuilderImageId;
@@ -227,7 +226,6 @@ contract DeployConfig is Script {
         multiproofGenesisBlockNumber = _readOr(_json, "$.multiproofGenesisBlockNumber", 0);
         multiproofBlockInterval = _readOr(_json, "$.multiproofBlockInterval", 100);
         multiproofIntermediateBlockInterval = _readOr(_json, "$.multiproofIntermediateBlockInterval", 10);
-        multiproofProofThreshold = _readOr(_json, "$.multiproofProofThreshold", 1);
 
         risc0SetBuilderImageId = bytes32(_readOr(_json, "$.risc0SetBuilderImageId", 0));
         nitroRootCert = bytes32(_readOr(_json, "$.nitroRootCert", 0));
diff --git a/scripts/deploy/DeployImplementations.s.sol b/scripts/deploy/DeployImplementations.s.sol
index a77e97296..cc73baa74 100644
--- a/scripts/deploy/DeployImplementations.s.sol
+++ b/scripts/deploy/DeployImplementations.s.sol
@@ -73,7 +73,6 @@ contract DeployImplementations is Script {
         uint256 l2ChainID;
         uint256 multiproofBlockInterval;
         uint256 multiproofIntermediateBlockInterval;
-        uint256 multiproofProofThreshold;
         // Outputs from DeploySuperchain.s.sol.
         ISuperchainConfig superchainConfigProxy;
         IProtocolVersions protocolVersionsProxy;
@@ -740,8 +739,7 @@ contract DeployImplementations is Script {
                     _input.multiproofConfigHash,
                     _input.l2ChainID,
                     _input.multiproofBlockInterval,
-                    _input.multiproofIntermediateBlockInterval,
-                    _input.multiproofProofThreshold
+                    _input.multiproofIntermediateBlockInterval
                 )
             )
         );
diff --git a/scripts/multiproof/DeployDevNoNitro.s.sol b/scripts/multiproof/DeployDevNoNitro.s.sol
index 7eaa5dea3..13cfb48a0 100644
--- a/scripts/multiproof/DeployDevNoNitro.s.sol
+++ b/scripts/multiproof/DeployDevNoNitro.s.sol
@@ -84,7 +84,6 @@ import { MockDelayedWETH } from "./mocks/MockDelayedWETH.sol";
 contract DeployDevNoNitro is Script {
     uint256 public constant BLOCK_INTERVAL = 100;
     uint256 public constant INTERMEDIATE_BLOCK_INTERVAL = 10;
-    uint256 public constant PROOF_THRESHOLD = 1;
     uint256 public constant INIT_BOND = 0.001 ether;
 
     DeployConfig public constant cfg =
@@ -183,8 +182,7 @@ contract DeployDevNoNitro is Script {
                 cfg.multiproofConfigHash(),
                 cfg.l2ChainID(),
                 BLOCK_INTERVAL,
-                INTERMEDIATE_BLOCK_INTERVAL,
-                PROOF_THRESHOLD
+                INTERMEDIATE_BLOCK_INTERVAL
             )
         );
 
diff --git a/scripts/multiproof/DeployDevWithNitro.s.sol b/scripts/multiproof/DeployDevWithNitro.s.sol
index 3eaa966c6..ed34a3f5e 100644
--- a/scripts/multiproof/DeployDevWithNitro.s.sol
+++ b/scripts/multiproof/DeployDevWithNitro.s.sol
@@ -68,7 +68,6 @@ import { MockDelayedWETH } from "./mocks/MockDelayedWETH.sol";
 contract DeployDevWithNitro is Script {
     uint256 public constant BLOCK_INTERVAL = 600;
     uint256 public constant INTERMEDIATE_BLOCK_INTERVAL = 30;
-    uint256 public constant PROOF_THRESHOLD = 1;
     uint256 public constant INIT_BOND = 0.00001 ether;
 
     DeployConfig public constant cfg =
@@ -178,8 +177,7 @@ contract DeployDevWithNitro is Script {
                 cfg.multiproofConfigHash(),
                 cfg.l2ChainID(),
                 BLOCK_INTERVAL,
-                INTERMEDIATE_BLOCK_INTERVAL,
-                PROOF_THRESHOLD
+                INTERMEDIATE_BLOCK_INTERVAL
             )
         );
 
diff --git a/snapshots/semver-lock.json b/snapshots/semver-lock.json
index c7ce7ce69..625403be7 100644
--- a/snapshots/semver-lock.json
+++ b/snapshots/semver-lock.json
@@ -232,24 +232,24 @@
     "sourceCodeHash": "0x955bd0c9b47e43219865e4e92abf28d916c96de20cbdf2f94c8ab14d02083759"
   },
   "src/multiproof/AggregateVerifier.sol:AggregateVerifier": {
-    "initCodeHash": "0x54168f8e087c104e6bcc532d2d323327205a14b4ad3597d14d6cadcc1924543b",
-    "sourceCodeHash": "0x36342bdca5dd7c41f408216377b4b0e1a6d076df6396c77a6350d5d67e499ebe"
+    "initCodeHash": "0x10bb6a60f21de103d1da9ff310a38f571c53a8113d04a029087648debf3f0341",
+    "sourceCodeHash": "0xcd4c508223b9b461ec230bde03c1630fe68cd00b82ee84c39b93d9f3c0d71d37"
   },
   "src/multiproof/tee/NitroEnclaveVerifier.sol:NitroEnclaveVerifier": {
     "initCodeHash": "0x100364f9b0c63a61538386ba91e73dcffba22d3f6dfe8efdbbf5ff347b6fce47",
-    "sourceCodeHash": "0xed5d92ee1f32f2fde5e89ba278f3142c8113c774f6ce7bd831781a8abe7bddf3"
+    "sourceCodeHash": "0xbedb66738a0ab18600b2ed21de51d552a210c4cc71b2c971440e325791dc3ce7"
   },
   "src/multiproof/tee/TEEProverRegistry.sol:TEEProverRegistry": {
     "initCodeHash": "0xfd1942e1c2f59b0aa72b33d698a948a53b6e4cf1040106f173fb5d89f63f57b0",
     "sourceCodeHash": "0xf1ec1f02f540da659a204b26acf986fdce7d7d63bba67a87923f52453fb92ccb"
   },
   "src/multiproof/tee/TEEVerifier.sol:TEEVerifier": {
-    "initCodeHash": "0x9f65f2d6e2c43512594f7a08fc0ea475d06f48d230690c3c85cc85dee3a9679a",
+    "initCodeHash": "0x655576cc21cc5c603d55fb4dd2a2f0ef57b11deeaabd3e539b0a70a5f7e2c9af",
     "sourceCodeHash": "0xd5e01b4f4e69313e56e51f2f46b7bbe699ef8dc24b8a6385b8ebac4162e05353"
   },
   "src/multiproof/zk/ZKVerifier.sol:ZkVerifier": {
-    "initCodeHash": "0xb50364a38af3ef7dbfab9a84210d08c5f61105638326e6a442828dfa5afc924d",
-    "sourceCodeHash": "0x0ffc935dbd768b4aad6c35e2d6826c556191cc5e8f20737045d1bc196b13e19f"
+    "initCodeHash": "0xcdc5bd5fa8ffad76358b8f34a53b8c0cb36540176c31f82d52546373d0bd5245",
+    "sourceCodeHash": "0xdb3774707dc5fa59c32636ab896eba284bb6466568bd04583d9767112857d405"
   },
   "src/revenue-share/FeeDisburser.sol:FeeDisburser": {
     "initCodeHash": "0x1278027e3756e2989e80c0a7b513e221a5fe0d3dbd9ded108375a29b2c1f3d57",
@@ -299,4 +299,4 @@
     "initCodeHash": "0x2bfce526f82622288333d53ca3f43a0a94306ba1bab99241daa845f8f4b18bd4",
     "sourceCodeHash": "0xf49d7b0187912a6bb67926a3222ae51121e9239495213c975b3b4b217ee57a1b"
   }
-}
+}
\ No newline at end of file
diff --git a/src/multiproof/AggregateVerifier.sol b/src/multiproof/AggregateVerifier.sol
index 30142622b..4c99f1d84 100644
--- a/src/multiproof/AggregateVerifier.sol
+++ b/src/multiproof/AggregateVerifier.sol
@@ -62,6 +62,9 @@ contract AggregateVerifier is Clone, ReentrancyGuard, ISemver {
 
     /// @notice The maximum number of blocks that EIP-2935 can look back (~8192).
     uint256 public constant EIP2935_WINDOW = 8191;
+
+    /// @notice The minimum number of proofs required to resolve the game.
+    uint256 public constant PROOF_THRESHOLD = 1;
     ////////////////////////////////////////////////////////////////
     //                         Immutables                         //
     ////////////////////////////////////////////////////////////////
@@ -109,9 +112,6 @@ contract AggregateVerifier is Clone, ReentrancyGuard, ISemver {
     /// @notice The game type ID.
     GameType internal immutable GAME_TYPE;
 
-    /// @notice The minimum number of proofs required to resolve the game.
-    uint256 public immutable PROOF_THRESHOLD;
-
     ////////////////////////////////////////////////////////////////
     //                         State Vars                         //
     ////////////////////////////////////////////////////////////////
@@ -261,7 +261,6 @@ contract AggregateVerifier is Clone, ReentrancyGuard, ISemver {
     /// @param l2ChainId The chain ID of the L2 network.
     /// @param blockInterval The block interval.
     /// @param intermediateBlockInterval The intermediate block interval.
-    /// @param proofThreshold The minimum number of proofs required to resolve the game.
     constructor(
         GameType gameType_,
         IAnchorStateRegistry anchorStateRegistry_,
@@ -273,17 +272,13 @@ contract AggregateVerifier is Clone, ReentrancyGuard, ISemver {
         bytes32 configHash,
         uint256 l2ChainId,
         uint256 blockInterval,
-        uint256 intermediateBlockInterval,
-        uint256 proofThreshold
+        uint256 intermediateBlockInterval
     ) {
         // Block interval and intermediate block interval must be positive and divisible.
         if (blockInterval == 0 || intermediateBlockInterval == 0 || blockInterval % intermediateBlockInterval != 0) {
             revert InvalidBlockInterval(blockInterval, intermediateBlockInterval);
         }
 
-        // Proof threshold must be between 1 and 2.
-        if (proofThreshold != 1 && proofThreshold != 2) revert InvalidProofThreshold();
-
         // Set up initial game state.
         GAME_TYPE = gameType_;
         ANCHOR_STATE_REGISTRY = anchorStateRegistry_;
@@ -298,7 +293,6 @@ contract AggregateVerifier is Clone, ReentrancyGuard, ISemver {
         L2_CHAIN_ID = l2ChainId;
         BLOCK_INTERVAL = blockInterval;
         INTERMEDIATE_BLOCK_INTERVAL = intermediateBlockInterval;
-        PROOF_THRESHOLD = proofThreshold;
 
         INITIALIZE_CALLDATA_SIZE = 0x8E + 0x20 * intermediateOutputRootsCount();
     }
@@ -454,6 +448,9 @@ contract AggregateVerifier is Clone, ReentrancyGuard, ISemver {
             status = GameStatus.CHALLENGER_WINS;
         } else {
             // Game must be completed with a valid proof and enough proofs.
+            if (_updateProofCount()) {
+                return status;
+            }
             if (!gameOver()) revert GameNotOver();
             if (proofCount < PROOF_THRESHOLD) revert NotEnoughProofs();
 
@@ -592,9 +589,6 @@ contract AggregateVerifier is Clone, ReentrancyGuard, ISemver {
 
         // Nullify the verifier to prevent further proof verification.
         if (proofType == ProofType.ZK) {
-            // Delete the challenged intermediate root if one existed.
-            delete counteredByIntermediateRootIndexPlusOne;
-
             IVerifier(ZK_VERIFIER).nullify();
         } else if (proofType == ProofType.TEE) {
             IVerifier(TEE_VERIFIER).nullify();
@@ -787,6 +781,10 @@ contract AggregateVerifier is Clone, ReentrancyGuard, ISemver {
     /// @dev Should only occur if challenged or nullified.
     function _proofRefutedUpdate(ProofType proofType) internal {
         delete proofTypeToProver[proofType];
+        // A ZK challenge is recorded in `counteredByIntermediateRootIndexPlusOne`; dropping the ZK proof clears it.
+        if (proofType == ProofType.ZK) {
+            delete counteredByIntermediateRootIndexPlusOne;
+        }
 
         // Should not be possible, but just in case.
         if (proofCount == 0) revert NotEnoughProofs();
@@ -812,6 +810,19 @@ contract AggregateVerifier is Clone, ReentrancyGuard, ISemver {
         expectedResolution = Timestamp.wrap(uint64(block.timestamp) + delay);
     }
 
+    /// @notice Updates the proof count and returns true if a proof was nullified.
+    function _updateProofCount() internal returns (bool) {
+        if (proofTypeToProver[ProofType.TEE] != address(0) && TEE_VERIFIER.nullified()) {
+            _proofRefutedUpdate(ProofType.TEE);
+            return true;
+        }
+        if (proofTypeToProver[ProofType.ZK] != address(0) && ZK_VERIFIER.nullified()) {
+            _proofRefutedUpdate(ProofType.ZK);
+            return true;
+        }
+        return false;
+    }
+
     function _getDelay() internal view returns (uint64) {
         if (proofCount >= 2) {
             return FAST_FINALIZATION_DELAY;
diff --git a/src/multiproof/Verifier.sol b/src/multiproof/Verifier.sol
index d9505f9ba..1857dde97 100644
--- a/src/multiproof/Verifier.sol
+++ b/src/multiproof/Verifier.sol
@@ -35,11 +35,13 @@ abstract contract Verifier is IVerifier {
 
     /// @notice Nullifies the verifier to prevent further proof verification.
     /// @dev Should only occur if a soundness issue is found.
-    /// @dev Should only be callable by a proper dispute game.
+    /// @dev Should only be callable by a registered, respected, not blacklisted, not retired dispute game.
     function nullify() external override {
         if (
-            !ANCHOR_STATE_REGISTRY.isGameProper(IDisputeGame(msg.sender))
+            !ANCHOR_STATE_REGISTRY.isGameRegistered(IDisputeGame(msg.sender))
                 || !ANCHOR_STATE_REGISTRY.isGameRespected(IDisputeGame(msg.sender))
+                || ANCHOR_STATE_REGISTRY.isGameBlacklisted(IDisputeGame(msg.sender))
+                || ANCHOR_STATE_REGISTRY.isGameRetired(IDisputeGame(msg.sender))
         ) revert NotProperGame();
         nullified = true;
 
diff --git a/src/multiproof/tee/NitroEnclaveVerifier.sol b/src/multiproof/tee/NitroEnclaveVerifier.sol
index b38a6ca9d..be4d3bc0f 100644
--- a/src/multiproof/tee/NitroEnclaveVerifier.sol
+++ b/src/multiproof/tee/NitroEnclaveVerifier.sol
@@ -591,8 +591,11 @@ contract NitroEnclaveVerifier is Ownable, INitroEnclaveVerifier, ISemver {
      * 5. Caches newly discovered certificates for future use
      *
      * The timestamp validation converts milliseconds to seconds and checks:
-     * - Attestation is not too old (timestamp + maxTimeDiff >= block.timestamp)
-     * - Attestation is not from the future (timestamp <= block.timestamp)
+     * - Attestation is not too old (timestamp + maxTimeDiff > block.timestamp)
+     * - Attestation is not from the future (timestamp < block.timestamp)
+     * Note that due to truncating timestamp from milliseconds, to seconds,
+     * some valid attestations may be rejected. However, this ensures all invalid
+     * timestamps are rejected.
      */
     function _verifyJournal(VerifierJournal memory journal) internal returns (VerifierJournal memory) {
         if (journal.result != VerificationResult.Success) {
diff --git a/src/multiproof/zk/ZKVerifier.sol b/src/multiproof/zk/ZKVerifier.sol
index f254b8bd4..7508eab6e 100644
--- a/src/multiproof/zk/ZKVerifier.sol
+++ b/src/multiproof/zk/ZKVerifier.sol
@@ -15,9 +15,6 @@ contract ZkVerifier is Verifier {
     /// @notice The SP1 verifier gateway contract.
     ISP1Verifier public immutable SP1_VERIFIER;
 
-    /// @notice Thrown when SP1 proof verification reverts.
-    error SP1VerificationFailed();
-
     /// @param sp1Verifier The deployed SP1 verifier gateway address.
     /// @param anchorStateRegistry The anchor state registry for nullification checks.
     constructor(ISP1Verifier sp1Verifier, IAnchorStateRegistry anchorStateRegistry) Verifier(anchorStateRegistry) {
diff --git a/test/multiproof/AggregateVerifier.t.sol b/test/multiproof/AggregateVerifier.t.sol
index 2e02d4801..c8bf67fd1 100644
--- a/test/multiproof/AggregateVerifier.t.sol
+++ b/test/multiproof/AggregateVerifier.t.sol
@@ -375,8 +375,7 @@ contract AggregateVerifierTest is BaseTest {
             CONFIG_HASH,
             L2_CHAIN_ID,
             0,
-            INTERMEDIATE_BLOCK_INTERVAL,
-            PROOF_THRESHOLD
+            INTERMEDIATE_BLOCK_INTERVAL
         );
 
         // Case 2: INTERMEDIATE_BLOCK_INTERVAL is 0
@@ -392,8 +391,7 @@ contract AggregateVerifierTest is BaseTest {
             CONFIG_HASH,
             L2_CHAIN_ID,
             BLOCK_INTERVAL,
-            0,
-            PROOF_THRESHOLD
+            0
         );
 
         // Case 3: BLOCK_INTERVAL is not divisible by INTERMEDIATE_BLOCK_INTERVAL
@@ -409,44 +407,7 @@ contract AggregateVerifierTest is BaseTest {
             CONFIG_HASH,
             L2_CHAIN_ID,
             3,
-            2,
-            PROOF_THRESHOLD
-        );
-    }
-
-    function testDeployWithInvalidProofThreshold() public {
-        // Case 1: PROOF_THRESHOLD is 0
-        vm.expectRevert(abi.encodeWithSelector(AggregateVerifier.InvalidProofThreshold.selector));
-        new AggregateVerifier(
-            AGGREGATE_VERIFIER_GAME_TYPE,
-            IAnchorStateRegistry(address(anchorStateRegistry)),
-            IDelayedWETH(payable(address(delayedWETH))),
-            IVerifier(address(teeVerifier)),
-            IVerifier(address(zkVerifier)),
-            TEE_IMAGE_HASH,
-            AggregateVerifier.ZkHashes(ZK_RANGE_HASH, ZK_AGGREGATE_HASH),
-            CONFIG_HASH,
-            L2_CHAIN_ID,
-            BLOCK_INTERVAL,
-            INTERMEDIATE_BLOCK_INTERVAL,
-            0
-        );
-
-        // Case 2: PROOF_THRESHOLD is > 2
-        vm.expectRevert(abi.encodeWithSelector(AggregateVerifier.InvalidProofThreshold.selector));
-        new AggregateVerifier(
-            AGGREGATE_VERIFIER_GAME_TYPE,
-            IAnchorStateRegistry(address(anchorStateRegistry)),
-            IDelayedWETH(payable(address(delayedWETH))),
-            IVerifier(address(teeVerifier)),
-            IVerifier(address(zkVerifier)),
-            TEE_IMAGE_HASH,
-            AggregateVerifier.ZkHashes(ZK_RANGE_HASH, ZK_AGGREGATE_HASH),
-            CONFIG_HASH,
-            L2_CHAIN_ID,
-            BLOCK_INTERVAL,
-            INTERMEDIATE_BLOCK_INTERVAL,
-            3
+            2
         );
     }
 }
diff --git a/test/multiproof/BaseTest.t.sol b/test/multiproof/BaseTest.t.sol
index f7f8d36e2..55c4d311d 100644
--- a/test/multiproof/BaseTest.t.sol
+++ b/test/multiproof/BaseTest.t.sol
@@ -40,7 +40,6 @@ contract BaseTest is Test {
     uint256 public constant DELAYED_WETH_DELAY = 1 days;
     // Finality delay handled by the AggregateVerifier
     uint256 public constant FINALITY_DELAY = 0 days;
-    uint256 public constant PROOF_THRESHOLD = 1;
 
     uint256 public currentL2BlockNumber = 0;
 
@@ -136,8 +135,7 @@ contract BaseTest is Test {
             CONFIG_HASH,
             L2_CHAIN_ID,
             BLOCK_INTERVAL,
-            INTERMEDIATE_BLOCK_INTERVAL,
-            PROOF_THRESHOLD
+            INTERMEDIATE_BLOCK_INTERVAL
         );
 
         // Set the implementation for the aggregate verifier
diff --git a/test/multiproof/Challenge.t.sol b/test/multiproof/Challenge.t.sol
index d9292fdcd..b36cf03e4 100644
--- a/test/multiproof/Challenge.t.sol
+++ b/test/multiproof/Challenge.t.sol
@@ -208,4 +208,102 @@ contract ChallengeTest is BaseTest {
         vm.expectRevert(Verifier.Nullified.selector);
         game.challenge(zkProof3, BLOCK_INTERVAL / INTERMEDIATE_BLOCK_INTERVAL - 1, rootClaim3.raw());
     }
+
+    /// @notice A TEE+ZK challenge on game A is cleared when another game nullifies the shared ZK verifier; A then
+    ///         resolves as defender after `SLOW_FINALIZATION_DELAY`.
+    function testChallengeRemovedWhenZkVerifierNullifiedByOtherGame() public {
+        currentL2BlockNumber += BLOCK_INTERVAL;
+
+        Claim rootClaimA = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "tee-challenge")));
+        bytes memory teeProofA = _generateProof("tee-ch-a", AggregateVerifier.ProofType.TEE);
+        AggregateVerifier gameA = _createAggregateVerifierGame(
+            TEE_PROVER, rootClaimA, currentL2BlockNumber, address(anchorStateRegistry), teeProofA
+        );
+
+        Claim rootChallenge = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "zk-challenge")));
+        bytes memory zkChallenge = _generateProof("zk-challenge", AggregateVerifier.ProofType.ZK);
+        vm.prank(ZK_PROVER);
+        gameA.challenge(zkChallenge, BLOCK_INTERVAL / INTERMEDIATE_BLOCK_INTERVAL - 1, rootChallenge.raw());
+
+        assertEq(gameA.proofCount(), 2);
+        assertGt(gameA.counteredByIntermediateRootIndexPlusOne(), 0);
+
+        currentL2BlockNumber += BLOCK_INTERVAL;
+        Claim rootClaimB = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "zk-only-b")));
+        bytes memory zkProofB = _generateProof("zk-init-b", AggregateVerifier.ProofType.ZK);
+        AggregateVerifier gameB =
+            _createAggregateVerifierGame(ZK_PROVER, rootClaimB, currentL2BlockNumber, address(gameA), zkProofB);
+
+        Claim rootNullifyB = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "zk-nullify-b")));
+        bytes memory zkNullifyB = _generateProof("zk-nullify-b", AggregateVerifier.ProofType.ZK);
+        uint256 lastIdx = BLOCK_INTERVAL / INTERMEDIATE_BLOCK_INTERVAL - 1;
+        gameB.nullify(zkNullifyB, lastIdx, rootNullifyB.raw());
+        assertTrue(zkVerifier.nullified());
+
+        assertEq(uint8(gameA.resolve()), uint8(GameStatus.IN_PROGRESS));
+        assertEq(gameA.proofCount(), 1);
+        assertEq(gameA.counteredByIntermediateRootIndexPlusOne(), 0);
+        assertEq(address(gameA.zkProver()), address(0));
+
+        vm.warp(block.timestamp + 7 days);
+        assertEq(uint8(gameA.resolve()), uint8(GameStatus.DEFENDER_WINS));
+        assertEq(gameA.bondRecipient(), TEE_PROVER);
+
+        uint256 balanceBefore = TEE_PROVER.balance;
+        gameA.claimCredit();
+        vm.warp(block.timestamp + DELAYED_WETH_DELAY);
+        gameA.claimCredit();
+        assertEq(TEE_PROVER.balance, balanceBefore + INIT_BOND);
+        assertEq(delayedWETH.balanceOf(address(gameA)), 0);
+    }
+
+    /// @notice Game A is created with TEE and challenged with ZK. Another game nullifies the shared TEE verifier.
+    ///         The first `resolve` persists the TEE refutation; after `SLOW_FINALIZATION_DELAY`, A finalizes as
+    ///         challenger wins and the bond goes to the ZK challenger.
+    function testChallengeWinsWhenSharedTeeVerifierNullifiedByOtherGame() public {
+        currentL2BlockNumber += BLOCK_INTERVAL;
+
+        Claim rootClaimA = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "tee-challenge-tee-null")));
+        bytes memory teeProofA = _generateProof("tee-proof-a", AggregateVerifier.ProofType.TEE);
+        AggregateVerifier gameA = _createAggregateVerifierGame(
+            TEE_PROVER, rootClaimA, currentL2BlockNumber, address(anchorStateRegistry), teeProofA
+        );
+
+        Claim rootChallenge = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "zk-challenge")));
+        bytes memory zkChallenge = _generateProof("zk-challenge", AggregateVerifier.ProofType.ZK);
+        vm.prank(ZK_PROVER);
+        gameA.challenge(zkChallenge, BLOCK_INTERVAL / INTERMEDIATE_BLOCK_INTERVAL - 1, rootChallenge.raw());
+
+        assertEq(gameA.proofCount(), 2);
+        assertGt(gameA.counteredByIntermediateRootIndexPlusOne(), 0);
+
+        currentL2BlockNumber += BLOCK_INTERVAL;
+        Claim rootClaimB = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "tee-only-b")));
+        bytes memory teeProofB = _generateProof("tee-init-b", AggregateVerifier.ProofType.TEE);
+        AggregateVerifier gameB =
+            _createAggregateVerifierGame(TEE_PROVER, rootClaimB, currentL2BlockNumber, address(gameA), teeProofB);
+
+        Claim rootNullifyB = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "tee-nullify-b")));
+        bytes memory teeNullifyB = _generateProof("tee-nullify-b", AggregateVerifier.ProofType.TEE);
+        uint256 lastIdx = BLOCK_INTERVAL / INTERMEDIATE_BLOCK_INTERVAL - 1;
+        gameB.nullify(teeNullifyB, lastIdx, rootNullifyB.raw());
+        assertTrue(teeVerifier.nullified());
+
+        assertEq(uint8(gameA.resolve()), uint8(GameStatus.IN_PROGRESS));
+        assertEq(gameA.proofCount(), 1);
+        assertGt(gameA.counteredByIntermediateRootIndexPlusOne(), 0);
+        assertEq(address(gameA.teeProver()), address(0));
+        assertEq(gameA.zkProver(), ZK_PROVER);
+
+        vm.warp(block.timestamp + 7 days);
+        assertEq(uint8(gameA.resolve()), uint8(GameStatus.CHALLENGER_WINS));
+        assertEq(gameA.bondRecipient(), ZK_PROVER);
+
+        uint256 balanceBefore = ZK_PROVER.balance;
+        gameA.claimCredit();
+        vm.warp(block.timestamp + DELAYED_WETH_DELAY);
+        gameA.claimCredit();
+        assertEq(ZK_PROVER.balance, balanceBefore + INIT_BOND);
+        assertEq(delayedWETH.balanceOf(address(gameA)), 0);
+    }
 }
diff --git a/test/multiproof/Nullify.t.sol b/test/multiproof/Nullify.t.sol
index c1057f10d..5bc8c4aa9 100644
--- a/test/multiproof/Nullify.t.sol
+++ b/test/multiproof/Nullify.t.sol
@@ -169,4 +169,125 @@ contract NullifyTest is BaseTest {
         assertEq(game1.gameCreator().balance, balanceBefore + INIT_BOND);
         assertEq(delayedWETH.balanceOf(address(game1)), 0);
     }
+
+    /// @notice `resolve` runs `_updateProofCount`; when the shared TEE verifier was nullified by another game,
+    ///         refutation persists and `resolve` returns early `IN_PROGRESS` (no `Resolved` event) instead of
+    /// reverting. @dev All clones share the same `MockVerifier` TEE instance; `Verifier.nullify` requires a proper
+    /// factory game.
+    function testResolveEarlyReturnWhenSharedTeeVerifierNullifiedByAnotherGame() public {
+        currentL2BlockNumber += BLOCK_INTERVAL;
+
+        Claim rootClaimA = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "game-a")));
+        bytes memory teeProofA = _generateProof("tee-proof-a", AggregateVerifier.ProofType.TEE);
+        AggregateVerifier gameA = _createAggregateVerifierGame(
+            TEE_PROVER, rootClaimA, currentL2BlockNumber, address(anchorStateRegistry), teeProofA
+        );
+
+        currentL2BlockNumber += BLOCK_INTERVAL;
+
+        Claim rootClaimB = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "game-b")));
+        bytes memory teeProofB = _generateProof("tee-proof-b", AggregateVerifier.ProofType.TEE);
+        AggregateVerifier gameB =
+            _createAggregateVerifierGame(TEE_PROVER, rootClaimB, currentL2BlockNumber, address(gameA), teeProofB);
+
+        vm.warp(block.timestamp + 7 days);
+        assertTrue(gameA.gameOver());
+        assertEq(gameA.proofCount(), 1);
+
+        Claim rootClaimNullify = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "nullify-b")));
+        bytes memory teeProofNullify = _generateProof("tee-nullify-b", AggregateVerifier.ProofType.TEE);
+        uint256 lastIntermediateIdx = BLOCK_INTERVAL / INTERMEDIATE_BLOCK_INTERVAL - 1;
+        gameB.nullify(teeProofNullify, lastIntermediateIdx, rootClaimNullify.raw());
+
+        assertTrue(teeVerifier.nullified());
+        assertEq(gameA.proofCount(), 1);
+
+        assertEq(uint8(gameA.resolve()), uint8(GameStatus.IN_PROGRESS));
+        assertEq(gameA.proofCount(), 0);
+        assertEq(gameA.expectedResolution().raw(), type(uint64).max);
+
+        vm.expectRevert(AggregateVerifier.GameNotOver.selector);
+        gameA.resolve();
+    }
+
+    /// @notice Same as `testResolveEarlyReturnWhenSharedTeeVerifierNullifiedByAnotherGame` but for the shared ZK
+    /// verifier.
+    function testResolveEarlyReturnWhenSharedZkVerifierNullifiedByAnotherGame() public {
+        currentL2BlockNumber += BLOCK_INTERVAL;
+
+        Claim rootClaimA = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "zk-game-a")));
+        bytes memory zkProofA = _generateProof("zk-proof-a", AggregateVerifier.ProofType.ZK);
+        AggregateVerifier gameA = _createAggregateVerifierGame(
+            ZK_PROVER, rootClaimA, currentL2BlockNumber, address(anchorStateRegistry), zkProofA
+        );
+
+        currentL2BlockNumber += BLOCK_INTERVAL;
+
+        Claim rootClaimB = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "zk-game-b")));
+        bytes memory zkProofB = _generateProof("zk-proof-b", AggregateVerifier.ProofType.ZK);
+        AggregateVerifier gameB =
+            _createAggregateVerifierGame(ZK_PROVER, rootClaimB, currentL2BlockNumber, address(gameA), zkProofB);
+
+        vm.warp(block.timestamp + 7 days);
+        assertTrue(gameA.gameOver());
+        assertEq(gameA.proofCount(), 1);
+
+        Claim rootClaimNullify = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "zk-nullify-b")));
+        bytes memory zkProofNullify = _generateProof("zk-nullify-b", AggregateVerifier.ProofType.ZK);
+        uint256 lastIntermediateIdx = BLOCK_INTERVAL / INTERMEDIATE_BLOCK_INTERVAL - 1;
+        gameB.nullify(zkProofNullify, lastIntermediateIdx, rootClaimNullify.raw());
+
+        assertTrue(zkVerifier.nullified());
+        assertEq(gameA.proofCount(), 1);
+
+        assertEq(uint8(gameA.resolve()), uint8(GameStatus.IN_PROGRESS));
+        assertEq(gameA.proofCount(), 0);
+        assertEq(gameA.expectedResolution().raw(), type(uint64).max);
+
+        vm.expectRevert(AggregateVerifier.GameNotOver.selector);
+        gameA.resolve();
+    }
+
+    /// @notice With TEE + ZK, the fast window is 1 day. Another game nullifies the shared ZK verifier; the first
+    ///         `resolve` persists the ZK refutation and returns `IN_PROGRESS`. After `SLOW_FINALIZATION_DELAY`
+    ///         (7 days) from that moment, a second `resolve` finalizes with only the TEE proof.
+    function testTwoProofsResolveDelayedAfterExternalVerifierNullify() public {
+        currentL2BlockNumber += BLOCK_INTERVAL;
+
+        Claim rootClaimA = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "dual-a")));
+        bytes memory teeProofA = _generateProof("tee-dual-a", AggregateVerifier.ProofType.TEE);
+        AggregateVerifier gameA = _createAggregateVerifierGame(
+            TEE_PROVER, rootClaimA, currentL2BlockNumber, address(anchorStateRegistry), teeProofA
+        );
+
+        bytes memory zkProofA = _generateProof("zk-dual-a", AggregateVerifier.ProofType.ZK);
+        vm.prank(ZK_PROVER);
+        gameA.verifyProposalProof(zkProofA);
+
+        assertEq(gameA.proofCount(), 2);
+        assertEq(gameA.expectedResolution().raw(), block.timestamp + 1 days);
+
+        vm.warp(block.timestamp + 1 days);
+        assertTrue(gameA.gameOver());
+
+        currentL2BlockNumber += BLOCK_INTERVAL;
+        Claim rootClaimB = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "dual-b")));
+        bytes memory zkProofB = _generateProof("zk-dual-b", AggregateVerifier.ProofType.ZK);
+        AggregateVerifier gameB =
+            _createAggregateVerifierGame(ZK_PROVER, rootClaimB, currentL2BlockNumber, address(gameA), zkProofB);
+
+        Claim rootClaimNullify = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "dual-nullify-b")));
+        bytes memory zkProofNullify = _generateProof("zk-nullify-dual", AggregateVerifier.ProofType.ZK);
+        uint256 lastIntermediateIdx = BLOCK_INTERVAL / INTERMEDIATE_BLOCK_INTERVAL - 1;
+        gameB.nullify(zkProofNullify, lastIntermediateIdx, rootClaimNullify.raw());
+        assertTrue(zkVerifier.nullified());
+
+        assertEq(uint8(gameA.resolve()), uint8(GameStatus.IN_PROGRESS));
+        assertEq(gameA.proofCount(), 1);
+        assertEq(gameA.expectedResolution().raw(), block.timestamp + 7 days);
+
+        vm.warp(block.timestamp + 7 days);
+        assertEq(uint8(gameA.resolve()), uint8(GameStatus.DEFENDER_WINS));
+        assertEq(uint8(gameA.status()), uint8(GameStatus.DEFENDER_WINS));
+    }
 }
diff --git a/test/opcm/DeployImplementations.t.sol b/test/opcm/DeployImplementations.t.sol
index 73315b82f..bfbcdbf50 100644
--- a/test/opcm/DeployImplementations.t.sol
+++ b/test/opcm/DeployImplementations.t.sol
@@ -243,7 +243,6 @@ contract DeployImplementations_Test is Test, FeatureFlags {
             8453, // l2ChainID
             100, // multiproofBlockInterval
             10, // multiproofIntermediateBlockInterval
-            1, // multiproofProofThreshold
             superchainConfigProxy,
             protocolVersionsProxy,
             superchainProxyAdmin,
@@ -539,7 +538,6 @@ contract DeployImplementations_Test is Test, FeatureFlags {
             8453, // l2ChainID
             100, // multiproofBlockInterval
             10, // multiproofIntermediateBlockInterval
-            1, // multiproofProofThreshold
             superchainConfigProxy,
             protocolVersionsProxy,
             superchainProxyAdmin,
diff --git a/test/opcm/DeployOPChain.t.sol b/test/opcm/DeployOPChain.t.sol
index b8520079c..cb2875a55 100644
--- a/test/opcm/DeployOPChain.t.sol
+++ b/test/opcm/DeployOPChain.t.sol
@@ -101,7 +101,6 @@ contract DeployOPChain_TestBase is Test, FeatureFlags {
                 l2ChainID: 8453,
                 multiproofBlockInterval: 100,
                 multiproofIntermediateBlockInterval: 10,
-                multiproofProofThreshold: 1,
                 superchainConfigProxy: dso.superchainConfigProxy,
                 protocolVersionsProxy: dso.protocolVersionsProxy,
                 superchainProxyAdmin: dso.superchainProxyAdmin,
