The gap
There is no class for a Disk ][ drive. The card (Disk2Controller) and the sequencer (Disk2NibbleEngine) are both modeled, so every drive-level fact landed in whichever of those two was nearest.
The rule that sorts it: share the path, not the memory. A real card has one logic state sequencer and one shift register, so it can only talk to one drive at a time. That part is modeled correctly. What went wrong is that state which survives deselection got stored where the shared path lives, because it happens to be manipulated through shared latches.
Per-drive facts currently stored once, on the card
m_phase stepper detent
m_quarterTrack head position -> issue #135
m_motorSpinupRemaining mechanism
m_motorSpindownCycles mechanism
Card facts currently mirrored down into each engine
m_shiftLoadMode = Q6 already Disk2Controller::m_q6
m_writeMode = Q7 already Disk2Controller::m_q7
Copied into the selected engine on every drive select, so the same bit lives in three places.
Engine members that belong to a drive
m_disk media binding
m_currentTrack head position (quarter-tracks, see naming note below)
m_motorOn spindle
m_bitPos rotational position of the medium
m_headWindow head and flux analog behavior; weak bits are a property
m_weakRngState of the medium, not of the reader
m_readNibbles per-drive activity counters
m_writeNibbles
Engine members that are genuinely card-level
m_lssState, m_lssClock the P6 sequencer state machine
m_readLatch, m_bus, m_latchIsFresh the 74LS323 shift register and data bus
Not modeled anywhere today
- The drive, hence this issue.
- Door state. Lives in
DriveWidgetState, a UI struct, and in DiskImageStore / BayChange. The core does not know a door exists.
- A port with no drive on it.
kDriveCount = 2, and both DiskImage and Disk2NibbleEngine are always constructed, so "one drive attached" cannot be expressed. The //c's optional external drive is currently a shell predicate about whether to draw a widget.
- The write-protect sensor. The guest can read the sense bit, but
Disk2Controller reaches straight into DiskImage::IsWriteProtected(). The notch is on the medium; the sensor is in the drive; there is no drive in between.
Naming hazard found on the way
Disk2NibbleEngine::GetCurrentTrack() returns quarter-tracks, 0 to 139, not whole tracks. Disk2Controller::SetCurrentTrack(m_quarterTrack) feeds it a quarter-track, and both call sites do ResolveQuarterTrack(m_currentTrack). The neighbouring kMaxTrack = 159 makes it worse. Disk2Controller::GetCurrentTrack(), by contrast, does return whole tracks. Two methods, one name, different units. Rename as part of this.
Shape of the work
Mostly relocation, not new abstraction: four members move down off the card, eight move sideways out of the engine, two stop being mirrored. Genuinely new modeling is the door and the empty port.
Testing
Disk2Controller and CassoEmuCore/Devices/Disk/ are both scenario-suite trigger paths, so RunTests.ps1 -Build -Scenario is required. The timing-adjacent parts argue for Dormann and Harte as well.
The gap
There is no class for a Disk ][ drive. The card (
Disk2Controller) and the sequencer (Disk2NibbleEngine) are both modeled, so every drive-level fact landed in whichever of those two was nearest.The rule that sorts it: share the path, not the memory. A real card has one logic state sequencer and one shift register, so it can only talk to one drive at a time. That part is modeled correctly. What went wrong is that state which survives deselection got stored where the shared path lives, because it happens to be manipulated through shared latches.
Per-drive facts currently stored once, on the card
Card facts currently mirrored down into each engine
Copied into the selected engine on every drive select, so the same bit lives in three places.
Engine members that belong to a drive
Engine members that are genuinely card-level
Not modeled anywhere today
DriveWidgetState, a UI struct, and inDiskImageStore/BayChange. The core does not know a door exists.kDriveCount = 2, and bothDiskImageandDisk2NibbleEngineare always constructed, so "one drive attached" cannot be expressed. The //c's optional external drive is currently a shell predicate about whether to draw a widget.Disk2Controllerreaches straight intoDiskImage::IsWriteProtected(). The notch is on the medium; the sensor is in the drive; there is no drive in between.Naming hazard found on the way
Disk2NibbleEngine::GetCurrentTrack()returns quarter-tracks, 0 to 139, not whole tracks.Disk2Controller::SetCurrentTrack(m_quarterTrack)feeds it a quarter-track, and both call sites doResolveQuarterTrack(m_currentTrack). The neighbouringkMaxTrack = 159makes it worse.Disk2Controller::GetCurrentTrack(), by contrast, does return whole tracks. Two methods, one name, different units. Rename as part of this.Shape of the work
Mostly relocation, not new abstraction: four members move down off the card, eight move sideways out of the engine, two stop being mirrored. Genuinely new modeling is the door and the empty port.
Testing
Disk2ControllerandCassoEmuCore/Devices/Disk/are both scenario-suite trigger paths, soRunTests.ps1 -Build -Scenariois required. The timing-adjacent parts argue for Dormann and Harte as well.