11#include <unordered_map>
12#include <unordered_set>
67[[nodiscard]] std::unique_ptr<PackingStrategy> make_packing_strategy(
70 switch (strategy_type) {
71 case PackingStrategyType::best_fusion:
72 return std::make_unique<BestFusionStrategy>();
73 case PackingStrategyType::backtracking: {
76 return std::make_unique<BacktrackingStrategy>(&diag);
78 return std::make_unique<BacktrackingStrategy>(
79 cfg.search_algorithm.value.value_or(SearchAlgorithm::dfs),
80 cfg.node_cutoff.value.value_or(1'000'000),
81 cfg.best_branches.value.value_or(std::numeric_limits<std::size_t>::max()),
82 cfg.smart_prune.value.value_or(
true),
85 case PackingStrategyType::overload_and_remove: {
88 return std::make_unique<OverloadAndRemoveStrategy>(&diag);
90 return std::make_unique<OverloadAndRemoveStrategy>(
91 cfg.max_attempts.value.value_or(20),
92 cfg.seed.value.value_or(42),
93 cfg.shuffle_strategy.value.value_or(ShuffleStrategy::noisy_ffd),
97 panic(
"Unhandled PackingStrategyType value.");
105struct TileAssignmentResult {
106 enum class Status { success, no_covering_palette, tile_not_found, tile_limit_reached };
108 Status status{Status::success};
109 std::optional<TilemapEntry> entry{};
112 std::vector<PaletteMatchResult<Rgba32>> match_results{};
114 std::size_t palette_index{0};
123struct AnimKeyframeData {
124 std::vector<CanonicalPixelTile<IndexPixel>> tiles;
125 std::vector<const Palette<Rgba32, palette::max_size> *> palettes;
126 std::vector<std::size_t> palette_indices;
135struct OverridePathInfo {
137 std::string tag_prefix;
139 std::string subject_template;
150class OverrideEntryValidator {
152 OverrideEntryValidator(
158 Rgba32 extrinsic_transparency,
159 const std::vector<std::optional<LayerType>> &explicit_layer_types)
160 : format_{format}, diag_{diag}, num_palettes_total_{num_palettes_total},
161 configured_layer_mode_{configured_layer_mode}, source_metatiles_{source_metatiles},
162 extrinsic_transparency_{extrinsic_transparency}, explicit_layer_types_{explicit_layer_types}
179 [[nodiscard]]
bool should_apply(
180 const OverridePathInfo &path,
181 const std::string &anim_name,
183 std::size_t tile_count)
const;
190 const std::vector<Metatile<Rgba32>> &source_metatiles_;
191 Rgba32 extrinsic_transparency_;
194 const std::vector<std::optional<LayerType>> &explicit_layer_types_;
197bool OverrideEntryValidator::should_apply(
198 const OverridePathInfo &path,
199 const std::string &anim_name,
201 std::size_t tile_count)
const
203 const std::string subject = format_.format(path.subject_template,
FormatParam{anim_name, Style::bold});
207 std::vector<std::string> lines;
209 subject + format_.format(
210 " override has frame_subtile {} but the animation only has {} tiles.",
213 diag_.error(path.tag_prefix +
"-frame-subtile-oob", lines);
218 if (entry.
metatile_id >= source_metatiles_.size()) {
219 std::vector<std::string> lines;
223 " override references metatile_id {} which is out of range.",
FormatParam{entry.metatile_id}));
224 lines.push_back(format_.format(
"This tileset has {} metatiles.",
FormatParam{source_metatiles_.size()}));
225 diag_.error(path.tag_prefix +
"-metatile-oob", lines);
231 std::vector<std::string> lines;
233 subject + format_.format(
234 " override has palette_index {} but the maximum palette index is {}.",
237 lines.push_back(format_.format(
238 "The GBA hardware only supports {} background palettes.",
FormatParam{palette::num_palettes}));
239 diag_.error(path.tag_prefix +
"-pal-index-oob", lines);
245 std::vector<std::string> lines;
247 subject + format_.format(
248 " override has palette_index {} but only {} palettes are configured.",
252 "Porytiles does not manage palettes beyond the configured count, so this override will render with "
253 "whatever colors occupy that slot.");
255 diag_.warning(path.tag_prefix +
"-pal-index-unused", lines);
261 if (configured_layer_mode_ == LayerMode::dual) {
262 const LayerType inferred = source_metatiles_.at(entry.
metatile_id).infer_layer_type(extrinsic_transparency_);
263 const bool explicit_set =
265 const LayerType effective = explicit_set ? explicit_layer_types_[entry.
metatile_id].value() : inferred;
267 std::vector<std::string> lines;
269 subject + format_.format(
270 " override targets the '{}' layer of metatile {} but dual-layer conversion drops that "
271 "layer ({} layer type '{}').",
274 FormatParam{explicit_set ? std::string{
"explicit"} : std::string{
"inferred"}},
276 lines.emplace_back(
"The override will be ignored.");
277 diag_.warning(path.tag_prefix +
"-dual-layer-drop", lines);
306 : tileset_{
tileset}, is_secondary_{is_secondary}, paired_primary_{paired_primary}, format_{format}, diag_{diag},
307 tile_printer_{tile_printer}, palette_printer_{palette_printer}, config_{config}, schema_{schema},
308 extrinsic_transparency_{}, num_palettes_in_primary_{}, num_palettes_total_{}, num_metatiles_in_primary_{},
309 num_tiles_in_primary_{}, num_tiles_per_metatile_{}, palette_hints_enabled_{}, palette_hints_{}
322 [[nodiscard]] std::unique_ptr<Tileset> pipeline_step_assemble_output();
325 [[nodiscard]] std::optional<TilemapEntry> pipeline_helper_try_reuse_porymap_tile(std::size_t tile_index);
326 [[nodiscard]] TileAssignmentResult
327 pipeline_helper_assign_tile_via_palette_match(
const PixelTile<Rgba32> &porytiles_tile, std::size_t flat_index);
332 pipeline_helper_build_color_index_map(
const std::vector<PaletteHint> &hints, std::size_t color_count_limit)
const;
336 pipeline_helper_build_keyframe_data(
const std::string &anim_name,
const Animation<Rgba32> &anim)
const;
338 void pipeline_helper_compile_animations();
339 void pipeline_helper_apply_manual_overrides();
344 [[nodiscard]] std::vector<std::optional<LayerType>> gather_explicit_layer_types()
const;
347 void pipeline_helper_apply_true_color_to_tiles_png();
349 [[nodiscard]]
bool is_secondary()
const
351 return is_secondary_;
354 [[nodiscard]]
bool has_paired_primary()
const
356 return paired_primary_ !=
nullptr;
360 void pipeline_helper_emit_no_matching_tile_error(
361 std::size_t tile_index,
363 std::size_t palette_index,
365 void pipeline_helper_emit_no_matching_palette_error(
367 void pipeline_helper_emit_tile_limit_error(std::size_t tile_index, std::size_t tile_limit);
372 const Tileset *paired_primary_;
399 std::vector<Metatile<Rgba32>> porytiles_metatiles_{};
400 std::vector<PixelTile<Rgba32>> porytiles_pixel_rgba_{};
401 std::vector<CanonicalPixelTile<Rgba32>> porytiles_canonical_pixel_rgba_{};
404 std::vector<TilemapEntry> porymap_tilemap_entries_{};
405 std::vector<Metatile<Rgba32>> porymap_metatiles_{};
406 std::vector<PixelTile<Rgba32>> porymap_pixel_rgba_{};
407 std::vector<CanonicalPixelTile<Rgba32>> porymap_canonical_pixel_rgba_{};
409 std::map<std::size_t, std::size_t> tile_to_palette_{};
412 std::unique_ptr<PorymapTilesetComponent> new_porymap_component_{};
413 std::unique_ptr<TilesPngWorkspace> tiles_workspace_{};
415 std::map<std::string, std::vector<std::size_t>> anim_palette_indices_{};
437 extrinsic_transparency_ = extrinsic_transparency;
438 num_palettes_in_primary_ = num_palettes_in_primary;
439 num_palettes_total_ = num_palettes_total;
440 num_metatiles_in_primary_ = num_metatiles_in_primary;
441 num_tiles_in_primary_ = num_tiles_in_primary;
442 num_tiles_total_ = num_tiles_total;
443 num_tiles_per_metatile_ = num_tiles_per_metatile;
444 palette_hints_enabled_ = palette_hints_enabled;
445 palette_hints_ = palette_hints;
446 tiles_edit_mode_ = tiles_edit_mode;
447 palettes_edit_mode_ = palettes_edit_mode;
448 tiles_palette_mode_ = tiles_palette_mode;
449 global_frame_linking_ = global_frame_linking;
450 per_anim_overrides_ = per_anim_overrides;
451 cross_tileset_anim_linking_ = cross_tileset_anim_linking;
457 if (has_paired_primary()) {
459 paired_primary_et, config_, extrinsic_transparency, paired_primary_->name(), std::unique_ptr<Tileset>);
460 paired_primary_extrinsic_transparency_ = std::move(paired_primary_et);
474 return pipeline_step_assemble_output();
484 metatileizer.metatileize(
485 tileset_.porytiles_component().bottom(),
486 tileset_.porytiles_component().middle(),
487 tileset_.porytiles_component().top()),
490 "Failed to metatileize input layer images for tileset '{}'.",
FormatParam{tileset_.name(), Style::bold}));
491 porytiles_metatiles_ = std::move(metatiles);
494 porytiles_pixel_rgba_ = metatile::decompose(porytiles_metatiles_);
495 porytiles_canonical_pixel_rgba_ = transform<CanonicalPixelTile<Rgba32>>(porytiles_pixel_rgba_);
502 LayerModeConverter layer_mode_converter{&format_, &diag_, &tile_printer_, extrinsic_transparency_};
503 MetatileDecompiler metatile_decompiler{&format_, &diag_, &tile_printer_, extrinsic_transparency_};
508 layer_mode_converter.triple_layerize(tileset_.porymap_component()),
511 "Failed to triple-layerize Porymap component for tileset '{}'.",
513 porymap_tilemap_entries_ = std::move(tilemap_entries);
517 metatile_decompiler.decompile_metatiles(
518 porymap_tilemap_entries_,
519 tileset_.porymap_component().tiles_png(),
520 tileset_.porymap_component().palettes()),
523 "Failed to decompile Porymap component for tileset '{}'.",
FormatParam{tileset_.name(), Style::bold}));
524 porymap_metatiles_ = std::move(metatiles);
532 porymap_pixel_rgba_ = metatile::decompose(porymap_metatiles_);
533 porymap_canonical_pixel_rgba_ = transform<CanonicalPixelTile<Rgba32>>(porymap_pixel_rgba_);
545 if (is_secondary() && tiles_edit_mode_ != ArtifactEditMode::optimize) {
546 std::vector<std::string> err_msg{};
547 err_msg.emplace_back(format_.format(
548 "Secondary compilation of tileset '{}' does not yet support tiles edit mode '{}'. For now, only '{}' is "
549 "supported for secondary tilesets. Support for '{}' and '{}' is planned for a future update.",
551 FormatParam{to_string(tiles_edit_mode_.value()), Style::bold},
559 if (is_secondary() && palettes_edit_mode_ != ArtifactEditMode::optimize) {
560 std::vector<std::string> err_msg{};
561 err_msg.emplace_back(format_.format(
562 "Secondary compilation of tileset '{}' does not yet support palettes edit mode '{}'. For now, only '{}' is "
563 "supported for secondary tilesets. Support for '{}' and '{}' is planned for a future update.",
565 FormatParam{to_string(palettes_edit_mode_.value()), Style::bold},
573 if (palettes_edit_mode_ == ArtifactEditMode::patch) {
574 std::vector<std::string> err_msg{};
575 err_msg.emplace_back(format_.format(
576 "Tileset '{}' uses palettes edit mode '{}', which is not yet implemented.",
578 FormatParam{to_string(palettes_edit_mode_.value()), Style::bold}));
583 if (palettes_edit_mode_ == ArtifactEditMode::optimize && tiles_edit_mode_ == ArtifactEditMode::locked) {
584 std::vector<std::string> err_msg{};
585 err_msg.emplace_back(format_.format(
586 "Tileset '{}' uses palettes edit mode '{}' with tiles edit mode '{}', which is not a valid combination. "
587 "Tiles are fundamentally dependent on palettes, so optimizing palettes while keeping tiles locked is "
601 std::size_t palette_start = is_secondary() ? num_palettes_in_primary_.value() : 0;
608 if (is_secondary() && has_paired_primary()) {
609 for (std::size_t palette_index = 0; palette_index < num_palettes_in_primary_.value(); ++palette_index) {
613 paired_primary_->name(),
614 paired_primary_->porymap_component().palette_at(palette_index),
620 if (palettes_edit_mode_ != ArtifactEditMode::optimize) {
622 for (std::size_t palette_index = palette_start; palette_index < tileset_.porymap_component().palettes().size();
628 tileset_.porymap_component().palettes().at(palette_index),
635 if (is_secondary()) {
636 for (std::size_t palette_index = 0; palette_index < num_palettes_in_primary_.value(); ++palette_index) {
637 if (palette_index < tileset_.porytiles_component().palettes().size() &&
638 tileset_.porytiles_component().palettes().at(palette_index).has_value()) {
640 "Secondary tileset '{}' defines a Porytiles override palette in primary slot '{}'.",
648 for (std::size_t palette_index = palette_start; palette_index < tileset_.porytiles_component().palettes().size();
650 if (tileset_.porytiles_component().palettes().at(palette_index).has_value()) {
655 tileset_.porytiles_component().palettes().at(palette_index).value(),
662 for (
const auto &hint : palette_hints_.value()) {
669 services, tileset_.name(), porytiles_metatiles_, tileset_.porytiles_component().anims()),
678 services, tileset_.name(), porytiles_metatiles_, tileset_.porytiles_component().anims()),
687 porytiles_metatiles_,
688 tileset_.porytiles_component().anims(),
689 tileset_.porytiles_component().palettes(),
690 palette_hints_.value()),
698 porytiles_metatiles_,
699 tileset_.porytiles_component().anims(),
700 tileset_.porytiles_component().palettes(),
701 palette_hints_.value(),
714 if (palettes_edit_mode_ == ArtifactEditMode::locked) {
717 new_porymap_palettes_[i] = tileset_.porymap_component().palettes()[i];
720 else if (palettes_edit_mode_ == ArtifactEditMode::optimize) {
724 panic(
"unexpected palettes ArtifactEditMode");
728 if (tiles_edit_mode_ == ArtifactEditMode::locked) {
732 const auto size_in_tiles = tileset_.porymap_component().tiles_png().size_in_tiles();
733 tiles_workspace_ = std::make_unique<TilesPngWorkspace>(tileset_.porymap_component().tiles_png(), size_in_tiles);
735 else if (tiles_edit_mode_ == ArtifactEditMode::patch) {
736 tiles_workspace_ = std::make_unique<TilesPngWorkspace>(
737 tileset_.porymap_component().tiles_png(), num_tiles_in_primary_.value());
739 else if (tiles_edit_mode_ == ArtifactEditMode::optimize) {
740 if (is_secondary()) {
741 if (has_paired_primary()) {
743 paired_primary_->porymap_component().tiles_png(),
744 num_tiles_in_primary_.value(),
745 num_tiles_total_.value()));
749 num_tiles_in_primary_.value(), num_tiles_total_.value()));
753 tiles_workspace_ = std::make_unique<TilesPngWorkspace>(num_tiles_in_primary_.value());
757 panic(
"unexpected tiles_edit_mode");
762 PT_TRY_CALL_CHAIN_ERR(pipeline_helper_register_animations(),
void,
"Failed to register animations.");
765 new_porymap_component_ = std::make_unique<PorymapTilesetComponent>();
772 bool matched_all_tiles =
true;
773 for (std::size_t i = 0; i < porytiles_pixel_rgba_.size(); i++) {
774 const auto &porytiles_tile = porytiles_pixel_rgba_[i];
777 if (tiles_edit_mode_ != ArtifactEditMode::optimize) {
778 if (
const auto maybe_tilemap_entry = pipeline_helper_try_reuse_porymap_tile(i);
779 maybe_tilemap_entry.has_value()) {
780 new_porymap_component_->push_back_tilemap_entry(maybe_tilemap_entry.value());
793 if (porytiles_tile.is_transparent(extrinsic_transparency_.value())) {
794 new_porymap_component_->push_back_tilemap_entry(
TilemapEntry{0, 0,
false,
false});
799 const auto tile_assignment_result = pipeline_helper_assign_tile_via_palette_match(porytiles_tile, i);
801 switch (tile_assignment_result.status) {
802 case TileAssignmentResult::Status::success:
803 new_porymap_component_->push_back_tilemap_entry(tile_assignment_result.entry.value());
806 case TileAssignmentResult::Status::no_covering_palette:
807 if (palettes_edit_mode_ == ArtifactEditMode::optimize) {
809 "ArtifactEditMode::optimize but no covering palette found - this should have failed at packing "
812 matched_all_tiles =
false;
813 pipeline_helper_emit_no_matching_palette_error(i, tile_assignment_result.match_results);
816 case TileAssignmentResult::Status::tile_not_found:
817 matched_all_tiles =
false;
818 pipeline_helper_emit_no_matching_tile_error(
820 tile_assignment_result.index_tile,
821 tile_assignment_result.palette_index,
822 tile_assignment_result.matched_palette);
825 case TileAssignmentResult::Status::tile_limit_reached:
826 matched_all_tiles =
false;
828 const std::size_t user_visible_tile_limit =
829 is_secondary() ? (num_tiles_total_.value() - num_tiles_in_primary_.value())
830 : num_tiles_in_primary_.value();
831 pipeline_helper_emit_tile_limit_error(i, user_visible_tile_limit);
837 if (tile_assignment_result.status == TileAssignmentResult::Status::tile_limit_reached) {
842 if (!matched_all_tiles) {
849 if (!is_secondary()) {
856std::unique_ptr<Tileset> CompilerTask::pipeline_step_assemble_output()
858 auto new_porytiles_component = std::make_unique<PorytilesTilesetComponent>(tileset_.porytiles_component());
861 for (
auto &[anim_name, anim] : new_porytiles_component->anims()) {
862 if (
auto maybe_offset = anim_tile_matcher_.tile_offset_for(anim_name); maybe_offset.has_value()) {
864 const std::size_t local_offset =
865 is_secondary() ? maybe_offset.value() - num_tiles_in_primary_.value() : maybe_offset.value();
867 anim.params(std::move(updated_params));
872 pipeline_helper_compile_animations();
875 pipeline_helper_apply_manual_overrides();
882 const std::vector<std::optional<LayerType>> explicit_layer_types = gather_explicit_layer_types();
884 LayerModeConverter layer_mode_converter{&format_, &diag_, &tile_printer_, extrinsic_transparency_};
886 if (configured_layer_mode == LayerMode::dual) {
887 const auto &dual_layerized = layer_mode_converter.dual_layerize(
888 new_porymap_component_->metatiles_bin(), porytiles_metatiles_, explicit_layer_types);
889 new_porymap_component_->metatiles_bin(dual_layerized);
893 for (std::size_t i = 0; i < porytiles_metatiles_.size(); i++) {
894 const auto &metatile = porytiles_metatiles_[i];
896 if (configured_layer_mode == LayerMode::dual) {
897 layer_type = metatile.infer_layer_type(extrinsic_transparency_.value());
902 const auto maybe_porytiles_attribute = tileset_.porytiles_component().get_attribute(i);
904 if (maybe_porytiles_attribute.has_value()) {
905 new_attribute = maybe_porytiles_attribute.value();
913 for (
const Field &field : schema_.value_fields()) {
914 new_attribute.
field(field.name(), field.default_value());
918 new_attribute.layer_type(new_attribute.explicit_layer_type().value_or(
layer_type));
919 new_porymap_component_->push_back_attribute(new_attribute);
923 if (tiles_edit_mode_ == ArtifactEditMode::optimize) {
924 if (is_secondary()) {
925 new_porymap_component_->tiles_png(tiles_workspace_->export_secondary_image(
926 num_tiles_in_primary_.value(), ExportFlipMode::original, ExportTrimMode::trim_trailing_transparent));
929 new_porymap_component_->tiles_png(
930 tiles_workspace_->export_image(ExportFlipMode::original, ExportTrimMode::trim_trailing_transparent));
934 new_porymap_component_->tiles_png(
935 tiles_workspace_->export_image(ExportFlipMode::original, ExportTrimMode::include_trailing_transparent));
939 if (is_secondary()) {
941 if (has_paired_primary()) {
942 for (std::size_t i = 0; i < num_palettes_in_primary_.value(); i++) {
943 new_porymap_component_->set_palette(i, paired_primary_->porymap_component().palette_at(i));
948 for (std::size_t i = 0; i < num_palettes_in_primary_.value(); i++) {
949 new_porymap_component_->set_palette(
954 for (std::size_t i = num_palettes_in_primary_.value(); i < num_palettes_total_.value(); i++) {
955 new_porymap_component_->set_palette(i, new_porymap_palettes_.at(i));
959 new_porymap_component_->set_palette(i, tileset_.porymap_component().palette_at(i));
964 new_porymap_component_->set_palette(i, new_porymap_palettes_.at(i));
969 if (tiles_palette_mode_ == TilesPaletteMode::true_color) {
970 pipeline_helper_apply_true_color_to_tiles_png();
974 return std::make_unique<Tileset>(
975 tileset_.name(), std::move(new_porytiles_component), std::move(new_porymap_component_));
978std::optional<TilemapEntry> CompilerTask::pipeline_helper_try_reuse_porymap_tile(std::size_t tile_index)
981 assert_or_panic(tile_index < porytiles_pixel_rgba_.size(),
"tile_index out of bounds for porytiles_pixel_rgba_");
983 porymap_pixel_rgba_.size() == porymap_canonical_pixel_rgba_.size(),
984 "porymap_pixel_rgba_.size() != porymap_canonical_pixel_rgba_.size()");
986 porymap_canonical_pixel_rgba_.size() == porymap_tilemap_entries_.size(),
987 "porymap_canonical_pixel_rgba_.size() != porymap_tilemap_entries_.size()");
989 if (tile_index >= porymap_pixel_rgba_.size()) {
994 const auto &porytiles_tile = porytiles_pixel_rgba_[tile_index];
995 const auto &porymap_tile = porymap_pixel_rgba_[tile_index];
996 const auto &canonical_porytiles_tile = porytiles_canonical_pixel_rgba_[tile_index];
997 const auto &canonical_porymap_tile = porymap_canonical_pixel_rgba_[tile_index];
998 const auto &porymap_tilemap_entry = porymap_tilemap_entries_[tile_index];
1001 if (porytiles_tile.equals_ignoring_transparency(porymap_tile, extrinsic_transparency_)) {
1002 return porymap_tilemap_entry;
1006 if (canonical_porytiles_tile.equals_ignoring_transparency(canonical_porymap_tile, extrinsic_transparency_)) {
1008 const bool pt_to_pm_hflip = canonical_porytiles_tile.h_flip() ^ canonical_porymap_tile.h_flip();
1009 const bool pt_to_pm_vflip = canonical_porytiles_tile.v_flip() ^ canonical_porymap_tile.v_flip();
1012 porymap_tilemap_entry.palette_index(),
1013 static_cast<bool>(porymap_tilemap_entry.h_flip() ^ pt_to_pm_hflip),
1014 static_cast<bool>(porymap_tilemap_entry.v_flip() ^ pt_to_pm_vflip)};
1018 return std::nullopt;
1021TileAssignmentResult CompilerTask::pipeline_helper_assign_tile_via_palette_match(
1024 TileAssignmentResult result{};
1032 std::size_t palette_index;
1033 if (tile_to_palette_.contains(flat_index)) {
1034 palette_index = tile_to_palette_.at(flat_index);
1037 std::vector<PaletteMatchResult<Rgba32>> matches =
1038 match_or_best(porytiles_tile, new_porymap_palettes_, extrinsic_transparency_.value(), 1);
1040 if (!matches.at(0).is_covered) {
1041 result.status = TileAssignmentResult::Status::no_covering_palette;
1042 result.match_results = std::move(matches);
1045 palette_index = matches.at(0).palette_index;
1048 const auto &matched_palette = new_porymap_palettes_.at(palette_index);
1049 const auto index_tile =
1057 bool should_check_anim_matcher =
true;
1058 if (tiles_edit_mode_ != ArtifactEditMode::optimize && flat_index < porymap_tilemap_entries_.size()) {
1059 const auto original_tile_index = porymap_tilemap_entries_[flat_index].tile_index();
1060 should_check_anim_matcher = anim_tile_matcher_.is_in_animation_range(original_tile_index);
1064 if (should_check_anim_matcher) {
1065 if (
const auto anim_match = anim_tile_matcher_.find_match(
1066 CanonicalPixelTile{porytiles_tile, extrinsic_transparency_.value()}, extrinsic_transparency_.value());
1067 anim_match.has_value()) {
1071 if (anim_match->is_cross_tileset) {
1072 std::vector<std::string> remark_lines;
1073 remark_lines.emplace_back(format_.format(
1074 "Tile at flat index '{}' matched primary animation '{}' subtile '{}'.",
1077 FormatParam{anim_match->keyframe_tile_idx, Style::bold}));
1078 diag_.remark(
"cross-tileset-anim-match", remark_lines);
1081 result.status = TileAssignmentResult::Status::success;
1084 anim_palette_indices_.at(anim_match->anim_name).at(anim_match->keyframe_tile_idx),
1086 anim_match->v_flip};
1099 const auto maybe_tile_index =
1100 (tiles_edit_mode_ == ArtifactEditMode::optimize)
1101 ? tiles_workspace_->first_occurrence_of(canonical_index_tile)
1102 : tiles_workspace_->first_occurrence_of_by_color(canonical_index_tile, matched_palette);
1104 if (maybe_tile_index.has_value()) {
1105 const auto workspace_tile_index = maybe_tile_index.value();
1118 if (is_secondary() && has_paired_primary()) {
1119 const auto &primary_porymap_anims = paired_primary_->porymap_component().anims();
1120 for (
const auto &[anim_name, anim] : primary_porymap_anims) {
1121 const std::size_t offset = anim.params().tile_offset();
1122 const std::size_t count = anim.params().tile_count();
1123 if (workspace_tile_index >= offset && workspace_tile_index < offset + count) {
1124 std::vector<std::string> warning_lines;
1125 if (cross_tileset_anim_linking_.value()) {
1126 warning_lines.emplace_back(format_.format(
1127 "Tile at flat index '{}' resolved to primary animation '{}' range via workspace lookup "
1128 "(not key frame matching).",
1131 warning_lines.emplace_back(
1132 "The tile may not visually match the key frame but produces identical indexed pixel data.");
1133 diag_.warning(
"cross-tileset-anim-fallthrough", warning_lines);
1136 warning_lines.emplace_back(format_.format(
1137 "Tile at flat index '{}' resolved to primary animation '{}' range via workspace "
1138 "deduplication, despite '{}' being disabled.",
1141 FormatParam{
"cross_tileset_anim_linking", Style::bold}));
1142 warning_lines.emplace_back(
"This tile will animate at runtime.");
1143 warning_lines.emplace_back(
1144 "To suppress, restructure your tile art to avoid matching primary animation pixels, or "
1145 "enable cross-tileset linking for explicit control.");
1146 diag_.warning(
"cross-tileset-anim-fallthrough-disabled", warning_lines);
1153 const auto workspace_tile = tiles_workspace_->tile_at(workspace_tile_index);
1154 const bool pt_to_pm_hflip = canonical_index_tile.h_flip() ^ workspace_tile.h_flip();
1155 const bool pt_to_pm_vflip = canonical_index_tile.v_flip() ^ workspace_tile.v_flip();
1156 result.status = TileAssignmentResult::Status::success;
1157 result.entry =
TilemapEntry{workspace_tile_index, palette_index, pt_to_pm_hflip, pt_to_pm_vflip};
1162 if (tiles_edit_mode_ == ArtifactEditMode::locked) {
1163 result.status = TileAssignmentResult::Status::tile_not_found;
1164 result.index_tile = index_tile;
1166 result.matched_palette = matched_palette;
1171 if (tiles_workspace_->at_capacity()) {
1172 result.status = TileAssignmentResult::Status::tile_limit_reached;
1177 const std::size_t inserted_index = tiles_workspace_->insert_tile(canonical_index_tile);
1178 const auto workspace_tile = tiles_workspace_->tile_at(inserted_index);
1179 result.status = TileAssignmentResult::Status::success;
1180 const bool pt_to_pm_hflip = canonical_index_tile.h_flip() ^ workspace_tile.h_flip();
1181 const bool pt_to_pm_vflip = canonical_index_tile.v_flip() ^ workspace_tile.v_flip();
1182 result.entry =
TilemapEntry{inserted_index, palette_index, pt_to_pm_hflip, pt_to_pm_vflip};
1191 const std::size_t color_count_limit =
1192 is_secondary() ? (num_palettes_total_.value() - num_palettes_in_primary_.value()) * (
palette::max_size - 1)
1196 pipeline_helper_build_color_index_map(palette_hints_.value(), color_count_limit),
1198 format_.format(
"Failed to build color index map for tileset '{}'.",
FormatParam{tileset_.name(), Style::bold}));
1204 auto strategy = make_packing_strategy(packing_strategy.value(), packing_strategy_params.value(), diag_);
1205 PalettePacker palette_packer{strategy.get(), &format_, &diag_, &tile_printer_, &palette_printer_};
1206 std::bitset<palette::num_palettes> available_palettes{0};
1207 if (is_secondary()) {
1208 if (has_paired_primary()) {
1212 for (std::size_t i = 0; i < num_palettes_in_primary_; i++) {
1213 available_palettes.set(i,
true);
1216 for (std::size_t i = num_palettes_in_primary_; i < num_palettes_total_; i++) {
1217 available_palettes.set(i,
true);
1221 for (std::size_t i = 0; i < num_palettes_in_primary_; i++) {
1222 available_palettes.set(i,
true);
1226 packing_params.
tiles_ = porytiles_pixel_rgba_;
1227 packing_params.anims_ = tileset_.porytiles_component().anims();
1228 packing_params.color_map_ = color_index_map;
1229 packing_params.extrinsic_transparency_ = extrinsic_transparency_.value();
1230 if (is_secondary()) {
1232 if (has_paired_primary()) {
1234 for (std::size_t i = 0; i < num_palettes_in_primary_.value(); ++i) {
1235 prefilled.at(i) = paired_primary_->porymap_component().palette_at(i);
1240 if (tileset_.porytiles_component().palette_at(i).has_value()) {
1241 prefilled.at(i) = tileset_.porytiles_component().palette_at(i).value();
1244 packing_params.prefilled_palettes_ = prefilled;
1247 packing_params.prefilled_palettes_ = tileset_.porytiles_component().palettes();
1249 packing_params.hints_ = palette_hints_.value();
1250 packing_params.available_palettes_ = available_palettes;
1251 packing_params.tile_sharing_packing_ = tile_sharing_packing;
1252 packing_params.tile_sharing_alignment_ = tile_sharing_alignment;
1255 if (is_secondary() && has_paired_primary()) {
1256 const auto &primary_porymap = paired_primary_->porymap_component();
1259 primary_indexed_tiles,
1260 tileizer.tileize(primary_porymap.tiles_png()),
1262 "Failed to tileize paired primary's tiles.png for cross-tileset shape group analysis.");
1268 LayerModeConverter layer_mode_converter{&format_, &diag_, &tile_printer_, extrinsic_transparency_.value()};
1270 primary_triple_entries,
1271 layer_mode_converter.triple_layerize(primary_porymap),
1273 "Failed to triple-layerize paired primary for cross-tileset shape group analysis.");
1277 std::set<std::pair<std::size_t, std::size_t>> seen_tile_palette_pairs;
1279 for (std::size_t slot = 0; slot < primary_triple_entries.size(); ++slot) {
1280 const auto &entry = primary_triple_entries.at(slot);
1281 if (entry.tile_index() == 0) {
1284 auto key = std::make_pair(entry.tile_index(), entry.
palette_index());
1285 if (seen_tile_palette_pairs.contains(key)) {
1288 seen_tile_palette_pairs.insert(key);
1290 if (entry.tile_index() >= primary_indexed_tiles.size()) {
1293 const auto &index_tile = primary_indexed_tiles.at(entry.tile_index());
1294 auto flipped_tile = index_tile.flip(entry.
h_flip(), entry.
v_flip());
1296 flipped_tile, primary_porymap.palette_at(entry.
palette_index()), extrinsic_transparency_.value());
1297 if (rgba_tile.is_transparent(extrinsic_transparency_.value())) {
1301 packing_params.primary_tiles_.emplace_back(
1308 palette_packer.pack_tiles(packing_params),
1310 format_.format(
"Failed to pack palettes for tileset '{}'.",
FormatParam{tileset_.name(), Style::bold}));
1312 tile_to_palette_ = std::move(palette_packing.tile_to_palette_);
1315 if (
const auto &maybe_packed_palette = palette_packing.palettes_.at(i); maybe_packed_palette.has_value()) {
1317 new_porymap_palettes_[i] = maybe_packed_palette.value();
1319 else if (tileset_.porytiles_component().palette_at(i).has_value()) {
1322 const auto &porytiles_palette = tileset_.porytiles_component().palette_at(i).value();
1326 if (!porytiles_palette.is_wildcard(0)) {
1327 resolved_palette.set(0, porytiles_palette.at(0));
1330 resolved_palette.set(0, extrinsic_transparency_.value());
1335 if (!porytiles_palette.is_wildcard(j)) {
1336 resolved_palette.set(j, porytiles_palette.at(j));
1340 new_porymap_palettes_[i] = resolved_palette;
1352 new_porymap_palettes_[i] = tileset_.porymap_component().palette_at(i);
1360 const std::vector<PaletteHint> &hints, std::size_t color_count_limit)
const
1364 for (
const auto &tile : porytiles_pixel_rgba_) {
1365 color_index_map.
add_tile(tile, extrinsic_transparency_.value());
1369 for (
const auto &anim : tileset_.porytiles_component().anims() | std::views::values) {
1370 color_index_map.add_anim(anim, extrinsic_transparency_.value());
1374 const std::size_t palette_start = is_secondary() ? num_palettes_in_primary_.value() : 0;
1375 const std::size_t palette_end = is_secondary() ? num_palettes_total_.value() : num_palettes_in_primary_.value();
1376 for (std::size_t palette_index = palette_start; palette_index < palette_end; ++palette_index) {
1377 const auto &maybe_porytiles_palette = tileset_.porytiles_component().palettes().at(palette_index);
1378 if (!maybe_porytiles_palette.has_value()) {
1381 color_index_map.add_palette(maybe_porytiles_palette.value(), extrinsic_transparency_.value());
1385 for (
const auto &hint : hints) {
1386 color_index_map.add_palette(hint.palette(), extrinsic_transparency_.value());
1390 if (color_index_map.size() > color_count_limit) {
1392 "color_index_map.size() > count_limit - this should have already been validated by "
1393 "pipeline_step_validate_input");
1400 if (is_secondary() && has_paired_primary()) {
1401 for (std::size_t i = 0; i < num_palettes_in_primary_.value(); ++i) {
1402 const auto &primary_palette = paired_primary_->porymap_component().palette_at(i);
1403 color_index_map.add_palette(primary_palette, extrinsic_transparency_.value());
1407 return color_index_map;
1411CompilerTask::pipeline_helper_build_keyframe_data(
const std::string &anim_name,
const Animation<Rgba32> &anim)
const
1414 const std::size_t tile_count = composite_frame.
tiles().size();
1416 AnimKeyframeData result;
1417 result.tiles.reserve(tile_count);
1418 result.palettes.reserve(tile_count);
1425 for (std::size_t tile_idx = 0; tile_idx < tile_count; ++tile_idx) {
1432 if (representative_tile.
is_transparent(extrinsic_transparency_.value())) {
1434 result.tiles.emplace_back(transparent_tile);
1435 result.palette_indices.push_back(0);
1436 result.palettes.push_back(&new_porymap_palettes_.at(0));
1448 std::vector<PaletteMatchResult<Rgba32>> matches =
1449 match_or_best(composite_rgba_tile, new_porymap_palettes_, extrinsic_transparency_.value(), 1);
1451 if (!matches.at(0).is_covered) {
1452 std::vector<std::string> err_lines;
1453 std::vector<std::vector<FormatParam>> err_params;
1456 err_lines.emplace_back(
"Animation '{}' composite subtile '{}': no matching palette found.");
1460 err_lines.emplace_back();
1461 err_params.emplace_back();
1462 err_lines.emplace_back(
"Closest N match(es) with covered colors highlighted:");
1463 err_lines.emplace_back();
1464 err_params.emplace_back();
1465 err_params.emplace_back();
1467 for (
const auto &match : matches) {
1468 if (match_idx != 0) {
1469 err_lines.emplace_back();
1470 err_params.emplace_back();
1472 err_lines.emplace_back(
"Palette match candidate: {}");
1474 for (
const auto &line : palette_printer_.print_rgba_palette_covered_missing(
1475 new_porymap_palettes_.at(match.palette_index), match.covered_colors, match.missing_colors)) {
1476 err_lines.push_back(line);
1477 err_params.emplace_back();
1486 const std::size_t palette_index = matches.at(0).palette_index;
1487 const auto &matched_palette = new_porymap_palettes_.at(palette_index);
1491 result.tiles.emplace_back(indexed_key_frame_tile);
1492 result.palette_indices.push_back(palette_index);
1494 result.palettes.push_back(&matched_palette);
1511 const auto &anims = tileset_.porytiles_component().anims();
1513 if (!anims.empty()) {
1515 if (tiles_edit_mode_ == ArtifactEditMode::optimize) {
1516 std::size_t total_keyframe_tiles = 0;
1517 for (
const auto &anim : anims | std::views::values) {
1519 total_keyframe_tiles += anim.
key_frame().tiles().size();
1522 total_keyframe_tiles += anim.
frames().begin()->second.tiles().size();
1525 const std::size_t anim_start = is_secondary() ? (num_tiles_in_primary_.value() + 1) : 1;
1526 tiles_workspace_->reserve_anim_slots(total_keyframe_tiles, anim_start);
1529 std::map<std::string, std::size_t> anim_offsets;
1530 std::map<std::string, std::vector<std::size_t>> anim_palette_indices;
1531 std::size_t current_offset = tiles_workspace_->anim_start_offset();
1533 const auto &per_anim_overrides = per_anim_overrides_.value();
1535 for (
const auto &[anim_name, anim] : anims) {
1537 panic(
"anim '" + anim_name +
"' has no frames");
1543 const std::size_t tile_count = keyframe_data.tiles.size();
1544 anim_palette_indices[anim_name] = keyframe_data.palette_indices;
1545 std::size_t offset{};
1549 (per_anim_overrides.contains(anim_name) && per_anim_overrides.at(anim_name).linking.has_value())
1550 ? per_anim_overrides_.
derive(per_anim_overrides.at(anim_name).linking)
1551 : global_frame_linking_;
1553 if (effective_linking == FrameLinking::manual && tiles_edit_mode_ != ArtifactEditMode::optimize) {
1559 if (json_offset == 0) {
1561 "Animation '{}' uses manual frame linking in '{}' mode but has no tile_offset in anim.json.",
1565 if (json_offset + tile_count > tiles_workspace_->capacity()) {
1567 "Animation '{}' tile_offset '{}' + tile_count '{}' exceeds tiles.png capacity '{}'.",
1573 offset = json_offset;
1577 if (tiles_edit_mode_ == ArtifactEditMode::optimize) {
1578 offset = current_offset;
1579 for (std::size_t i = 0; i < tile_count; ++i) {
1580 const std::size_t reserved_index = current_offset - tiles_workspace_->anim_start_offset();
1581 tiles_workspace_->place_anim_tile(reserved_index, keyframe_data.tiles[i]);
1585 else if (tiles_edit_mode_ == ArtifactEditMode::patch) {
1587 if (
const auto existing_offset = tiles_workspace_->find_existing_contiguous_tiles_by_color(
1588 keyframe_data.tiles, keyframe_data.palettes);
1589 existing_offset.has_value()) {
1590 offset = existing_offset.value();
1594 const auto free_offset = tiles_workspace_->find_contiguous_transparent_slots(tile_count);
1595 free_offset.has_value()) {
1596 tiles_workspace_->place_tiles_at(free_offset.value(), keyframe_data.tiles);
1597 offset = free_offset.value();
1601 "Animation '{}' requires {} contiguous tiles but no sufficient space found.",
1606 else if (tiles_edit_mode_ == ArtifactEditMode::locked) {
1609 const auto existing_offset = tiles_workspace_->find_existing_contiguous_tiles_by_color(
1610 keyframe_data.tiles, keyframe_data.palettes);
1611 if (existing_offset.has_value()) {
1612 offset = existing_offset.value();
1615 std::vector<std::string> err_msg{};
1616 err_msg.emplace_back(format_.format(
1617 "Animation '{}' keyframes not found in existing tiles.png.",
1619 err_msg.emplace_back(format_.format(
1620 "Cannot proceed due to '{}' setting '{}'.",
1628 panic(
"unexpected tiles_edit_mode");
1632 anim_offsets[anim_name] = offset;
1635 for (
const auto &[anim_name, anim] : anims) {
1636 anim_palette_indices_[anim_name] = anim_palette_indices.at(anim_name);
1637 anim_tile_matcher_.register_animation(
1638 anim_name, anim, anim_offsets.at(anim_name), extrinsic_transparency_.value());
1647 if (is_secondary() && has_paired_primary() && cross_tileset_anim_linking_.value()) {
1648 const auto &primary_porytiles_anims = paired_primary_->porytiles_component().anims();
1649 const auto &primary_porymap_anims = paired_primary_->porymap_component().anims();
1659 std::map<std::size_t, std::size_t> primary_tile_palette_map;
1660 for (
const auto &entry : paired_primary_->porymap_component().metatiles_bin()) {
1661 if (entry.tile_index() == 0) {
1664 primary_tile_palette_map.try_emplace(entry.tile_index(), entry.
palette_index());
1668 std::vector<Palette<Rgba32, palette::max_size>> primary_palettes;
1669 primary_palettes.reserve(num_palettes_in_primary_.value());
1670 for (std::size_t i = 0; i < num_palettes_in_primary_.value(); ++i) {
1671 primary_palettes.push_back(paired_primary_->porymap_component().palette_at(i));
1675 for (
const auto &primary_anim_name : primary_porymap_anims | std::views::keys) {
1676 if (!primary_porytiles_anims.contains(primary_anim_name)) {
1679 "Primary animation '{}' exists in compiled Porymap data but not in Porytiles source.",
1681 "The paired primary tileset has uncompiled changes. Recompile it before compiling this "
1686 for (
const auto &[prim_anim_name, prim_anim] : primary_porytiles_anims) {
1687 if (!primary_porymap_anims.contains(prim_anim_name)) {
1690 "Primary animation '{}' exists in Porytiles source but not in compiled Porymap data.",
1692 "The paired primary tileset has uncompiled changes. Recompile it before compiling this "
1695 if (!prim_anim.has_key_frame()) {
1698 std::vector<std::string> remark_lines;
1699 remark_lines.emplace_back(format_.format(
1700 "Primary animation '{}' has no key frame (likely manual frame linking).",
1702 remark_lines.emplace_back(
"Cross-tileset key frame matching is not possible for this animation.");
1703 diag_.remark(
"cross-tileset-anim-skip-no-keyframe", remark_lines);
1712 if (anims.contains(prim_anim_name)) {
1713 std::vector<std::string> err_msg{};
1714 err_msg.emplace_back(format_.format(
1715 "Primary animation '{}' has the same name as a secondary animation.",
1717 err_msg.emplace_back(
1718 "Cross-tileset animation linking requires unique animation names across primary and secondary.");
1719 err_msg.emplace_back(
"Rename the secondary (or primary) animation so the names are distinct.");
1724 const auto &prim_porymap_anim = primary_porymap_anims.at(prim_anim_name);
1725 const std::size_t prim_tile_offset = prim_porymap_anim.params().tile_offset();
1727 const std::size_t prim_tile_count = prim_anim.key_frame().tile_count();
1752 for (std::size_t i = 0; i < prim_tile_count; ++i) {
1753 if (prim_anim.key_frame().tile_at(i).is_transparent(paired_primary_extrinsic_transparency_.value())) {
1757 prim_anim.key_frame().tile_at(i), paired_primary_extrinsic_transparency_.value()};
1758 auto match = anim_tile_matcher_.find_match(
canonical, paired_primary_extrinsic_transparency_.value());
1759 if (match.has_value() && !match->is_cross_tileset) {
1762 "Primary animation '{}' subtile '{}' has identical RGBA data to secondary animation '{}' "
1768 "Cross-tileset animation linking requires unique key frame subtiles across primary and "
1770 "Fix the secondary (or primary) animation art so key frame subtiles are visually distinct."}};
1779 prim_anim.composite_frame(paired_primary_extrinsic_transparency_.value());
1781 std::vector<std::size_t> subtile_palette_indices;
1782 subtile_palette_indices.reserve(prim_tile_count);
1783 for (std::size_t i = 0; i < prim_tile_count; ++i) {
1784 const std::size_t abs_tile_index = prim_tile_offset + i;
1786 if (prim_anim.key_frame().tile_at(i).is_transparent(paired_primary_extrinsic_transparency_.value())) {
1788 subtile_palette_indices.push_back(0);
1792 if (primary_tile_palette_map.contains(abs_tile_index)) {
1793 subtile_palette_indices.push_back(primary_tile_palette_map.at(abs_tile_index));
1799 composite.
tile_at(i), primary_palettes, paired_primary_extrinsic_transparency_.value(), 1);
1800 if (matches.at(0).is_covered) {
1801 subtile_palette_indices.push_back(matches.at(0).palette_index);
1802 std::vector<std::string> remark_lines;
1803 remark_lines.emplace_back(format_.format(
1804 "Primary animation '{}' subtile '{}' (tile_index='{}') resolved via RGBA palette fallback "
1809 FormatParam{matches.at(0).palette_index, Style::bold}));
1810 remark_lines.emplace_back(
1811 "Subtile is not referenced by any primary metatile but its colors match a primary "
1813 diag_.remark(
"cross-tileset-anim-rgba-fallback", remark_lines);
1818 "Primary animation '{}' subtile '{}' (tile_index='{}') is not referenced by any "
1820 "metatile entry and its colors do not fully match any primary palette.",
1824 "Cannot determine the correct palette index for cross-tileset linking.",
1825 "Recompile the primary tileset, or verify that all primary animation subtiles are used "
1826 "in at least one primary metatile."}};
1831 anim_palette_indices_[prim_anim_name] = subtile_palette_indices;
1832 anim_tile_matcher_.register_animation(
1836 paired_primary_extrinsic_transparency_.value(),
1853 const auto &anims = tileset_.porytiles_component().anims();
1854 if (anims.empty()) {
1860 std::unordered_set<std::size_t> referenced_tile_indices;
1861 for (
const auto &entry : new_porymap_component_->metatiles_bin()) {
1862 if (entry.tile_index() == 0) {
1865 referenced_tile_indices.insert(entry.tile_index());
1868 for (
const auto &[anim_name, anim] : anims) {
1873 auto maybe_tile_offset = anim_tile_matcher_.tile_offset_for(anim_name);
1874 if (!maybe_tile_offset.has_value()) {
1875 panic(
"animation '" + anim_name +
"' not registered in anim_tile_matcher_");
1877 const std::size_t tile_offset = maybe_tile_offset.value();
1878 const std::size_t tile_count = anim.
key_frame().tile_count();
1880 for (std::size_t i = 0; i < tile_count; ++i) {
1881 if (anim.
key_frame().tile_at(i).is_transparent(extrinsic_transparency_.value())) {
1884 const std::size_t abs_tile_index = tile_offset + i;
1885 if (!referenced_tile_indices.contains(abs_tile_index)) {
1886 std::vector<std::string> warn_lines;
1887 warn_lines.emplace_back(format_.format(
1888 "Primary animation '{}' subtile '{}' (tile_index='{}') is not referenced by any metatile:",
1892 warn_lines.append_range(
1893 tile_printer_.print_tile(anim.
key_frame().tile_at(i), extrinsic_transparency_.value()));
1894 warn_lines.emplace_back(
1895 "Palette assignment for this subtile will use RGBA fallback matching during secondary "
1898 diag_.warning(
"primary-anim-unreferenced-subtile", warn_lines);
1906void CompilerTask::pipeline_helper_compile_animations()
1908 const auto &source_anims = tileset_.porytiles_component().anims();
1911 if (source_anims.empty()) {
1915 for (
const auto &[anim_name, source_anim] : source_anims) {
1917 auto maybe_tile_offset = anim_tile_matcher_.tile_offset_for(anim_name);
1918 if (!maybe_tile_offset.has_value()) {
1919 panic(
"animation '" + anim_name +
"' not registered in anim_tile_matcher_");
1921 const std::size_t tile_offset = maybe_tile_offset.value();
1924 const AnimFrame<Rgba32> composite = source_anim.composite_frame(extrinsic_transparency_.value());
1925 const std::size_t tile_count = composite.
tile_count();
1928 std::vector<std::size_t> subtile_palette_indices;
1929 subtile_palette_indices.reserve(tile_count);
1931 for (std::size_t tile_idx = 0; tile_idx < tile_count; ++tile_idx) {
1934 std::vector<PaletteMatchResult<Rgba32>> matches =
1935 match_or_best(composite_tile, new_porymap_palettes_, extrinsic_transparency_.value(), 1);
1937 if (!matches.at(0).is_covered) {
1939 "animation '" + anim_name +
"' subtile " + std::to_string(tile_idx) +
1940 " has no covering palette during compilation");
1943 subtile_palette_indices.push_back(matches.at(0).palette_index);
1947 const std::size_t frame_palette_index = subtile_palette_indices.at(0);
1948 const bool uses_multiple_palettes =
1949 !std::ranges::all_of(subtile_palette_indices, [&](std::size_t idx) {
return idx == frame_palette_index; });
1951 if (uses_multiple_palettes) {
1952 std::vector<std::string> warning_lines;
1953 warning_lines.emplace_back(format_.format(
1954 "Animation '{}' uses multiple palettes across subtiles.",
FormatParam{anim_name, Style::bold}));
1955 warning_lines.emplace_back(format_.format(
1956 "Porymap-component frame PNGs will be saved using palette '{}' for display purposes.",
1957 FormatParam{palette_filename(frame_palette_index), Style::bold}));
1958 diag_.warning(
"multi-palette-animation", warning_lines);
1962 const auto &fixed_palette = new_porymap_palettes_.at(frame_palette_index);
1964 for (std::size_t i = 0; i < fixed_palette.size(); ++i) {
1965 if (fixed_palette.is_wildcard(i)) {
1966 panic(
"Porymap palette '" + std::to_string(frame_palette_index) +
"' has illegal wildcard");
1968 anim_palette.add(fixed_palette.at(i));
1974 for (
const auto &[frame_name, source_frame] : source_anim.frames()) {
1975 std::vector<PixelTile<IndexPixel>> frame_index_tiles;
1976 frame_index_tiles.reserve(tile_count);
1978 for (std::size_t tile_idx = 0; tile_idx < tile_count; ++tile_idx) {
1980 const auto &palette = new_porymap_palettes_.
at(subtile_palette_indices[tile_idx]);
1982 frame_index_tiles.push_back(
1986 AnimFrame frame{frame_name, std::move(frame_index_tiles)};
1988 compiled_anim.put_frame(frame_name, std::move(frame));
1993 const std::size_t local_offset = is_secondary() ? tile_offset - num_tiles_in_primary_.value() : tile_offset;
1996 compiled_anim.params(std::move(params));
1999 new_porymap_component_->add_anim(std::move(compiled_anim));
2003std::vector<std::optional<LayerType>> CompilerTask::gather_explicit_layer_types()
const
2005 std::vector<std::optional<LayerType>> explicit_layer_types;
2006 explicit_layer_types.reserve(porytiles_metatiles_.size());
2007 for (std::size_t i = 0; i < porytiles_metatiles_.size(); i++) {
2008 const auto maybe_attribute = tileset_.porytiles_component().get_attribute(i);
2009 explicit_layer_types.push_back(
2010 maybe_attribute.has_value() ? maybe_attribute.value().explicit_layer_type() : std::nullopt);
2012 return explicit_layer_types;
2015void CompilerTask::pipeline_helper_apply_manual_overrides()
2017 const auto &source_anims = tileset_.porytiles_component().anims();
2020 if (source_anims.empty() && tileset_.porytiles_component().primary_anim_overrides().empty()) {
2024 const auto &per_anim_overrides = per_anim_overrides_.value();
2028 const std::vector<std::optional<LayerType>> explicit_layer_types = gather_explicit_layer_types();
2030 const OverrideEntryValidator validator{
2033 num_palettes_total_,
2035 porytiles_metatiles_,
2036 extrinsic_transparency_.value(),
2037 explicit_layer_types};
2038 const OverridePathInfo manual_path{
"manual",
"Animation '{}'"};
2039 const OverridePathInfo primary_refs_path{
"primary-references",
"Primary reference '{}'"};
2041 for (
const auto &[anim_name, source_anim] : source_anims) {
2044 (per_anim_overrides.contains(anim_name) && per_anim_overrides.at(anim_name).linking.has_value())
2045 ? per_anim_overrides_.
derive(per_anim_overrides.at(anim_name).linking)
2046 : global_frame_linking_;
2048 const auto &overrides = source_anim.params().overrides();
2050 switch (effective_linking) {
2051 case FrameLinking::automatic: {
2052 if (!overrides.empty()) {
2053 std::vector<std::string> warning_lines;
2054 warning_lines.emplace_back(format_.format(
2055 "Animation '{}' has frame_linking 'automatic' but overrides are present in anim.json.",
2057 warning_lines.emplace_back(
"The overrides will be ignored.");
2058 diag_.warning(
"automatic-mode-overrides-ignored", warning_lines);
2063 case FrameLinking::manual: {
2064 if (overrides.empty()) {
2065 std::vector<std::string> warning_lines;
2066 warning_lines.emplace_back(format_.format(
2067 "Animation '{}' has frame_linking '{}' but no overrides are present in anim.json.",
2070 warning_lines.emplace_back(
"Animation tiles will not be linked to any metatiles.");
2071 diag_.warning(
"manual-no-overrides", warning_lines);
2076 auto maybe_tile_offset = anim_tile_matcher_.tile_offset_for(anim_name);
2077 if (!maybe_tile_offset.has_value()) {
2078 panic(
"animation '" + anim_name +
"' not registered in anim_tile_matcher_");
2080 const std::size_t tile_offset = maybe_tile_offset.value();
2082 auto maybe_tile_count = anim_tile_matcher_.tile_count_for(anim_name);
2083 if (!maybe_tile_count.has_value()) {
2084 panic(
"animation '" + anim_name +
"' not registered in anim_tile_matcher_");
2086 const std::size_t tile_count = maybe_tile_count.value();
2089 auto &metatiles_bin = new_porymap_component_->metatiles_bin();
2090 for (
const auto &entry : overrides) {
2091 if (!validator.should_apply(manual_path, anim_name, entry, tile_count)) {
2095 const std::size_t bin_index =
2098 static_cast<std::size_t
>(entry.
subtile);
2100 const std::size_t absolute_tile = tile_offset + entry.
frame_subtile;
2101 metatiles_bin.at(bin_index) =
2107 case FrameLinking::hybrid: {
2108 std::vector<std::string> err_lines;
2109 err_lines.emplace_back(format_.format(
2110 "Hybrid frame linking is not yet implemented (animation '{}').",
FormatParam{anim_name, Style::bold}));
2111 err_lines.emplace_back(
"Use 'automatic' or 'manual' frame linking until hybrid support.");
2113 diag_.error(
"hybrid-frame-linking-not-implemented", err_lines);
2118 panic(
"unhandled value for FrameLinking");
2123 const auto &primary_refs = tileset_.porytiles_component().primary_anim_overrides();
2125 if (!primary_refs.empty() && !is_secondary()) {
2126 std::vector<std::string> err_lines;
2127 err_lines.emplace_back(format_.format(
2128 "Primary tilesets cannot have '{}' in anim.json.",
FormatParam{
"primary_references", Style::bold}));
2129 err_lines.emplace_back(
"Only secondary tilesets may reference primary animation tiles.");
2130 diag_.error(
"primary-references-on-primary", err_lines);
2134 if (!primary_refs.empty() && is_secondary()) {
2135 if (!has_paired_primary()) {
2136 std::vector<std::string> err_lines;
2137 err_lines.emplace_back(format_.format(
2138 "The '{}' section requires a paired primary tileset (pairing mode must not be off).",
2140 diag_.error(
"primary-references-no-paired-primary", err_lines);
2144 const auto &primary_anims = paired_primary_->porymap_component().anims();
2145 auto &metatiles_bin = new_porymap_component_->metatiles_bin();
2147 for (
const auto &[prim_anim_name, entries] : primary_refs) {
2148 if (!primary_anims.contains(prim_anim_name)) {
2149 std::vector<std::string> err_lines;
2150 err_lines.emplace_back(format_.format(
2151 "Primary animation '{}' referenced in '{}' was not found in the paired primary tileset.",
2154 diag_.error(
"primary-references-anim-not-found", err_lines);
2158 const auto &prim_anim = primary_anims.at(prim_anim_name);
2159 const std::size_t prim_tile_offset = prim_anim.params().tile_offset();
2160 const std::size_t prim_tile_count = prim_anim.params().tile_count();
2162 for (
const auto &entry : entries) {
2163 if (!validator.should_apply(primary_refs_path, prim_anim_name, entry, prim_tile_count)) {
2167 const std::size_t bin_index =
2170 static_cast<std::size_t
>(entry.
subtile);
2172 const std::size_t absolute_tile = prim_tile_offset + entry.
frame_subtile;
2173 metatiles_bin.at(bin_index) =
2180void CompilerTask::pipeline_helper_apply_true_color_to_tiles_png()
2183 std::unordered_map<std::size_t, std::size_t> tile_to_first_palette;
2184 std::unordered_map<std::size_t, std::set<std::size_t>> tile_to_all_palettes;
2188 const std::size_t tile_index_offset = is_secondary() ? num_tiles_in_primary_.value() : 0;
2194 const std::size_t palette_index_offset =
2195 (is_secondary() && !has_paired_primary()) ? num_palettes_in_primary_.value() : 0;
2198 const std::size_t default_display_palette = is_secondary() ? num_palettes_in_primary_.value() : 0;
2200 for (
const auto &entry : new_porymap_component_->metatiles_bin()) {
2201 const auto tile_idx = entry.tile_index();
2204 if (tile_idx == 0) {
2208 tile_to_all_palettes[tile_idx].insert(palette_idx);
2210 if (!tile_to_first_palette.contains(tile_idx)) {
2211 tile_to_first_palette[tile_idx] = palette_idx;
2216 for (
const auto &[anim_name, source_anim] : tileset_.porytiles_component().anims()) {
2217 auto maybe_tile_offset = anim_tile_matcher_.tile_offset_for(anim_name);
2218 if (!maybe_tile_offset.has_value()) {
2222 const std::size_t tile_offset = maybe_tile_offset.value();
2223 const AnimFrame<Rgba32> composite = source_anim.composite_frame(extrinsic_transparency_.value());
2224 const std::size_t tile_count = composite.
tile_count();
2226 for (std::size_t subtile_idx = 0; subtile_idx < tile_count; ++subtile_idx) {
2227 const std::size_t absolute_tile_idx = tile_offset + subtile_idx;
2229 if (tile_to_first_palette.contains(absolute_tile_idx)) {
2234 std::vector<PaletteMatchResult<Rgba32>> matches =
2235 match_or_best(composite_tile, new_porymap_palettes_, extrinsic_transparency_.value(), 1);
2237 if (matches.at(0).is_covered) {
2238 const std::size_t matched_palette_idx = matches.at(0).palette_index;
2239 tile_to_first_palette[absolute_tile_idx] = matched_palette_idx;
2242 const auto &tiles_img = new_porymap_component_->tiles_png();
2252 constexpr auto tag =
"true-color-anim-only-tile";
2253 std::vector<std::string> remark_lines;
2254 remark_lines.emplace_back(format_.format(
2255 "Tile index '{}' (animation '{}', subtile '{}') is not referenced in metatiles.",
2259 remark_lines.emplace_back(format_.format(
2260 "Using '{}' for true-color encoding (determined via palette matching).",
2261 FormatParam{palette_filename(matched_palette_idx), Style::bold}));
2265 index_tile, new_porymap_palettes_.at(matched_palette_idx), extrinsic_transparency_.value());
2266 remark_lines.emplace_back();
2267 remark_lines.append_range(tile_printer_.print_tile(rgba_tile, extrinsic_transparency_.value()));
2269 diag_.remark(tag, remark_lines);
2275 for (
const auto &[absolute_tile_idx, palettes] : tile_to_all_palettes) {
2276 if (palettes.size() > 1) {
2278 if (absolute_tile_idx < tile_index_offset) {
2283 const auto &tiles_img = new_porymap_component_->tiles_png();
2292 constexpr auto tag =
"true-color-multi-palette-tile";
2293 std::vector<std::string> remark_lines;
2294 remark_lines.emplace_back(format_.format(
2295 "Tile index '{}' is used with multiple palettes.",
FormatParam{absolute_tile_idx, Style::bold}));
2297 std::string palette_list;
2298 for (
const auto palette : palettes) {
2299 if (!palette_list.empty()) {
2300 palette_list +=
", ";
2305 const std::size_t selected_palette_idx = tile_to_first_palette.at(absolute_tile_idx);
2306 remark_lines.emplace_back(format_.format(
2307 "Palettes used: {}; tiles.png will display using '{}'.",
2309 FormatParam{palette_filename(selected_palette_idx), Style::bold}));
2312 for (
const auto palette_idx : palettes) {
2313 remark_lines.emplace_back();
2314 remark_lines.emplace_back(
2315 format_.format(
"{} resolution:",
FormatParam{palette_filename(palette_idx), Style::bold}));
2317 index_tile, new_porymap_palettes_.at(palette_idx), extrinsic_transparency_.value());
2318 remark_lines.append_range(tile_printer_.print_tile(rgba_tile, extrinsic_transparency_.value()));
2321 diag_.remark(tag, remark_lines);
2331 for (std::size_t tile_idx = 1; tile_idx < total_tiles; ++tile_idx) {
2332 const std::size_t absolute_tile_idx = tile_idx + tile_index_offset;
2333 if (!tile_to_first_palette.contains(absolute_tile_idx)) {
2343 constexpr auto tag =
"true-color-unreferenced-tile";
2344 std::vector<std::string> remark_lines;
2345 remark_lines.emplace_back(format_.format(
2346 "Tile index '{}' is not referenced in metatiles or animations.",
2349 remark_lines.emplace_back(
"This tile may be used by a secondary tileset, or it may be completely unused.");
2350 remark_lines.emplace_back(format_.format(
2351 "Displaying using '{}' for color resolution.",
2352 FormatParam{palette_filename(default_display_palette), Style::bold}));
2356 index_tile, new_porymap_palettes_.at(default_display_palette), extrinsic_transparency_.value());
2357 remark_lines.emplace_back();
2358 remark_lines.append_range(tile_printer_.print_tile(rgba_tile, extrinsic_transparency_.value()));
2360 diag_.remark(tag, remark_lines);
2364 const std::size_t palette_idx = tile_to_first_palette.at(absolute_tile_idx);
2365 const std::size_t tile_row = tile_idx / tiles_per_row;
2366 const std::size_t tile_col = tile_idx % tiles_per_row;
2372 const std::size_t row = pixel_row_start + py;
2373 const std::size_t col = pixel_col_start + px;
2375 const std::size_t color_idx = old_pixel.
color_index();
2376 const std::size_t new_index = ((palette_idx - palette_index_offset) << 4) | color_idx;
2384 if (!is_secondary()) {
2387 else if (has_paired_primary()) {
2391 num_palettes = num_palettes_total_.value() - num_palettes_in_primary_.value();
2393 std::vector<Rgba32> true_color_palette;
2397 const auto &palette = new_porymap_palettes_.at(i + palette_index_offset);
2399 true_color_palette.push_back(palette.at(color_idx));
2403 tiles_img.
palette(std::move(true_color_palette));
2404 new_porymap_component_->tiles_png(tiles_img);
2407void CompilerTask::pipeline_helper_emit_no_matching_tile_error(
2408 std::size_t tile_index,
2410 std::size_t palette_index,
2413 constexpr auto tag =
"no-matching-tile";
2417 std::vector<std::string> no_match_err{};
2418 no_match_err.emplace_back(format_.format(
2419 "{}: no matching tile found",
2420 FormatParam{metatile::message_header(format_, metatile_index, layer, subtile), Style::bold}));
2421 no_match_err.append_range(tile_printer_.print_metatile_tile_highlight(
2422 porytiles_metatiles_.at(metatile_index), layer, subtile, extrinsic_transparency_));
2423 diag_.error(tag, no_match_err);
2426 std::vector<std::string> palette_note{};
2427 palette_note.emplace_back(
2428 format_.format(
"matched palette '{}':",
FormatParam{palette_filename(palette_index), Style::bold}));
2429 palette_note.append_range(palette_printer_.print_rgba_palette(matched_palette));
2430 diag_.error_note(tag, palette_note);
2433 std::vector<std::string> tile_note{};
2434 tile_note.emplace_back(
"generated index tile:");
2435 tile_note.append_range(tile_printer_.print_tile(index_tile, extrinsic_transparency_.value()));
2436 diag_.error_note(tag, tile_note);
2439void CompilerTask::pipeline_helper_emit_no_matching_palette_error(
2442 constexpr auto tag =
"no-matching-palette";
2446 std::vector<std::string> no_match_err{};
2447 no_match_err.emplace_back(format_.format(
2448 "{}: no matching palette found",
2449 FormatParam{metatile::message_header(format_, metatile_index, layer, subtile), Style::bold}));
2450 no_match_err.append_range(tile_printer_.print_metatile_tile_highlight(
2451 porytiles_metatiles_.at(metatile_index), layer, subtile, extrinsic_transparency_));
2452 diag_.error(tag, no_match_err);
2455 std::vector<std::string> closest_n_note{};
2456 closest_n_note.emplace_back(
"closest N match(es) with covered colors highlighted:");
2457 int match_index = 0;
2458 for (
const auto &match : matches) {
2459 if (match_index != 0) {
2461 closest_n_note.emplace_back();
2463 closest_n_note.push_back(format_.format(
2464 "Palette match candidate: {}",
FormatParam{palette_filename(match.palette_index), Style::bold}));
2465 closest_n_note.append_range(palette_printer_.print_rgba_palette_covered_missing(
2466 new_porymap_palettes_.at(match.palette_index), match.covered_colors, match.missing_colors));
2467 closest_n_note.emplace_back();
2468 closest_n_note.push_back(format_.format(
2469 "Uncovered pixels with {}:",
FormatParam{palette_filename(match.palette_index), Style::bold}));
2470 closest_n_note.append_range(tile_printer_.print_metatile_pixel_highlights(
2471 porytiles_metatiles_.at(metatile_index),
2474 match.uncovered_pixel_indices,
2475 extrinsic_transparency_));
2478 diag_.error_note(tag, closest_n_note);
2481void CompilerTask::pipeline_helper_emit_tile_limit_error(std::size_t tile_index, std::size_t tile_limit)
2483 constexpr auto tag =
"tile-limit";
2487 std::vector<std::string> tile_limit_error{};
2488 tile_limit_error.emplace_back(format_.format(
2489 "{}: hit limit of '{}' unique tiles",
2490 FormatParam{metatile::message_header(format_, metatile_index, layer, subtile), Style::bold},
2492 tile_limit_error.append_range(tile_printer_.print_metatile_tile_highlight(
2493 porytiles_metatiles_.at(metatile_index), layer, subtile, extrinsic_transparency_));
2494 diag_.error(tag, tile_limit_error);
2497 std::vector<std::string> note_text;
2498 if (is_secondary()) {
2499 note_text.append_range(
2503 note_text.push_back(
2504 format_.format(
"Tile limit is '{}' due to configuration.",
FormatParam{tile_limit, Style::bold}));
2505 note_text.emplace_back();
2508 diag_.error_note(tag, note_text);
2519 tileset, is_secondary, paired_primary, *format_, *diag_, *tile_printer_, *palette_printer_, *config_, *schema_};
#define PT_TRY_ASSIGN_CHAIN_ERR(var, expr, return_type,...)
Unwraps a ChainableResult, chaining a new error message on failure.
#define PT_TRY_CALL_PASS_ERR(expr, return_type)
Unwraps a void ChainableResult, passing through the error chain with an empty FormattableError when t...
#define PT_TRY_ASSIGN_PASS_ERR(var, expr, return_type)
Unwraps a ChainableResult, passing through the error chain with an empty FormattableError when types ...
#define PT_TRY_CALL_PASS_SAME_ERR(expr)
Unwraps a void ChainableResult, passing through the error unchanged when types match.
#define PT_TRY_CALL_CHAIN_ERR(expr, return_type,...)
Unwraps a void ChainableResult, chaining a new error message on failure.
Represents a single frame of an animation, containing tiles and a frame name.
const Palette< Rgba32 > & palette() const
const PixelTile< PixelType > & tile_at(std::size_t index) const
std::size_t tile_count() const
const std::vector< PixelTile< PixelType > > & tiles() const
Configuration parameters for a single tileset animation.
std::size_t tile_offset() const
Returns the VRAM tile offset for this animation.
std::size_t tile_count() const
Returns the number of tiles per animation frame.
Matches tiles against animation keyframe tiles for compilation.
A complete tileset animation with name, configuration, and frame data.
const AnimParams & params() const
AnimFrame< PixelType > composite_frame(const PixelType &extrinsic_transparency) const
Returns the "composite" frame for this animation.
bool has_frames() const
Checks if this animation has any frames.
bool has_key_frame() const
Checks if this animation has a key frame set.
const AnimFrame< PixelType > & key_frame() const
Returns the key frame of this animation.
const std::map< std::string, AnimFrame< PixelType > > & frames() const
A PixelTile representation that stores the canonical (lexicographically minimal) orientation among al...
A result type that maintains a chainable sequence of errors for debugging and error reporting.
T & value() &
Returns a reference to the contained success value.
A bidirectional mapping between pixel color values and sequential integer indices.
void add_tile(const PixelTile< PixelType > &tile)
Adds colors from a single tile to the mapping using intrinsic transparency.
A container that wraps a configuration value with its name and source information.
ConfigValue< U > derive(const ConfigPODField< U > &override) const
Creates a child ConfigValue from a ConfigPODField, inheriting this value's source provenance.
Interface that defines a complete domain layer configuration.
One named bit-field within a metatile attribute layout.
Service for converting images into collections of 8x8 tiles.
A template for two-dimensional images with arbitrarily typed pixel values.
void set(std::size_t i, PixelType pixel)
Sets the pixel value at a given one-dimensional pixel index.
std::size_t size_in_tiles() const
Gets the number of 8x8 tile regions in this image.
const std::optional< std::vector< Rgba32 > > & palette() const
PixelType at(std::size_t i) const
Fetches the pixel value at a given one-dimensional pixel index.
Represents an indexed color pixel.
std::size_t color_index() const
Returns the color index within a palette (lower 4 bits).
Domain service that packs ColorSets into hardware palettes.
A collection of printer functions for the Palette and related types.
A generic palette container for colors that support transparency checking.
An 8x8 tile backed by literal-array-based per-pixel storage of an arbitrary pixel type.
bool is_transparent() const
Checks if this entire PixelTile is transparent (intrinsic transparency only).
PixelType at(std::size_t i) const
Represents a 32-bit RGBA color.
static constexpr std::uint8_t alpha_opaque
A validated metatile attribute layout: an ordered set of non-overlapping fields.
static const Style bold
Bold text formatting.
Abstract base class for applying text styling with context-aware formatting.
A collection of printer functions for various tile types.
Represents a tilemap entry referencing a tile with palette and flip attributes.
std::size_t tile_index() const
std::size_t palette_index() const
static TilesPngWorkspace for_secondary(const Image< IndexPixel > &primary_tiles_png, std::size_t primary_tile_count, std::size_t total_capacity)
Creates a workspace pre-loaded with primary tiles for secondary tileset compilation.
static TilesPngWorkspace for_standalone_secondary(std::size_t primary_tile_count, std::size_t total_capacity)
Creates a workspace for standalone secondary compilation with no paired primary.
ChainableResult< std::unique_ptr< Tileset > > compile(const Tileset &tileset, bool is_secondary=false, const Tileset *paired_primary=nullptr) const
Compiles the given Tileset, producing a new Tileset with compiled Porymap assets.
A complete tileset containing both Porytiles and Porymap components.
Abstract class for structured error reporting and diagnostic output.
constexpr std::size_t max_size
constexpr std::size_t num_palettes
constexpr std::size_t side_length_pix
PixelTile< PixelType > extract_single_tile(const Image< PixelType > &img, std::size_t tile_idx, std::size_t tiles_per_row=metatile::metatiles_per_row *metatile::tiles_per_side)
Extracts a single tile from an image at a given tile index.
ChainableResult< void > validate_palette_hint(const TilesetCompileValidatorServices &services, const std::string &tileset_name, const PaletteHint &hint)
Validates a user-specified palette hint for correctness.
LayerMode
Specifies whether a metatile uses dual-layer or triple-layer mode.
void panic(const StringViewSourceLoc &s)
Unconditionally terminates the program with a panic message.
ChainableResult< void > validate_porymap_palette(const TilesetCompileValidatorServices &services, const std::string &tileset_name, const Palette< Rgba32, palette::max_size > &palette, std::size_t palette_index)
Validates a Porymap palette for correctness according to GBA hardware constraints.
void assert_or_panic(bool condition, const StringViewSourceLoc &s)
Conditionally panics if the given condition is false.
ChainableResult< void > validate_anim_frames(const TilesetCompileValidatorServices &services, const std::string &tileset_name, const std::map< std::string, Animation< Rgba32 > > &anims)
Validates animation frames for correctness according to tileset compilation constraints.
ChainableResult< void > validate_alpha_channels(const TilesetCompileValidatorServices &services, const std::string &tileset_name, const std::vector< Metatile< Rgba32 > > &metatiles, const std::map< std::string, Animation< Rgba32 > > &anims)
Validates that all pixel alpha channels in provided input have valid values.
@ canonical
Export tiles in canonical form without applying flip transformations.
LayerMode layer_mode_from_val(std::size_t s)
Converts a numeric value to LayerMode.
PixelTile< IndexPixel > index_tile_from_color_tile(const PixelTile< ColorType > &tile, const Palette< ColorType, N > &palette)
Converts a PixelTile<ColorType> to indexed form using a palette (intrinsic transparency only).
ChainableResult< void > validate_global_color_count(const TilesetCompileValidatorServices &services, const std::string &tileset_name, bool is_secondary, const std::vector< Metatile< Rgba32 > > &metatiles, const std::map< std::string, Animation< Rgba32 > > &anims, const std::array< std::optional< Palette< Rgba32, palette::max_size > >, palette::num_palettes > &porytiles_palettes, const std::vector< PaletteHint > &hints)
Validates that the total unique color count across all input does not exceed the global limit.
std::vector< std::string > build_subtraction_limit_lines(const TextFormatter &format, std::string_view label, std::size_t computed_limit, const ConfigValue< std::size_t > &total_cfg, const ConfigValue< std::size_t > &primary_cfg)
Builds note lines explaining a derived limit computed as total minus primary.
ChainableResult< void > validate_porytiles_palette(const TilesetCompileValidatorServices &services, const std::string &tileset_name, const Palette< Rgba32, palette::max_size > &palette, std::size_t palette_index)
Validates a user-specified Porytiles override palette for correctness.
ChainableResult< void > validate_precision_loss(const TilesetCompileValidatorServices &services, const std::string &tileset_name, const std::vector< Metatile< Rgba32 > > &metatiles, const std::map< std::string, Animation< Rgba32 > > &anims, const std::array< std::optional< Palette< Rgba32, palette::max_size > >, palette::num_palettes > &porytiles_palettes, const std::vector< PaletteHint > &hints, const std::optional< std::array< Palette< Rgba32, palette::max_size >, palette::num_palettes > > &porymap_palettes)
Validates that no colors will suffer unacceptable precision loss during GBA color conversion.
std::vector< std::string > format_config_note(const TextFormatter &format, const ConfigValue< T > &config)
Format a ConfigValue into diagnostic note lines.
ChainableResult< void > validate_metatile_count(const TilesetCompileValidatorServices &services, const std::string &tileset_name, bool is_secondary, const std::vector< Metatile< Rgba32 > > &metatiles)
Validates that the metatile count does not exceed the configured limit.
ChainableResult< void > validate_layer_mode(const TilesetCompileValidatorServices &services, const std::string &tileset_name, const std::vector< Metatile< Rgba32 > > &metatiles)
Validates that metatiles conform to the configured layer mode.
LayerType
Specifies which layers of a metatile are used for rendering.
std::vector< std::string > format_config_note_with_separator(const TextFormatter &format, const ConfigValue< T > &config)
Format a ConfigValue into diagnostic note lines with a separator.
std::vector< PaletteMatchResult< ColorType > > match_or_best(const PixelTile< ColorType > &tile, const PaletteContainer &palettes, const ColorType &extrinsic, std::size_t top_n)
Finds the best palette match(es) for a tile (extrinsic transparency).
std::string palette_filename(std::size_t palette_index)
Constructs a palette filename from a palette index.
PixelTile< ColorType > color_tile_from_index_tile(const PixelTile< IndexPixel > &index_tile, const Palette< ColorType, N > &palette)
Converts a PixelTile<IndexPixel> to a PixelTile<ColorType> using a palette (intrinsic transparency).
@ tileset
Configuration scoped to a specific tileset.
ChainableResult< void > validate_tile_color_count(const TilesetCompileValidatorServices &services, const std::string &tileset_name, const std::vector< Metatile< Rgba32 > > &metatiles, const std::map< std::string, Animation< Rgba32 > > &anims)
Validates that each individual tile does not exceed the per-tile color limit.
std::string to_string(const PrimaryPairingMode m)
Converts a PrimaryPairingMode to its canonical string representation.
PackingStrategyType
Selects the palette packing algorithm to use during tileset compilation.
Utility functions for string manipulation and formatting.
A manual override that maps a specific metatile entry to an animation subtile.
metatile::Layer layer
The layer within the metatile (bottom, middle, or top).
bool v_flip
Whether the tile is vertically flipped.
std::size_t frame_subtile
Zero-based index into the animation's tile range (tile_offset + frame_subtile = actual tile index).
bool h_flip
Whether the tile is horizontally flipped.
std::size_t metatile_id
The metatile ID this override applies to (corresponds to JSON "id" field).
std::size_t palette_index
The palette index to use for this tile.
metatile::Subtile subtile
The subtile position within the layer (northwest, northeast, southwest, southeast).
A reconstructed RGBA tile from a compiled primary tileset, tagged with its first metatile slot locati...
The input parameters for a packing operation.
std::vector< PixelTile< Rgba32 > > tiles_
Raw pixel tiles to pack into palettes.
Container for per-strategy packing parameters.
BacktrackingParams backtracking
OverloadAndRemoveParams overload_and_remove
Result type for palette matching operations.
Parameter store for common services used by tileset compile validators.
Validation functions for tileset compile job input.
#define PT_UNWRAP_TILESET_CONFIG_REF(ref, config, tileset_name, return_type)
Unwraps a tileset-scoped config value via reference access, returning early if the value is not avail...
#define PT_UNWRAP_TILESET_CONFIG_REF_AS(var, ref, config, tileset_name, return_type)
Unwraps a tileset-scoped config value via reference access into an explicitly named local variable,...