182 return absl::InvalidArgumentError(
"Room pointer is null");
198 std::array<bool, kGridSize * kGridSize> occupied{};
207 ResolveTrackObjectDimensions(obj, options, dimension_service);
208 int base_x = obj.x_ + dims.offset_x_tiles;
209 int base_y = obj.y_ + dims.offset_y_tiles;
210 int w = std::max(1, dims.width_tiles);
211 int h = std::max(1, dims.height_tiles);
213 for (
int dy = 0; dy < h; ++dy) {
214 for (
int dx = 0; dx < w; ++dx) {
215 int gx = base_x + dx;
216 int gy = base_y + dy;
217 if (gx >= 0 && gx < kGridSize && gy >= 0 && gy < kGridSize) {
218 occupied[gy * kGridSize + gx] =
true;
225 for (
int y = 0; y < kGridSize; ++y) {
226 for (
int x = 0; x < kGridSize; ++x) {
227 if (!occupied[y * kGridSize + x])
230 bool up = (y > 0) && occupied[(y - 1) * kGridSize + x];
231 bool down = (y < kGridSize - 1) && occupied[(y + 1) * kGridSize + x];
232 bool left = (x > 0) && occupied[y * kGridSize + (x - 1)];
233 bool right = (x < kGridSize - 1) && occupied[y * kGridSize + (x + 1)];
235 uint8_t tile = ClassifyTile(up, down, left, right);
239 if (tile >= 0xB7 && tile <= 0xBA)
241 if (tile >= 0xB2 && tile <= 0xB5)
248 if (sx < 0 || sx >= kGridSize || sy < 0 || sy >= kGridSize)
250 size_t idx = sy * kGridSize + sx;
252 if (IsCornerTile(tile)) {
262 if (ox < 0 || ox >= kGridSize || oy < 0 || oy >= kGridSize)
264 size_t idx = oy * kGridSize + ox;
279 return absl::InvalidArgumentError(
"ROM not loaded");
282 return absl::OutOfRangeError(
"Room ID out of range");
285 const auto& data = rom->
vector();
287 return absl::FailedPreconditionError(
"ROM vector is empty");
292 static_cast<int>(data.size())) {
293 return absl::FailedPreconditionError(
294 "Custom collision pointer table not present in this ROM");
297 return absl::FailedPreconditionError(
298 "Custom collision data region not present in this ROM");
301 return absl::FailedPreconditionError(
302 "Custom collision data region truncated (ROM too small)");
311 "CustomCollisionPointers"));
315 "CustomCollisionData"));
320 std::vector<uint8_t> encoded;
321 encoded.push_back(0xF0);
322 encoded.push_back(0xF0);
324 for (
int y = 0; y < kGridSize; ++y) {
325 for (
int x = 0; x < kGridSize; ++x) {
326 uint8_t tile = map.
tiles[y * kGridSize + x];
329 uint16_t offset =
static_cast<uint16_t
>(y * kGridSize + x);
330 encoded.push_back(offset & 0xFF);
331 encoded.push_back(offset >> 8);
332 encoded.push_back(tile);
335 encoded.push_back(0xFF);
336 encoded.push_back(0xFF);
340 const size_t safe_end =
341 std::min(
static_cast<size_t>(data.size()),
346 if (ptr_offset + 2 >=
static_cast<int>(data.size()))
349 uint32_t snes_ptr = data[ptr_offset] | (data[ptr_offset + 1] << 8) |
350 (data[ptr_offset + 2] << 16);
356 return absl::FailedPreconditionError(
357 absl::StrFormat(
"Custom collision pointer for room 0x%02X points "
358 "before data region (pc=0x%06X)",
362 return absl::FailedPreconditionError(
363 absl::StrFormat(
"Custom collision pointer for room 0x%02X overlaps "
364 "WaterFill reserved region (pc=0x%06X)",
367 if (pc >= data.size()) {
368 return absl::OutOfRangeError(
"Custom collision pointer out of ROM range");
372 bool single_mode =
false;
373 bool found_end_marker =
false;
374 while (cursor + 1 < safe_end) {
375 uint16_t val = data[cursor] | (data[cursor + 1] << 8);
377 if (val == kCollisionEndMarker) {
378 found_end_marker =
true;
381 if (val == kCollisionSingleTileMarker) {
387 if (cursor + 1 >= safe_end)
389 uint8_t w = data[cursor];
390 uint8_t h = data[cursor + 1];
399 if (!found_end_marker && cursor + 1 >= safe_end) {
400 return absl::FailedPreconditionError(
401 absl::StrFormat(
"Custom collision data for room 0x%02X is "
402 "unterminated before WaterFill reserved region",
405 if (cursor > max_used_pc) {
406 max_used_pc =
static_cast<uint32_t
>(cursor);
411 uint32_t write_pos = max_used_pc;
413 return absl::ResourceExhaustedError(absl::StrFormat(
414 "Not enough collision data space. Need %d bytes at 0x%06X, "
415 "region ends at 0x%06X",
419 if (write_pos + encoded.size() > data.size()) {
420 return absl::OutOfRangeError(
421 absl::StrFormat(
"ROM too small for custom collision write (need "
422 "end=0x%06X, size=0x%06X)",
423 write_pos + encoded.size(), data.size()));
426 rom->
WriteVector(
static_cast<int>(write_pos), std::move(encoded)));
429 uint32_t snes_addr =
PcToSnes(write_pos);
435 return absl::OkStatus();
440 int min_x = kGridSize, max_x = 0, min_y = kGridSize, max_y = 0;
441 for (
int y = 0; y < kGridSize; ++y) {
442 for (
int x = 0; x < kGridSize; ++x) {
443 if (map.
tiles[y * kGridSize + x] != 0) {
444 min_x = std::min(min_x, x);
445 max_x = std::max(max_x, x);
446 min_y = std::min(min_y, y);
447 max_y = std::max(max_y, y);
456 min_x = std::max(0, min_x - 1);
457 min_y = std::max(0, min_y - 1);
458 max_x = std::min(kGridSize - 1, max_x + 1);
459 max_y = std::min(kGridSize - 1, max_y + 1);
461 std::stringstream ss;
464 for (
int x = min_x; x <= max_x; ++x) {
465 ss << absl::StrFormat(
"%X", x % 16);
469 for (
int y = min_y; y <= max_y; ++y) {
470 ss << absl::StrFormat(
"%02X: ", y);
471 for (
int x = min_x; x <= max_x; ++x) {
472 uint8_t tile = map.
tiles[y * kGridSize + x];
473 ss << TileToChar(tile);