10#include <unordered_map>
11#include <unordered_set>
65[[nodiscard]] std::unique_ptr<PackingStrategy> make_packing_strategy(
68 switch (strategy_type) {
69 case PackingStrategyType::best_fusion:
70 return std::make_unique<BestFusionStrategy>();
71 case PackingStrategyType::backtracking: {
74 return std::make_unique<BacktrackingStrategy>(&diag);
76 return std::make_unique<BacktrackingStrategy>(
77 cfg.search_algorithm.value.value_or(SearchAlgorithm::dfs),
78 cfg.node_cutoff.value.value_or(1'000'000),
79 cfg.best_branches.value.value_or(std::numeric_limits<std::size_t>::max()),
80 cfg.smart_prune.value.value_or(
true),
83 case PackingStrategyType::overload_and_remove: {
86 return std::make_unique<OverloadAndRemoveStrategy>(&diag);
88 return std::make_unique<OverloadAndRemoveStrategy>(
89 cfg.max_attempts.value.value_or(20),
90 cfg.seed.value.value_or(42),
91 cfg.shuffle_strategy.value.value_or(ShuffleStrategy::noisy_ffd),
95 panic(
"Unhandled PackingStrategyType value.");
105struct TileAssignmentResult {
106 enum class Status { success, no_covering_pal, 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 pal_index{0};
125struct AnimKeyframeData {
126 std::vector<CanonicalPixelTile<IndexPixel>> tiles;
127 std::vector<const Palette<Rgba32, pal::max_size> *> palettes;
128 std::vector<std::size_t> pal_indices;
153 : tileset_{
tileset}, is_secondary_{is_secondary}, paired_primary_{paired_primary}, format_{format}, diag_{diag},
154 tile_printer_{tile_printer}, pal_printer_{pal_printer}, config_{config}, extrinsic_transparency_{},
155 num_pals_in_primary_{}, num_pals_total_{}, num_metatiles_in_primary_{}, num_tiles_in_primary_{},
156 num_tiles_per_metatile_{}, pal_hints_enabled_{}, pal_hints_{}
169 [[nodiscard]] std::unique_ptr<Tileset> pipeline_step_assemble_output();
172 [[nodiscard]] std::optional<TilemapEntry> pipeline_helper_try_reuse_porymap_tile(std::size_t tile_index);
173 [[nodiscard]] TileAssignmentResult
174 pipeline_helper_assign_tile_via_pal_match(
const PixelTile<Rgba32> &porytiles_tile, std::size_t flat_index);
179 pipeline_helper_build_color_index_map(
const std::vector<PaletteHint> &hints, std::size_t color_count_limit)
const;
183 pipeline_helper_build_keyframe_data(
const std::string &anim_name,
const Animation<Rgba32> &anim)
const;
185 void pipeline_helper_compile_animations();
186 void pipeline_helper_apply_manual_overrides();
189 void pipeline_helper_apply_true_color_to_tiles_png();
191 [[nodiscard]]
bool is_secondary()
const
193 return is_secondary_;
196 [[nodiscard]]
bool has_paired_primary()
const
198 return paired_primary_ !=
nullptr;
202 void pipeline_helper_emit_no_matching_tile_error(
203 std::size_t tile_index,
205 std::size_t pal_index,
207 void pipeline_helper_emit_no_matching_pal_error(
209 void pipeline_helper_emit_tile_limit_error(std::size_t tile_index, std::size_t tile_limit);
214 const Tileset *paired_primary_;
240 std::vector<Metatile<Rgba32>> porytiles_metatiles_{};
241 std::vector<PixelTile<Rgba32>> porytiles_pixel_rgba_{};
242 std::vector<CanonicalPixelTile<Rgba32>> porytiles_canonical_pixel_rgba_{};
245 std::vector<TilemapEntry> porymap_tilemap_entries_{};
246 std::vector<Metatile<Rgba32>> porymap_metatiles_{};
247 std::vector<PixelTile<Rgba32>> porymap_pixel_rgba_{};
248 std::vector<CanonicalPixelTile<Rgba32>> porymap_canonical_pixel_rgba_{};
249 std::array<Palette<Rgba32, pal::max_size>,
pal::num_pals> new_porymap_pals_{};
250 std::map<std::size_t, std::size_t> tile_to_pal_{};
253 std::unique_ptr<PorymapTilesetComponent> new_porymap_component_{};
254 std::unique_ptr<TilesPngWorkspace> tiles_workspace_{};
256 std::map<std::string, std::vector<std::size_t>> anim_pal_indices_{};
278 extrinsic_transparency_ = extrinsic_transparency;
279 num_pals_in_primary_ = num_pals_in_primary;
280 num_pals_total_ = num_pals_total;
281 num_metatiles_in_primary_ = num_metatiles_in_primary;
282 num_tiles_in_primary_ = num_tiles_in_primary;
283 num_tiles_total_ = num_tiles_total;
284 num_tiles_per_metatile_ = num_tiles_per_metatile;
285 pal_hints_enabled_ = pal_hints_enabled;
286 pal_hints_ = pal_hints;
287 tiles_edit_mode_ = tiles_edit_mode;
288 pals_edit_mode_ = pals_edit_mode;
289 tiles_pal_mode_ = tiles_pal_mode;
290 global_frame_linking_ = global_frame_linking;
291 per_anim_overrides_ = per_anim_overrides;
292 cross_tileset_anim_linking_ = cross_tileset_anim_linking;
300 if (has_paired_primary()) {
301 paired_primary_extrinsic_transparency_ =
302 config_.extrinsic_transparency(ConfigScopeType::tileset, paired_primary_->name()).value();
316 return pipeline_step_assemble_output();
326 metatileizer.metatileize(
327 tileset_.porytiles_component().bottom(),
328 tileset_.porytiles_component().middle(),
329 tileset_.porytiles_component().top()),
331 "failed to metatileize input layer images for " + tileset_.name());
332 porytiles_metatiles_ = std::move(metatiles);
335 porytiles_pixel_rgba_ = metatile::decompose(porytiles_metatiles_);
336 porytiles_canonical_pixel_rgba_ = transform<CanonicalPixelTile<Rgba32>>(porytiles_pixel_rgba_);
343 LayerModeConverter layer_mode_converter{&format_, &diag_, &tile_printer_, extrinsic_transparency_};
344 MetatileDecompiler metatile_decompiler{&format_, &diag_, &tile_printer_, extrinsic_transparency_};
349 layer_mode_converter.triple_layerize(tileset_.porymap_component()),
351 std::format(
"Failed to triple-layerize Porymap component for tileset '{}'.", tileset_.name()));
352 porymap_tilemap_entries_ = std::move(tilemap_entries);
356 metatile_decompiler.decompile_metatiles(
357 porymap_tilemap_entries_, tileset_.porymap_component().tiles_png(), tileset_.porymap_component().pals()),
359 std::format(
"Failed to decompile Porymap component for tileset '{}'.", tileset_.name()));
360 porymap_metatiles_ = std::move(metatiles);
370 porymap_pixel_rgba_ = metatile::decompose(porymap_metatiles_);
371 porymap_canonical_pixel_rgba_ = transform<CanonicalPixelTile<Rgba32>>(porymap_pixel_rgba_);
385 if (is_secondary() && tiles_edit_mode_ != ArtifactEditMode::optimize) {
386 std::vector<std::string> err_msg{};
387 err_msg.emplace_back(format_.format(
388 "Secondary compilation of tileset '{}' does not yet support tiles edit mode '{}'.",
390 FormatParam{to_string(tiles_edit_mode_.value()), Style::bold}));
391 err_msg.emplace_back(format_.format(
392 "For now, only '{}' is supported for secondary tilesets.",
FormatParam{
"optimize", Style::bold}));
393 err_msg.emplace_back(format_.format(
394 "Support for '{}' and '{}' is planned for a future update.",
401 if (is_secondary() && pals_edit_mode_ != ArtifactEditMode::optimize) {
402 std::vector<std::string> err_msg{};
403 err_msg.emplace_back(format_.format(
404 "Secondary compilation of tileset '{}' does not yet support pals edit mode '{}'.",
406 FormatParam{to_string(pals_edit_mode_.value()), Style::bold}));
407 err_msg.emplace_back(format_.format(
408 "For now, only '{}' is supported for secondary tilesets.",
FormatParam{
"optimize", Style::bold}));
409 err_msg.emplace_back(format_.format(
410 "Support for '{}' and '{}' is planned for a future update.",
417 if (pals_edit_mode_ == ArtifactEditMode::patch) {
418 std::vector<std::string> err_msg{};
419 err_msg.emplace_back(format_.format(
420 "Tileset '{}' uses pals edit mode '{}', which is not yet implemented.",
422 FormatParam{to_string(pals_edit_mode_.value()), Style::bold}));
427 if (pals_edit_mode_ == ArtifactEditMode::optimize && tiles_edit_mode_ == ArtifactEditMode::locked) {
428 std::vector<std::string> err_msg{};
429 err_msg.emplace_back(format_.format(
430 "Tileset '{}' uses palettes edit mode '{}' with tiles edit mode '{}', which is not a valid combination.",
434 err_msg.emplace_back(
435 "Tiles are fundamentally dependent on palettes, so optimizing palettes while keeping "
436 "tiles locked is not coherent.");
446 std::size_t pal_start = is_secondary() ? num_pals_in_primary_.value() : 0;
455 if (is_secondary() && has_paired_primary()) {
456 for (std::size_t pal_index = 0; pal_index < num_pals_in_primary_.value(); ++pal_index) {
460 paired_primary_->name(),
461 paired_primary_->porymap_component().pal_at(pal_index),
467 if (pals_edit_mode_ != ArtifactEditMode::optimize) {
469 for (std::size_t pal_index = pal_start; pal_index < tileset_.porymap_component().pals().size(); ++pal_index) {
472 services, tileset_.name(), tileset_.porymap_component().pals().at(pal_index), pal_index),
478 if (is_secondary()) {
479 for (std::size_t pal_index = 0; pal_index < num_pals_in_primary_.value(); ++pal_index) {
480 if (pal_index < tileset_.porytiles_component().pals().size() &&
481 tileset_.porytiles_component().pals().at(pal_index).has_value()) {
483 "Secondary tileset '{}' defines a Porytiles override palette in primary slot '{}'.",
491 for (std::size_t pal_index = pal_start; pal_index < tileset_.porytiles_component().pals().size(); ++pal_index) {
492 if (tileset_.porytiles_component().pals().at(pal_index).has_value()) {
495 services, tileset_.name(), tileset_.porytiles_component().pals().at(pal_index).value(), pal_index),
501 for (
const auto &hint : pal_hints_.value()) {
508 services, tileset_.name(), porytiles_metatiles_, tileset_.porytiles_component().anims()),
517 services, tileset_.name(), porytiles_metatiles_, tileset_.porytiles_component().anims()),
526 porytiles_metatiles_,
527 tileset_.porytiles_component().anims(),
528 tileset_.porytiles_component().pals(),
537 porytiles_metatiles_,
538 tileset_.porytiles_component().anims(),
539 tileset_.porytiles_component().pals(),
553 if (pals_edit_mode_ == ArtifactEditMode::locked) {
556 new_porymap_pals_[i] = tileset_.porymap_component().pals()[i];
559 else if (pals_edit_mode_ == ArtifactEditMode::optimize) {
563 panic(
"unexpected pals ArtifactEditMode");
567 if (tiles_edit_mode_ == ArtifactEditMode::locked) {
573 const auto size_in_tiles = tileset_.porymap_component().tiles_png().size_in_tiles();
574 tiles_workspace_ = std::make_unique<TilesPngWorkspace>(tileset_.porymap_component().tiles_png(), size_in_tiles);
576 else if (tiles_edit_mode_ == ArtifactEditMode::patch) {
577 tiles_workspace_ = std::make_unique<TilesPngWorkspace>(
578 tileset_.porymap_component().tiles_png(), num_tiles_in_primary_.value());
580 else if (tiles_edit_mode_ == ArtifactEditMode::optimize) {
581 if (is_secondary()) {
582 if (has_paired_primary()) {
584 paired_primary_->porymap_component().tiles_png(),
585 num_tiles_in_primary_.value(),
586 num_tiles_total_.value()));
590 num_tiles_in_primary_.value(), num_tiles_total_.value()));
594 tiles_workspace_ = std::make_unique<TilesPngWorkspace>(num_tiles_in_primary_.value());
598 panic(
"unexpected tiles_edit_mode");
603 PT_TRY_CALL_CHAIN_ERR(pipeline_helper_register_animations(),
void,
"Failed to register animations.");
606 new_porymap_component_ = std::make_unique<PorymapTilesetComponent>();
613 bool matched_all_tiles =
true;
614 for (std::size_t i = 0; i < porytiles_pixel_rgba_.size(); i++) {
615 const auto &porytiles_tile = porytiles_pixel_rgba_[i];
618 if (tiles_edit_mode_ != ArtifactEditMode::optimize) {
619 if (
const auto maybe_tilemap_entry = pipeline_helper_try_reuse_porymap_tile(i);
620 maybe_tilemap_entry.has_value()) {
621 new_porymap_component_->push_back_tilemap_entry(maybe_tilemap_entry.value());
636 if (porytiles_tile.is_transparent(extrinsic_transparency_.value())) {
637 new_porymap_component_->push_back_tilemap_entry(
TilemapEntry{0, 0,
false,
false});
642 const auto tile_assignment_result = pipeline_helper_assign_tile_via_pal_match(porytiles_tile, i);
644 switch (tile_assignment_result.status) {
645 case TileAssignmentResult::Status::success:
646 new_porymap_component_->push_back_tilemap_entry(tile_assignment_result.entry.value());
649 case TileAssignmentResult::Status::no_covering_pal:
650 if (pals_edit_mode_ == ArtifactEditMode::optimize) {
651 panic(
"ArtifactEditMode::optimize but no covering pal found - this should have failed at packing step");
653 matched_all_tiles =
false;
654 pipeline_helper_emit_no_matching_pal_error(i, tile_assignment_result.match_results);
657 case TileAssignmentResult::Status::tile_not_found:
658 matched_all_tiles =
false;
659 pipeline_helper_emit_no_matching_tile_error(
661 tile_assignment_result.index_tile,
662 tile_assignment_result.pal_index,
663 tile_assignment_result.matched_pal);
666 case TileAssignmentResult::Status::tile_limit_reached:
667 matched_all_tiles =
false;
669 const std::size_t user_visible_tile_limit =
670 is_secondary() ? (num_tiles_total_.value() - num_tiles_in_primary_.value())
671 : num_tiles_in_primary_.value();
672 pipeline_helper_emit_tile_limit_error(i, user_visible_tile_limit);
678 if (tile_assignment_result.status == TileAssignmentResult::Status::tile_limit_reached) {
683 if (!matched_all_tiles) {
692 if (!is_secondary()) {
699std::unique_ptr<Tileset> CompilerTask::pipeline_step_assemble_output()
701 auto new_porytiles_component = std::make_unique<PorytilesTilesetComponent>(tileset_.porytiles_component());
704 for (
auto &[anim_name, anim] : new_porytiles_component->anims()) {
705 if (
auto maybe_offset = anim_tile_matcher_.tile_offset_for(anim_name); maybe_offset.has_value()) {
707 const std::size_t local_offset =
708 is_secondary() ? maybe_offset.value() - num_tiles_in_primary_.value() : maybe_offset.value();
710 anim.params(std::move(updated_params));
715 pipeline_helper_compile_animations();
718 pipeline_helper_apply_manual_overrides();
725 LayerModeConverter layer_mode_converter{&format_, &diag_, &tile_printer_, extrinsic_transparency_};
727 if (configured_layer_mode == LayerMode::dual) {
728 const auto &dual_layerized =
729 layer_mode_converter.dual_layerize(new_porymap_component_->metatiles_bin(), porytiles_metatiles_);
730 new_porymap_component_->metatiles_bin(dual_layerized);
734 for (std::size_t i = 0; i < porytiles_metatiles_.size(); i++) {
735 const auto &metatile = porytiles_metatiles_[i];
737 if (configured_layer_mode == LayerMode::dual) {
738 layer_type = metatile.infer_layer_type(extrinsic_transparency_.value());
741 layer_type = LayerType::normal;
743 const auto maybe_porytiles_attr = tileset_.porytiles_component().get_attribute(i);
745 if (maybe_porytiles_attr.has_value()) {
746 new_attr = maybe_porytiles_attr.value();
749 new_porymap_component_->push_back_attribute(new_attr);
753 if (tiles_edit_mode_ == ArtifactEditMode::optimize) {
754 if (is_secondary()) {
755 new_porymap_component_->tiles_png(tiles_workspace_->export_secondary_image(
756 num_tiles_in_primary_.value(), ExportFlipMode::original, ExportTrimMode::trim_trailing_transparent));
759 new_porymap_component_->tiles_png(
760 tiles_workspace_->export_image(ExportFlipMode::original, ExportTrimMode::trim_trailing_transparent));
764 new_porymap_component_->tiles_png(
765 tiles_workspace_->export_image(ExportFlipMode::original, ExportTrimMode::include_trailing_transparent));
769 if (is_secondary()) {
771 if (has_paired_primary()) {
772 for (std::size_t i = 0; i < num_pals_in_primary_.value(); i++) {
773 new_porymap_component_->set_pal(i, paired_primary_->porymap_component().pal_at(i));
778 for (std::size_t i = 0; i < num_pals_in_primary_.value(); i++) {
779 new_porymap_component_->set_pal(
784 for (std::size_t i = num_pals_in_primary_.value(); i < num_pals_total_.value(); i++) {
785 new_porymap_component_->set_pal(i, new_porymap_pals_.at(i));
788 for (std::size_t i = num_pals_total_.value(); i <
pal::num_pals; i++) {
789 new_porymap_component_->set_pal(i, tileset_.porymap_component().pal_at(i));
794 new_porymap_component_->set_pal(i, new_porymap_pals_.at(i));
799 if (tiles_pal_mode_ == TilesPalMode::true_color) {
800 pipeline_helper_apply_true_color_to_tiles_png();
804 return std::make_unique<Tileset>(
805 tileset_.name(), std::move(new_porytiles_component), std::move(new_porymap_component_));
808std::optional<TilemapEntry> CompilerTask::pipeline_helper_try_reuse_porymap_tile(std::size_t tile_index)
811 assert_or_panic(tile_index < porytiles_pixel_rgba_.size(),
"tile_index out of bounds for porytiles_pixel_rgba_");
813 porymap_pixel_rgba_.size() == porymap_canonical_pixel_rgba_.size(),
814 "porymap_pixel_rgba_.size() != porymap_canonical_pixel_rgba_.size()");
816 porymap_canonical_pixel_rgba_.size() == porymap_tilemap_entries_.size(),
817 "porymap_canonical_pixel_rgba_.size() != porymap_tilemap_entries_.size()");
819 if (tile_index >= porymap_pixel_rgba_.size()) {
824 const auto &porytiles_tile = porytiles_pixel_rgba_[tile_index];
825 const auto &porymap_tile = porymap_pixel_rgba_[tile_index];
826 const auto &canonical_porytiles_tile = porytiles_canonical_pixel_rgba_[tile_index];
827 const auto &canonical_porymap_tile = porymap_canonical_pixel_rgba_[tile_index];
828 const auto &porymap_tilemap_entry = porymap_tilemap_entries_[tile_index];
831 if (porytiles_tile.equals_ignoring_transparency(porymap_tile, extrinsic_transparency_)) {
832 return porymap_tilemap_entry;
836 if (canonical_porytiles_tile.equals_ignoring_transparency(canonical_porymap_tile, extrinsic_transparency_)) {
838 const bool pt_to_pm_hflip = canonical_porytiles_tile.h_flip() ^ canonical_porymap_tile.h_flip();
839 const bool pt_to_pm_vflip = canonical_porytiles_tile.v_flip() ^ canonical_porymap_tile.v_flip();
842 porymap_tilemap_entry.pal_index(),
843 static_cast<bool>(porymap_tilemap_entry.h_flip() ^ pt_to_pm_hflip),
844 static_cast<bool>(porymap_tilemap_entry.v_flip() ^ pt_to_pm_vflip)};
852CompilerTask::pipeline_helper_assign_tile_via_pal_match(
const PixelTile<Rgba32> &porytiles_tile, std::size_t flat_index)
854 TileAssignmentResult result{};
864 std::size_t pal_index;
865 if (tile_to_pal_.contains(flat_index)) {
866 pal_index = tile_to_pal_.at(flat_index);
869 std::vector<PaletteMatchResult<Rgba32>> matches =
870 match_or_best(porytiles_tile, new_porymap_pals_, extrinsic_transparency_.value(), 1);
872 if (!matches.at(0).is_covered) {
873 result.status = TileAssignmentResult::Status::no_covering_pal;
874 result.match_results = std::move(matches);
877 pal_index = matches.at(0).pal_index;
880 const auto &matched_pal = new_porymap_pals_.at(pal_index);
890 bool should_check_anim_matcher =
true;
891 if (tiles_edit_mode_ != ArtifactEditMode::optimize && flat_index < porymap_tilemap_entries_.size()) {
892 const auto original_tile_index = porymap_tilemap_entries_[flat_index].tile_index();
893 should_check_anim_matcher = anim_tile_matcher_.is_in_animation_range(original_tile_index);
897 if (should_check_anim_matcher) {
898 if (
const auto anim_match = anim_tile_matcher_.find_match(
899 CanonicalPixelTile{porytiles_tile, extrinsic_transparency_.value()}, extrinsic_transparency_.value());
900 anim_match.has_value()) {
901 if (anim_match->is_cross_tileset) {
902 std::vector<std::string> remark_lines;
903 remark_lines.emplace_back(format_.format(
904 "Tile at flat index '{}' matched primary animation '{}' subtile '{}'.",
907 FormatParam{anim_match->keyframe_tile_idx, Style::bold}));
908 diag_.remark(
"cross-tileset-anim-match", remark_lines);
911 result.status = TileAssignmentResult::Status::success;
914 anim_pal_indices_.at(anim_match->anim_name).at(anim_match->keyframe_tile_idx),
931 const auto maybe_tile_index =
932 (tiles_edit_mode_ == ArtifactEditMode::optimize)
933 ? tiles_workspace_->first_occurrence_of(canonical_index_tile)
934 : tiles_workspace_->first_occurrence_of_by_color(canonical_index_tile, matched_pal);
936 if (maybe_tile_index.has_value()) {
937 const auto workspace_tile_index = maybe_tile_index.value();
947 if (is_secondary() && has_paired_primary()) {
948 const auto &primary_porymap_anims = paired_primary_->porymap_component().anims();
949 for (
const auto &[anim_name, anim] : primary_porymap_anims) {
950 const std::size_t offset = anim.params().tile_offset();
951 const std::size_t count = anim.params().tile_count();
952 if (workspace_tile_index >= offset && workspace_tile_index < offset + count) {
953 std::vector<std::string> warning_lines;
954 if (cross_tileset_anim_linking_.value()) {
955 warning_lines.emplace_back(format_.format(
956 "Tile at flat index '{}' resolved to primary animation '{}' range via workspace lookup "
957 "(not key frame matching).",
960 warning_lines.emplace_back(
961 "The tile may not visually match the key frame but produces identical indexed pixel data.");
962 diag_.warning(
"cross-tileset-anim-fallthrough", warning_lines);
965 warning_lines.emplace_back(format_.format(
966 "Tile at flat index '{}' resolved to primary animation '{}' range via workspace "
967 "deduplication, despite '{}' being disabled.",
970 FormatParam{
"cross_tileset_anim_linking", Style::bold}));
971 warning_lines.emplace_back(
"This tile will animate at runtime.");
972 warning_lines.emplace_back(
973 "To suppress, restructure your tile art to avoid matching primary animation pixels, or "
974 "enable cross-tileset linking for explicit control.");
975 diag_.warning(
"cross-tileset-anim-fallthrough-disabled", warning_lines);
982 const auto workspace_tile = tiles_workspace_->tile_at(workspace_tile_index);
983 const bool pt_to_pm_hflip = canonical_index_tile.h_flip() ^ workspace_tile.h_flip();
984 const bool pt_to_pm_vflip = canonical_index_tile.v_flip() ^ workspace_tile.v_flip();
985 result.status = TileAssignmentResult::Status::success;
986 result.entry =
TilemapEntry{workspace_tile_index, pal_index, pt_to_pm_hflip, pt_to_pm_vflip};
991 if (tiles_edit_mode_ == ArtifactEditMode::locked) {
992 result.status = TileAssignmentResult::Status::tile_not_found;
993 result.index_tile = index_tile;
995 result.matched_pal = matched_pal;
1000 if (tiles_workspace_->at_capacity()) {
1001 result.status = TileAssignmentResult::Status::tile_limit_reached;
1006 const std::size_t inserted_index = tiles_workspace_->insert_tile(canonical_index_tile);
1007 const auto workspace_tile = tiles_workspace_->tile_at(inserted_index);
1008 result.status = TileAssignmentResult::Status::success;
1009 const bool pt_to_pm_hflip = canonical_index_tile.h_flip() ^ workspace_tile.h_flip();
1010 const bool pt_to_pm_vflip = canonical_index_tile.v_flip() ^ workspace_tile.v_flip();
1011 result.entry =
TilemapEntry{inserted_index, pal_index, pt_to_pm_hflip, pt_to_pm_vflip};
1021 const std::size_t color_count_limit =
1022 is_secondary() ? (num_pals_total_.value() - num_pals_in_primary_.value()) * (
pal::max_size - 1)
1026 pipeline_helper_build_color_index_map(pal_hints_.value(), color_count_limit),
1028 std::format(
"Failed to build color index map for tileset '{}'.", tileset_.name()));
1034 auto strategy = make_packing_strategy(packing_strategy.value(), packing_strategy_params.value(), diag_);
1035 PalettePacker pal_packer{strategy.get(), &format_, &diag_, &tile_printer_, &pal_printer_};
1036 std::bitset<pal::num_pals> available_pals{0};
1037 if (is_secondary()) {
1038 if (has_paired_primary()) {
1042 for (std::size_t i = 0; i < num_pals_in_primary_; i++) {
1043 available_pals.set(i,
true);
1046 for (std::size_t i = num_pals_in_primary_; i < num_pals_total_; i++) {
1047 available_pals.set(i,
true);
1051 for (std::size_t i = 0; i < num_pals_in_primary_; i++) {
1052 available_pals.set(i,
true);
1056 packing_params.
tiles_ = porytiles_pixel_rgba_;
1057 packing_params.anims_ = tileset_.porytiles_component().anims();
1058 packing_params.color_map_ = color_index_map;
1059 packing_params.extrinsic_transparency_ = extrinsic_transparency_.value();
1060 if (is_secondary()) {
1061 std::array<std::optional<Palette<Rgba32, pal::max_size>>,
pal::num_pals> prefilled{};
1062 if (has_paired_primary()) {
1064 for (std::size_t i = 0; i < num_pals_in_primary_.value(); ++i) {
1065 prefilled.at(i) = paired_primary_->porymap_component().pal_at(i);
1069 for (std::size_t i = num_pals_in_primary_.value(); i <
pal::num_pals; ++i) {
1070 if (tileset_.porytiles_component().pal_at(i).has_value()) {
1071 prefilled.at(i) = tileset_.porytiles_component().pal_at(i).value();
1074 packing_params.prefilled_pals_ = prefilled;
1077 packing_params.prefilled_pals_ = tileset_.porytiles_component().pals();
1079 packing_params.hints_ = pal_hints_.value();
1080 packing_params.available_pals_ = available_pals;
1081 packing_params.tile_sharing_packing_ = tile_sharing_packing;
1082 packing_params.tile_sharing_alignment_ = tile_sharing_alignment;
1085 if (is_secondary() && has_paired_primary()) {
1086 const auto &primary_porymap = paired_primary_->porymap_component();
1089 primary_indexed_tiles,
1090 tileizer.tileize(primary_porymap.tiles_png()),
1092 "Failed to tileize paired primary's tiles.png for cross-tileset shape group analysis.");
1100 LayerModeConverter layer_mode_converter{&format_, &diag_, &tile_printer_, extrinsic_transparency_.value()};
1102 primary_triple_entries,
1103 layer_mode_converter.triple_layerize(primary_porymap),
1105 "Failed to triple-layerize paired primary for cross-tileset shape group analysis.");
1109 std::set<std::pair<std::size_t, std::size_t>> seen_tile_pal_pairs;
1111 for (std::size_t slot = 0; slot < primary_triple_entries.size(); ++slot) {
1112 const auto &entry = primary_triple_entries.at(slot);
1113 if (entry.tile_index() == 0) {
1116 auto key = std::make_pair(entry.tile_index(), entry.pal_index());
1117 if (seen_tile_pal_pairs.contains(key)) {
1120 seen_tile_pal_pairs.insert(key);
1122 if (entry.tile_index() >= primary_indexed_tiles.size()) {
1125 const auto &index_tile = primary_indexed_tiles.at(entry.tile_index());
1126 auto flipped_tile = index_tile.flip(entry.h_flip(), entry.v_flip());
1128 flipped_tile, primary_porymap.pal_at(entry.pal_index()), extrinsic_transparency_.value());
1129 if (rgba_tile.is_transparent(extrinsic_transparency_.value())) {
1133 packing_params.primary_tiles_.emplace_back(
1140 pal_packer.pack_tiles(packing_params),
1142 format_.format(
"Failed to pack palettes for tileset '{}'.",
FormatParam{tileset_.name(), Style::bold}));
1144 tile_to_pal_ = std::move(pal_packing.tile_to_pal_);
1147 if (
const auto &maybe_packed_pal = pal_packing.pals_.at(i); maybe_packed_pal.has_value()) {
1149 new_porymap_pals_[i] = maybe_packed_pal.value();
1151 else if (tileset_.porytiles_component().pal_at(i).has_value()) {
1156 const auto &porytiles_pal = tileset_.porytiles_component().pal_at(i).value();
1160 if (!porytiles_pal.is_wildcard(0)) {
1161 resolved_pal.set(0, porytiles_pal.at(0));
1164 resolved_pal.set(0, extrinsic_transparency_.value());
1169 if (!porytiles_pal.is_wildcard(j)) {
1170 resolved_pal.set(j, porytiles_pal.at(j));
1174 new_porymap_pals_[i] = resolved_pal;
1188 new_porymap_pals_[i] = tileset_.porymap_component().pal_at(i);
1196 const std::vector<PaletteHint> &hints, std::size_t color_count_limit)
const
1200 for (
const auto &tile : porytiles_pixel_rgba_) {
1201 color_index_map.
add_tile(tile, extrinsic_transparency_.value());
1205 for (
const auto &anim : tileset_.porytiles_component().anims() | std::views::values) {
1206 color_index_map.add_anim(anim, extrinsic_transparency_.value());
1210 const std::size_t pal_start = is_secondary() ? num_pals_in_primary_.value() : 0;
1211 const std::size_t pal_end = is_secondary() ? num_pals_total_.value() : num_pals_in_primary_.value();
1212 for (std::size_t pal_index = pal_start; pal_index < pal_end; ++pal_index) {
1213 const auto &maybe_porytiles_pal = tileset_.porytiles_component().pals().at(pal_index);
1214 if (!maybe_porytiles_pal.has_value()) {
1217 color_index_map.add_pal(maybe_porytiles_pal.value(), extrinsic_transparency_.value());
1221 for (
const auto &hint : hints) {
1222 color_index_map.add_pal(hint.pal(), extrinsic_transparency_.value());
1226 if (color_index_map.size() > color_count_limit) {
1228 "color_index_map.size() > count_limit - this should have already been validated by "
1229 "pipeline_step_validate_input");
1238 if (is_secondary() && has_paired_primary()) {
1239 for (std::size_t i = 0; i < num_pals_in_primary_.value(); ++i) {
1240 const auto &primary_pal = paired_primary_->porymap_component().pal_at(i);
1241 color_index_map.add_pal(primary_pal, extrinsic_transparency_.value());
1245 return color_index_map;
1249CompilerTask::pipeline_helper_build_keyframe_data(
const std::string &anim_name,
const Animation<Rgba32> &anim)
const
1252 const std::size_t tile_count = composite_frame.
tiles().size();
1254 AnimKeyframeData result;
1255 result.tiles.reserve(tile_count);
1256 result.palettes.reserve(tile_count);
1265 for (std::size_t tile_idx = 0; tile_idx < tile_count; ++tile_idx) {
1274 if (representative_tile.
is_transparent(extrinsic_transparency_.value())) {
1276 result.tiles.emplace_back(transparent_tile);
1277 result.pal_indices.push_back(0);
1278 result.palettes.push_back(&new_porymap_pals_.at(0));
1292 std::vector<PaletteMatchResult<Rgba32>> matches =
1293 match_or_best(composite_rgba_tile, new_porymap_pals_, extrinsic_transparency_.value(), 1);
1295 if (!matches.at(0).is_covered) {
1296 std::vector<std::string> err_lines;
1297 std::vector<std::vector<FormatParam>> err_params;
1300 err_lines.emplace_back(
"Animation '{}' composite subtile '{}': no matching palette found.");
1304 err_lines.emplace_back();
1305 err_params.emplace_back();
1306 err_lines.emplace_back(
"Closest N match(es) with covered colors highlighted:");
1307 err_lines.emplace_back();
1308 err_params.emplace_back();
1309 err_params.emplace_back();
1311 for (
const auto &match : matches) {
1312 if (match_idx != 0) {
1313 err_lines.emplace_back();
1314 err_params.emplace_back();
1316 err_lines.emplace_back(
"Palette match candidate: {}");
1318 for (
const auto &line : pal_printer_.print_rgba_palette_covered_missing(
1319 new_porymap_pals_.at(match.pal_index), match.covered_colors, match.missing_colors)) {
1320 err_lines.push_back(line);
1321 err_params.emplace_back();
1330 const std::size_t pal_index = matches.at(0).pal_index;
1331 const auto &matched_pal = new_porymap_pals_.at(pal_index);
1335 result.tiles.emplace_back(indexed_key_frame_tile);
1336 result.pal_indices.push_back(pal_index);
1338 result.palettes.push_back(&matched_pal);
1357 const auto &anims = tileset_.porytiles_component().anims();
1359 if (!anims.empty()) {
1361 if (tiles_edit_mode_ == ArtifactEditMode::optimize) {
1362 std::size_t total_keyframe_tiles = 0;
1363 for (
const auto &anim : anims | std::views::values) {
1365 total_keyframe_tiles += anim.
key_frame().tiles().size();
1368 total_keyframe_tiles += anim.
frames().begin()->second.tiles().size();
1371 const std::size_t anim_start = is_secondary() ? (num_tiles_in_primary_.value() + 1) : 1;
1372 tiles_workspace_->reserve_anim_slots(total_keyframe_tiles, anim_start);
1375 std::map<std::string, std::size_t> anim_offsets;
1376 std::map<std::string, std::vector<std::size_t>> anim_pal_indices;
1377 std::size_t current_offset = tiles_workspace_->anim_start_offset();
1379 const auto &per_anim_overrides = per_anim_overrides_.value();
1381 for (
const auto &[anim_name, anim] : anims) {
1383 panic(
"anim '" + anim_name +
"' has no frames");
1389 const std::size_t tile_count = keyframe_data.tiles.size();
1390 anim_pal_indices[anim_name] = keyframe_data.pal_indices;
1391 std::size_t offset{};
1395 (per_anim_overrides.contains(anim_name) && per_anim_overrides.at(anim_name).linking.has_value())
1396 ? per_anim_overrides_.
derive(per_anim_overrides.at(anim_name).linking)
1397 : global_frame_linking_;
1399 if (effective_linking == FrameLinking::manual && tiles_edit_mode_ != ArtifactEditMode::optimize) {
1407 if (json_offset == 0) {
1409 "Animation '{}' uses manual frame linking in '{}' mode but has no tile_offset in anim.json.",
1413 if (json_offset + tile_count > tiles_workspace_->capacity()) {
1415 "Animation '{}' tile_offset '{}' + tile_count '{}' exceeds tiles.png capacity '{}'.",
1421 offset = json_offset;
1425 if (tiles_edit_mode_ == ArtifactEditMode::optimize) {
1426 offset = current_offset;
1427 for (std::size_t i = 0; i < tile_count; ++i) {
1428 const std::size_t reserved_index = current_offset - tiles_workspace_->anim_start_offset();
1429 tiles_workspace_->place_anim_tile(reserved_index, keyframe_data.tiles[i]);
1433 else if (tiles_edit_mode_ == ArtifactEditMode::patch) {
1435 if (
const auto existing_offset = tiles_workspace_->find_existing_contiguous_tiles_by_color(
1436 keyframe_data.tiles, keyframe_data.palettes);
1437 existing_offset.has_value()) {
1438 offset = existing_offset.value();
1442 const auto free_offset = tiles_workspace_->find_contiguous_transparent_slots(tile_count);
1443 free_offset.has_value()) {
1444 tiles_workspace_->place_tiles_at(free_offset.value(), keyframe_data.tiles);
1445 offset = free_offset.value();
1449 "Animation '{}' requires {} contiguous tiles but no sufficient space found.",
1454 else if (tiles_edit_mode_ == ArtifactEditMode::locked) {
1457 const auto existing_offset = tiles_workspace_->find_existing_contiguous_tiles_by_color(
1458 keyframe_data.tiles, keyframe_data.palettes);
1459 if (existing_offset.has_value()) {
1460 offset = existing_offset.value();
1463 std::vector<std::string> err_msg{};
1464 err_msg.emplace_back(format_.format(
1465 "Animation '{}' keyframes not found in existing tiles.png.",
1467 err_msg.emplace_back(format_.format(
1468 "Cannot proceed due to '{}' setting '{}'.",
1476 panic(
"unexpected tiles_edit_mode");
1480 anim_offsets[anim_name] = offset;
1483 for (
const auto &[anim_name, anim] : anims) {
1484 anim_pal_indices_[anim_name] = anim_pal_indices.at(anim_name);
1485 anim_tile_matcher_.register_animation(
1486 anim_name, anim, anim_offsets.at(anim_name), extrinsic_transparency_.value());
1492 if (is_secondary() && has_paired_primary() && cross_tileset_anim_linking_.value()) {
1493 const auto &primary_porytiles_anims = paired_primary_->porytiles_component().anims();
1494 const auto &primary_porymap_anims = paired_primary_->porymap_component().anims();
1503 std::map<std::size_t, std::size_t> primary_tile_pal_map;
1504 for (
const auto &entry : paired_primary_->porymap_component().metatiles_bin()) {
1505 if (entry.tile_index() == 0) {
1508 primary_tile_pal_map.try_emplace(entry.tile_index(), entry.pal_index());
1512 std::vector<Palette<Rgba32, pal::max_size>> primary_palettes;
1513 primary_palettes.reserve(num_pals_in_primary_.value());
1514 for (std::size_t i = 0; i < num_pals_in_primary_.value(); ++i) {
1515 primary_palettes.push_back(paired_primary_->porymap_component().pal_at(i));
1519 for (
const auto &prim_anim_name : primary_porymap_anims | std::views::keys) {
1520 if (!primary_porytiles_anims.contains(prim_anim_name)) {
1523 "Primary animation '{}' exists in compiled Porymap data but not in Porytiles source.",
1525 "The paired primary tileset has uncompiled changes. Recompile it before compiling this "
1530 for (
const auto &[prim_anim_name, prim_anim] : primary_porytiles_anims) {
1531 if (!primary_porymap_anims.contains(prim_anim_name)) {
1534 "Primary animation '{}' exists in Porytiles source but not in compiled Porymap data.",
1536 "The paired primary tileset has uncompiled changes. Recompile it before compiling this "
1539 if (!prim_anim.has_key_frame()) {
1544 std::vector<std::string> remark_lines;
1545 remark_lines.emplace_back(format_.format(
1546 "Primary animation '{}' has no key frame (likely manual frame linking).",
1548 remark_lines.emplace_back(
"Cross-tileset key frame matching is not possible for this animation.");
1549 diag_.remark(
"cross-tileset-anim-skip-no-keyframe", remark_lines);
1553 const auto &prim_porymap_anim = primary_porymap_anims.at(prim_anim_name);
1554 const std::size_t prim_tile_offset = prim_porymap_anim.params().tile_offset();
1556 const std::size_t prim_tile_count = prim_anim.key_frame().tile_count();
1574 for (std::size_t i = 0; i < prim_tile_count; ++i) {
1575 if (prim_anim.key_frame().tile_at(i).is_transparent(paired_primary_extrinsic_transparency_.value())) {
1579 prim_anim.key_frame().tile_at(i), paired_primary_extrinsic_transparency_.value()};
1580 auto match = anim_tile_matcher_.find_match(
canonical, paired_primary_extrinsic_transparency_.value());
1581 if (match.has_value() && !match->is_cross_tileset) {
1584 "Primary animation '{}' subtile '{}' has identical RGBA data to secondary animation '{}' "
1590 "Cross-tileset animation linking requires unique key frame subtiles across primary and "
1592 "Fix the secondary (or primary) animation art so key frame subtiles are visually distinct."}};
1603 prim_anim.composite_frame(paired_primary_extrinsic_transparency_.value());
1605 std::vector<std::size_t> subtile_pal_indices;
1606 subtile_pal_indices.reserve(prim_tile_count);
1607 for (std::size_t i = 0; i < prim_tile_count; ++i) {
1608 const std::size_t abs_tile_index = prim_tile_offset + i;
1610 if (prim_anim.key_frame().tile_at(i).is_transparent(paired_primary_extrinsic_transparency_.value())) {
1612 subtile_pal_indices.push_back(0);
1616 if (primary_tile_pal_map.contains(abs_tile_index)) {
1617 subtile_pal_indices.push_back(primary_tile_pal_map.at(abs_tile_index));
1625 composite.
tile_at(i), primary_palettes, paired_primary_extrinsic_transparency_.value(), 1);
1626 if (matches.at(0).is_covered) {
1627 subtile_pal_indices.push_back(matches.at(0).pal_index);
1628 std::vector<std::string> remark_lines;
1629 remark_lines.emplace_back(format_.format(
1630 "Primary animation '{}' subtile '{}' (tile_index='{}') resolved via RGBA palette fallback "
1635 FormatParam{matches.at(0).pal_index, Style::bold}));
1636 remark_lines.emplace_back(
1637 "Subtile is not referenced by any primary metatile but its colors match a primary "
1639 diag_.remark(
"cross-tileset-anim-rgba-fallback", remark_lines);
1644 "Primary animation '{}' subtile '{}' (tile_index='{}') is not referenced by any "
1646 "metatile entry and its colors do not fully match any primary palette.",
1650 "Cannot determine the correct palette index for cross-tileset linking.",
1651 "Recompile the primary tileset, or verify that all primary animation subtiles",
1652 "are used in at least one primary metatile."}};
1657 anim_pal_indices_[prim_anim_name] = subtile_pal_indices;
1658 anim_tile_matcher_.register_animation(
1662 paired_primary_extrinsic_transparency_.value(),
1681 const auto &anims = tileset_.porytiles_component().anims();
1682 if (anims.empty()) {
1690 std::unordered_set<std::size_t> referenced_tile_indices;
1691 for (
const auto &entry : new_porymap_component_->metatiles_bin()) {
1692 if (entry.tile_index() == 0) {
1695 referenced_tile_indices.insert(entry.tile_index());
1698 for (
const auto &[anim_name, anim] : anims) {
1703 auto maybe_tile_offset = anim_tile_matcher_.tile_offset_for(anim_name);
1704 if (!maybe_tile_offset.has_value()) {
1705 panic(
"animation '" + anim_name +
"' not registered in anim_tile_matcher_");
1707 const std::size_t tile_offset = maybe_tile_offset.value();
1708 const std::size_t tile_count = anim.
key_frame().tile_count();
1710 for (std::size_t i = 0; i < tile_count; ++i) {
1711 if (anim.
key_frame().tile_at(i).is_transparent(extrinsic_transparency_.value())) {
1714 const std::size_t abs_tile_index = tile_offset + i;
1715 if (!referenced_tile_indices.contains(abs_tile_index)) {
1716 std::vector<std::string> warn_lines;
1717 warn_lines.emplace_back(format_.format(
1718 "Primary animation '{}' subtile '{}' (tile_index='{}') is not referenced by any metatile:",
1722 warn_lines.append_range(
1723 tile_printer_.print_tile(anim.
key_frame().tile_at(i), extrinsic_transparency_.value()));
1724 warn_lines.emplace_back(
1725 "Palette assignment for this subtile will use RGBA fallback matching during secondary "
1728 diag_.warning(
"primary-anim-unreferenced-subtile", warn_lines);
1736void CompilerTask::pipeline_helper_compile_animations()
1738 const auto &source_anims = tileset_.porytiles_component().anims();
1741 if (source_anims.empty()) {
1745 for (
const auto &[anim_name, source_anim] : source_anims) {
1747 auto maybe_tile_offset = anim_tile_matcher_.tile_offset_for(anim_name);
1748 if (!maybe_tile_offset.has_value()) {
1749 panic(
"animation '" + anim_name +
"' not registered in anim_tile_matcher_");
1751 const std::size_t tile_offset = maybe_tile_offset.value();
1754 const AnimFrame<Rgba32> composite = source_anim.composite_frame(extrinsic_transparency_.value());
1755 const std::size_t tile_count = composite.
tile_count();
1758 std::vector<std::size_t> subtile_pal_indices;
1759 subtile_pal_indices.reserve(tile_count);
1761 for (std::size_t tile_idx = 0; tile_idx < tile_count; ++tile_idx) {
1764 std::vector<PaletteMatchResult<Rgba32>> matches =
1765 match_or_best(composite_tile, new_porymap_pals_, extrinsic_transparency_.value(), 1);
1767 if (!matches.at(0).is_covered) {
1769 "animation '" + anim_name +
"' subtile " + std::to_string(tile_idx) +
1770 " has no covering palette during compilation");
1773 subtile_pal_indices.push_back(matches.at(0).pal_index);
1777 const std::size_t frame_pal_index = subtile_pal_indices.at(0);
1778 const bool uses_multiple_palettes =
1779 !std::ranges::all_of(subtile_pal_indices, [&](std::size_t idx) {
return idx == frame_pal_index; });
1781 if (uses_multiple_palettes) {
1782 std::vector<std::string> warning_lines;
1783 warning_lines.emplace_back(format_.format(
1784 "Animation '{}' uses multiple palettes across subtiles.",
FormatParam{anim_name, Style::bold}));
1785 warning_lines.emplace_back(format_.format(
1786 "Porymap-component frame PNGs will be saved using palette '{}' for display purposes.",
1787 FormatParam{pal_filename(frame_pal_index), Style::bold}));
1788 diag_.warning(
"multi-palette-animation", warning_lines);
1792 const auto &fixed_pal = new_porymap_pals_.at(frame_pal_index);
1794 for (std::size_t i = 0; i < fixed_pal.size(); ++i) {
1795 if (fixed_pal.is_wildcard(i)) {
1796 panic(
"Porymap pal '" + std::to_string(frame_pal_index) +
"' has illegal wildcard");
1798 anim_palette.add(fixed_pal.at(i));
1804 for (
const auto &[frame_name, source_frame] : source_anim.frames()) {
1805 std::vector<PixelTile<IndexPixel>> frame_index_tiles;
1806 frame_index_tiles.reserve(tile_count);
1808 for (std::size_t tile_idx = 0; tile_idx < tile_count; ++tile_idx) {
1810 const auto &pal = new_porymap_pals_.
at(subtile_pal_indices[tile_idx]);
1812 frame_index_tiles.push_back(
1816 AnimFrame frame{frame_name, std::move(frame_index_tiles)};
1818 compiled_anim.put_frame(frame_name, std::move(frame));
1823 const std::size_t local_offset = is_secondary() ? tile_offset - num_tiles_in_primary_.value() : tile_offset;
1826 compiled_anim.params(std::move(params));
1829 new_porymap_component_->add_anim(std::move(compiled_anim));
1833void CompilerTask::pipeline_helper_apply_manual_overrides()
1835 const auto &source_anims = tileset_.porytiles_component().anims();
1836 if (source_anims.empty()) {
1840 const auto &per_anim_overrides = per_anim_overrides_.value();
1842 for (
const auto &[anim_name, source_anim] : source_anims) {
1845 (per_anim_overrides.contains(anim_name) && per_anim_overrides.at(anim_name).linking.has_value())
1846 ? per_anim_overrides_.
derive(per_anim_overrides.at(anim_name).linking)
1847 : global_frame_linking_;
1849 const auto &overrides = source_anim.params().overrides();
1851 switch (effective_linking) {
1852 case FrameLinking::automatic: {
1853 if (!overrides.empty()) {
1854 std::vector<std::string> warning_lines;
1855 warning_lines.emplace_back(format_.format(
1856 "Animation '{}' has frame_linking 'automatic' but overrides are present in anim.json.",
1858 warning_lines.emplace_back(
"The overrides will be ignored.");
1859 diag_.warning(
"automatic-mode-overrides-ignored", warning_lines);
1864 case FrameLinking::manual: {
1865 if (overrides.empty()) {
1866 std::vector<std::string> warning_lines;
1867 warning_lines.emplace_back(format_.format(
1868 "Animation '{}' has frame_linking '{}' but no overrides are present in anim.json.",
1871 warning_lines.emplace_back(
"Animation tiles will not be linked to any metatiles.");
1872 diag_.warning(
"manual-no-overrides", warning_lines);
1877 auto maybe_tile_offset = anim_tile_matcher_.tile_offset_for(anim_name);
1878 if (!maybe_tile_offset.has_value()) {
1879 panic(
"animation '" + anim_name +
"' not registered in anim_tile_matcher_");
1881 const std::size_t tile_offset = maybe_tile_offset.value();
1884 auto &metatiles_bin = new_porymap_component_->metatiles_bin();
1885 for (
const auto &entry : overrides) {
1886 const std::size_t bin_index =
1889 static_cast<std::size_t
>(entry.subtile);
1891 if (bin_index >= metatiles_bin.size()) {
1893 "animation '" + anim_name +
"' override references metatile_id " +
1894 std::to_string(entry.metatile_id) +
" which is out of range");
1897 const std::size_t absolute_tile = tile_offset + entry.frame_subtile;
1898 metatiles_bin[bin_index] =
TilemapEntry{absolute_tile, entry.
pal_index, entry.h_flip, entry.v_flip};
1903 case FrameLinking::hybrid: {
1904 std::vector<std::string> err_lines;
1905 err_lines.emplace_back(format_.format(
1906 "Hybrid frame linking is not yet implemented (animation '{}').",
FormatParam{anim_name, Style::bold}));
1907 err_lines.emplace_back(
"Use 'automatic' or 'manual' frame linking until hybrid support.");
1909 diag_.error(
"hybrid-frame-linking-not-implemented", err_lines);
1914 panic(
"unhandled value for FrameLinking");
1919 const auto &primary_refs = tileset_.porytiles_component().primary_anim_overrides();
1921 if (!primary_refs.empty() && !is_secondary()) {
1922 std::vector<std::string> err_lines;
1923 err_lines.emplace_back(format_.format(
1924 "Primary tilesets cannot have '{}' in anim.json.",
FormatParam{
"primary_references", Style::bold}));
1925 err_lines.emplace_back(
"Only secondary tilesets may reference primary animation tiles.");
1926 diag_.error(
"primary-references-on-primary", err_lines);
1930 if (!primary_refs.empty() && is_secondary()) {
1931 if (!has_paired_primary()) {
1932 std::vector<std::string> err_lines;
1933 err_lines.emplace_back(format_.format(
1934 "The '{}' section requires a paired primary tileset (pairing mode must not be off).",
1936 diag_.error(
"primary-references-no-paired-primary", err_lines);
1940 const auto &primary_anims = paired_primary_->porymap_component().anims();
1941 auto &metatiles_bin = new_porymap_component_->metatiles_bin();
1943 for (
const auto &[prim_anim_name, entries] : primary_refs) {
1944 if (!primary_anims.contains(prim_anim_name)) {
1945 std::vector<std::string> err_lines;
1946 err_lines.emplace_back(format_.format(
1947 "Primary animation '{}' referenced in '{}' was not found in the paired primary tileset.",
1950 diag_.error(
"primary-references-anim-not-found", err_lines);
1954 const auto &prim_anim = primary_anims.at(prim_anim_name);
1955 const std::size_t prim_tile_offset = prim_anim.params().tile_offset();
1956 const std::size_t prim_tile_count = prim_anim.params().tile_count();
1958 for (
const auto &entry : entries) {
1959 if (entry.frame_subtile >= prim_tile_count) {
1960 std::vector<std::string> err_lines;
1961 err_lines.emplace_back(format_.format(
1962 "Primary reference '{}' override has frame_subtile {} but primary animation only has {} tiles.",
1966 diag_.error(
"primary-references-frame-subtile-oob", err_lines);
1970 const std::size_t bin_index =
1973 static_cast<std::size_t
>(entry.subtile);
1975 if (bin_index >= metatiles_bin.size()) {
1976 std::vector<std::string> err_lines;
1977 err_lines.emplace_back(format_.format(
1978 "Primary reference '{}' override references metatile_id {} which is out of range.",
1981 diag_.error(
"primary-references-metatile-oob", err_lines);
1985 const std::size_t absolute_tile = prim_tile_offset + entry.frame_subtile;
1986 metatiles_bin.at(bin_index) =
TilemapEntry{absolute_tile, entry.
pal_index, entry.h_flip, entry.v_flip};
1992void CompilerTask::pipeline_helper_apply_true_color_to_tiles_png()
1995 std::unordered_map<std::size_t, std::size_t> tile_to_first_pal;
1996 std::unordered_map<std::size_t, std::set<std::size_t>> tile_to_all_pals;
2000 const std::size_t tile_index_offset = is_secondary() ? num_tiles_in_primary_.value() : 0;
2006 const std::size_t pal_index_offset = (is_secondary() && !has_paired_primary()) ? num_pals_in_primary_.value() : 0;
2009 const std::size_t default_display_pal = is_secondary() ? num_pals_in_primary_.value() : 0;
2011 for (
const auto &entry : new_porymap_component_->metatiles_bin()) {
2012 const auto tile_idx = entry.tile_index();
2013 const auto pal_idx = entry.pal_index();
2015 if (tile_idx == 0) {
2019 tile_to_all_pals[tile_idx].insert(pal_idx);
2021 if (!tile_to_first_pal.contains(tile_idx)) {
2022 tile_to_first_pal[tile_idx] = pal_idx;
2027 for (
const auto &[anim_name, source_anim] : tileset_.porytiles_component().anims()) {
2028 auto maybe_tile_offset = anim_tile_matcher_.tile_offset_for(anim_name);
2029 if (!maybe_tile_offset.has_value()) {
2033 const std::size_t tile_offset = maybe_tile_offset.value();
2034 const AnimFrame<Rgba32> composite = source_anim.composite_frame(extrinsic_transparency_.value());
2035 const std::size_t tile_count = composite.
tile_count();
2037 for (std::size_t subtile_idx = 0; subtile_idx < tile_count; ++subtile_idx) {
2038 const std::size_t absolute_tile_idx = tile_offset + subtile_idx;
2040 if (tile_to_first_pal.contains(absolute_tile_idx)) {
2045 std::vector<PaletteMatchResult<Rgba32>> matches =
2046 match_or_best(composite_tile, new_porymap_pals_, extrinsic_transparency_.value(), 1);
2048 if (matches.at(0).is_covered) {
2049 const std::size_t matched_pal_idx = matches.at(0).pal_index;
2050 tile_to_first_pal[absolute_tile_idx] = matched_pal_idx;
2053 const auto &tiles_img = new_porymap_component_->tiles_png();
2063 constexpr auto tag =
"true-color-anim-only-tile";
2064 std::vector<std::string> remark_lines;
2065 remark_lines.emplace_back(format_.format(
2066 "Tile index '{}' (animation '{}', subtile '{}') is not referenced in metatiles.",
2070 remark_lines.emplace_back(format_.format(
2071 "Using '{}' for true-color encoding (determined via palette matching).",
2072 FormatParam{pal_filename(matched_pal_idx), Style::bold}));
2076 index_tile, new_porymap_pals_.at(matched_pal_idx), extrinsic_transparency_.value());
2077 remark_lines.emplace_back();
2078 remark_lines.append_range(tile_printer_.print_tile(rgba_tile, extrinsic_transparency_.value()));
2080 diag_.remark(tag, remark_lines);
2086 for (
const auto &[absolute_tile_idx, pals] : tile_to_all_pals) {
2087 if (pals.size() > 1) {
2089 if (absolute_tile_idx < tile_index_offset) {
2094 const auto &tiles_img = new_porymap_component_->tiles_png();
2103 constexpr auto tag =
"true-color-multi-palette-tile";
2104 std::vector<std::string> remark_lines;
2105 remark_lines.emplace_back(format_.format(
2106 "Tile index '{}' is used with multiple palettes.",
FormatParam{absolute_tile_idx, Style::bold}));
2108 std::string pal_list;
2109 for (
const auto pal : pals) {
2110 if (!pal_list.empty()) {
2116 const std::size_t selected_pal_idx = tile_to_first_pal.at(absolute_tile_idx);
2117 remark_lines.emplace_back(format_.format(
2118 "Palettes used: {}; tiles.png will display using '{}'.",
2120 FormatParam{pal_filename(selected_pal_idx), Style::bold}));
2123 for (
const auto pal_idx : pals) {
2124 remark_lines.emplace_back();
2125 remark_lines.emplace_back(
2126 format_.format(
"{} resolution:",
FormatParam{pal_filename(pal_idx), Style::bold}));
2128 index_tile, new_porymap_pals_.at(pal_idx), extrinsic_transparency_.value());
2129 remark_lines.append_range(tile_printer_.print_tile(rgba_tile, extrinsic_transparency_.value()));
2132 diag_.remark(tag, remark_lines);
2142 for (std::size_t tile_idx = 1; tile_idx < total_tiles; ++tile_idx) {
2143 const std::size_t absolute_tile_idx = tile_idx + tile_index_offset;
2144 if (!tile_to_first_pal.contains(absolute_tile_idx)) {
2154 constexpr auto tag =
"true-color-unreferenced-tile";
2155 std::vector<std::string> remark_lines;
2156 remark_lines.emplace_back(format_.format(
2157 "Tile index '{}' is not referenced in metatiles or animations.",
2160 remark_lines.emplace_back(
"This tile may be used by a secondary tileset, or it may be completely unused.");
2161 remark_lines.emplace_back(format_.format(
2162 "Displaying using '{}' for color resolution.",
2163 FormatParam{pal_filename(default_display_pal), Style::bold}));
2167 index_tile, new_porymap_pals_.at(default_display_pal), extrinsic_transparency_.value());
2168 remark_lines.emplace_back();
2169 remark_lines.append_range(tile_printer_.print_tile(rgba_tile, extrinsic_transparency_.value()));
2171 diag_.remark(tag, remark_lines);
2175 const std::size_t pal_idx = tile_to_first_pal.at(absolute_tile_idx);
2176 const std::size_t tile_row = tile_idx / tiles_per_row;
2177 const std::size_t tile_col = tile_idx % tiles_per_row;
2183 const std::size_t row = pixel_row_start + py;
2184 const std::size_t col = pixel_col_start + px;
2186 const std::size_t color_idx = old_pixel.
color_index();
2187 const std::size_t new_index = ((pal_idx - pal_index_offset) << 4) | color_idx;
2195 if (!is_secondary()) {
2196 num_pals = num_pals_in_primary_.value();
2198 else if (has_paired_primary()) {
2199 num_pals = num_pals_total_.value();
2202 num_pals = num_pals_total_.value() - num_pals_in_primary_.value();
2204 std::vector<Rgba32> true_color_palette;
2207 for (std::size_t i = 0; i <
num_pals; ++i) {
2208 const auto &pal = new_porymap_pals_.at(i + pal_index_offset);
2209 for (std::size_t color_idx = 0; color_idx <
pal::max_size; ++color_idx) {
2210 true_color_palette.push_back(pal.at(color_idx));
2214 tiles_img.
palette(std::move(true_color_palette));
2215 new_porymap_component_->tiles_png(tiles_img);
2218void CompilerTask::pipeline_helper_emit_no_matching_tile_error(
2219 std::size_t tile_index,
2221 std::size_t pal_index,
2224 constexpr auto tag =
"no-matching-tile";
2228 std::vector<std::string> no_match_err{};
2229 no_match_err.emplace_back(format_.format(
2230 "{}: no matching tile found",
2231 FormatParam{metatile::message_header(format_, metatile_index, layer, subtile), Style::bold}));
2232 no_match_err.append_range(tile_printer_.print_metatile_tile_highlight(
2233 porytiles_metatiles_.at(metatile_index), layer, subtile, extrinsic_transparency_));
2234 diag_.error(tag, no_match_err);
2237 std::vector<std::string> pal_note{};
2238 pal_note.emplace_back(format_.format(
"matched palette '{}':",
FormatParam{pal_filename(pal_index), Style::bold}));
2239 pal_note.append_range(pal_printer_.print_rgba_pal(matched_pal));
2240 diag_.error_note(tag, pal_note);
2243 std::vector<std::string> tile_note{};
2244 tile_note.emplace_back(
"generated index tile:");
2245 tile_note.append_range(tile_printer_.print_tile(index_tile, extrinsic_transparency_.value()));
2246 diag_.error_note(tag, tile_note);
2249void CompilerTask::pipeline_helper_emit_no_matching_pal_error(
2252 constexpr auto tag =
"no-matching-palette";
2256 std::vector<std::string> no_match_err{};
2257 no_match_err.emplace_back(format_.format(
2258 "{}: no matching palette found",
2259 FormatParam{metatile::message_header(format_, metatile_index, layer, subtile), Style::bold}));
2260 no_match_err.append_range(tile_printer_.print_metatile_tile_highlight(
2261 porytiles_metatiles_.at(metatile_index), layer, subtile, extrinsic_transparency_));
2262 diag_.error(tag, no_match_err);
2265 std::vector<std::string> closest_n_note{};
2266 closest_n_note.emplace_back(
"closest N match(es) with covered colors highlighted:");
2267 int match_index = 0;
2268 for (
const auto &match : matches) {
2269 if (match_index != 0) {
2271 closest_n_note.emplace_back();
2273 closest_n_note.push_back(
2274 format_.format(
"Palette match candidate: {}",
FormatParam{pal_filename(match.pal_index), Style::bold}));
2275 closest_n_note.append_range(pal_printer_.print_rgba_palette_covered_missing(
2276 new_porymap_pals_.at(match.pal_index), match.covered_colors, match.missing_colors));
2277 closest_n_note.emplace_back();
2278 closest_n_note.push_back(
2279 format_.format(
"Uncovered pixels with {}:",
FormatParam{pal_filename(match.pal_index), Style::bold}));
2280 closest_n_note.append_range(tile_printer_.print_metatile_pixel_highlights(
2281 porytiles_metatiles_.at(metatile_index),
2284 match.uncovered_pixel_indices,
2285 extrinsic_transparency_));
2288 diag_.error_note(tag, closest_n_note);
2291void CompilerTask::pipeline_helper_emit_tile_limit_error(std::size_t tile_index, std::size_t tile_limit)
2293 constexpr auto tag =
"tile-limit";
2297 std::vector<std::string> tile_limit_error{};
2298 tile_limit_error.emplace_back(format_.format(
2299 "{}: hit limit of '{}' unique tiles",
2300 FormatParam{metatile::message_header(format_, metatile_index, layer, subtile), Style::bold},
2302 tile_limit_error.append_range(tile_printer_.print_metatile_tile_highlight(
2303 porytiles_metatiles_.at(metatile_index), layer, subtile, extrinsic_transparency_));
2304 diag_.error(tag, tile_limit_error);
2307 std::vector<std::string> note_text;
2308 if (is_secondary()) {
2309 note_text.append_range(
2313 note_text.push_back(
2314 format_.format(
"Tile limit is '{}' due to configuration.",
FormatParam{tile_limit, Style::bold}));
2315 note_text.emplace_back();
2318 diag_.error_note(tag, note_text);
2328 CompilerTask task{
tileset, is_secondary, paired_primary, *format_, *diag_, *tile_printer_, *pal_printer_, *config_};
#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.
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
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 pal_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 num_pals
constexpr std::size_t max_size
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_porymap_pal(const TilesetCompileValidatorServices &services, const std::string &tileset_name, const Palette< Rgba32, pal::max_size > &pal, std::size_t pal_index)
Validates a Porymap palette for correctness according to GBA hardware constraints.
void panic(const StringViewSourceLoc &s)
Unconditionally terminates the program with a panic message.
void assert_or_panic(bool condition, const StringViewSourceLoc &s)
Conditionally panics if the given condition is false.
ChainableResult< void > validate_porytiles_pal(const TilesetCompileValidatorServices &services, const std::string &tileset_name, const Palette< Rgba32, pal::max_size > &pal, std::size_t pal_index)
Validates a user-specified Porytiles override palette for correctness.
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.
ChainableResult< void > validate_pal_hint(const TilesetCompileValidatorServices &services, const std::string &tileset_name, const PaletteHint &hint)
Validates a user-specified palette hint for correctness.
@ 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).
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.
std::string pal_filename(std::size_t pal_index)
Constructs a palette filename from a palette index.
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).
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, pal::max_size > >, pal::num_pals > &porytiles_pals, const std::vector< PaletteHint > &hints, const std::optional< std::array< Palette< Rgba32, pal::max_size >, pal::num_pals > > &porymap_pals)
Validates that no colors will suffer unacceptable precision loss during GBA color conversion.
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.
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, pal::max_size > >, pal::num_pals > &porytiles_pals, const std::vector< PaletteHint > &hints)
Validates that the total unique color count across all input does not exceed the global limit.
PackingStrategyType
Selects the palette packing algorithm to use during tileset compilation.
Utility functions for string manipulation and formatting.
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...