rustynes_core/vs_db.rs
1//! Vs. System per-game database (v2.7.0).
2//!
3//! Closes two gaps in Vs. System support:
4//!
5//! 1. **PPU palette.** iNES-1.0 dumps carry no NES 2.0 byte-13, so the cartridge
6//! parser defaults every Vs. cart to [`VsPpuType::Rp2C03`]
7//! ([`crate::rustynes_mappers::parse`] §arcade detection). Many Vs. games used a
8//! 2C04-000x or RC2C03 PPU whose colour LUT differs from the 2C03's, so an
9//! iNES-1.0 dump renders with the wrong colours. This table supplies the
10//! correct [`VsPpuType`] keyed on the ROM SHA-256; the frontend applies it
11//! via [`crate::Nes::set_vs_ppu_type`] (always — the DB is authoritative for
12//! the palette).
13//!
14//! 2. **DIP-switch presets.** Vs. arcade games read an 8-bit DIP bank through
15//! the upper bits of `$4016`/`$4017` (coinage, difficulty, lives, PPU-type,
16//! etc.). The frontend's default DIP is `0`, which is not always the
17//! factory-shipment setting; this table supplies each game's documented
18//! default so the frontend can apply it when the user has not set an explicit
19//! `[vs] dip` (precedence: explicit config dip > DB > 0).
20//!
21//! ## DIP-byte encoding
22//!
23//! The `vs_dip` byte here is in **this emulator's** encoding: DIP switch 1 =
24//! bit 0 .. DIP switch 8 = bit 7. The bus overlay maps DIP1 -> `$4016` bit 3,
25//! DIP2 -> `$4016` bit 4, and DIP3..8 -> `$4017` bits 2..7 (see
26//! `LockstepBus::vs_overlay_4016` / `vs_overlay_4017`). Each value below is
27//! MAME's documented `DSW0` factory default for the corresponding game
28//! (the bitwise-OR of the per-field `PORT_DIPNAME` defaults in MAME's
29//! `src/mame/nintendo/vsnes.cpp`), which is exactly the `vs_dip` byte the
30//! overlay consumes (switch 1 = bit 0). On the real dual-system boards there is
31//! a second DIP bank (`DSW1`, for the sub-CPU); this single-CPU model only
32//! exposes `DSW0`.
33//!
34//! ## Sources
35//!
36//! - DIP defaults: MAME `src/mame/nintendo/vsnes.cpp` `INPUT_PORTS_START`
37//! blocks (`PORT_DIPNAME(<mask>, <default>, ...)`).
38//! - PPU types: MAME `src/mame/nintendo/vsnes.cpp` — the per-game `ROM_START`
39//! block's `PALETTE_2C04_000x` / `PALETTE_STANDARD` macro is the authoritative
40//! PPU assignment (each `.pal` ROM is dumped from real hardware). The fceux
41//! `src/vsuni.cpp` "Games/PPU list. Information copied from MAME" table is a
42//! secondary cross-check; both agree for every game below. Verified
43//! 2026-06-11 against MAME `master`. (For dual-system carts the `ppu1`
44//! master-CPU PALETTE is used.)
45//!
46//! ## Caveats
47//!
48//! - The dual-system games (Balloon Fight / Tennis / Mahjong / Wrecking Crew)
49//! run two CPUs/PPUs, modelled since v2.0.0 beta.5 by
50//! [`crate::VsDualSystem`] (routed via [`crate::Emu::from_rom`], which
51//! consults this table's `dual_system` flag). Their `DSW0` defaults are
52//! `0x00` (Balloon Fight / Tennis / Mahjong) per MAME.
53//! - Two titles (Balloon Fight, Wrecking Crew) additionally carry a
54//! **second, COMBINED-dump entry** with a different SHA-256: the
55//! pre-existing 32 KiB-PRG entry (main-CPU program only -- boot cannot
56//! complete, the sub-CPU program is absent) coexists with a new 64 KiB-PRG
57//! entry (main half + sub half concatenated) once the sub-CPU program was
58//! located. See `docs/audit/vs-dualsystem-combined-dumps-2026-07-02.md`. Tennis and
59//! Mahjong remain 32 KiB-only -- no sub-CPU dump has been located for
60//! either.
61//!
62//! The table is `&'static`, sorted by SHA-256, and binary-searched. It is
63//! `no_std`-safe (const data only).
64
65use rustynes_mappers::VsPpuType;
66
67/// A single Vs. System per-game database entry.
68#[derive(Clone, Copy, Debug, Eq, PartialEq)]
69pub struct VsDbEntry {
70 /// The game's factory-default DIP-switch bank, in this emulator's encoding
71 /// (switch 1 = bit 0 .. switch 8 = bit 7). MAME `DSW0` default.
72 pub vs_dip: u8,
73 /// The correct Vs. System PPU type (output palette + 2C05 quirks). Supplies
74 /// the right colour LUT for iNES-1.0 dumps that default to the 2C03.
75 pub vs_ppu_type: VsPpuType,
76 /// `true` for a Vs. **`DualSystem`** cart (two CPUs + two PPUs sharing an
77 /// inter-CPU latch — Tennis / Mahjong / Wrecking Crew / Balloon Fight).
78 /// v2.0.0 beta.5: [`crate::Emu::from_rom`] routes these to the full
79 /// two-console [`crate::VsDualSystem`] wrapper. This flag is the
80 /// load-bearing detection source for the circulating iNES-1.0 dumps,
81 /// whose headers carry no NES 2.0 byte-13 Vs. hardware type (see
82 /// `docs/audit/vs-dualsystem-design-2026-06-11.md`).
83 pub dual_system: bool,
84}
85
86/// Internal key+value record. Kept private; [`lookup`] returns the value only.
87struct Record {
88 sha256: [u8; 32],
89 entry: VsDbEntry,
90}
91
92const fn entry(sha256: [u8; 32], vs_dip: u8, vs_ppu_type: VsPpuType) -> Record {
93 Record {
94 sha256,
95 entry: VsDbEntry {
96 vs_dip,
97 vs_ppu_type,
98 dual_system: false,
99 },
100 }
101}
102
103/// Like [`entry`] but flags the cart as a Vs. **`DualSystem`** title.
104const fn entry_dual(sha256: [u8; 32], vs_dip: u8, vs_ppu_type: VsPpuType) -> Record {
105 Record {
106 sha256,
107 entry: VsDbEntry {
108 vs_dip,
109 vs_ppu_type,
110 dual_system: true,
111 },
112 }
113}
114
115/// The embedded database, sorted ascending by SHA-256 (enforced by a unit test).
116///
117/// Each comment records the game and the MAME `DSW0` default / PPU source.
118static DB: &[Record] = &[
119 // Vs. Duck Hunt (hack) -- MAME duckhunt DSW0=0x28
120 // PPU RC2C03: vsnes.cpp ROM_START(duckhunt) -> PALETTE_STANDARD (rp2c0x.pal).
121 entry(
122 [
123 0x16, 0x0d, 0x43, 0xde, 0x97, 0x7f, 0xbc, 0x8e, 0x24, 0x11, 0x23, 0x54, 0xe7, 0x0e,
124 0x40, 0xcf, 0xf8, 0x43, 0x10, 0x16, 0x7b, 0x07, 0xfb, 0x0b, 0x65, 0xeb, 0xc8, 0x19,
125 0xfc, 0xf0, 0xca, 0x0c,
126 ],
127 0x28,
128 VsPpuType::Rp2C03,
129 ),
130 // Vs. Gradius (hack) -- MAME vsgradus DSW0=0x80
131 // PPU RP2C04-0001: vsnes.cpp ROM_START(vsgradus) -> PALETTE_2C04_0001.
132 entry(
133 [
134 0x29, 0x01, 0x11, 0x92, 0x9a, 0x34, 0x10, 0x5e, 0x73, 0x56, 0x2d, 0xba, 0x99, 0x69,
135 0x01, 0x1d, 0x65, 0x2a, 0xb8, 0x17, 0x67, 0xee, 0x1c, 0x41, 0x74, 0xd2, 0x7b, 0x7c,
136 0x33, 0x95, 0xaa, 0x78,
137 ],
138 0x80,
139 VsPpuType::Rp2C04_0001,
140 ),
141 // Vs. Super Mario Bros. -- MAME suprmrio DSW0=0x10
142 // PPU RP2C04-0004: vsnes.cpp ROM_START(suprmrio) -> PALETTE_2C04_0004.
143 entry(
144 [
145 0x2f, 0xaa, 0xb7, 0xa4, 0x83, 0xf9, 0xa3, 0x33, 0x06, 0x6c, 0x54, 0x27, 0xee, 0x78,
146 0xb8, 0xf0, 0x0a, 0xe3, 0x00, 0x31, 0x84, 0xbe, 0xd4, 0xad, 0x2f, 0xd0, 0xe0, 0xad,
147 0xa3, 0xb0, 0xb1, 0x9b,
148 ],
149 0x10,
150 VsPpuType::Rp2C04_0004,
151 ),
152 // Vs. Excitebike (hack) -- MAME excitebk DSW0=0x00
153 // PPU RP2C04-0003: vsnes.cpp ROM_START(excitebk/excitebko) -> PALETTE_2C04_0003.
154 // (US "palette 3" set; the Japanese excitebkj set is RP2C04-0004 -- a
155 // different ROM, not staged here.)
156 entry(
157 [
158 0x31, 0xff, 0x4e, 0x40, 0xe0, 0xac, 0x32, 0x9d, 0x20, 0x7b, 0xb6, 0xa0, 0xc3, 0xaa,
159 0x65, 0x98, 0x1e, 0xa9, 0xa3, 0x67, 0x63, 0xf6, 0x79, 0x5d, 0x14, 0xf0, 0x3f, 0x87,
160 0x4c, 0xb2, 0x35, 0xc2,
161 ],
162 0x00,
163 VsPpuType::Rp2C04_0003,
164 ),
165 // Vs. Pinball (hack) -- MAME vspinbal DSW0=0x01
166 // PPU RP2C04-0001: vsnes.cpp ROM_START(vspinbal) [US set] -> PALETTE_2C04_0001.
167 // (The Japanese vspinbalj set is RC2C03B/PALETTE_STANDARD -- not staged here.)
168 entry(
169 [
170 0x44, 0xf4, 0x34, 0x07, 0x0b, 0xf9, 0x10, 0xf4, 0xe5, 0x13, 0x5d, 0x22, 0xba, 0x65,
171 0xb8, 0xc7, 0x49, 0x2c, 0xca, 0xf3, 0x25, 0xaa, 0xc1, 0x91, 0xd0, 0xab, 0xf9, 0xac,
172 0xb3, 0xa3, 0xce, 0xc9,
173 ],
174 0x01,
175 VsPpuType::Rp2C04_0001,
176 ),
177 // Vs. Wrecking Crew (DualSystem, COMBINED 64 KiB dump: main PRG half +
178 // sub-CPU PRG half, MAME `.6d/.6c/.6b/.6a`) -- MAME wrecking DSW0=0xF8.
179 // PPU RP2C04-0002: vsnes.cpp ROM_START(wrecking) ppu1 -> PALETTE_2C04_0002.
180 // Distinct SHA-256 from the pre-existing 32 KiB-only "GVS Wrecking
181 // Crew.nes" entry below (same game, same DIP/PPU -- the dump
182 // completeness is what differs). See
183 // `docs/audit/vs-dualsystem-combined-dumps-2026-07-02.md` for how this dump was
184 // assembled and verified.
185 entry_dual(
186 [
187 0x4c, 0xde, 0x98, 0x3b, 0x9d, 0x03, 0x40, 0x2b, 0x32, 0x4e, 0x28, 0x15, 0x18, 0x92,
188 0x68, 0x04, 0x1b, 0x31, 0x3a, 0x93, 0x77, 0xd7, 0xf0, 0x85, 0x6c, 0xf8, 0xd3, 0xa9,
189 0x79, 0x07, 0x08, 0xfe,
190 ],
191 0xF8,
192 VsPpuType::Rp2C04_0002,
193 ),
194 // Vs. Stroke & Match Golf -- MAME smgolf DSW0=0x21
195 // PPU RP2C04-0002: vsnes.cpp ROM_START(smgolf) -> PALETTE_2C04_0002.
196 entry(
197 [
198 0x50, 0x65, 0xc6, 0x9c, 0x1e, 0x8b, 0x09, 0x81, 0x8f, 0x37, 0x4d, 0xd5, 0xa3, 0xb3,
199 0x43, 0xa8, 0xde, 0x28, 0x36, 0x3a, 0xf5, 0x91, 0x60, 0x91, 0x53, 0x66, 0x33, 0x95,
200 0x52, 0x02, 0x01, 0x4c,
201 ],
202 0x21,
203 VsPpuType::Rp2C04_0002,
204 ),
205 // Vs. Tennis (DualSystem) -- MAME vstennis DSW0=0x00
206 // PPU RC2C03: vsnes.cpp ROM_START(vstennis) ppu1 -> PALETTE_STANDARD.
207 entry_dual(
208 [
209 0x52, 0x93, 0x4e, 0x98, 0x16, 0x7d, 0xf4, 0x7d, 0xe5, 0x2a, 0xbc, 0x2c, 0x1f, 0x56,
210 0x53, 0xb5, 0x32, 0x93, 0xba, 0x66, 0x7b, 0x91, 0xd2, 0xdf, 0x2d, 0x58, 0x27, 0x41,
211 0xf5, 0x0a, 0x45, 0x9c,
212 ],
213 0x00,
214 VsPpuType::Rp2C03,
215 ),
216 // Vs. Mahjong (DualSystem) -- MAME vsmahjng DSW0=0x00
217 // PPU RC2C03: vsnes.cpp ROM_START(vsmahjng) ppu1 -> PALETTE_STANDARD.
218 entry_dual(
219 [
220 0x63, 0x47, 0x05, 0x57, 0xaf, 0xb7, 0xb9, 0xd5, 0x76, 0x63, 0xcc, 0xc6, 0xe9, 0xb4,
221 0xd6, 0xcd, 0x70, 0x02, 0x6e, 0xf0, 0x1a, 0x77, 0xdb, 0xb4, 0x66, 0xab, 0xa1, 0xb1,
222 0xd3, 0xe4, 0xf5, 0x12,
223 ],
224 0x00,
225 VsPpuType::Rp2C03,
226 ),
227 // Vs. Wrecking Crew (DualSystem) -- MAME wrecking DSW0=0xF8
228 // PPU RP2C04-0002: vsnes.cpp ROM_START(wrecking) ppu1 -> PALETTE_2C04_0002.
229 entry_dual(
230 [
231 0x85, 0xd5, 0xf1, 0x74, 0xfe, 0x94, 0xcc, 0xba, 0x9d, 0x70, 0x2e, 0x01, 0xc0, 0xf7,
232 0x2d, 0xcc, 0x56, 0x9b, 0xc2, 0x44, 0x70, 0xd3, 0x4a, 0x36, 0xbb, 0xd5, 0x9a, 0xed,
233 0x9b, 0xb2, 0x9d, 0x7d,
234 ],
235 0xF8,
236 VsPpuType::Rp2C04_0002,
237 ),
238 // Vs. Balloon Fight (DualSystem, COMBINED 64 KiB dump: main PRG half +
239 // sub-CPU PRG half, MAME `.6d/.6c/.6b/.6a`) -- MAME balonfgt DSW0=0x00.
240 // PPU RP2C04-0003: vsnes.cpp ROM_START(balonfgt) ppu1 -> PALETTE_2C04_0003.
241 // Distinct SHA-256 from the pre-existing 32 KiB-only "GVS Balloon
242 // Fight.nes" entry above (same game, same DIP/PPU -- the dump
243 // completeness is what differs). See
244 // `docs/audit/vs-dualsystem-combined-dumps-2026-07-02.md` for how this dump was
245 // assembled and verified.
246 entry_dual(
247 [
248 0x8b, 0x32, 0x80, 0xe6, 0x51, 0xf3, 0xf8, 0xd9, 0x9d, 0xe0, 0x46, 0xcd, 0xd2, 0x71,
249 0x0e, 0x2f, 0xf5, 0xf9, 0x4b, 0x5d, 0xd4, 0x22, 0x49, 0x82, 0xcc, 0xa4, 0xa9, 0xa7,
250 0x26, 0x44, 0x41, 0xcb,
251 ],
252 0x00,
253 VsPpuType::Rp2C04_0003,
254 ),
255 // Vs. The Goonies (hack) -- MAME goonies DSW0=0x80
256 // PPU RP2C04-0003: vsnes.cpp ROM_START(goonies) -> PALETTE_2C04_0003.
257 entry(
258 [
259 0xae, 0xe9, 0x8d, 0xa8, 0x5b, 0xe8, 0x10, 0x2d, 0x41, 0xbd, 0x21, 0x2a, 0xe1, 0x5d,
260 0x11, 0x40, 0x35, 0xc0, 0x8d, 0x52, 0x2b, 0xaf, 0x22, 0x2e, 0xdb, 0x12, 0x56, 0xb8,
261 0xc9, 0x3f, 0x3b, 0x7d,
262 ],
263 0x80,
264 VsPpuType::Rp2C04_0003,
265 ),
266 // Vs. T.K.O. Boxing (hack) -- MAME tkoboxng DSW0=0x00
267 // PPU RP2C04-0003: vsnes.cpp ROM_START(tkoboxng) -> PALETTE_2C04_0003.
268 entry(
269 [
270 0xb8, 0x15, 0x8a, 0x64, 0xa1, 0xc8, 0xb6, 0x7b, 0x53, 0x0a, 0x01, 0x06, 0x78, 0x77,
271 0x2c, 0x43, 0xb0, 0xae, 0xd7, 0x20, 0xbb, 0x28, 0xf2, 0x09, 0x4a, 0xce, 0xe2, 0xf5,
272 0x92, 0x78, 0x9c, 0xc9,
273 ],
274 0x00,
275 VsPpuType::Rp2C04_0003,
276 ),
277 // Vs. Castlevania -- MAME cstlevna DSW0=0x00
278 // PPU RP2C04-0002: vsnes.cpp ROM_START(cstlevna) -> PALETTE_2C04_0002.
279 entry(
280 [
281 0xca, 0xbc, 0x23, 0x0a, 0x7f, 0x8c, 0x36, 0x6e, 0xd4, 0x05, 0xfb, 0x83, 0x1e, 0x42,
282 0xc3, 0x58, 0xa1, 0xf1, 0x40, 0x8a, 0x42, 0x77, 0x17, 0x16, 0x1c, 0xd1, 0xdd, 0x3d,
283 0x86, 0x8d, 0x35, 0x1b,
284 ],
285 0x00,
286 VsPpuType::Rp2C04_0002,
287 ),
288 // Vs. Ice Climber (hack) -- MAME iceclimb DSW0=0x00
289 // PPU RP2C04-0004: vsnes.cpp ROM_START(iceclimb) -> PALETTE_2C04_0004.
290 entry(
291 [
292 0xda, 0x2d, 0x91, 0xc8, 0x47, 0xbf, 0x59, 0x56, 0xeb, 0xe2, 0x6a, 0x0d, 0x64, 0x38,
293 0x20, 0x18, 0x9a, 0x3f, 0xa5, 0xe1, 0xdd, 0x71, 0x5d, 0xd7, 0x76, 0x77, 0x0a, 0x61,
294 0x5e, 0xf2, 0x69, 0x8e,
295 ],
296 0x00,
297 VsPpuType::Rp2C04_0004,
298 ),
299 // Vs. Excitebike -- MAME excitebk DSW0=0x00
300 // PPU RP2C04-0003: vsnes.cpp ROM_START(excitebk/excitebko) -> PALETTE_2C04_0003.
301 // (Staged dump's PRG bank-0 CRC32 = 7e54df1d = MAME `excitebko`, palette 3.)
302 entry(
303 [
304 0xea, 0x27, 0x8a, 0x35, 0xa0, 0x50, 0x17, 0xa8, 0x04, 0x9f, 0x0b, 0xa9, 0x6e, 0x06,
305 0x0a, 0x26, 0xf5, 0x50, 0xed, 0x92, 0x02, 0xf8, 0xee, 0x62, 0x50, 0x2b, 0xef, 0x50,
306 0xcb, 0x04, 0x5b, 0x23,
307 ],
308 0x00,
309 VsPpuType::Rp2C04_0003,
310 ),
311 // Vs. Clu Clu Land -- MAME cluclu DSW0=0x10
312 // PPU RP2C04-0004: vsnes.cpp ROM_START(cluclu) -> PALETTE_2C04_0004.
313 entry(
314 [
315 0xfb, 0x43, 0x24, 0x81, 0x06, 0xa4, 0x20, 0x25, 0x90, 0x84, 0x0c, 0xca, 0x68, 0x89,
316 0x5a, 0xb4, 0xb2, 0xe9, 0x4c, 0x49, 0xf8, 0x2a, 0xa1, 0x5c, 0x7c, 0x23, 0x26, 0x99,
317 0xed, 0x7a, 0xb9, 0x0a,
318 ],
319 0x10,
320 VsPpuType::Rp2C04_0004,
321 ),
322 // Vs. Balloon Fight (DualSystem) -- MAME balonfgt DSW0=0x00
323 // PPU RP2C04-0003: vsnes.cpp ROM_START(balonfgt) ppu1 -> PALETTE_2C04_0003.
324 // (Not in the fceux vsuni.cpp PPU list -- fceux skips the DualSystem carts;
325 // MAME `balonfgt` is the authoritative source.)
326 entry_dual(
327 [
328 0xfd, 0xa8, 0x4d, 0x8d, 0xcd, 0xe6, 0x90, 0xb1, 0x5a, 0xcf, 0x8f, 0x11, 0xb2, 0x7d,
329 0x61, 0x3d, 0x57, 0x1a, 0x65, 0xb2, 0xb3, 0x47, 0x19, 0xc6, 0xe0, 0x3e, 0x7f, 0x00,
330 0xe3, 0xb7, 0x09, 0x6b,
331 ],
332 0x00,
333 VsPpuType::Rp2C04_0003,
334 ),
335];
336
337/// Look up a Vs. System per-game database entry by ROM SHA-256.
338///
339/// Returns `Some(entry)` when the hash is in the embedded table, `None`
340/// otherwise. Binary search over the sorted table; `no_std`-safe.
341#[must_use]
342pub fn lookup(sha256: &[u8; 32]) -> Option<VsDbEntry> {
343 DB.binary_search_by(|rec| rec.sha256.cmp(sha256))
344 .ok()
345 .map(|i| DB[i].entry)
346}
347
348#[cfg(test)]
349mod tests {
350 use super::*;
351
352 #[test]
353 fn db_is_sorted_by_sha256() {
354 for w in DB.windows(2) {
355 assert!(
356 w[0].sha256 < w[1].sha256,
357 "vs_db must be sorted ascending by SHA-256 (binary search invariant)"
358 );
359 }
360 }
361
362 #[test]
363 fn lookup_hit_returns_entry() {
364 // Vs. Castlevania -- the first byte 0xca entry.
365 let sha = [
366 0xca, 0xbc, 0x23, 0x0a, 0x7f, 0x8c, 0x36, 0x6e, 0xd4, 0x05, 0xfb, 0x83, 0x1e, 0x42,
367 0xc3, 0x58, 0xa1, 0xf1, 0x40, 0x8a, 0x42, 0x77, 0x17, 0x16, 0x1c, 0xd1, 0xdd, 0x3d,
368 0x86, 0x8d, 0x35, 0x1b,
369 ];
370 let e = lookup(&sha).expect("castlevania present");
371 assert_eq!(e.vs_dip, 0x00);
372 assert_eq!(e.vs_ppu_type, VsPpuType::Rp2C04_0002);
373 }
374
375 #[test]
376 fn lookup_miss_returns_none() {
377 assert_eq!(lookup(&[0u8; 32]), None);
378 assert_eq!(lookup(&[0xffu8; 32]), None);
379 }
380
381 #[test]
382 fn exactly_six_dualsystem_rows_across_the_four_carts_are_flagged() {
383 // Tennis / Mahjong / Wrecking Crew / Balloon Fight are the four
384 // DualSystem *games*; every other entry (single-system) is not
385 // flagged. The flag lets the frontend warn instead of
386 // black-screening on a two-CPU cart -- and lets `Emu::from_rom`
387 // route to the two-console wrapper.
388 //
389 // The row COUNT is 6, not 4: Tennis and Mahjong have only the
390 // original 32 KiB-PRG (main-CPU-only) dump staged (no sub-CPU
391 // program has been located for either), while Balloon Fight and
392 // Wrecking Crew each additionally have a 64 KiB-PRG COMBINED dump
393 // (main half + sub half) once the sub-CPU program was located --
394 // see the "Caveats" doc section above and
395 // `docs/audit/vs-dualsystem-combined-dumps-2026-07-02.md`. The 32 KiB-only
396 // entries for those two titles are kept: loading that specific
397 // (incomplete) dump still correctly flags `dual_system` (and thus
398 // still routes to the wrapper), it just can't complete the boot
399 // handshake -- which is expected/harmless, not a bug.
400 let dual = DB.iter().filter(|r| r.entry.dual_system).count();
401 assert_eq!(
402 dual, 6,
403 "expected exactly 6 DualSystem rows (4 games, 2 of which have both an incomplete and a complete dump entry)"
404 );
405 // And the flag survives a lookup round-trip for every flagged record.
406 for rec in DB.iter().filter(|r| r.entry.dual_system) {
407 assert!(lookup(&rec.sha256).is_some_and(|e| e.dual_system));
408 }
409 }
410
411 #[test]
412 fn every_entry_is_findable() {
413 for rec in DB {
414 let e = lookup(&rec.sha256).expect("entry findable");
415 assert_eq!(e, rec.entry);
416 }
417 }
418}