6#if defined(__EMSCRIPTEN__)
13 std::string invoke(
const std::string&,
const std::string&)
override {
14 return R
"({"ok":false,"status":"Unsupported","error":"PixelWorld MCP automation is unavailable on the Web build"})";
18PixelWorldAutomation automation;
29#include <Poco/Dynamic/Var.h>
30#include <Poco/JSON/Array.h>
31#include <Poco/JSON/Object.h>
32#include <Poco/JSON/Parser.h>
33#include <Poco/JSON/Stringifier.h>
42using Object = Poco::JSON::Object;
44std::string
stringify(Object::Ptr
object) {
45 std::ostringstream out;
46 Poco::JSON::Stringifier::stringify(Poco::Dynamic::Var(
object), out);
51 std::ostringstream out;
52 Poco::JSON::Stringifier::stringify(
value, out);
56Object::Ptr response(
bool ok) {
57 Object::Ptr out(
new Object);
64 Object::Ptr out = response(
false);
71Object::Ptr
parse(
const std::string& json) {
73 return Poco::JSON::Parser().parse(json).extract<Object::Ptr>();
75 return Object::Ptr(
new Object);
79std::string hex(std::span<const std::byte>
bytes) {
80 std::ostringstream out;
81 out << std::hex << std::setfill(
'0');
87 if ((
value.size() & 1U) != 0)
90 "pixelworld.automation"));
91 std::vector<std::byte>
bytes;
96 in >> std::hex >> parsed;
100 "pixelworld.automation"));
101 bytes.push_back(std::byte(parsed));
108 std::string invoke(
const std::string&
operation,
const std::string& requestJson)
override {
109 auto args =
parse(requestJson);
112 Poco::JSON::Array::Ptr items(
new Poco::JSON::Array);
114 Object::Ptr item(
new Object);
115 item->set(
"world",
world.link.world);
116 item->set(
"epoch",
world.link.epoch);
117 item->set(
"seed",
world.seed);
118 item->set(
"revision",
world.revision);
119 item->set(
"tick",
world.tick);
120 item->set(
"lastEditSequence",
world.lastEditSequence);
121 item->set(
"chunks",
world.chunks);
122 item->set(
"activeChunks",
world.activeChunks);
123 item->set(
"paused",
world.paused);
126 auto out = response(
true);
127 out->set(
"worlds", items);
133 if (!encoded.ok())
return failure(encoded);
134 auto out = response(
true);
135 out->set(
"fingerprint", std::to_string(catalog.fingerprint()));
136 out->set(
"catalog", encoded.value());
140 std::string document;
141 if (args->has(
"catalog")) {
142 const auto value = args->get(
"catalog");
146 if (!decoded.ok())
return failure(decoded);
148 if (!canonical.ok())
return failure(canonical);
149 auto out = response(
true);
150 out->set(
"fingerprint", std::to_string(decoded.value().fingerprint()));
151 out->set(
"materialCount", decoded.value().definitions().size());
152 out->set(
"reactionCount", decoded.value().reactions().size());
153 out->set(
"phaseRuleCount", decoded.value().phaseRules().size());
154 out->set(
"canonicalCatalog", canonical.value());
158 std::string document;
159 if (args->has(
"catalog")) {
160 const auto value = args->get(
"catalog");
163 std::uint64_t expected = 0;
165 expected = std::stoull(args->optValue<std::string>(
"expectedFingerprint",
"0"));
167 auto out = response(
false);
168 out->set(
"error",
"expectedFingerprint must be an unsigned decimal string");
171 auto result = control.reloadMaterialCatalog(
172 args->optValue<std::uint64_t>(
"world", 0), document, expected);
173 if (!result.
ok())
return failure(result);
174 auto out = response(
true);
175 out->set(
"fingerprintBefore", std::to_string(result.
value().fingerprintBefore));
176 out->set(
"fingerprintAfter", std::to_string(result.
value().fingerprintAfter));
177 out->set(
"revisionAfter", result.
value().revisionAfter);
178 out->set(
"worldEpoch", result.
value().worldEpoch);
179 out->set(
"chunksRebuilt", result.
value().chunksRebuilt);
180 out->set(
"replayHistoryInvalidated", result.
value().replayHistoryInvalidated);
183 const auto worldId = args->optValue<std::uint64_t>(
"world", 0);
185 auto result = control.setPaused(worldId, args->optValue<
bool>(
"paused",
true));
186 if (!result.
ok())
return failure(result);
187 auto out = response(
true);
188 out->set(
"paused", control.world(worldId).value().paused);
192 auto result = control.step(worldId, args->optValue<std::uint32_t>(
"count", 1));
193 if (!result.
ok())
return failure(result);
194 const auto& receipt = result.
value();
195 auto out = response(
true);
196 out->set(
"steps", receipt.steps);
197 out->set(
"firstTick", receipt.firstTick);
198 out->set(
"lastTick", receipt.lastTick);
199 out->set(
"elapsedMicroseconds", receipt.elapsedMicroseconds);
200 out->set(
"cellsChanged", receipt.finalStep.cellsChanged);
201 out->set(
"cellsMoved", receipt.finalStep.cellsMoved);
205 PixelEditCommand command;
206 command.sequence = args->optValue<std::uint64_t>(
"sequence", 0);
207 const std::string
kind = args->optValue<std::string>(
"kind",
"paint");
211 command.centerX = args->optValue<
int>(
"x", 0);
212 command.centerY = args->optValue<
int>(
"y", 0);
213 command.radius = args->optValue<
int>(
"radius", 0);
214 command.material =
MaterialId(args->optValue<
unsigned>(
"material", 0));
215 command.strength = args->optValue<
int>(
"strength", 0);
216 command.temperatureDelta = std::int16_t(std::clamp(
217 args->optValue<
int>(
"temperatureDelta", 0), -32768, 32767));
218 auto result = control.applyEdit(worldId, command);
219 if (!result.
ok())
return failure(result);
220 auto out = response(
true);
221 out->set(
"revisionBefore", result.
value().revisionBefore);
222 out->set(
"revisionAfter", result.
value().revisionAfter);
223 out->set(
"cellsChanged", result.
value().cellsChanged);
227 PixelChunkRegion region{args->optValue<
int>(
"minX", 0), args->optValue<
int>(
"minY", 0),
228 args->optValue<
int>(
"maxX", 0), args->optValue<
int>(
"maxY", 0)};
229 auto result = control.chunkDiagnostics(worldId, region);
230 if (!result.
ok())
return failure(result);
231 Poco::JSON::Array::Ptr chunks(
new Poco::JSON::Array);
232 for (
const auto& diagnostic : result.
value()) {
233 Object::Ptr item(
new Object);
234 item->set(
"x", diagnostic.x);
235 item->set(
"y", diagnostic.y);
236 item->set(
"revision", diagnostic.revision);
237 item->set(
"nonAirCells", diagnostic.nonAirCells);
238 item->set(
"mobileCells", diagnostic.mobileCells);
239 item->set(
"minimumTemperature", diagnostic.minimumTemperature);
240 item->set(
"maximumTemperature", diagnostic.maximumTemperature);
241 item->set(
"idleTicks", diagnostic.idleTicks);
242 item->set(
"active", diagnostic.active);
245 auto out = response(
true);
246 out->set(
"chunks", chunks);
250 auto result = control.performanceSamples(
251 worldId, args->optValue<std::uint32_t>(
"limit", 120));
252 if (!result.
ok())
return failure(result);
253 Poco::JSON::Array::Ptr samples(
new Poco::JSON::Array);
254 for (
const auto& sample : result.
value()) {
255 Object::Ptr item(
new Object);
256 item->set(
"tick",
sample.tick);
257 item->set(
"elapsedMicroseconds",
sample.elapsedMicroseconds);
258 item->set(
"chunksVisited",
sample.stats.chunksVisited);
259 item->set(
"cellsVisited",
sample.stats.cellsVisited);
260 item->set(
"cellsChanged",
sample.stats.cellsChanged);
261 item->set(
"cellsMoved",
sample.stats.cellsMoved);
262 item->set(
"parallelTasks",
sample.stats.parallelTasks);
265 auto out = response(
true);
266 out->set(
"samples", samples);
270 auto result = control.captureSnapshot(worldId);
271 if (!result.
ok())
return failure(result);
272 auto out = response(
true);
273 out->set(
"encoding",
"hex");
274 out->set(
"snapshot", hex(result.
value()));
278 auto decoded = unhex(args->optValue<std::string>(
"snapshot",
""));
279 if (!decoded.ok())
return failure(decoded);
280 auto result = control.restoreSnapshot(worldId, decoded.value());
281 if (!result.
ok())
return failure(result);
284 auto out = response(
false);
285 out->set(
"error",
"unknown PixelWorld automation operation");
290PixelWorldAutomation automation;
ActionParameterOperation operation
std::unordered_map< PhysicsWorldHandle::index_type, World3D * > worlds
static Diagnostic error(DiagnosticCode code, std::string message, std::string path={}, DiagnosticDetails details={}, std::string source={})
Construct an error diagnostic with the standard error severity.
Module-neutral JSON bridge from DevTools/MCP to PixelWorld tooling.
Move-only operation result carrying either a value or Status.
const T & value() const &
Borrow the value from a const lvalue after checking success.
bool ok() const noexcept
Whether this result represents a non-failure outcome.
const Status & status() const noexcept
Inspect the structured operation status.
StatusCode code() const noexcept
Stable operation outcome.
const std::vector< Diagnostic > & diagnostics() const noexcept
All diagnostics in stable insertion order.
bool hasDiagnostics() const noexcept
True when this status has at least one diagnostic.
static MaterialCatalog builtIn()
Construct the engine's canonical built-in catalog.
Result< std::string > stringify(const eve::Value &value)
Serialize the canonical owning Value as deterministic compact JSON.
eve::Result< MaterialCatalog > decodeMaterialCatalogJson(std::string_view json)
Decode and validate schema eve.pixelworld.material-catalog version 1.
PixelWorldControlService & pixelWorldControlService()
Process-lifetime PixelWorld tooling registry.
void unregisterPixelWorldAutomation()
MaterialId
Compact stable material identifier stored in each authoritative cell.
eve::Result< std::string > encodeMaterialCatalogJson(const MaterialCatalog &catalog)
Encode schema eve.pixelworld.material-catalog version 1 canonically.
void registerPixelWorldAutomation()
double sample(const Heightmap &map, double u, double v)
Sample.
ModelData parse(std::span< const uint8_t > bytes)
Parse.
constexpr std::string_view statusCodeName(StatusCode code) noexcept
Return the stable protocol spelling of a status code.