NES.java

1
package com.rox;
2
3
import com.rox.apu.APU;
4
import com.rox.audio.AudioOutput;
5
import com.rox.audio.Resampler;
6
import com.rox.audio.SpeakerAudioOutput;
7
import com.rox.cartridge.Cartridge;
8
import com.rox.clock.Clock;
9
import com.rox.clock.FPSClock;
10
import com.rox.clock.TickRateMonitor;
11
import com.rox.cpu.mos6502.MOS6502;
12
import com.rox.debug.cpu.InstructionTrace;
13
import com.rox.debug.memory.MemoryEditor;
14
import com.rox.input.ControllerConfiguration;
15
import com.rox.mem.*;
16
import com.rox.ppu.PPU;
17
import com.rox.save.SystemSnapshot;
18
import com.rox.time.SystemTimeSource;
19
import com.rox.time.ThreadSleeper;
20
import com.rox.video.VideoOutput;
21
22
import javax.sound.sampled.LineUnavailableException;
23
import java.util.Map;
24
import java.util.concurrent.CountDownLatch;
25
import java.util.zip.CRC32;
26
27
public class NES {
28
    private static final long CPU_HZ = 1_789_773;
29
    /**
30
     * Far beyond any real instruction (7 cycles max, plus a 514-cycle OAM DMA stall and a 7-cycle
31
     * interrupt entry) - only reached if the CPU somehow never finishes, which would otherwise hang the
32
     * UI thread that asked for the step.
33
     */
34
    static final int MAX_CYCLES_PER_STEPPED_INSTRUCTION = 10_000;
35
    /** Over three NTSC frames (29,780.5 CPU cycles each) - only reached if the PPU somehow stops finishing frames. */
36
    static final int MAX_CYCLES_PER_STEPPED_FRAME = 100_000;
37
    /** The NES's 2KB of CPU RAM - NESMemoryBus mirrors it across $0000-$1FFF. */
38
    private static final int CPU_RAM_SIZE = NESMemoryBus.CPU_RAM_MIRROR_MASK + 1;
39
    private static final long AUDIO_SAMPLE_RATE_HZ = 44_100;
40
    /**
41
     * Gives the JVM's JIT time to compile the emulation's hot per-cycle tick loop, and lets the ring buffer build
42
     * up a cushion, before audio playback (and thus underrun risk) begins. See {@link SpeakerAudioOutput} class doc
43
     * for why draining starts empty otherwise
44
     */
45
    private static final long AUDIO_WARM_UP_MILLIS = 300;
46
47
    private final MOS6502 cpu;
48
    private final APU apu;
49
    private final PPU ppu;
50
    private final Clock clock;
51
    private final LatchedMemoryBus memoryBus;
52
    private final AudioOutput audioOutput;
53
    private final RAM ram;
54
    private final Cartridge cartridge;
55
    private final TickRateMonitor tickRateMonitor;
56
    private final MemoryEditor memoryEditor;
57
    private final InstructionTrace instructionTrace;
58
    /** set only once pause() has genuinely parked the clock - the precondition for capturing/restoring state while powered on (see captureSnapshot()) */
59
    private volatile boolean paused;
60
    /** every frame handed to videoOutput - what stepFrame() watches for */
61
    private volatile long framesPresented;
62
    /** set by {@link #powerOff()}, so {@link #powerOn()} skips warm-up and audio once a shutdown is asked for */
63
    private volatile boolean stopRequested;
64
    /** guards {@link #powerOn()}'s setup and audio start against {@link #powerOff()} */
65
    private final Object lifecycleLock = new Object();
66
67
    public NES(final Cartridge cartridge) throws LineUnavailableException {
68
        this(new SpeakerAudioOutput(), VideoOutput.NO_OP, ControllerConfiguration.NONE, cartridge);
69
    }
70
71
    /** Like {@link #NES(Cartridge)}, but presenting frames to {@code videoOutput} as they complete. */
72
    public NES(final VideoOutput videoOutput, final Cartridge cartridge) throws LineUnavailableException {
73
        this(new SpeakerAudioOutput(), videoOutput, ControllerConfiguration.NONE, cartridge);
74
    }
75
76
    /** Like {@link #NES(VideoOutput, Cartridge)}, but driven by {@code controllers} rather than {@link ControllerConfiguration#NONE}. */
77
    public NES(final VideoOutput videoOutput, final ControllerConfiguration controllers, final Cartridge cartridge) throws LineUnavailableException {
78
        this(new SpeakerAudioOutput(), videoOutput, controllers, cartridge);
79
    }
80
81
    NES(final AudioOutput audioOutput, final Cartridge cartridge){
82
        this(audioOutput, VideoOutput.NO_OP, ControllerConfiguration.NONE, cartridge);
83
    }
84
85
    NES(final AudioOutput audioOutput, final VideoOutput videoOutput, final Cartridge cartridge){
86
        this(audioOutput, videoOutput, ControllerConfiguration.NONE, cartridge);
87
    }
88
89
    NES(final AudioOutput audioOutput, final VideoOutput videoOutput, final ControllerConfiguration controllers, final Cartridge cartridge){
90
        this(audioOutput, videoOutput, controllers, cartridge, new FPSClock(CPU_HZ, 60, new SystemTimeSource(), new ThreadSleeper()));
91
    }
92
93
    /** Test-only entry point for injecting a fake {@link Clock} - see {@code NESTest}'s race-reproducing double. */
94
    NES(final AudioOutput audioOutput, final Cartridge cartridge, final Clock clock){
95
        this(audioOutput, VideoOutput.NO_OP, cartridge, clock);
96
    }
97
98
    /** Test-only entry point for injecting both a fake {@link Clock} and a mocked {@link VideoOutput}. */
99
    NES(final AudioOutput audioOutput, final VideoOutput videoOutput, final Cartridge cartridge, final Clock clock){
100
        this(audioOutput, videoOutput, ControllerConfiguration.NONE, cartridge, clock);
101
    }
102
103
    /** Test-only entry point for injecting a fake {@link Clock} alongside a real {@link ControllerConfiguration}. */
104
    NES(final AudioOutput audioOutput, final VideoOutput videoOutput, final ControllerConfiguration controllers,
105
        final Cartridge cartridge, final Clock clock){
106
        this.ram = new RAM(CPU_RAM_SIZE);
107
        this.cartridge = cartridge;
108
        final MemoryBus ramBus = new MemoryBus8Bit(ram);
109
        //DMC's own sample-address generator (see DMCChannel) only ever produces addresses in
110
        //$8000-$FFFF (base $C000+, wrapping no lower than $8000) - always within cartridge range,
111
        //so the cartridge alone is a complete, correct DMA source with no need to route through
112
        //NESMemoryBus (which would need apu itself to construct, a circular dependency)
113
        this.apu = new APU(cartridge);
114
        this.ppu = new PPU(cartridge);
115
        this.memoryBus = new Latched8BitMemoryBus(new NESMemoryBus(ramBus, apu, cartridge, ppu, controllers));
116
        this.cpu = new MOS6502(memoryBus);
117
        this.tickRateMonitor = new TickRateMonitor(new SystemTimeSource());
118
        this.memoryEditor = new MemoryEditor(ram::write, cartridge::writePrgRamWithoutSaving);
119
        this.instructionTrace = new InstructionTrace(cpu::isAtInstructionBoundary, cpu::programCounter,
120
                DebugCapture.sideEffectFreePeek(ram::read, cartridge), cpu::packedRegisters, cpu::packedFlags,
121
                InstructionTrace.DEFAULT_CAPACITY);
122
        this.clock = clock;
123
        this.audioOutput = audioOutput;
124
125
        final Resampler resampler = new Resampler(CPU_HZ, AUDIO_SAMPLE_RATE_HZ);
126 1 1. <init> : removed call to com/rox/clock/Clock::addListener → KILLED
        clock.addListener(tickRateMonitor);
127 1 1. <init> : removed call to com/rox/clock/Clock::addListener → KILLED
        clock.addListener(cpu);
128
        //right after the CPU, so a locked byte is back at its value before anything else sees the tick's state
129 1 1. <init> : removed call to com/rox/clock/Clock::addListener → KILLED
        clock.addListener(memoryEditor);
130 1 1. <init> : removed call to com/rox/clock/Clock::addListener → KILLED
        clock.addListener(apu);
131 1 1. <init> : removed call to com/rox/clock/Clock::addListener → KILLED
        clock.addListener(ppu);
132 4 1. lambda$new$0 : negated conditional → KILLED
2. <init> : removed call to com/rox/clock/Clock::addListener → KILLED
3. lambda$new$0 : removed call to com/rox/cpu/mos6502/MOS6502::setIRQLine → KILLED
4. lambda$new$0 : negated conditional → KILLED
        clock.addListener(() -> cpu.setIRQLine(apu.isIrqAsserted() || cartridge.isIrqAsserted()));
133 1 1. <init> : removed call to com/rox/clock/Clock::addListener → KILLED
        clock.addListener(() -> {
134 1 1. lambda$new$1 : negated conditional → KILLED
            if (ppu.consumeNmiEdge()){
135 1 1. lambda$new$1 : removed call to com/rox/cpu/mos6502/MOS6502::signalNMI → KILLED
                cpu.signalNMI();
136
            }
137
        });
138 1 1. <init> : removed call to com/rox/clock/Clock::addListener → KILLED
        clock.addListener(() -> {
139
            final int stallCycles = ppu.consumeOamDmaStallCycles();
140 2 1. lambda$new$2 : changed conditional boundary → SURVIVED
2. lambda$new$2 : negated conditional → KILLED
            if (stallCycles > 0){
141 1 1. lambda$new$2 : removed call to com/rox/cpu/mos6502/MOS6502::stall → KILLED
                cpu.stall(stallCycles);
142
            }
143
        });
144
        //after the OAM DMA stall listener above, so a stall starting this tick holds recording back
145
        //rather than the same instruction being recorded twice - see InstructionTrace's class doc
146 1 1. <init> : removed call to com/rox/clock/Clock::addListener → KILLED
        clock.addListener(instructionTrace);
147 2 1. <init> : removed call to com/rox/clock/Clock::addListener → KILLED
2. lambda$new$3 : removed call to java/util/OptionalDouble::ifPresent → KILLED
        clock.addListener(() -> resampler.accept(apu.outputSample()).ifPresent(audioOutput::write));
148 1 1. <init> : removed call to com/rox/clock/Clock::addListener → KILLED
        clock.addListener(() -> {
149
            //polling the (possibly real, physical) gamepads is folded into this same frame-ready gate
150
            //rather than a second, separate clock.addListener - PPU.consumeFrameReady() is a one-shot
151
            //flag (it resets itself the moment it's read true), so a second listener also calling it
152
            //would simply never see true, having already been beaten to it by this one
153 1 1. lambda$new$4 : negated conditional → KILLED
            if (ppu.consumeFrameReady()){
154 1 1. lambda$new$4 : Replaced long addition with subtraction → KILLED
                framesPresented++;
155 1 1. lambda$new$4 : removed call to com/rox/video/VideoOutput::present → KILLED
                videoOutput.present(ppu.rgbFramebuffer());
156 1 1. lambda$new$4 : removed call to com/rox/input/Controller::poll → KILLED
                controllers.player1().poll();
157 1 1. lambda$new$4 : removed call to com/rox/input/Controller::poll → KILLED
                controllers.player2().poll();
158 1 1. lambda$new$4 : removed call to com/rox/input/Controller::poll → KILLED
                controllers.player3().poll();
159 1 1. lambda$new$4 : removed call to com/rox/input/Controller::poll → KILLED
                controllers.player4().poll();
160
            }
161
        });
162
    }
163
164
    public void powerOn(){
165
        //signalled once the clock thread is executing, so a slow thread start can't eat into the warm-up
166
        final CountDownLatch clockStarted = new CountDownLatch(1);
167
        final Thread clockThread = new Thread(() -> {
168 1 1. lambda$powerOn$0 : removed call to java/util/concurrent/CountDownLatch::countDown → TIMED_OUT
            clockStarted.countDown();
169 1 1. lambda$powerOn$0 : removed call to com/rox/clock/Clock::run → KILLED
            clock.run();
170
        });
171
        synchronized (lifecycleLock){
172 1 1. powerOn : removed call to com/rox/cpu/mos6502/MOS6502::reset → KILLED
            cpu.reset();
173
            stopRequested = false;
174 1 1. powerOn : removed call to com/rox/clock/Clock::prepareToRun → TIMED_OUT
            clock.prepareToRun(); //a stop() from here on ends the run, even one landing before run() starts
175 1 1. powerOn : removed call to java/lang/Thread::start → TIMED_OUT
            clockThread.start();
176
        }
177
178
        boolean interrupted = false;
179
        try {
180 1 1. powerOn : removed call to java/util/concurrent/CountDownLatch::await → SURVIVED
            clockStarted.await();
181 1 1. powerOn : negated conditional → KILLED
            if (!stopRequested){
182 1 1. powerOn : removed call to java/lang/Thread::sleep → KILLED
                Thread.sleep(AUDIO_WARM_UP_MILLIS);
183
            }
184
        } catch (InterruptedException e){
185
            interrupted = true;
186
        }
187
        synchronized (lifecycleLock){
188
            //under the lock, so a powerOff() can't land between this check and start() and leave audio on
189 2 1. powerOn : negated conditional → KILLED
2. powerOn : negated conditional → KILLED
            if (!interrupted && !stopRequested){
190 1 1. powerOn : removed call to com/rox/audio/AudioOutput::start → KILLED
                audioOutput.start();
191
            }
192
        }
193
194 1 1. powerOn : negated conditional → TIMED_OUT
        if (interrupted){
195 1 1. powerOn : removed call to com/rox/clock/Clock::stop → TIMED_OUT
            clock.stop();
196
        }
197
        //retry join() until the thread has genuinely terminated - a single interrupted join() would
198
        //otherwise return early without actually waiting, silently leaving the clock thread running
199 1 1. powerOn : negated conditional → TIMED_OUT
        while (clockThread.isAlive()){
200
            try {
201 1 1. powerOn : removed call to java/lang/Thread::join → TIMED_OUT
                clockThread.join();
202
            } catch (InterruptedException e){
203
                //an interrupt caught only here (audio already started) must still stop the clock - nothing else will
204
                interrupted = true;
205 1 1. powerOn : removed call to com/rox/clock/Clock::stop → TIMED_OUT
                clock.stop();
206
            }
207
        }
208
        //standard practice even though nothing in this method (or its callers) observes the flag
209
        //afterward - powerOn() returns right after this, so this is an accepted, unobservable pitest
210
        //survivor rather than something worth chasing a test for
211 1 1. powerOn : negated conditional → SURVIVED
        if (interrupted){
212 1 1. powerOn : removed call to java/lang/Thread::interrupt → SURVIVED
            Thread.currentThread().interrupt();
213
        }
214
    }
215
216
    /**
217
     * Freezes a running emulation without resetting or tearing anything down: the clock parks at the
218
     * end of its current frame, then the CPU is single-stepped to the end of whatever instruction it
219
     * was part-way through - so CPU state is fully captured by {@code getEnvironmentSnapshot()} for as
220
     * long as the pause lasts - and audio is silenced. {@link #resume()} carries on from exactly there.
221
     *
222
     * @return false if there was nothing running to pause
223
     */
224
    public boolean pause(){
225 1 1. pause : negated conditional → KILLED
        if (!clock.pause()){
226 1 1. pause : replaced boolean return with true for com/rox/NES::pause → KILLED
            return false;
227
        }
228
        //safe to tick from this thread: a true pause() guarantees the clock thread is parked
229 1 1. pause : removed call to com/rox/NES::finishInFlightInstruction → KILLED
        finishInFlightInstruction();
230 1 1. pause : removed call to com/rox/audio/AudioOutput::pause → KILLED
        audioOutput.pause();
231
        paused = true;
232 1 1. pause : replaced boolean return with false for com/rox/NES::pause → KILLED
        return true;
233
    }
234
235
    /** True from a successful {@link #pause()} until {@link #resume()}. */
236
    public boolean isPaused(){
237 2 1. isPaused : replaced boolean return with true for com/rox/NES::isPaused → KILLED
2. isPaused : replaced boolean return with false for com/rox/NES::isPaused → KILLED
        return paused;
238
    }
239
240
    /** Carry on after a {@link #pause()}; does nothing if not paused. */
241
    public void resume(){
242
        paused = false;
243 1 1. resume : removed call to com/rox/audio/AudioOutput::resume → KILLED
        audioOutput.resume();
244 1 1. resume : removed call to com/rox/clock/Clock::resume → KILLED
        clock.resume();
245
    }
246
247
    /**
248
     * The whole system's state, for a save state or debug capture. Only while {@link #pause()}d (or
249
     * before/after the clock runs at all) - otherwise the clock thread would be changing it mid-copy.
250
     * A clock that simply stopped (e.g. after {@link #powerOff()}) can leave the CPU part-way through an
251
     * instruction, unlike a pause; that instruction is finished first, so this may advance the system
252
     * by a few cycles.
253
     *
254
     * <p>Not captured: controller shift registers (a pause landing mid-controller-read could misread
255
     * one frame of input after a restore), and the audio resampler's fractional position.
256
     *
257
     * @throws IllegalStateException if the clock is running and not paused
258
     */
259
    public SystemSnapshot captureSnapshot(){
260 1 1. captureSnapshot : removed call to com/rox/NES::requireNothingTicking → KILLED
        requireNothingTicking();
261 1 1. captureSnapshot : removed call to com/rox/NES::finishInFlightInstruction → KILLED
        finishInFlightInstruction();
262 1 1. captureSnapshot : replaced return value with null for com/rox/NES::captureSnapshot → KILLED
        return new SystemSnapshot(romCrc32(cartridge), cpu.snapshot(), ppu.snapshot(), apu.snapshot(), ram.snapshot(),
263
                cartridge.snapshot());
264
    }
265
266
    /**
267
     * Puts the whole system back as {@code snapshot} captured it. Same "paused or not running"
268
     * precondition as {@link #captureSnapshot()}.
269
     *
270
     * @throws IllegalStateException if the clock is running and not paused
271
     * @throws IllegalArgumentException if {@code snapshot} was taken with a different cartridge, or any of
272
     * its parts is the wrong shape - checked up front, so a rejected snapshot changes nothing
273
     */
274
    public void restoreSnapshot(final SystemSnapshot snapshot){
275 1 1. restoreSnapshot : removed call to com/rox/NES::requireNothingTicking → KILLED
        requireNothingTicking();
276 1 1. restoreSnapshot : negated conditional → KILLED
        if (snapshot.romCrc32() != romCrc32(cartridge)){
277
            throw new IllegalArgumentException("Snapshot was taken with a different cartridge");
278
        }
279
        //every component that can reject a malformed snapshot (array sizes, mapper type) is checked before
280
        //any is restored - a CRC-valid save file can still decode to the wrong shapes, and stopping
281
        //part-way would leave a mix of old and restored state. The APU and CPU snapshots are plain
282
        //values with nothing to reject.
283 1 1. restoreSnapshot : removed call to com/rox/cartridge/Cartridge::checkRestorable → SURVIVED
        cartridge.checkRestorable(snapshot.mapper());
284 1 1. restoreSnapshot : removed call to com/rox/mem/RAM::checkRestorable → KILLED
        ram.checkRestorable(snapshot.ram());
285 1 1. restoreSnapshot : removed call to com/rox/ppu/PPU::checkRestorable → KILLED
        ppu.checkRestorable(snapshot.ppu());
286 1 1. restoreSnapshot : removed call to com/rox/cartridge/Cartridge::restore → KILLED
        cartridge.restore(snapshot.mapper());
287 1 1. restoreSnapshot : removed call to com/rox/mem/RAM::restore → KILLED
        ram.restore(snapshot.ram());
288 1 1. restoreSnapshot : removed call to com/rox/ppu/PPU::restore → KILLED
        ppu.restore(snapshot.ppu());
289 1 1. restoreSnapshot : removed call to com/rox/apu/APU::restore → KILLED
        apu.restore(snapshot.apu());
290 1 1. restoreSnapshot : removed call to com/rox/cpu/mos6502/MOS6502::restore → KILLED
        cpu.restore(snapshot.cpu());
291
    }
292
293
    /** Ticks until the CPU is between instructions - callers must already know nothing else is ticking. */
294
    /**
295
     * Runs exactly one instruction - from wherever the CPU is, to the next instruction boundary (so a
296
     * part-finished instruction is just finished). An OAM DMA stall or interrupt entry that follows is
297
     * part of the same step. Only while {@link #pause()}d (or with the clock not running), like
298
     * {@link #captureSnapshot()}.
299
     *
300
     * @throws IllegalStateException if the clock is running and not paused, or the CPU doesn't reach an
301
     * instruction boundary within {@link #MAX_CYCLES_PER_STEPPED_INSTRUCTION} cycles
302
     */
303
    public void stepInstruction(){
304 1 1. stepInstruction : removed call to com/rox/NES::requireNothingTicking → KILLED
        requireNothingTicking();
305
        int cycles = 0;
306
        do {
307 2 1. stepInstruction : Changed increment from 1 to -1 → KILLED
2. stepInstruction : negated conditional → KILLED
            if (cycles++ == MAX_CYCLES_PER_STEPPED_INSTRUCTION){
308
                throw new IllegalStateException("CPU didn't finish an instruction within "
309
                        + MAX_CYCLES_PER_STEPPED_INSTRUCTION + " cycles");
310
            }
311 1 1. stepInstruction : removed call to com/rox/clock/Clock::tick → KILLED
            clock.tick();
312 1 1. stepInstruction : negated conditional → KILLED
        } while (!cpu.isAtInstructionBoundary());
313
    }
314
315
    /**
316
     * Runs exactly {@code cycles} CPU cycles - which can leave the CPU part-way through an instruction.
317
     * Same "paused or not running" precondition as {@link #stepInstruction()}.
318
     *
319
     * @throws IllegalArgumentException if {@code cycles} isn't positive
320
     */
321
    public void stepCycles(final int cycles){
322 1 1. stepCycles : removed call to com/rox/NES::requireNothingTicking → KILLED
        requireNothingTicking();
323 2 1. stepCycles : changed conditional boundary → KILLED
2. stepCycles : negated conditional → KILLED
        if (cycles < 1){
324
            throw new IllegalArgumentException("Step at least one cycle, not " + cycles);
325
        }
326 2 1. stepCycles : changed conditional boundary → KILLED
2. stepCycles : negated conditional → KILLED
        for (int i = 0; i < cycles; i++){
327 1 1. stepCycles : removed call to com/rox/clock/Clock::tick → KILLED
            clock.tick();
328
        }
329
    }
330
331
    /**
332
     * Runs until the next frame is finished and presented (the start of vertical blanking), then
333
     * finishes the instruction in progress - so each step shows exactly one new frame and stops at the
334
     * same point in it. A fixed cycle count can't do that: an NTSC frame is 29,780.5 CPU cycles. Same
335
     * "paused or not running" precondition as {@link #stepInstruction()}.
336
     *
337
     * @throws IllegalStateException if the clock is running and not paused, or no frame finishes within
338
     * {@link #MAX_CYCLES_PER_STEPPED_FRAME} cycles
339
     */
340
    public void stepFrame(){
341 1 1. stepFrame : removed call to com/rox/NES::stepFrame → KILLED
        stepFrame(MAX_CYCLES_PER_STEPPED_FRAME);
342
    }
343
344
    /** {@link #stepFrame()} with a given cap - a test seam, since a real PPU always finishes a frame well inside the real one. */
345
    void stepFrame(final int maxCycles){
346 1 1. stepFrame : removed call to com/rox/NES::requireNothingTicking → KILLED
        requireNothingTicking();
347
        final long startingFrame = framesPresented;
348
        int cycles = 0;
349 2 1. stepFrame : changed conditional boundary → KILLED
2. stepFrame : negated conditional → KILLED
        while (framesPresented <= startingFrame){
350 2 1. stepFrame : Changed increment from 1 to -1 → KILLED
2. stepFrame : negated conditional → KILLED
            if (cycles++ == maxCycles){
351
                throw new IllegalStateException("No frame finished within " + maxCycles + " cycles");
352
            }
353 1 1. stepFrame : removed call to com/rox/clock/Clock::tick → KILLED
            clock.tick();
354
        }
355 1 1. stepFrame : removed call to com/rox/NES::finishInFlightInstruction → KILLED
        finishInFlightInstruction();
356
    }
357
358
    private void finishInFlightInstruction(){
359 1 1. finishInFlightInstruction : negated conditional → KILLED
        while (!cpu.isAtInstructionBoundary()){
360 1 1. finishInFlightInstruction : removed call to com/rox/clock/Clock::tick → TIMED_OUT
            clock.tick();
361
        }
362
    }
363
364
    /**
365
     * A debugging edit: sets one byte of CPU RAM ({@code $0000-$1FFF}, mirrored) or PRG-RAM
366
     * ({@code $6000-$7FFF}) any time. A PRG-RAM edit doesn't trigger a battery save.
367
     * <ul>
368
     *     <li><b>running?</b>: applied by the clock thread at its next tick, never written from this thread into memory the emulation is using.</li>
369
     *     <li><b>paused/not running?</b>: applied now!</li>
370
     * </ul>
371
     * 
372
     * @throws IllegalArgumentException for any other address, or a value outside 0-255
373
     */
374
    public void pokeMemory(final int address, final int value){
375 1 1. pokeMemory : negated conditional → KILLED
        if (nothingTicking()){
376 1 1. pokeMemory : removed call to com/rox/debug/memory/MemoryEditor::write → KILLED
            memoryEditor.write(address, value);
377
        } else {
378 1 1. pokeMemory : removed call to com/rox/debug/memory/MemoryEditor::queue → KILLED
            memoryEditor.queue(address, value);
379
        }
380
    }
381
382
    /**
383
     * Holds a byte at {@code value} - set now (as {@link #pokeMemory}), then re-applied every tick, so
384
     * whatever the game writes there doesn't stick - until {@link #unlockMemory}.
385
     *
386
     * @throws IllegalArgumentException as {@link #pokeMemory}
387
     */
388
    public void lockMemory(final int address, final int value){
389 1 1. lockMemory : removed call to com/rox/debug/memory/MemoryEditor::lock → KILLED
        memoryEditor.lock(address, value);
390 1 1. lockMemory : removed call to com/rox/NES::pokeMemory → KILLED
        pokeMemory(address, value);
391
    }
392
393
    public void unlockMemory(final int address){
394 1 1. unlockMemory : removed call to com/rox/debug/memory/MemoryEditor::unlock → KILLED
        memoryEditor.unlock(address);
395
    }
396
397
    /** Locked bytes, address to value, in address order (RAM by its real {@code $0000-$07FF} address). */
398
    public Map<Integer, Integer> lockedMemory(){
399 1 1. lockedMemory : replaced return value with Collections.emptyMap for com/rox/NES::lockedMemory → KILLED
        return memoryEditor.locks();
400
    }
401
402
    private boolean nothingTicking(){
403 3 1. nothingTicking : negated conditional → KILLED
2. nothingTicking : replaced boolean return with true for com/rox/NES::nothingTicking → KILLED
3. nothingTicking : negated conditional → KILLED
        return !clock.isRunning() || paused;
404
    }
405
406
    private void requireNothingTicking(){
407 1 1. requireNothingTicking : negated conditional → KILLED
        if (!nothingTicking()){
408
            throw new IllegalStateException("Pause the NES before capturing, restoring or stepping it");
409
        }
410
    }
411
412
    /** Identifies a cartridge's ROM (PRG then CHR) - what {@link SystemSnapshot#romCrc32()} is checked against. */
413
    private static long romCrc32(final Cartridge cartridge){
414
        final CRC32 crc = new CRC32();
415 1 1. romCrc32 : removed call to java/util/zip/CRC32::update → KILLED
        crc.update(cartridge.rom().prgRom());
416 1 1. romCrc32 : removed call to java/util/zip/CRC32::update → KILLED
        crc.update(cartridge.rom().chrRom());
417 1 1. romCrc32 : replaced long return with 0 for com/rox/NES::romCrc32 → KILLED
        return crc.getValue();
418
    }
419
420
    /**
421
     * Stops the emulation for good. Returns only once nothing can tick any more - even when called while
422
     * {@link #powerOn()} is still starting the clock - so {@link #pokeMemory}, {@link #captureSnapshot} or a
423
     * step can then act from this thread safely.
424
     */
425
    public void powerOff(){
426
        synchronized (lifecycleLock){
427
            stopRequested = true;
428 1 1. powerOff : removed call to com/rox/clock/Clock::stopAndWait → TIMED_OUT
            clock.stopAndWait();
429 1 1. powerOff : removed call to com/rox/audio/AudioOutput::stop → KILLED
            audioOutput.stop();
430
        }
431
    }
432
433
    MOS6502 cpu(){
434 1 1. cpu : replaced return value with null for com/rox/NES::cpu → KILLED
        return cpu;
435
    }
436
437
    APU apu(){
438 1 1. apu : replaced return value with null for com/rox/NES::apu → KILLED
        return apu;
439
    }
440
441
    PPU ppu(){
442 1 1. ppu : replaced return value with null for com/rox/NES::ppu → KILLED
        return ppu;
443
    }
444
445
    RAM ram(){
446 1 1. ram : replaced return value with null for com/rox/NES::ram → KILLED
        return ram;
447
    }
448
449
    TickRateMonitor tickRateMonitor(){
450 1 1. tickRateMonitor : replaced return value with null for com/rox/NES::tickRateMonitor → KILLED
        return tickRateMonitor;
451
    }
452
453
    InstructionTrace instructionTrace(){
454 1 1. instructionTrace : replaced return value with null for com/rox/NES::instructionTrace → KILLED
        return instructionTrace;
455
    }
456
457
    /** The CPU clock rate a real NTSC NES runs at - what {@link #tickRateMonitor()}'s measured rate should match. */
458
    static long intendedCpuHz(){
459 1 1. intendedCpuHz : replaced long return with 0 for com/rox/NES::intendedCpuHz → KILLED
        return CPU_HZ;
460
    }
461
462
    Cartridge cartridge(){
463 1 1. cartridge : replaced return value with null for com/rox/NES::cartridge → KILLED
        return cartridge;
464
    }
465
466
    Clock clock(){
467 1 1. clock : replaced return value with null for com/rox/NES::clock → KILLED
        return clock;
468
    }
469
}

Mutations

126

1.1
Location : <init>
Killed by : com.rox.NESTest.[engine:junit-jupiter]/[class:com.rox.NESTest]/[method:stepInstructionGivesUpRatherThanHangingOnAnInstructionThatNeverEnds()]
removed call to com/rox/clock/Clock::addListener → KILLED

127

1.1
Location : <init>
Killed by : com.rox.NESTest.[engine:junit-jupiter]/[class:com.rox.NESTest]/[method:stepInstructionFromMidInstructionJustFinishesIt()]
removed call to com/rox/clock/Clock::addListener → KILLED

129

1.1
Location : <init>
Killed by : com.rox.NESTest.[engine:junit-jupiter]/[class:com.rox.NESTest]/[method:pokeWhileRunningWaitsForTheClockThreadsNextTick()]
removed call to com/rox/clock/Clock::addListener → KILLED

130

1.1
Location : <init>
Killed by : com.rox.NESTest.[engine:junit-jupiter]/[class:com.rox.NESTest]/[method:dmcSampleFetchesReadFromCartridgePrgRomNotInternalRam()]
removed call to com/rox/clock/Clock::addListener → KILLED

131

1.1
Location : <init>
Killed by : com.rox.NESTest.[engine:junit-jupiter]/[class:com.rox.NESTest]/[method:vblankNmiReachesTheCpusRealNmiLine()]
removed call to com/rox/clock/Clock::addListener → KILLED

132

1.1
Location : lambda$new$0
Killed by : com.rox.NESTest.[engine:junit-jupiter]/[class:com.rox.NESTest]/[method:mapperIrqReachesTheCpusRealIrqLine()]
negated conditional → KILLED

2.2
Location : <init>
Killed by : com.rox.NESTest.[engine:junit-jupiter]/[class:com.rox.NESTest]/[method:mapperIrqReachesTheCpusRealIrqLine()]
removed call to com/rox/clock/Clock::addListener → KILLED

3.3
Location : lambda$new$0
Killed by : com.rox.NESTest.[engine:junit-jupiter]/[class:com.rox.NESTest]/[method:mapperIrqReachesTheCpusRealIrqLine()]
removed call to com/rox/cpu/mos6502/MOS6502::setIRQLine → KILLED

4.4
Location : lambda$new$0
Killed by : com.rox.NESTest.[engine:junit-jupiter]/[class:com.rox.NESTest]/[method:vblankNmiDoesNotFireBeforeTheRealVblankEdge()]
negated conditional → KILLED

133

1.1
Location : <init>
Killed by : com.rox.NESTest.[engine:junit-jupiter]/[class:com.rox.NESTest]/[method:vblankNmiReachesTheCpusRealNmiLine()]
removed call to com/rox/clock/Clock::addListener → KILLED

134

1.1
Location : lambda$new$1
Killed by : com.rox.NESTest.[engine:junit-jupiter]/[class:com.rox.NESTest]/[method:vblankNmiDoesNotFireBeforeTheRealVblankEdge()]
negated conditional → KILLED

135

1.1
Location : lambda$new$1
Killed by : com.rox.NESTest.[engine:junit-jupiter]/[class:com.rox.NESTest]/[method:vblankNmiReachesTheCpusRealNmiLine()]
removed call to com/rox/cpu/mos6502/MOS6502::signalNMI → KILLED

138

1.1
Location : <init>
Killed by : com.rox.NESTest.[engine:junit-jupiter]/[class:com.rox.NESTest]/[method:stepInstructionIncludesAnOamDmaStallThatFollows()]
removed call to com/rox/clock/Clock::addListener → KILLED

140

1.1
Location : lambda$new$2
Killed by : none
changed conditional boundary → SURVIVED
Covering tests

2.2
Location : lambda$new$2
Killed by : com.rox.NESTest.[engine:junit-jupiter]/[class:com.rox.NESTest]/[method:stepInstructionIncludesAnOamDmaStallThatFollows()]
negated conditional → KILLED

141

1.1
Location : lambda$new$2
Killed by : com.rox.NESTest.[engine:junit-jupiter]/[class:com.rox.NESTest]/[method:stepInstructionIncludesAnOamDmaStallThatFollows()]
removed call to com/rox/cpu/mos6502/MOS6502::stall → KILLED

146

1.1
Location : <init>
Killed by : com.rox.NESTest.[engine:junit-jupiter]/[class:com.rox.NESTest]/[method:instructionTraceRecordsEachInstructionOnceEvenAcrossAnOamDmaStall()]
removed call to com/rox/clock/Clock::addListener → KILLED

147

1.1
Location : <init>
Killed by : com.rox.NESTest.[engine:junit-jupiter]/[class:com.rox.NESTest]/[method:powerOffCalledImmediatelyAfterPowerOnStillStopsTheClockRatherThanLeakingBothThreads()]
removed call to com/rox/clock/Clock::addListener → KILLED

2.2
Location : lambda$new$3
Killed by : com.rox.NESTest.[engine:junit-jupiter]/[class:com.rox.NESTest]/[method:powerOnGracefullyHandlesInterruptionDuringTheWarmUpSleepItself()]
removed call to java/util/OptionalDouble::ifPresent → KILLED

148

1.1
Location : <init>
Killed by : com.rox.NESTest.[engine:junit-jupiter]/[class:com.rox.NESTest]/[method:powerOnResetsTheCpuToTheCartridgesResetVector()]
removed call to com/rox/clock/Clock::addListener → KILLED

153

1.1
Location : lambda$new$4
Killed by : com.rox.NESTest.[engine:junit-jupiter]/[class:com.rox.NESTest]/[method:stepFrameGivesUpRatherThanHangingIfNoFrameFinishes()]
negated conditional → KILLED

154

1.1
Location : lambda$new$4
Killed by : com.rox.NESTest.[engine:junit-jupiter]/[class:com.rox.NESTest]/[method:stepFrameRunsToTheNextPresentedFrameThenFinishesTheInstruction()]
Replaced long addition with subtraction → KILLED

155

1.1
Location : lambda$new$4
Killed by : com.rox.NESTest.[engine:junit-jupiter]/[class:com.rox.NESTest]/[method:frameReadyPresentsTheFramebufferToVideoOutput()]
removed call to com/rox/video/VideoOutput::present → KILLED

156

1.1
Location : lambda$new$4
Killed by : com.rox.NESTest.[engine:junit-jupiter]/[class:com.rox.NESTest]/[method:frameReadyPollsAllFourConfiguredControllers()]
removed call to com/rox/input/Controller::poll → KILLED

157

1.1
Location : lambda$new$4
Killed by : com.rox.NESTest.[engine:junit-jupiter]/[class:com.rox.NESTest]/[method:frameReadyPollsAllFourConfiguredControllers()]
removed call to com/rox/input/Controller::poll → KILLED

158

1.1
Location : lambda$new$4
Killed by : com.rox.NESTest.[engine:junit-jupiter]/[class:com.rox.NESTest]/[method:frameReadyPollsAllFourConfiguredControllers()]
removed call to com/rox/input/Controller::poll → KILLED

159

1.1
Location : lambda$new$4
Killed by : com.rox.NESTest.[engine:junit-jupiter]/[class:com.rox.NESTest]/[method:frameReadyPollsAllFourConfiguredControllers()]
removed call to com/rox/input/Controller::poll → KILLED

168

1.1
Location : lambda$powerOn$0
Killed by : none
removed call to java/util/concurrent/CountDownLatch::countDown → TIMED_OUT

169

1.1
Location : lambda$powerOn$0
Killed by : com.rox.NESTest.[engine:junit-jupiter]/[class:com.rox.NESTest]/[method:powerOnGracefullyHandlesInterruptionDuringTheWarmUpSleepItself()]
removed call to com/rox/clock/Clock::run → KILLED

172

1.1
Location : powerOn
Killed by : com.rox.NESTest.[engine:junit-jupiter]/[class:com.rox.NESTest]/[method:powerOnResetsTheCpuToTheCartridgesResetVector()]
removed call to com/rox/cpu/mos6502/MOS6502::reset → KILLED

174

1.1
Location : powerOn
Killed by : none
removed call to com/rox/clock/Clock::prepareToRun → TIMED_OUT

175

1.1
Location : powerOn
Killed by : none
removed call to java/lang/Thread::start → TIMED_OUT

180

1.1
Location : powerOn
Killed by : none
removed call to java/util/concurrent/CountDownLatch::await → SURVIVED
Covering tests

181

1.1
Location : powerOn
Killed by : com.rox.NESTest.[engine:junit-jupiter]/[class:com.rox.NESTest]/[method:powerOffCalledImmediatelyAfterPowerOnStillStopsTheClockRatherThanLeakingBothThreads()]
negated conditional → KILLED

182

1.1
Location : powerOn
Killed by : com.rox.NESTest.[engine:junit-jupiter]/[class:com.rox.NESTest]/[method:powerOnGracefullyHandlesInterruptionDuringTheWarmUpSleepItself()]
removed call to java/lang/Thread::sleep → KILLED

189

1.1
Location : powerOn
Killed by : com.rox.NESTest.[engine:junit-jupiter]/[class:com.rox.NESTest]/[method:powerOffCalledImmediatelyAfterPowerOnStillStopsTheClockRatherThanLeakingBothThreads()]
negated conditional → KILLED

2.2
Location : powerOn
Killed by : com.rox.NESTest.[engine:junit-jupiter]/[class:com.rox.NESTest]/[method:powerOnGracefullyHandlesInterruptionDuringWarmUpInsteadOfStartingAudioOrLeakingTheClock()]
negated conditional → KILLED

190

1.1
Location : powerOn
Killed by : com.rox.NESTest.[engine:junit-jupiter]/[class:com.rox.NESTest]/[method:powerOnGracefullyHandlesInterruptionAfterAudioHasAlreadyStarted()]
removed call to com/rox/audio/AudioOutput::start → KILLED

194

1.1
Location : powerOn
Killed by : none
negated conditional → TIMED_OUT

195

1.1
Location : powerOn
Killed by : none
removed call to com/rox/clock/Clock::stop → TIMED_OUT

199

1.1
Location : powerOn
Killed by : none
negated conditional → TIMED_OUT

201

1.1
Location : powerOn
Killed by : none
removed call to java/lang/Thread::join → TIMED_OUT

205

1.1
Location : powerOn
Killed by : none
removed call to com/rox/clock/Clock::stop → TIMED_OUT

211

1.1
Location : powerOn
Killed by : none
negated conditional → SURVIVED
Covering tests

212

1.1
Location : powerOn
Killed by : none
removed call to java/lang/Thread::interrupt → SURVIVED
Covering tests

225

1.1
Location : pause
Killed by : com.rox.NESTest.[engine:junit-jupiter]/[class:com.rox.NESTest]/[method:pauseBeforePowerOnHasNothingToPauseAndLeavesAudioAlone()]
negated conditional → KILLED

226

1.1
Location : pause
Killed by : com.rox.NESTest.[engine:junit-jupiter]/[class:com.rox.NESTest]/[method:pauseBeforePowerOnHasNothingToPauseAndLeavesAudioAlone()]
replaced boolean return with true for com/rox/NES::pause → KILLED

229

1.1
Location : pause
Killed by : com.rox.NESTest.[engine:junit-jupiter]/[class:com.rox.NESTest]/[method:pauseFinishesTheInstructionTheCpuWasPartWayThroughBeforeSilencingAudio()]
removed call to com/rox/NES::finishInFlightInstruction → KILLED

230

1.1
Location : pause
Killed by : com.rox.NESTest.[engine:junit-jupiter]/[class:com.rox.NESTest]/[method:pauseFinishesTheInstructionTheCpuWasPartWayThroughBeforeSilencingAudio()]
removed call to com/rox/audio/AudioOutput::pause → KILLED

232

1.1
Location : pause
Killed by : com.rox.NESTest.[engine:junit-jupiter]/[class:com.rox.NESTest]/[method:pokeWhilePausedAppliesImmediately()]
replaced boolean return with false for com/rox/NES::pause → KILLED

237

1.1
Location : isPaused
Killed by : com.rox.NesCpuDebugSourceTest.[engine:junit-jupiter]/[class:com.rox.NesCpuDebugSourceTest]/[method:pauseAndResumeDriveTheNes()]
replaced boolean return with true for com/rox/NES::isPaused → KILLED

2.2
Location : isPaused
Killed by : com.rox.NesCpuDebugSourceTest.[engine:junit-jupiter]/[class:com.rox.NesCpuDebugSourceTest]/[method:pauseAndResumeDriveTheNes()]
replaced boolean return with false for com/rox/NES::isPaused → KILLED

243

1.1
Location : resume
Killed by : com.rox.NESTest.[engine:junit-jupiter]/[class:com.rox.NESTest]/[method:resumeUnpausesAudioBeforeTheClockStartsFeedingItAgain()]
removed call to com/rox/audio/AudioOutput::resume → KILLED

244

1.1
Location : resume
Killed by : com.rox.NESTest.[engine:junit-jupiter]/[class:com.rox.NESTest]/[method:resumeUnpausesAudioBeforeTheClockStartsFeedingItAgain()]
removed call to com/rox/clock/Clock::resume → KILLED

260

1.1
Location : captureSnapshot
Killed by : com.rox.NESTest.[engine:junit-jupiter]/[class:com.rox.NESTest]/[method:captureAndRestoreAreRefusedWhileRunningButAllowedOncePaused()]
removed call to com/rox/NES::requireNothingTicking → KILLED

261

1.1
Location : captureSnapshot
Killed by : com.rox.NESTest.[engine:junit-jupiter]/[class:com.rox.NESTest]/[method:captureWithTheClockStoppedMidInstructionFinishesThatInstructionFirst()]
removed call to com/rox/NES::finishInFlightInstruction → KILLED

262

1.1
Location : captureSnapshot
Killed by : com.rox.NESTest.[engine:junit-jupiter]/[class:com.rox.NESTest]/[method:restoreRefusesASnapshotFromACartridgeDifferingOnlyInChrRom()]
replaced return value with null for com/rox/NES::captureSnapshot → KILLED

275

1.1
Location : restoreSnapshot
Killed by : com.rox.NESTest.[engine:junit-jupiter]/[class:com.rox.NESTest]/[method:captureAndRestoreAreRefusedWhileRunningButAllowedOncePaused()]
removed call to com/rox/NES::requireNothingTicking → KILLED

276

1.1
Location : restoreSnapshot
Killed by : com.rox.NESTest.[engine:junit-jupiter]/[class:com.rox.NESTest]/[method:restoreRefusesASnapshotFromACartridgeDifferingOnlyInChrRom()]
negated conditional → KILLED

283

1.1
Location : restoreSnapshot
Killed by : none
removed call to com/rox/cartridge/Cartridge::checkRestorable → SURVIVED
Covering tests

284

1.1
Location : restoreSnapshot
Killed by : com.rox.NESTest.[engine:junit-jupiter]/[class:com.rox.NESTest]/[method:restoreRejectingAMalformedPartChangesNothing()]
removed call to com/rox/mem/RAM::checkRestorable → KILLED

285

1.1
Location : restoreSnapshot
Killed by : com.rox.NESTest.[engine:junit-jupiter]/[class:com.rox.NESTest]/[method:restoreRejectingAMalformedPartChangesNothing()]
removed call to com/rox/ppu/PPU::checkRestorable → KILLED

286

1.1
Location : restoreSnapshot
Killed by : com.rox.NESTest.[engine:junit-jupiter]/[class:com.rox.NESTest]/[method:restoredNesRunsInLockstepWithTheOriginal()]
removed call to com/rox/cartridge/Cartridge::restore → KILLED

287

1.1
Location : restoreSnapshot
Killed by : com.rox.NESTest.[engine:junit-jupiter]/[class:com.rox.NESTest]/[method:restoredNesRunsInLockstepWithTheOriginal()]
removed call to com/rox/mem/RAM::restore → KILLED

288

1.1
Location : restoreSnapshot
Killed by : com.rox.NESTest.[engine:junit-jupiter]/[class:com.rox.NESTest]/[method:restoredNesRunsInLockstepWithTheOriginal()]
removed call to com/rox/ppu/PPU::restore → KILLED

289

1.1
Location : restoreSnapshot
Killed by : com.rox.NESTest.[engine:junit-jupiter]/[class:com.rox.NESTest]/[method:restoredNesRunsInLockstepWithTheOriginal()]
removed call to com/rox/apu/APU::restore → KILLED

290

1.1
Location : restoreSnapshot
Killed by : com.rox.NESTest.[engine:junit-jupiter]/[class:com.rox.NESTest]/[method:restoredNesRunsInLockstepWithTheOriginal()]
removed call to com/rox/cpu/mos6502/MOS6502::restore → KILLED

304

1.1
Location : stepInstruction
Killed by : com.rox.NESTest.[engine:junit-jupiter]/[class:com.rox.NESTest]/[method:steppingIsRefusedWhileRunningUnpaused()]
removed call to com/rox/NES::requireNothingTicking → KILLED

307

1.1
Location : stepInstruction
Killed by : com.rox.NESTest.[engine:junit-jupiter]/[class:com.rox.NESTest]/[method:stepInstructionGivesUpRatherThanHangingOnAnInstructionThatNeverEnds()]
Changed increment from 1 to -1 → KILLED

2.2
Location : stepInstruction
Killed by : com.rox.NESTest.[engine:junit-jupiter]/[class:com.rox.NESTest]/[method:stepInstructionGivesUpRatherThanHangingOnAnInstructionThatNeverEnds()]
negated conditional → KILLED

311

1.1
Location : stepInstruction
Killed by : com.rox.NESTest.[engine:junit-jupiter]/[class:com.rox.NESTest]/[method:stepInstructionGivesUpRatherThanHangingOnAnInstructionThatNeverEnds()]
removed call to com/rox/clock/Clock::tick → KILLED

312

1.1
Location : stepInstruction
Killed by : com.rox.NESTest.[engine:junit-jupiter]/[class:com.rox.NESTest]/[method:stepInstructionGivesUpRatherThanHangingOnAnInstructionThatNeverEnds()]
negated conditional → KILLED

322

1.1
Location : stepCycles
Killed by : com.rox.NESTest.[engine:junit-jupiter]/[class:com.rox.NESTest]/[method:steppingIsRefusedWhileRunningUnpaused()]
removed call to com/rox/NES::requireNothingTicking → KILLED

323

1.1
Location : stepCycles
Killed by : com.rox.NESTest.[engine:junit-jupiter]/[class:com.rox.NESTest]/[method:stepCyclesRunsExactlyThatManyCycles()]
changed conditional boundary → KILLED

2.2
Location : stepCycles
Killed by : com.rox.NESTest.[engine:junit-jupiter]/[class:com.rox.NESTest]/[method:stepCyclesRejectsNothingOrLess()]
negated conditional → KILLED

326

1.1
Location : stepCycles
Killed by : com.rox.NESTest.[engine:junit-jupiter]/[class:com.rox.NESTest]/[method:stepCyclesRunsExactlyThatManyCycles()]
changed conditional boundary → KILLED

2.2
Location : stepCycles
Killed by : com.rox.NESTest.[engine:junit-jupiter]/[class:com.rox.NESTest]/[method:stepCyclesRunsExactlyThatManyCycles()]
negated conditional → KILLED

327

1.1
Location : stepCycles
Killed by : com.rox.NESTest.[engine:junit-jupiter]/[class:com.rox.NESTest]/[method:stepCyclesRunsExactlyThatManyCycles()]
removed call to com/rox/clock/Clock::tick → KILLED

341

1.1
Location : stepFrame
Killed by : com.rox.NESTest.[engine:junit-jupiter]/[class:com.rox.NESTest]/[method:stepFrameIsRefusedWhileRunningUnpaused()]
removed call to com/rox/NES::stepFrame → KILLED

346

1.1
Location : stepFrame
Killed by : com.rox.NESTest.[engine:junit-jupiter]/[class:com.rox.NESTest]/[method:stepFrameIsRefusedWhileRunningUnpaused()]
removed call to com/rox/NES::requireNothingTicking → KILLED

349

1.1
Location : stepFrame
Killed by : com.rox.NESTest.[engine:junit-jupiter]/[class:com.rox.NESTest]/[method:stepFrameGivesUpRatherThanHangingIfNoFrameFinishes()]
changed conditional boundary → KILLED

2.2
Location : stepFrame
Killed by : com.rox.NESTest.[engine:junit-jupiter]/[class:com.rox.NESTest]/[method:stepFrameGivesUpRatherThanHangingIfNoFrameFinishes()]
negated conditional → KILLED

350

1.1
Location : stepFrame
Killed by : com.rox.NESTest.[engine:junit-jupiter]/[class:com.rox.NESTest]/[method:stepFrameGivesUpRatherThanHangingIfNoFrameFinishes()]
Changed increment from 1 to -1 → KILLED

2.2
Location : stepFrame
Killed by : com.rox.NESTest.[engine:junit-jupiter]/[class:com.rox.NESTest]/[method:stepFrameGivesUpRatherThanHangingIfNoFrameFinishes()]
negated conditional → KILLED

353

1.1
Location : stepFrame
Killed by : com.rox.NESTest.[engine:junit-jupiter]/[class:com.rox.NESTest]/[method:stepFrameGivesUpRatherThanHangingIfNoFrameFinishes()]
removed call to com/rox/clock/Clock::tick → KILLED

355

1.1
Location : stepFrame
Killed by : com.rox.NESTest.[engine:junit-jupiter]/[class:com.rox.NESTest]/[method:stepFrameRunsToTheNextPresentedFrameThenFinishesTheInstruction()]
removed call to com/rox/NES::finishInFlightInstruction → KILLED

359

1.1
Location : finishInFlightInstruction
Killed by : com.rox.NESTest.[engine:junit-jupiter]/[class:com.rox.NESTest]/[method:restoreRefusesASnapshotFromACartridgeDifferingOnlyInChrRom()]
negated conditional → KILLED

360

1.1
Location : finishInFlightInstruction
Killed by : none
removed call to com/rox/clock/Clock::tick → TIMED_OUT

375

1.1
Location : pokeMemory
Killed by : com.rox.NESTest.[engine:junit-jupiter]/[class:com.rox.NESTest]/[method:pokeWhileNotRunningAppliesImmediatelyIncludingThroughMirrors()]
negated conditional → KILLED

376

1.1
Location : pokeMemory
Killed by : com.rox.NESTest.[engine:junit-jupiter]/[class:com.rox.NESTest]/[method:pokeRefusesRegistersAndRomWithoutTouchingAnything()]
removed call to com/rox/debug/memory/MemoryEditor::write → KILLED

378

1.1
Location : pokeMemory
Killed by : com.rox.NESTest.[engine:junit-jupiter]/[class:com.rox.NESTest]/[method:pokeWhileRunningWaitsForTheClockThreadsNextTick()]
removed call to com/rox/debug/memory/MemoryEditor::queue → KILLED

389

1.1
Location : lockMemory
Killed by : com.rox.NESTest.[engine:junit-jupiter]/[class:com.rox.NESTest]/[method:aLockHoldsAgainstTheGamesOwnWritesUntilUnlocked()]
removed call to com/rox/debug/memory/MemoryEditor::lock → KILLED

390

1.1
Location : lockMemory
Killed by : com.rox.NESTest.[engine:junit-jupiter]/[class:com.rox.NESTest]/[method:lockingWhileNotRunningSetsTheValueStraightAway()]
removed call to com/rox/NES::pokeMemory → KILLED

394

1.1
Location : unlockMemory
Killed by : com.rox.NESTest.[engine:junit-jupiter]/[class:com.rox.NESTest]/[method:aLockHoldsAgainstTheGamesOwnWritesUntilUnlocked()]
removed call to com/rox/debug/memory/MemoryEditor::unlock → KILLED

399

1.1
Location : lockedMemory
Killed by : com.rox.NESTest.[engine:junit-jupiter]/[class:com.rox.NESTest]/[method:aLockHoldsAgainstTheGamesOwnWritesUntilUnlocked()]
replaced return value with Collections.emptyMap for com/rox/NES::lockedMemory → KILLED

403

1.1
Location : nothingTicking
Killed by : com.rox.NESTest.[engine:junit-jupiter]/[class:com.rox.NESTest]/[method:steppingIsRefusedWhileRunningUnpaused()]
negated conditional → KILLED

2.2
Location : nothingTicking
Killed by : com.rox.NESTest.[engine:junit-jupiter]/[class:com.rox.NESTest]/[method:steppingIsRefusedWhileRunningUnpaused()]
replaced boolean return with true for com/rox/NES::nothingTicking → KILLED

3.3
Location : nothingTicking
Killed by : com.rox.NESTest.[engine:junit-jupiter]/[class:com.rox.NESTest]/[method:steppingIsRefusedWhileRunningUnpaused()]
negated conditional → KILLED

407

1.1
Location : requireNothingTicking
Killed by : com.rox.NESTest.[engine:junit-jupiter]/[class:com.rox.NESTest]/[method:steppingIsRefusedWhileRunningUnpaused()]
negated conditional → KILLED

415

1.1
Location : romCrc32
Killed by : com.rox.NESTest.[engine:junit-jupiter]/[class:com.rox.NESTest]/[method:restoreRefusesASnapshotFromADifferentCartridge()]
removed call to java/util/zip/CRC32::update → KILLED

416

1.1
Location : romCrc32
Killed by : com.rox.NESTest.[engine:junit-jupiter]/[class:com.rox.NESTest]/[method:restoreRefusesASnapshotFromACartridgeDifferingOnlyInChrRom()]
removed call to java/util/zip/CRC32::update → KILLED

417

1.1
Location : romCrc32
Killed by : com.rox.NESTest.[engine:junit-jupiter]/[class:com.rox.NESTest]/[method:restoreRefusesASnapshotFromACartridgeDifferingOnlyInChrRom()]
replaced long return with 0 for com/rox/NES::romCrc32 → KILLED

428

1.1
Location : powerOff
Killed by : none
removed call to com/rox/clock/Clock::stopAndWait → TIMED_OUT

429

1.1
Location : powerOff
Killed by : com.rox.NESTest.[engine:junit-jupiter]/[class:com.rox.NESTest]/[method:powerOffWhileTheClockIsStillStartingLeavesNothingTickingAfterwards()]
removed call to com/rox/audio/AudioOutput::stop → KILLED

434

1.1
Location : cpu
Killed by : com.rox.NESTest.[engine:junit-jupiter]/[class:com.rox.NESTest]/[method:pokeWhileNotRunningAppliesImmediatelyIncludingThroughMirrors()]
replaced return value with null for com/rox/NES::cpu → KILLED

438

1.1
Location : apu
Killed by : com.rox.NESTest.[engine:junit-jupiter]/[class:com.rox.NESTest]/[method:dmcSampleFetchesReadFromCartridgePrgRomNotInternalRam()]
replaced return value with null for com/rox/NES::apu → KILLED

442

1.1
Location : ppu
Killed by : com.rox.NESTest.[engine:junit-jupiter]/[class:com.rox.NESTest]/[method:vblankNmiDoesNotFireBeforeTheRealVblankEdge()]
replaced return value with null for com/rox/NES::ppu → KILLED

446

1.1
Location : ram
Killed by : com.rox.NESTest.[engine:junit-jupiter]/[class:com.rox.NESTest]/[method:pokeWhileNotRunningAppliesImmediatelyIncludingThroughMirrors()]
replaced return value with null for com/rox/NES::ram → KILLED

450

1.1
Location : tickRateMonitor
Killed by : com.rox.NESTest.[engine:junit-jupiter]/[class:com.rox.NESTest]/[method:stepCyclesRejectsNothingOrLess()]
replaced return value with null for com/rox/NES::tickRateMonitor → KILLED

454

1.1
Location : instructionTrace
Killed by : com.rox.NESTest.[engine:junit-jupiter]/[class:com.rox.NESTest]/[method:instructionTraceRecordsEachInstructionOnceEvenAcrossAnOamDmaStall()]
replaced return value with null for com/rox/NES::instructionTrace → KILLED

459

1.1
Location : intendedCpuHz
Killed by : com.rox.NESTest.[engine:junit-jupiter]/[class:com.rox.NESTest]/[method:intendedCpuHzIsTheNtscCpuClock()]
replaced long return with 0 for com/rox/NES::intendedCpuHz → KILLED

463

1.1
Location : cartridge
Killed by : com.rox.NESTest.[engine:junit-jupiter]/[class:com.rox.NESTest]/[method:pokeRefusesRegistersAndRomWithoutTouchingAnything()]
replaced return value with null for com/rox/NES::cartridge → KILLED

467

1.1
Location : clock
Killed by : com.rox.NESTest.[engine:junit-jupiter]/[class:com.rox.NESTest]/[method:powerOnGracefullyHandlesInterruptionDuringWarmUpInsteadOfStartingAudioOrLeakingTheClock()]
replaced return value with null for com/rox/NES::clock → KILLED

Active mutators

Tests examined


Report generated by PIT 1.22.1