LinearCounter.java

1
package com.rox.apu;
2
3
import com.rox.clock.ClockWatcher;
4
5
/**
6
 * NES triangle channel's linear counter ($4008). A second, independently-configured counter
7
 * (alongside the triangle's shared {@link LengthCounter}) that must also be nonzero for the
8
 * triangle's sequencer to advance - this is what lets games give the triangle a note duration
9
 * shorter than the length counter's coarse table allows.
10
 *
11
 * The control flag (bit7 of $4008) is the same physical bit as the channel's length-counter halt
12
 * flag - a hardware quirk, not a coincidence - so callers read both meanings from the one write.
13
 */
14
public class LinearCounter implements ClockWatcher {
15
    private static final int CONTROL_FLAG_BIT = 0x80;
16
    private static final int RELOAD_VALUE_MASK = 0x7F;
17
18
    private int reloadValue;
19
    private boolean controlFlagSet;
20
    private int counter;
21
    private boolean reloadFlagSet;
22
23
    /**
24
     * Handle a $4008 write.
25
     *
26
     * Register: CRRR RRRR
27
     * C (bit 7): control flag (shared with the channel's length-counter halt flag)
28
     * R (bits 0-6): reload value
29
     */
30
    public void writeControlRegister(final int value){
31 2 1. writeControlRegister : Replaced bitwise AND with OR → KILLED
2. writeControlRegister : negated conditional → KILLED
        controlFlagSet = (value & CONTROL_FLAG_BIT) != 0;
32 1 1. writeControlRegister : Replaced bitwise AND with OR → KILLED
        reloadValue = value & RELOAD_VALUE_MASK;
33
    }
34
35
    /** Requests a reload on the next {@link #tick()} - triggered by a $400B (timer high) write. */
36
    public void requestReload(){
37
        reloadFlagSet = true;
38
    }
39
40
    /**
41
     * Quarter-frame clock: reloads from {@link #reloadValue} if a reload is pending, else decrements
42
     * towards 0; the reload flag itself only survives past this tick if the control flag is set.
43
     */
44
    @Override
45
    public void tick(){
46 1 1. tick : negated conditional → KILLED
        if (reloadFlagSet){
47
            counter = reloadValue;
48 2 1. tick : changed conditional boundary → KILLED
2. tick : negated conditional → KILLED
        } else if (counter > 0){
49 1 1. tick : Replaced integer subtraction with addition → KILLED
            counter--;
50
        }
51 1 1. tick : negated conditional → KILLED
        if (!controlFlagSet){
52
            reloadFlagSet = false;
53
        }
54
    }
55
56
    public boolean isZero(){
57 2 1. isZero : replaced boolean return with true for com/rox/apu/LinearCounter::isZero → KILLED
2. isZero : negated conditional → KILLED
        return counter == 0;
58
    }
59
60
    /** Everything a {@link LinearCounter} needs to carry on exactly where it was. */
61
    public record Snapshot(int reloadValue, boolean controlFlagSet, int counter, boolean reloadFlagSet) {
62
    }
63
64
    public Snapshot snapshot(){
65 1 1. snapshot : replaced return value with null for com/rox/apu/LinearCounter::snapshot → KILLED
        return new Snapshot(reloadValue, controlFlagSet, counter, reloadFlagSet);
66
    }
67
68
    public void restore(final Snapshot snapshot){
69
        reloadValue = snapshot.reloadValue();
70
        controlFlagSet = snapshot.controlFlagSet();
71
        counter = snapshot.counter();
72
        reloadFlagSet = snapshot.reloadFlagSet();
73
    }
74
}

Mutations

31

1.1
Location : writeControlRegister
Killed by : com.rox.apu.LinearCounterTest.[engine:junit-jupiter]/[class:com.rox.apu.LinearCounterTest]/[method:withoutAPendingReloadTickDecrementsTowardsZero()]
Replaced bitwise AND with OR → KILLED

2.2
Location : writeControlRegister
Killed by : com.rox.apu.LinearCounterTest.[engine:junit-jupiter]/[class:com.rox.apu.LinearCounterTest]/[method:withoutAPendingReloadTickDecrementsTowardsZero()]
negated conditional → KILLED

32

1.1
Location : writeControlRegister
Killed by : com.rox.apu.LinearCounterTest.[engine:junit-jupiter]/[class:com.rox.apu.LinearCounterTest]/[method:decrementingHoldsAtZeroOnceReached()]
Replaced bitwise AND with OR → KILLED

46

1.1
Location : tick
Killed by : com.rox.apu.LinearCounterTest.[engine:junit-jupiter]/[class:com.rox.apu.LinearCounterTest]/[method:tickWithoutAnyReloadRequestedAndCounterAlreadyZeroStaysZero()]
negated conditional → KILLED

48

1.1
Location : tick
Killed by : com.rox.apu.LinearCounterTest.[engine:junit-jupiter]/[class:com.rox.apu.LinearCounterTest]/[method:tickWithoutAnyReloadRequestedAndCounterAlreadyZeroStaysZero()]
changed conditional boundary → KILLED

2.2
Location : tick
Killed by : com.rox.apu.LinearCounterTest.[engine:junit-jupiter]/[class:com.rox.apu.LinearCounterTest]/[method:tickWithoutAnyReloadRequestedAndCounterAlreadyZeroStaysZero()]
negated conditional → KILLED

49

1.1
Location : tick
Killed by : com.rox.apu.LinearCounterTest.[engine:junit-jupiter]/[class:com.rox.apu.LinearCounterTest]/[method:withoutAPendingReloadTickDecrementsTowardsZero()]
Replaced integer subtraction with addition → KILLED

51

1.1
Location : tick
Killed by : com.rox.apu.LinearCounterTest.[engine:junit-jupiter]/[class:com.rox.apu.LinearCounterTest]/[method:withoutAPendingReloadTickDecrementsTowardsZero()]
negated conditional → KILLED

57

1.1
Location : isZero
Killed by : com.rox.apu.LinearCounterTest.[engine:junit-jupiter]/[class:com.rox.apu.LinearCounterTest]/[method:withControlFlagSetReloadFlagPersistsAcrossTicks()]
replaced boolean return with true for com/rox/apu/LinearCounter::isZero → KILLED

2.2
Location : isZero
Killed by : com.rox.apu.LinearCounterTest.[engine:junit-jupiter]/[class:com.rox.apu.LinearCounterTest]/[method:startsAtZero()]
negated conditional → KILLED

65

1.1
Location : snapshot
Killed by : com.rox.apu.ApuSnapshotTest.[engine:jqwik]/[class:com.rox.apu.ApuSnapshotTest]/[property:linearCounterRoundTrips(int, boolean, int, boolean)]
replaced return value with null for com/rox/apu/LinearCounter::snapshot → KILLED

Active mutators

Tests examined


Report generated by PIT 1.22.1