502absl::Status DungeonObjectValidateCommandHandler::Execute(
505 auto object_arg = parser.
GetInt(
"object");
506 auto size_arg = parser.
GetInt(
"size");
507 auto room_arg = parser.
GetInt(
"room");
508 auto report_arg = parser.
GetString(
"report");
509 auto trace_out_arg = parser.
GetString(
"trace-out");
510 bool verbose = parser.
HasFlag(
"verbose");
511 const bool room_mode = room_arg.ok();
512 const int room_id = room_mode ? room_arg.value() : -1;
513 if (room_mode && (room_id < 0 || room_id >= kNumRooms)) {
514 return absl::InvalidArgumentError(
515 absl::StrFormat(
"Room ID must be between 0 and %d", kNumRooms - 1));
519 auto& dimension_table = zelda3::ObjectDimensionTable::Get();
520 auto load_status = dimension_table.LoadFromRom(rom);
521 if (!load_status.ok()) {
524 auto& dim_service = zelda3::DimensionService::Get();
526 std::vector<int> object_ids = BuildObjectIds(object_arg);
527 std::vector<int> sizes = BuildSizes(size_arg);
530 std::vector<zelda3::ObjectDrawer::TileTrace> trace;
538 int mismatch_count = 0;
539 int empty_trace_count = 0;
540 int negative_offset_count = 0;
541 int skipped_nothing = 0;
542 int room_object_count = 0;
543 std::vector<ValidationResult> mismatches;
544 std::vector<ValidationResult> empty_traces;
545 std::vector<TraceDumpCase> trace_cases;
547 ReportPaths report_paths = ResolveReportPaths(
548 report_arg.has_value() ? report_arg.value() : std::string());
549 const bool write_trace_dump = trace_out_arg.has_value();
550 if (write_trace_dump) {
551 trace_cases.reserve(object_ids.size() * sizes.size());
554 bool prev_custom_objects = core::FeatureFlags::get().kEnableCustomObjects;
555 core::FeatureFlags::get().kEnableCustomObjects =
false;
558 zelda3::Room room = zelda3::LoadRoomHeaderFromRom(rom, room_id);
561 room_object_count =
static_cast<int>(room_objects.size());
562 if (write_trace_dump) {
563 trace_cases.reserve(room_objects.size());
566 for (
size_t idx = 0; idx < room_objects.size(); ++idx) {
567 const auto& room_obj = room_objects[idx];
568 int object_id = room_obj.id_;
570 if (routine_id == zelda3::DrawRoutineIds::kNothing) {
579 auto draw_status = drawer.
DrawObject(obj, bg1, bg2, palette_group);
580 if (!draw_status.ok()) {
584 auto expected_bounds = dim_service.GetDimensions(obj);
585 expected_bounds = detail::ClipSelectionBoundsToRoom(
586 object_id, obj.
size_, expected_bounds, obj.
x_, obj.
y_);
588 TraceBounds bounds = ComputeBounds(trace);
589 if (write_trace_dump) {
590 TraceDumpCase trace_case{};
591 trace_case.object_id = object_id;
592 trace_case.size = obj.
size_;
593 trace_case.has_room_context =
true;
594 trace_case.room_id = room_id;
595 trace_case.object_index =
static_cast<int>(idx);
596 trace_case.object_x = obj.
x_;
597 trace_case.object_y = obj.
y_;
599 trace_case.tiles = NormalizeTrace(trace);
600 trace_cases.push_back(std::move(trace_case));
603 ValidationResult result{};
604 result.object_id = object_id;
605 result.size = obj.
size_;
606 result.has_room_context =
true;
607 result.room_id = room_id;
608 result.object_index =
static_cast<int>(idx);
609 result.object_x = obj.
x_;
610 result.object_y = obj.
y_;
612 result.expected_width = expected_bounds.width_tiles;
613 result.expected_height = expected_bounds.height_tiles;
614 result.expected_offset_x = expected_bounds.offset_x_tiles;
615 result.expected_offset_y = expected_bounds.offset_y_tiles;
617 if (!bounds.has_tiles) {
619 result.has_tiles =
false;
620 result.trace_width = 0;
621 result.trace_height = 0;
622 result.trace_min_x = 0;
623 result.trace_min_y = 0;
624 result.trace_offset_x = 0;
625 result.trace_offset_y = 0;
626 result.size_mismatch =
true;
627 result.offset_mismatch =
false;
629 mismatches.push_back(result);
631 empty_traces.push_back(result);
636 result.has_tiles =
true;
637 result.trace_width = bounds.width;
638 result.trace_height = bounds.height;
639 result.trace_min_x = bounds.min_x;
640 result.trace_min_y = bounds.min_y;
641 result.trace_offset_x = bounds.min_x - obj.
x_;
642 result.trace_offset_y = bounds.min_y - obj.
y_;
643 result.size_mismatch = (bounds.width != expected_bounds.width_tiles ||
644 bounds.height != expected_bounds.height_tiles);
645 result.offset_mismatch =
646 (result.trace_offset_x != expected_bounds.offset_x_tiles ||
647 result.trace_offset_y != expected_bounds.offset_y_tiles);
649 if (expected_bounds.offset_x_tiles < 0 ||
650 expected_bounds.offset_y_tiles < 0) {
651 negative_offset_count++;
654 if (result.size_mismatch || result.offset_mismatch) {
656 mismatches.push_back(result);
660 for (
int object_id : object_ids) {
662 if (routine_id == zelda3::DrawRoutineIds::kNothing) {
666 for (
int size : sizes) {
674 auto expected_bounds = dim_service.GetDimensions(temp_obj);
675 const auto [origin_x, origin_y] =
676 ChooseOriginForExpectedBounds(expected_bounds);
679 static_cast<uint8_t
>(size), 0);
680 obj.
layer_ = zelda3::RoomObject::LayerType::BG1;
684 expected_bounds = dim_service.GetDimensions(obj);
685 expected_bounds = detail::ClipSelectionBoundsToRoom(
686 object_id, size, expected_bounds, obj.
x_, obj.
y_);
688 if (expected_bounds.offset_x_tiles < 0 ||
689 expected_bounds.offset_y_tiles < 0) {
690 negative_offset_count++;
693 auto draw_status = drawer.
DrawObject(obj, bg1, bg2, palette_group);
694 if (!draw_status.ok()) {
698 TraceBounds bounds = ComputeBounds(trace);
699 if (write_trace_dump) {
700 TraceDumpCase trace_case{};
701 trace_case.object_id = object_id;
702 trace_case.size = size;
703 trace_case.tiles = NormalizeTrace(trace);
704 trace_cases.push_back(std::move(trace_case));
706 if (!bounds.has_tiles) {
708 ValidationResult result{};
709 result.object_id = object_id;
711 result.has_tiles =
false;
712 result.trace_width = 0;
713 result.trace_height = 0;
714 result.trace_min_x = 0;
715 result.trace_min_y = 0;
716 result.expected_width = expected_bounds.width_tiles;
717 result.expected_height = expected_bounds.height_tiles;
718 result.expected_offset_x = expected_bounds.offset_x_tiles;
719 result.expected_offset_y = expected_bounds.offset_y_tiles;
720 result.size_mismatch =
true;
721 result.offset_mismatch =
false;
723 mismatches.push_back(result);
725 empty_traces.push_back(result);
730 ValidationResult result{};
731 result.object_id = object_id;
733 result.has_tiles =
true;
734 result.trace_width = bounds.width;
735 result.trace_height = bounds.height;
736 result.trace_min_x = bounds.min_x;
737 result.trace_min_y = bounds.min_y;
738 result.trace_offset_x = bounds.min_x - obj.
x_;
739 result.trace_offset_y = bounds.min_y - obj.
y_;
740 result.expected_width = expected_bounds.width_tiles;
741 result.expected_height = expected_bounds.height_tiles;
742 result.expected_offset_x = expected_bounds.offset_x_tiles;
743 result.expected_offset_y = expected_bounds.offset_y_tiles;
744 result.size_mismatch = (bounds.width != expected_bounds.width_tiles ||
745 bounds.height != expected_bounds.height_tiles);
746 result.offset_mismatch =
747 (result.trace_offset_x != expected_bounds.offset_x_tiles ||
748 result.trace_offset_y != expected_bounds.offset_y_tiles);
750 if (result.size_mismatch || result.offset_mismatch) {
752 mismatches.push_back(result);
758 core::FeatureFlags::get().kEnableCustomObjects = prev_custom_objects;
760 const int object_count =
761 room_mode ? room_object_count :
static_cast<int>(object_ids.size());
763 WriteJsonReport(report_paths, room_mode, object_count,
764 room_mode ? 1 :
static_cast<int>(sizes.size()),
765 total_tests, mismatch_count, empty_trace_count,
766 negative_offset_count, skipped_nothing, mismatches);
767 if (!json_status.ok()) {
771 auto csv_status = WriteCsvReport(report_paths, room_mode, mismatches);
772 if (!csv_status.ok()) {
776 if (write_trace_dump) {
777 auto trace_status = WriteTraceDump(trace_out_arg.value(), room_mode,
778 empty_trace_count, trace_cases);
779 if (!trace_status.ok()) {
782 formatter.
AddField(
"trace_dump", trace_out_arg.value());
785 formatter.
AddField(
"object_count", object_count);
787 room_mode ? 1 :
static_cast<int>(sizes.size()));
788 formatter.
AddField(
"test_cases", total_tests);
789 formatter.
AddField(
"mismatch_count", mismatch_count);
790 formatter.
AddField(
"empty_traces", empty_trace_count);
791 formatter.
AddField(
"negative_offsets", negative_offset_count);
792 formatter.
AddField(
"skipped_nothing", skipped_nothing);
794 formatter.
AddField(
"room_id", room_id);
796 formatter.
AddField(
"report_json", report_paths.json_path);
797 formatter.
AddField(
"report_csv", report_paths.csv_path);
800 for (
const auto& result : mismatches) {
802 : result.FormatText(room_mode));
808 for (
const auto& result : empty_traces) {
810 : result.FormatText(room_mode));
815 return absl::OkStatus();