yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
sprite_behavior.h
Go to the documentation of this file.
1#ifndef YAZE_APP_EDITOR_SPRITE_SPRITE_BEHAVIOR_H_
2#define YAZE_APP_EDITOR_SPRITE_SPRITE_BEHAVIOR_H_
3
4#include <algorithm>
5#include <cctype>
6#include <sstream>
7
8#include "absl/strings/str_format.h"
13
15inline constexpr std::array<const char*, 3> kSourcePaths = {
16 "Core/sprite_macros.asm", "Core/sprite_functions.asm", "Core/symbols.asm"};
17// Reviewed source contracts; changes require review, never automatic acceptance.
18inline constexpr std::array<const char*, 3> kReviewedCodeSha256 = {
19 "07a4aca7fab35159c10abe0804f5de6f2455c32ed1acc7f00abe69b4ad09acd8",
20 "d727ced1802805fbebada4d5bcffca4f366adcb23c14ef9bb1985afa48b93f39",
21 "5914fb1de28a4612cfac189d20d22586a04526f5f6d0d3a2307489413dee22ab"};
22
23inline std::string NormalizeContractSource(const std::string& text) {
24 std::istringstream stream(text);
25 std::string result, line;
26 while (std::getline(stream, line)) {
27 line = line.substr(0, line.find(';'));
28 for (unsigned char c : line)
29 if (!std::isspace(c))
30 result += c;
31 }
32 return result;
33}
34
35inline absl::StatusOr<std::array<std::string, 3>> ReadReviewedProfile(
36 const std::string& root) {
37 std::array<std::string, 3> hashes;
38 for (size_t i = 0; i < kSourcePaths.size(); ++i) {
39 auto source = zelda3::ReadSpriteSourceFile(root, kSourcePaths[i]);
40 if (!source.ok())
41 return source.status();
43 NormalizeContractSource(source->content)) != kReviewedCodeSha256[i])
44 return absl::FailedPreconditionError(
45 std::string("Oracle action contract changed; review required: ") +
46 kSourcePaths[i]);
47 hashes[i] = core::ComputeSourceArtifactSha256(source->content);
48 }
49 return hashes;
50}
51
52inline absl::Status VerifyProfile(const std::string& root,
53 const project::SpriteBehavior& behavior) {
54 if (behavior.profile != "oracle_actions_v1")
55 return absl::InvalidArgumentError("Unsupported behavior profile");
56 auto current = ReadReviewedProfile(root);
57 if (!current.ok())
58 return current.status();
59 if (*current != behavior.source_sha256)
60 return absl::FailedPreconditionError(
61 "Behavior source changed since profile binding; review and rebind the "
62 "profile");
63 return absl::OkStatus();
64}
65
66inline absl::Status Validate(const project::SpriteBehavior& behavior,
67 const zsprite::ZSprite& sprite) {
68 auto checked =
70 if (!checked.ok())
71 return checked.status();
72 for (const auto& action : behavior.actions) {
73 if (action.animation >= static_cast<int>(sprite.animations.size()))
74 return absl::FailedPreconditionError(
75 "Action references a missing animation");
76 const auto& animation = sprite.animations[action.animation];
77 if (animation.frame_start > animation.frame_end ||
78 animation.frame_end >= sprite.editor.Frames.size() ||
79 animation.frame_end == 255 || animation.frame_speed == 0)
80 return absl::FailedPreconditionError(
81 "Oracle actions require valid animation ranges ending below 255 and "
82 "a nonzero wait");
83 }
84 return absl::OkStatus();
85}
86
87// Removes only unreferenced actions. IDs above the deleted action shift together.
88inline absl::Status RemoveAction(project::SpriteBehavior& behavior, int index) {
89 if (index < 0 || index >= static_cast<int>(behavior.actions.size()) ||
90 behavior.actions.size() <= 1)
91 return absl::InvalidArgumentError("Cannot delete this action");
92 for (size_t i = 0; i < behavior.actions.size(); ++i) {
93 if (static_cast<int>(i) == index)
94 continue;
95 const auto& action = behavior.actions[i];
96 if (action.message_next == index || action.timer_next == index)
97 return absl::FailedPreconditionError(
98 "Redirect incoming transitions before deleting this action");
99 }
100 behavior.actions.erase(behavior.actions.begin() + index);
101 for (auto& action : behavior.actions) {
102 if (action.message_next > index)
103 --action.message_next;
104 if (action.timer_next > index)
105 --action.timer_next;
106 }
107 return absl::OkStatus();
108}
109
110// Pure generation. The editor verifies source hashes immediately before calling.
111// The output has no org, registration, hooks, or source mutation.
112inline absl::StatusOr<std::string> GenerateCandidate(
113 const project::SpriteBehavior& behavior, const zsprite::ZSprite& sprite,
114 const std::string& prefix) {
115 RETURN_IF_ERROR(Validate(behavior, sprite));
116 if (prefix.empty() || prefix.size() > 64 ||
117 !(std::isalpha(static_cast<unsigned char>(prefix[0])) ||
118 prefix[0] == '_') ||
119 prefix.find_first_not_of(
120 "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_") !=
121 std::string::npos)
122 return absl::InvalidArgumentError(
123 "Use an ASM identifier as the behavior label prefix");
124 auto label = [&](const char* type, int index) {
125 return prefix + "_" + type + std::to_string(index);
126 };
127 auto byte = [](int value) {
128 return absl::StrFormat("$%02X", value & 255);
129 };
130 std::ostringstream out;
131 out << "; Oracle action candidate. Review bank placement and RAM ownership "
132 "before integration.\n"
133 << "; Same-bank JSR entry/RTS return; A/X/Y 8-bit, X=sprite slot, "
134 "DBR must map low WRAM.\n"
135 << "; Caller: Init once; Main only when Sprite_CheckActive succeeds. "
136 "Keep draw frame base zero.\n"
137 << "; Owns SprAction, SprFrame, SprTimerA/B, SprXSpeed/YSpeed. Helpers "
138 "clobber scratch/flags.\n"
139 << "; Engine decrements timers. Dialogue transition means message "
140 "accepted, not dialogue completed.\n"
141 << "; No subtype selection, registration, or draw routine is "
142 "generated.\n\n"
143 << prefix << "_Init:\n JMP " << label("Enter", 0) << "\n\n"
144 << prefix << "_Main:\n LDA.w SprAction, X\n CMP.b #"
145 << byte(behavior.actions.size())
146 << "\n BCC .dispatch\n RTS\n.dispatch\n JSL JumpTableLocal\n";
147 for (size_t i = 0; i < behavior.actions.size(); ++i)
148 out << " dw " << label("Action", i) << "\n";
149 for (size_t i = 0; i < behavior.actions.size(); ++i) {
150 const auto& action = behavior.actions[i];
151 const auto& animation = sprite.animations[action.animation];
152 out << "\n"
153 << label("Action", i) << ":\n %PlayAnimation("
154 << int(animation.frame_start) << ", " << int(animation.frame_end)
155 << ", " << int(animation.frame_speed) << ")\n";
156 if (action.block_player)
157 out << " JSL Sprite_PlayerCantPassThrough\n";
158 if (action.message_id >= 0) {
159 out << " %ShowSolicitedMessage("
160 << absl::StrFormat("$%04X", action.message_id)
161 << ")\n BCC .no_message\n";
162 if (action.message_next >= 0)
163 out << " JMP " << label("Enter", action.message_next) << "\n";
164 else
165 out << " RTS\n";
166 out << ".no_message\n";
167 }
168 if (action.timer_next >= 0)
169 out << " LDA.w SprTimerA, X\n BNE .timer_pending\n JMP "
170 << label("Enter", action.timer_next) << "\n.timer_pending\n";
171 if (action.bounce_tiles)
172 out << " JSL Sprite_BounceFromTileCollision\n";
173 if (action.move)
174 out << " JSL Sprite_Move\n";
175 out << " RTS\n\n" << label("Enter", i) << ":\n";
176 for (const auto& entry : std::vector<std::pair<const char*, int>>{
177 {"SprAction", static_cast<int>(i)},
178 {"SprFrame", animation.frame_start},
179 {"SprTimerB", animation.frame_speed},
180 {"SprTimerA", action.timer_ticks},
181 {"SprXSpeed", action.x_speed},
182 {"SprYSpeed", action.y_speed}})
183 out << " LDA.b #" << byte(entry.second) << " : STA.w " << entry.first
184 << ", X\n";
185 out << " RTS\n";
186 }
187 return out.str();
188}
189} // namespace yaze::editor::sprite_behavior
190#endif
std::string ComputeSourceArtifactSha256(std::string_view content)
std::string NormalizeContractSource(const std::string &text)
absl::StatusOr< std::array< std::string, 3 > > ReadReviewedProfile(const std::string &root)
constexpr std::array< const char *, 3 > kSourcePaths
absl::Status VerifyProfile(const std::string &root, const project::SpriteBehavior &behavior)
absl::Status RemoveAction(project::SpriteBehavior &behavior, int index)
constexpr std::array< const char *, 3 > kReviewedCodeSha256
absl::Status Validate(const project::SpriteBehavior &behavior, const zsprite::ZSprite &sprite)
absl::StatusOr< std::string > GenerateCandidate(const project::SpriteBehavior &behavior, const zsprite::ZSprite &sprite, const std::string &prefix)
absl::StatusOr< SpriteBehavior > SpriteBehaviorFromJson(const nlohmann::json &json)
nlohmann::json SpriteBehaviorToJson(const SpriteBehavior &behavior)
absl::StatusOr< SpriteSourceDocument > ReadSpriteSourceFile(const std::string &source_root, const std::string &relative_path)
#define RETURN_IF_ERROR(expr)
Definition snes.cc:22
std::vector< Frame > Frames
Definition zsprite.h:125
std::vector< AnimationGroup > animations
Definition zsprite.h:445
std::vector< SpriteBehaviorAction > actions
std::array< std::string, 3 > source_sha256