Files
EVEngine/test/editor_biome_target.cpp
西风逍遥游andCursor 24502babef Nest satellite packages under their host modules.
Keep CMake module names and trim switches, but stop flattening editing/editor and runtime bridges as top-level src/modules directories. Parent scans skip nested facets so runtime-3d can drop authoring by directory leaf.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-06 17:56:12 +08:00

142 lines
6.8 KiB
C++

#include "procgen/biome/editing/BiomeTarget.h"
#include "procgen/biome/editor/BiomeEditorModule.h"
#include "common/Capability.h"
#include "common/EditorAutomation.h"
#include "editor/Editor.h"
#include "procgen/PointSet.h"
#include "procgen/SpatialData.h"
#include "zeroerr/assert.h"
#include "zeroerr/unittest.h"
#include <map>
using namespace eve::biome_editing;
using namespace eve::editing;
namespace {
SelectionSnapshot select(const BiomeDocumentTarget& target, const ObjectId& id,
const std::string& type) {
SelectionSnapshot selection;
selection.channel = "biome";
selection.items.push_back(
{SelectionDomain::Asset, TargetId(target.targetId()), StableId(id.value()), type});
return selection;
}
void apply(BiomeDocumentTarget& target, EditorResult<DomainOperation> operation) {
REQUIRE(operation.ok());
REQUIRE(target.applyDomainOperation(operation.value()).ok());
}
class SpatialResolver final : public IBiomeSpatialResolver {
public:
EditorResult<eve::procgen::SpatialData*> resolve(const std::string& asset) const override {
const auto found = assets.find(asset);
if (found == assets.end())
return eve::editing::failed<eve::procgen::SpatialData*>(
EditorStatus::NotFound, RuleId("test.biome.spatial"), "missing spatial asset");
return eve::editing::applied<eve::procgen::SpatialData*>(found->second);
}
std::map<std::string, eve::procgen::SpatialData*> assets;
};
} // namespace
TEST_CASE("editor.biome.properties_are_reversible_and_snapshot_load_is_atomic") {
BiomeDocumentTarget target("world.biome");
BiomeLayerValue layer{ObjectId("forest"), "forest", "areas/forest.spatial", 1, 1.f, {}};
apply(target, target.makeCreateLayer(layer));
apply(target, target.makeCreateAsset(
layer.id, {ObjectId("oak"), "models/oak.prefab", 2.f, .8f, 1.2f, true}));
const auto selection = select(target, ObjectId("oak"), "biome.asset");
const auto scale = target.makeSet(selection, PropertyPath("asset.scale"),
EditorValue::Array{.5, 1.5}, PropertySetMode::Absolute);
REQUIRE(scale.ok());
REQUIRE(target.applyDomainOperation(scale.value()).ok());
CHECK_EQ(target.layers()[0].assets[0].minScale, .5f);
DomainOperation undo = scale.value();
undo.payload = scale.value().inverse;
REQUIRE(target.applyDomainOperation(undo).ok());
CHECK_EQ(target.layers()[0].assets[0].minScale, .8f);
const EditorValue before = target.snapshotValue();
EditorValue invalid = before;
auto* root = invalid.getIf<EditorValue::Object>();
auto* content = (*root)["content"].getIf<EditorValue::Object>();
auto* layers = (*content)["layers"].getIf<EditorValue::Array>();
auto* first = (*layers)[0].getIf<EditorValue::Object>();
(*first)["density"] = 2.0;
CHECK_EQ(static_cast<int>(target.loadSnapshot(invalid).code()),
static_cast<int>(EditorStatus::Rejected));
CHECK_EQ(target.snapshotValue(), before);
}
TEST_CASE("editor.biome.runtime_publishes_candidates_and_previews_deterministically") {
BiomeDocumentTarget target("world.biome");
apply(target, target.makeCreateLayer(
{ObjectId("forest"), "forest", "areas/forest.spatial", 1, 1.f, {}}));
apply(target, target.makeCreateAsset(
ObjectId("forest"),
{ObjectId("oak"), "models/oak.prefab", 1.f, 1.f, 1.f, false}));
eve::procgen::SpatialData domain =
eve::procgen::SpatialData::box(0.f, 0.f, 0.f, 4.f, 0.f, 4.f);
SpatialResolver resolver;
resolver.assets["areas/forest.spatial"] = &domain;
BiomeDocumentRuntime runtime;
REQUIRE(runtime.publish(target, resolver).ok());
const Revision published = runtime.revision();
auto first = runtime.preview(&domain, 2.f, 42, 0.f, published);
auto second = runtime.preview(&domain, 2.f, 42, 0.f, published);
REQUIRE(first.ok());
REQUIRE(second.ok());
REQUIRE(bool(first.value()));
REQUIRE(bool(second.value()));
CHECK_EQ(first.value()->getCount(), second.value()->getCount());
for (int i = 0; i < first.value()->getCount(); ++i) {
CHECK_EQ(first.value()->getStringAttribute(i, "asset", ""),
second.value()->getStringAttribute(i, "asset", ""));
CHECK_EQ(first.value()->getPointSeed(i), second.value()->getPointSeed(i));
}
CHECK_EQ(static_cast<int>(runtime.preview(&domain, 2.f, 42, 0.f, published + 1).code()),
static_cast<int>(EditorStatus::Conflict));
SpatialResolver missing;
CHECK_EQ(static_cast<int>(runtime.publish(target, missing).code()),
static_cast<int>(EditorStatus::NotFound));
CHECK_EQ(runtime.revision(), published);
}
TEST_CASE("editor.biome.adapter_registers_commands_and_owns_automation_targets") {
using eve::editor::Editor;
Editor editor;
eve::biome_editor::BiomeEditorModule adapter;
auto* automation = eve::cap::query<eve::IEditorAutomation>();
REQUIRE(automation != nullptr);
const std::string commands = automation->invoke("commands", "{}");
CHECK(commands.find("biome.layer.create.v1") != std::string::npos);
CHECK(commands.find("biome.asset.create.v1") != std::string::npos);
CHECK(commands.find("biome.property.set.v1") != std::string::npos);
const std::string created = automation->invoke(
"target-create",
R"({"target":"agent.biome","type":"biome","layer":"forest","spatial":"areas/forest.spatial"})");
REQUIRE(created.find("\"status\":\"applied\"") != std::string::npos);
const std::string asset = automation->invoke(
"execute",
R"({"target":"agent.biome","command":"biome.asset.create.v1","payload":{"layer":"forest","id":"oak","asset":"models/oak.prefab","weight":2,"minScale":0.8,"maxScale":1.2}})");
REQUIRE(asset.find("\"status\":\"applied\"") != std::string::npos);
const std::string density = automation->invoke(
"execute",
R"({"target":"agent.biome","command":"biome.property.set.v1","payload":{"item":"forest","type":"biome.layer","path":"layer.density","value":0.5}})");
REQUIRE(density.find("\"status\":\"applied\"") != std::string::npos);
const std::string inspected = automation->invoke("inspect", R"({"target":"agent.biome"})");
CHECK(inspected.find("forest") != std::string::npos);
CHECK(inspected.find("oak") != std::string::npos);
CHECK(inspected.find("0.5") != std::string::npos);
const std::string undone = automation->invoke("undo", R"({"target":"agent.biome"})");
CHECK(undone.find("\"status\":\"applied\"") != std::string::npos);
const std::string afterUndo = automation->invoke("inspect", R"({"target":"agent.biome"})");
CHECK(afterUndo.find("0.5") == std::string::npos);
const std::string closed = automation->invoke("target-close", R"({"target":"agent.biome"})");
CHECK(closed.find("\"status\":\"applied\"") != std::string::npos);
}