51 if (parser.
GetString(
"min-d6-track-rooms").has_value()) {
52 auto int_or = parser.
GetInt(
"min-d6-track-rooms");
54 return absl::InvalidArgumentError(
55 "--min-d6-track-rooms: value must be a non-negative integer");
58 return absl::InvalidArgumentError(
59 "--min-d6-track-rooms: value must be >= 0");
66 if (
auto rp = parser.
GetString(
"report"); rp.has_value() && !rp->empty()) {
67 const std::filesystem::path rp_path(*rp);
69 const bool existed_before = std::filesystem::exists(rp_path, ec);
70 std::ofstream probe(*rp, std::ios::out | std::ios::binary | std::ios::app);
71 if (!probe.is_open()) {
72 return absl::PermissionDeniedError(
73 absl::StrFormat(
"oracle-smoke-check: cannot open report file "
78 if (!existed_before) {
79 std::filesystem::remove(rp_path, ec);
82 return absl::OkStatus();
88 const bool strict_readiness = parser.
HasFlag(
"strict-readiness");
91 int min_d6_track_rooms = 0;
92 if (
auto int_or = parser.
GetInt(
"min-d6-track-rooms"); int_or.ok()) {
93 min_d6_track_rooms = *int_or;
107 const auto structural =
109 const bool d4_structural_ok = structural.ok();
110 bool d4_water_fill_rooms_ok =
true;
111 for (
const auto& error : structural.errors) {
112 if (error.code ==
"ORACLE_REQUIRED_WATER_FILL_ROOM_MISSING" ||
113 error.code ==
"ORACLE_REQUIRED_WATER_FILL_ROOM_OUT_OF_RANGE" ||
114 error.code ==
"ORACLE_WATER_FILL_REGION_MISSING" ||
115 error.code ==
"ORACLE_WATER_FILL_HEADER_CORRUPT" ||
116 error.code ==
"ORACLE_WATER_FILL_TABLE_INVALID") {
117 d4_water_fill_rooms_ok =
false;
126 const bool collision_write_support =
132 bool d4_required_ok =
false;
133 const char* d4_readiness_state =
"skipped";
134 if (collision_write_support) {
141 d4_required_ok = d4_required.ok();
142 d4_readiness_state =
"ran";
153 const bool d6_ok = minecart_handler
154 .
Run({
"--rooms=0xA8,0xB8,0xD8,0xDA",
155 "--include-track-objects",
"--format=json"},
160 int track_rooms_found = 0;
162 const auto d6_doc = json::parse(d6_out,
nullptr,
false);
163 if (!d6_doc.is_discarded() && d6_doc.contains(
"Dungeon Minecart Audit")) {
164 for (
const auto& room :
165 d6_doc[
"Dungeon Minecart Audit"].value(
"rooms", json::array())) {
166 if (!room.value(
"track_object_subtypes", json::array()).empty()) {
172 const bool meets_min_track_rooms =
173 (min_d6_track_rooms == 0 || track_rooms_found >= min_d6_track_rooms);
179 const char* d3_readiness_state =
"skipped";
180 if (collision_write_support) {
187 d3_ok = d3_required.ok();
188 d3_readiness_state =
"ran";
195 bool overall_ok = d4_structural_ok && d6_ok && meets_min_track_rooms;
196 if (strict_readiness) {
200 if (collision_write_support) {
201 overall_ok = overall_ok && d4_required_ok && d3_ok;
209 report[
"ok"] = overall_ok;
210 report[
"status"] = overall_ok ?
"pass" :
"fail";
211 report[
"strict_readiness"] = strict_readiness;
213 json d4_json = json::object();
214 d4_json[
"structural_ok"] = d4_structural_ok;
215 d4_json[
"water_fill_rooms_ok"] = d4_water_fill_rooms_ok;
216 d4_json[
"required_water_fill_rooms"] = json::array({
"0x25",
"0x27"});
217 d4_json[
"required_rooms_check"] = d4_readiness_state;
218 if (collision_write_support) {
219 d4_json[
"required_rooms_ok"] = d4_required_ok;
221 d4_json[
"required_rooms"] = json::array({
"0x25",
"0x27"});
222 json structural_errors = json::array();
223 for (
const auto& err : structural.errors) {
224 structural_errors.push_back({{
"code", err.code}, {
"message", err.message}});
226 d4_json[
"structural_errors"] = std::move(structural_errors);
228 json d6_json = json::object();
229 d6_json[
"ok"] = d6_ok;
230 d6_json[
"rooms"] = json::array({
"0xA8",
"0xB8",
"0xD8",
"0xDA"});
231 d6_json[
"track_rooms_found"] = track_rooms_found;
232 d6_json[
"min_track_rooms"] = min_d6_track_rooms;
233 d6_json[
"meets_min_track_rooms"] = meets_min_track_rooms;
235 json d3_json = json::object();
236 d3_json[
"readiness_check"] = d3_readiness_state;
237 if (collision_write_support) {
238 d3_json[
"ok"] = d3_ok;
240 d3_json[
"required_rooms"] = json::array({
"0x32"});
242 report[
"checks"] = json::object();
243 report[
"checks"][
"d4_zora_temple"] = std::move(d4_json);
244 report[
"checks"][
"d6_goron_mines"] = std::move(d6_json);
245 report[
"checks"][
"d3_kalyxo_castle"] = std::move(d3_json);
251 formatter.
AddField(
"ok", overall_ok);
253 overall_ok ? std::string(
"pass") : std::string(
"fail"));
254 formatter.
AddField(
"strict_readiness", strict_readiness);
259 formatter.
AddField(
"structural_ok", d4_structural_ok);
260 formatter.
AddField(
"water_fill_rooms_ok", d4_water_fill_rooms_ok);
261 formatter.
AddField(
"required_rooms_check", std::string(d4_readiness_state));
262 if (collision_write_support) {
263 formatter.
AddField(
"required_rooms_ok", d4_required_ok);
269 formatter.
AddField(
"track_rooms_found", track_rooms_found);
270 formatter.
AddField(
"min_track_rooms", min_d6_track_rooms);
271 formatter.
AddField(
"meets_min_track_rooms", meets_min_track_rooms);
275 formatter.
AddField(
"readiness_check", std::string(d3_readiness_state));
276 if (collision_write_support) {
287 if (
auto rp = parser.
GetString(
"report"); rp.has_value() && !rp->empty()) {
288 std::ofstream report_file(
289 *rp, std::ios::out | std::ios::binary | std::ios::trunc);
290 if (!report_file.is_open()) {
291 return absl::PermissionDeniedError(absl::StrFormat(
292 "oracle-smoke-check: cannot open report file: %s", *rp));
294 report_file << report.dump(2) <<
"\n";
296 if (report_file.fail()) {
297 return absl::InternalError(absl::StrFormat(
298 "oracle-smoke-check: failed writing report: %s", *rp));
306 const bool structural_failed =
307 !d4_structural_ok || !d6_ok || !meets_min_track_rooms;
308 return absl::FailedPreconditionError(absl::StrFormat(
309 "oracle-smoke-check failed %s",
310 structural_failed ?
"(structural)" :
"(strict-readiness)"));
312 return absl::OkStatus();