9#include "absl/status/status.h"
10#include "absl/strings/str_format.h"
25 context.
world = world;
32 if (context.
room < 0 || context.
room > 0x127 ||
36 return absl::InvalidArgumentError(
"Wall profile context is out of range");
42 std::error_code error;
43 const auto status = std::filesystem::symlink_status(path, error);
44 if (error && error != std::errc::no_such_file_or_directory) {
45 return absl::FailedPreconditionError(
"Cannot inspect preview output: " +
48 if (std::filesystem::exists(status) || std::filesystem::is_symlink(status)) {
49 return absl::AlreadyExistsError(
50 "Preview output must be a fresh artifact: " + path.string());
52 return absl::OkStatus();
56 const std::filesystem::path& output,
58 const std::vector<std::filesystem::path>& inputs) {
61 for (
const auto& input : inputs) {
65 return absl::InvalidArgumentError(
66 "Preview output aliases a protected input: " + input.string());
69 return absl::OkStatus();
74 "; Generated wall-profile PREVIEW overlay; not a room-scoped ROM patch.\n"
76 std::optional<uint32_t> previous;
77 for (
const auto& [pc, word] : patterns.
words) {
78 if (!previous || pc != *previous + 2) {
79 result += absl::StrFormat(
"org $%06X\n", pc + 0x8000);
81 result += absl::StrFormat(
" dw $%04X\n", word);
94 "Validate and resolve a strict room-selected wall profile; optionally "
95 "emit a fresh read-only preview ASM, without changing ROM or project.";
97 {
"--source",
"Version-1 wall profile JSON collection",
""},
98 {
"--room",
"Room ID 0..0x127 (decimal or 0x-prefixed)",
""},
99 {
"--world",
"Optional explicit world 0 or 1",
""},
100 {
"--entrance",
"Optional explicit entrance 0..0x84",
""},
101 {
"--legacy-source",
"Literal legacy ASM; defaults to project descriptor",
103 {
"--output",
"Optional fresh preview ASM artifact; never overwritten",
112 for (
const auto* field :
113 {
"source",
"room",
"world",
"entrance",
"legacy-source",
"output"}) {
114 if (
const auto value = parser.
GetString(field); value && value->empty()) {
115 return absl::InvalidArgumentError(std::string(
"--") + field +
119 return ParseContext(parser).status();
126 if (rom !=
nullptr && !rom->
filename().empty()) {
140 std::shared_ptr<const zelda3::DungeonWallProfileCollection> collection;
144 std::optional<std::string> legacy_path = parser.
GetString(
"legacy-source");
145 if (!legacy_path &&
project_ !=
nullptr &&
155 std::set<uint32_t> profile_addresses;
157 for (
const auto& profile : collection->profiles()) {
160 for (
const auto& pattern : profile.patterns) {
161 for (
size_t i = 0; i < pattern.words.size(); ++i) {
162 profile_addresses.insert(pattern.pc_offset + i * 2);
167 const auto output = parser.
GetString(
"output");
170 return absl::FailedPreconditionError(
171 "No resolved words to emit; retain the existing ROM/project "
177 std::vector<std::filesystem::path> inputs{collection->source_path()};
186 const std::string preview = PreviewAsm(resolved.
patterns());
189 return parsed.status();
191 return absl::DataLossError(
"Preview ASM changed resolved tile words");
194 .
subject =
"wall profile preview", .published_file =
"preview ASM"};
195 std::unique_ptr<core::SourceArtifactPublicationLock> lock;
200 std::vector<core::SourceArtifactUpdate> updates{
201 {collection->source_path(), collection->source_text(),
202 collection->source_text()}};
207 updates.push_back({
destination, std::nullopt, preview});
209 *lock, std::move(updates),
211 auto readback = zelda3::LoadDungeonTilePatterns(destination.string());
213 return readback.status();
214 if (readback->words != resolved.patterns().words) {
215 return absl::DataLossError(
"Preview ASM readback differs");
217 return absl::OkStatus();
221 formatter.AddField(
"source", collection->source_path());
223 collection->source_text()));
224 formatter.AddField(
"selected", resolved.profile_id().has_value());
225 formatter.AddField(
"selected_profile", resolved.profile_id().value_or(
""));
226 formatter.AddField(
"room", context.room);
227 formatter.AddField(
"resolved_word_count",
228 static_cast<uint64_t
>(resolved.patterns().words.size()));
229 formatter.AddField(
"profile_word_count",
230 static_cast<uint64_t
>(profile_addresses.size()));
231 formatter.AddField(
"legacy_word_count",
232 static_cast<uint64_t
>(resolved.patterns().words.size() -
233 profile_addresses.size()));
234 formatter.AddField(
"preview_written", output.has_value());
235 formatter.AddField(
"rom_written",
false);
236 formatter.AddField(
"scope",
"preview_overlay_only_not_native_dispatch");
237 formatter.BeginArray(
"words");
238 for (
const auto& [pc, word] : resolved.patterns().words) {
239 formatter.BeginObject();
240 formatter.AddField(
"pc_offset",
static_cast<uint64_t
>(pc));
241 formatter.AddField(
"word",
static_cast<int>(word));
242 formatter.AddField(
"origin",
243 profile_addresses.contains(pc) ?
"profile" :
"legacy");
244 formatter.EndObject();
246 formatter.EndArray();
247 return absl::OkStatus();
The Rom class is used to load, save, and modify Rom data. This is a generic SNES ROM container and do...
absl::Status ValidateArgs(const resources::ArgumentParser &parser) override
Validate command arguments.
absl::Status Execute(Rom *rom, const resources::ArgumentParser &parser, resources::OutputFormatter &formatter) override
Execute the command business logic.
absl::Status ExecuteWithContext(Rom *rom, const resources::ArgumentParser &parser, resources::OutputFormatter &formatter, const resources::CommandInvocationContext &context) override
Execute with immutable, invocation-scoped ROM path identity.
Descriptor Describe() const override
Provide metadata for TUI/help summaries.
Utility for parsing common CLI argument patterns.
std::optional< std::string > GetString(const std::string &name) const
Parse a named argument (e.g., –format=json or –format json)
absl::Status RequireArgs(const std::vector< std::string > &required) const
Validate that required arguments are present.
absl::StatusOr< int > GetInt(const std::string &name) const
Parse an integer argument (supports hex with 0x prefix)
project::YazeProject * project_
const std::optional< std::string > & profile_id() const
const DungeonTilePatterns & patterns() const
struct destination destination
Room transition destination.
#define ASSIGN_OR_RETURN(type_variable_name, expression)
absl::StatusOr< zelda3::WallProfileContext > ParseContext(const resources::ArgumentParser &parser)
absl::Status ProtectOutput(const std::filesystem::path &output, const resources::CommandInvocationContext &invocation, const std::vector< std::filesystem::path > &inputs)
absl::Status RequireMissing(const std::filesystem::path &path)
std::string PreviewAsm(const zelda3::DungeonTilePatterns &patterns)
absl::StatusOr< std::filesystem::path > ResolveStableArtifactPath(const std::filesystem::path &path)
absl::Status RejectArtifactRomAliases(absl::string_view option_name, const std::filesystem::path &artifact_path, const CommandInvocationContext &invocation_context)
absl::StatusOr< bool > PathsAlias(const std::filesystem::path &lhs, const std::filesystem::path &rhs)
absl::Status PublishSourceArtifacts(const SourceArtifactPublicationLock &lock, std::vector< SourceArtifactUpdate > updates, std::string_view expected_primary_sha256, SourceArtifactReadbackValidator readback_validator)
std::string ComputeSourceArtifactSha256(std::string_view content)
absl::StatusOr< std::unique_ptr< SourceArtifactPublicationLock > > AcquireSourceArtifactPublicationLock(const std::vector< fs::path > &targets, const SourceArtifactPublisherLabels &labels, const std::vector< fs::path > &read_only_targets)
absl::StatusOr< std::shared_ptr< const DungeonWallProfileCollection > > LoadDungeonWallProfiles(const std::string &path)
absl::StatusOr< ResolvedWallProfile > ResolveWallProfile(const DungeonWallProfileCollection &collection, const WallProfileContext &context, const DungeonTilePatterns &legacy)
absl::StatusOr< DungeonTilePatterns > LoadDungeonTilePatterns(const std::string &path)
absl::StatusOr< DungeonTilePatterns > ParseDungeonTilePatterns(absl::string_view source)
#define RETURN_IF_ERROR(expr)
Room transition destination.
std::vector< DescriptorEntry > entries
std::optional< std::filesystem::path > active_rom_path
std::optional< std::filesystem::path > source_rom_path
std::string GetAbsolutePath(const std::string &relative_path) const
std::string dungeon_tile_patterns
std::map< uint32_t, uint16_t > words
std::optional< int > world
std::optional< int > entrance