yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
game_parity_gate.h
Go to the documentation of this file.
1#ifndef YAZE_ZELDA3_DUNGEON_GAME_PARITY_GATE_H
2#define YAZE_ZELDA3_DUNGEON_GAME_PARITY_GATE_H
3
4// Pass/fail gate for dungeon rendering parity against tilemaps captured from
5// the game (scripts/agents/capture-game-room-tilemaps.py).
6//
7// The gate checks three things, in order, and reports every problem it finds:
8// 1. The ROM under test is the ROM the captures were made from.
9// 2. Every room the baseline requires has a complete, uncorrupted capture.
10// 3. The differences between yaze and the game are exactly the reviewed
11// differences in the baseline: nothing new, nothing changed, nothing
12// stale, and nothing that has not been reviewed.
13//
14// Verification never writes a baseline. Candidate baselines are produced by a
15// separate, opt-in step and every candidate entry starts unreviewed, so running
16// the tests cannot approve a new difference.
17
18#include <cstdint>
19#include <filesystem>
20#include <map>
21#include <span>
22#include <string>
23#include <string_view>
24#include <vector>
25
26#include "absl/status/status.h"
27#include "absl/status/statusor.h"
28
29namespace yaze::zelda3::parity {
30
31// The capture script hashes the ROM file it loaded into the emulator. The
32// vanilla captures were made from a copy of the 1 MB ROM zero-padded to 2 MB,
33// so the manifest's rom_sha1 is the hash of that padded image, not of the
34// original file. The gate hashes the ROM zero-padded to at least this size
35// (never truncated): the original 1 MB file and its padded copy then have the
36// same identity, while any other byte change does not.
37inline constexpr size_t kCaptureRomMinSize = 0x200000;
38std::string CaptureRomSha1(std::span<const uint8_t> rom);
39
40// What a baseline expects of the capture folder it is checked against.
42 std::string rom_sha1; // full 40-digit hex
43 std::string entrance; // manifest "entrance", e.g. "0x34" or "auto"
44 std::string room_flags; // manifest "room_flags", empty when absent
45 std::vector<int> required_rooms;
46};
47
48// A capture folder whose manifest matched the expectation and whose required
49// room files were all present, the right size, and matched their SHA-1.
50struct CaptureSet {
51 std::filesystem::path dir;
52 std::map<int, std::vector<uint8_t>> room_tilemaps; // raw room_XXX.tilemap
53};
54
55// Checks the manifest against `expected` and `rom_sha1` (the identity of the
56// ROM under test), then loads every required room. The error lists every
57// problem, one per line.
58absl::StatusOr<CaptureSet> LoadVerifiedCaptureSet(
59 const std::filesystem::path& dir, const CaptureExpectation& expected,
60 std::string_view rom_sha1);
61
62// Identifies a difference without storing ROM-derived tile words in the
63// repository. Changes whenever either word changes.
64std::string DifferenceDigest(uint16_t game_word, uint16_t yaze_word);
65
66// One tile where yaze and the game disagree.
68 int room_id = -1;
69 int layer = 1; // 1 = upper tilemap ($7E2000), 2 = lower ($7E4000)
70 int x = 0;
71 int y = 0;
72 uint16_t game = 0;
73 uint16_t yaze = 0;
74};
75
76// A reviewed group of expected differences in one room that share a reason.
78 int layer = 1;
79 int x = 0;
80 int y = 0;
81 std::string digest;
82};
84 int room_id = -1;
85 std::string reason; // short slug, e.g. "hidden-chest"
86 std::string evidence; // what in the game shows this is expected
87 bool reviewed = false;
88 std::vector<BaselineTile> tiles;
89};
90struct Baseline {
91 int version = 1;
92 std::string state; // e.g. "default", "all-flags", "auto-entrance"
94 std::vector<BaselineGroup> groups;
95};
96
97absl::StatusOr<Baseline> ParseBaseline(std::string_view json);
98std::string SerializeBaseline(const Baseline& baseline);
99
101 enum class Kind {
102 kNew, // a difference the baseline does not list
103 kChanged, // listed, but the game or yaze word changed
104 kStale, // listed, but yaze now matches the game there
105 kUnreviewed, // listed in a group that is not reviewed or has no evidence
106 kDuplicate, // the same tile is listed twice
107 };
109 int room_id = -1;
110 int layer = 1;
111 int x = 0;
112 int y = 0;
113 std::string detail;
114};
115const char* GateFindingKindName(GateFinding::Kind kind);
116std::string DescribeFinding(const GateFinding& finding);
117
119 std::vector<GateFinding> findings;
121 bool ok() const { return findings.empty(); }
122};
123
124// Compares the observed differences of the rooms in `rooms_checked` with the
125// baseline. ParseBaseline only accepts groups for required rooms, and
126// LoadVerifiedCaptureSet requires every one of those to be captured, so every
127// group is checked.
128GateReport EvaluateParity(const Baseline& baseline,
129 const std::vector<ObservedDifference>& observed,
130 const std::vector<int>& rooms_checked);
131
132} // namespace yaze::zelda3::parity
133
134#endif // YAZE_ZELDA3_DUNGEON_GAME_PARITY_GATE_H
std::string SerializeBaseline(const Baseline &baseline)
std::string DifferenceDigest(uint16_t game_word, uint16_t yaze_word)
absl::StatusOr< CaptureSet > LoadVerifiedCaptureSet(const std::filesystem::path &dir, const CaptureExpectation &expected, std::string_view rom_sha1)
GateReport EvaluateParity(const Baseline &baseline, const std::vector< ObservedDifference > &observed, const std::vector< int > &rooms_checked)
const char * GateFindingKindName(GateFinding::Kind kind)
std::string CaptureRomSha1(std::span< const uint8_t > rom)
absl::StatusOr< Baseline > ParseBaseline(std::string_view text)
constexpr size_t kCaptureRomMinSize
std::string DescribeFinding(const GateFinding &finding)
nlohmann::json json
std::vector< BaselineTile > tiles
std::vector< BaselineGroup > groups
std::map< int, std::vector< uint8_t > > room_tilemaps
std::vector< GateFinding > findings