54 RestoreAddresses restore;
58 std::string catalog_path = parser.
GetString(
"catalog").value_or(
"");
60 if (
auto path = parser.
GetString(
"project-file")) {
63 if (catalog_path.empty()) {
67 std::optional<core::TerrainCatalog> catalog;
68 if (!catalog_path.empty()) {
69 std::ifstream file(catalog_path);
71 return absl::NotFoundError(
"Cannot open terrain catalog: " +
73 std::ostringstream text;
76 catalog = std::move(parsed);
84 auto ow = std::make_unique<zelda3::Overworld>(rom);
85 ow->SetGameData(&data);
87 const int parent = ow->overworld_map(requested)->parent();
89 parent / 64 != requested / 64) {
90 return absl::FailedPreconditionError(
91 "Invalid area parent/world relationship");
94 const auto* map = ow->overworld_map(parent);
95 int width = 32, height = 32;
102 const int world = parent / 64;
103 const int origin_x = (parent % 8) * 32;
104 const int origin_y = ((parent % 64) / 8) * 32;
105 if (origin_x + width > 256 || origin_y + height > (world == 2 ? 128 : 256)) {
106 return absl::FailedPreconditionError(
"Area exceeds supported world bounds");
108 json profile = {{
"adapter", kAdapter},
111 {
"area_graphics", map->area_graphics()},
112 {
"area_palette", map->area_palette()},
113 {
"main_palette", map->main_palette()},
114 {
"background_color", map->area_specific_bg_color()},
115 {
"subscreen_overlay", map->subscreen_overlay()},
116 {
"animated_gfx", map->animated_gfx()},
117 {
"graphics_fingerprint",
119 {
"runtime_overrides",
"not_applied"}};
120 profile[
"sheets"] = json::array();
121 for (
int i = 0; i < 16; ++i)
122 profile[
"sheets"].push_back(map->static_graphics(i));
123 profile[
"palette"] = json::array();
124 for (
const auto& color : map->current_palette())
125 profile[
"palette"].push_back(color.snes());
126 if (map->current_graphics().empty() || map->current_palette().empty()) {
127 return absl::FailedPreconditionError(
128 "Map graphics or palette did not resolve");
131 std::map<int, json> occurrences;
132 auto& tiles = ow->GetMapTiles(world);
133 for (
int y = 0; y < height; ++y) {
134 for (
int x = 0; x < width; ++x) {
135 const int id = tiles[origin_x + x][origin_y + y];
136 if (
id >=
static_cast<int>(ow->tiles16().size())) {
137 return absl::DataLossError(
138 "Map references an unavailable Tile16 definition");
140 if (!occurrences.count(
id))
141 occurrences[id] = json::array();
142 occurrences[id].push_back({x, y});
145 json records = json::array();
146 const auto types = ow->all_tiles_types();
147 for (
const auto& [
id, uses] : occurrences) {
148 const auto& tile = ow->tiles16()[id];
149 const gfx::TileInfo quadrants[] = {tile.tile0_, tile.tile1_, tile.tile2_,
151 json pieces = json::array();
152 std::array<uint8_t, 256> transparency{};
153 int quadrant_index = 0;
154 std::string definition;
155 for (
const auto& q : quadrants) {
157 definition.push_back(
static_cast<char>(word & 255));
158 definition.push_back(
static_cast<char>(word >> 8));
159 json piece = {{
"tile8_id", q.id_},
160 {
"palette", q.palette_},
161 {
"priority", q.over_},
162 {
"flip_x", q.horizontal_mirror_},
163 {
"flip_y", q.vertical_mirror_},
165 {
"tile_type",
nullptr}};
166 if (q.id_ < types.size())
167 piece[
"tile_type"] = types[q.id_];
172 q.id_, q.horizontal_mirror_, q.vertical_mirror_));
173 for (
int py = 0; py < 8; ++py) {
174 for (
int px = 0; px < 8; ++px) {
175 const auto zero = mask[py * 8 + px];
178 (quadrant_index / 2 * 8 + py) * 16 + quadrant_index % 2 * 8 + px;
182 piece[
"transparent_pixels"] = transparent;
183 pieces.push_back(piece);
187 json labels = json::array();
189 labels = catalog->Resolve(kAdapter,
id, definition_id, profile_id);
190 json pixels = json::array();
191 const auto& blockset = map->current_tile16_blockset();
192 for (
int py = 0; py < 16; ++py) {
193 for (
int px = 0; px < 16; ++px) {
194 const size_t offset = (
id / 8) * 2048 + (
id % 8) * 16 + py * 128 + px;
195 if (offset >= blockset.size())
196 return absl::DataLossError(
"Tile16 preview unavailable");
197 pixels.push_back(blockset[offset]);
200 records.push_back({{
"tile_id",
id},
202 {
"transparent_mask", transparency},
203 {
"definition_fingerprint", definition_id},
204 {
"quadrants", pieces},
205 {
"occurrences", uses},
206 {
"labels", labels}});
209 {
"schema_version", 1},
210 {
"adapter", kAdapter},
211 {
"transparency_basis",
"decoded_source_zero_before_palette_remapping"},
213 {
"requested_map", requested},
214 {
"parent_area", parent},
215 {
"width_tiles", width},
216 {
"height_tiles", height},
217 {
"expanded_tile16", ow->expanded_tile16()},
218 {
"profile_fingerprint", profile_id},
219 {
"profile", profile},
221 "selected parent area only; not a global collision impact report"},
223 "raw Tile8 lookup only; runtime hooks and high IDs unresolved"},
224 {
"records", records}};
225 const std::string path = *parser.
GetString(
"out");
227 return absl::InternalError(
228 "Read-only catalog extraction changed the in-memory ROM");
231#ifndef __EMSCRIPTEN__
235 return absl::UnimplementedError(
236 "Terrain catalog file export requires a native build");
239 formatter.
AddField(
"tiles",
static_cast<int>(records.size()));
240 formatter.
AddField(
"parent_area", parent);
241 formatter.
AddField(
"rom_modified",
false);
242 return absl::OkStatus();