ROADMAP.md

This commit is contained in:
2026-01-09 22:23:00 +00:00
parent cafd1c76c1
commit a3b0121e47
3 changed files with 175 additions and 137 deletions

View File

@@ -47,6 +47,34 @@
"type": "luma_range",
"min_luma": 0.01,
"max_luma": 0.95
},
{
"type": "mean_color",
"color": [0.4, 0.45, 0.5],
"tolerance": 0.25
},
{
"type": "sample_points",
"points": [
{
"x": 0.25,
"y": 0.25,
"color": [0.2, 0.3, 0.4],
"tolerance": 0.35
},
{
"x": 0.5,
"y": 0.5,
"color": [0.1, 0.2, 0.6],
"tolerance": 0.4
},
{
"x": 0.75,
"y": 0.75,
"color": [0.8, 0.85, 0.9],
"tolerance": 0.3
}
]
}
]
}

View File

@@ -49,3 +49,7 @@ Avoid stagnation; improve structure, clarity, and expressiveness over time.
Roadmap discipline.
If you run out of ROADMAP.md tasks, stop and report.
We should reassess priorities before inventing work.
Prefer JSON workflows over LUA - Steps can be written in host language.
Vulkan can be a bit sketchy at first, very cryptic crashes. Try OpenGL.

View File

@@ -74,6 +74,19 @@ std::filesystem::path GetSeedConfigPath() {
return repoRoot / "config" / "seed_runtime.json";
}
std::string DeterminePreferredRenderer() {
if (const char* envRenderer = std::getenv("BGFX_RENDERER")) {
return envRenderer;
}
if (const char* videoDriver = std::getenv("SDL_VIDEODRIVER")) {
std::string driver(videoDriver);
if (driver == "offscreen" || driver == "dummy") {
return "opengl";
}
}
return "opengl";
}
std::optional<std::string> ReadFileContents(const std::filesystem::path& path) {
std::ifstream input(path);
if (!input) {
@@ -385,154 +398,146 @@ bool RunGpuRenderTest(int& failures,
if (success) {
std::cout << "GPU render test: Validating full render pipeline with scene geometry\n";
// Load and render the actual cube scene to catch color, geometry, and animation issues
auto scriptPath = GetCubeScriptPath();
auto configPath = GetSeedConfigPath();
auto configJson = ReadFileContents(configPath);
if (!configJson) {
std::cerr << "GPU render test failed: could not load scene config\n";
auto sceneConfigService = configService;
auto meshService = std::make_shared<sdl3cpp::services::impl::MeshService>(sceneConfigService, logger);
auto audioService = std::make_shared<StubAudioCommandService>();
auto physicsService = std::make_shared<sdl3cpp::services::impl::PhysicsBridgeService>(logger);
auto engineService = std::make_shared<sdl3cpp::services::impl::ScriptEngineService>(
scriptPath,
logger,
meshService,
audioService,
physicsService,
nullptr,
nullptr,
sceneConfigService,
false);
engineService->Initialize();
auto sceneScriptService = std::make_shared<sdl3cpp::services::impl::SceneScriptService>(engineService, logger);
auto objects = sceneScriptService->LoadSceneObjects();
if (objects.size() != 15) {
std::cerr << "GPU render test: Scene loaded " << objects.size() << " objects, expected 15\n";
++failures;
success = false;
} else {
auto sceneConfigService = std::make_shared<CubeDemoConfigService>(scriptPath, *configJson, "vulkan");
auto meshService = std::make_shared<sdl3cpp::services::impl::MeshService>(sceneConfigService, logger);
auto audioService = std::make_shared<StubAudioCommandService>();
auto physicsService = std::make_shared<sdl3cpp::services::impl::PhysicsBridgeService>(logger);
auto engineService = std::make_shared<sdl3cpp::services::impl::ScriptEngineService>(
scriptPath,
logger,
meshService,
audioService,
physicsService,
nullptr,
nullptr,
sceneConfigService,
false);
engineService->Initialize();
auto sceneScriptService = std::make_shared<sdl3cpp::services::impl::SceneScriptService>(engineService, logger);
auto objects = sceneScriptService->LoadSceneObjects();
if (objects.size() != 15) {
std::cerr << "GPU render test: Scene loaded " << objects.size() << " objects, expected 15\n";
}
// Validate all geometry is present
bool hasFloor = false;
bool hasCeiling = false;
int wallCount = 0;
int lanternCount = 0;
bool hasCube = false;
for (const auto& obj : objects) {
const auto& type = obj.objectType;
if (type == "floor") {
hasFloor = true;
} else if (type == "ceiling") {
hasCeiling = true;
} else if (type == "wall") {
wallCount++;
} else if (type == "lantern") {
lanternCount++;
} else if (type == "physics_cube" || type == "spinning_cube") {
hasCube = true;
}
}
Assert(hasFloor, "GPU render test: Missing floor geometry", failures);
Assert(hasCeiling, "GPU render test: Missing ceiling geometry", failures);
Assert(wallCount == 4, "GPU render test: Expected 4 walls, got " + std::to_string(wallCount), failures);
Assert(lanternCount == 8, "GPU render test: Expected 8 lanterns, got " + std::to_string(lanternCount), failures);
Assert(hasCube, "GPU render test: Missing physics cube geometry", failures);
// Validate all scene objects have valid shader keys (critical for rendering)
for (size_t i = 0; i < objects.size(); ++i) {
const auto& obj = objects[i];
Assert(!obj.shaderKeys.empty(),
"GPU render test: Object " + std::to_string(i) + " (" + obj.objectType + ") has no shader keys",
failures);
// Validate room geometry (floor, ceiling, walls) has expected shader keys
if (obj.objectType == "floor") {
Assert(!obj.shaderKeys.empty() && obj.shaderKeys.front() == "floor",
"GPU render test: Floor should have shader key 'floor'", failures);
Assert(obj.vertices.size() >= 100,
"GPU render test: Floor should have tessellated geometry (expected >= 100 vertices, got " +
std::to_string(obj.vertices.size()) + ")", failures);
} else if (obj.objectType == "ceiling") {
Assert(!obj.shaderKeys.empty() && obj.shaderKeys.front() == "ceiling",
"GPU render test: Ceiling should have shader key 'ceiling'", failures);
Assert(obj.vertices.size() >= 100,
"GPU render test: Ceiling should have tessellated geometry (expected >= 100 vertices, got " +
std::to_string(obj.vertices.size()) + ")", failures);
} else if (obj.objectType == "wall") {
Assert(!obj.shaderKeys.empty() && obj.shaderKeys.front() == "wall",
"GPU render test: Wall should have shader key 'wall'", failures);
}
}
// Create actual render buffers and render multiple frames to test animation
auto ecsService = std::make_shared<sdl3cpp::services::impl::EcsService>(logger);
auto sceneService = std::make_shared<sdl3cpp::services::impl::SceneService>(sceneScriptService, ecsService, logger);
sceneService->LoadScene(objects);
auto device = backend.CreateDevice();
const int testFrames = 5;
std::cout << "GPU render test: Rendering " << testFrames << " frames to validate pipeline\n";
for (int frame = 0; frame < testFrames; ++frame) {
float elapsedTime = frame * 0.016f; // ~60 FPS
auto renderCommands = sceneService->GetRenderCommands(elapsedTime);
if (renderCommands.size() != objects.size()) {
std::cerr << "GPU render test: Frame " << frame << " produced "
<< renderCommands.size() << " commands, expected " << objects.size() << '\n';
++failures;
success = false;
break;
}
// Validate all geometry is present
bool hasFloor = false;
bool hasCeiling = false;
int wallCount = 0;
int lanternCount = 0;
bool hasCube = false;
for (const auto& obj : objects) {
const auto& type = obj.objectType;
if (type == "floor") {
hasFloor = true;
} else if (type == "ceiling") {
hasCeiling = true;
} else if (type == "wall") {
wallCount++;
} else if (type == "lantern") {
lanternCount++;
} else if (type == "physics_cube" || type == "spinning_cube") {
hasCube = true;
}
}
Assert(hasFloor, "GPU render test: Missing floor geometry", failures);
Assert(hasCeiling, "GPU render test: Missing ceiling geometry", failures);
Assert(wallCount == 4, "GPU render test: Expected 4 walls, got " + std::to_string(wallCount), failures);
Assert(lanternCount == 8, "GPU render test: Expected 8 lanterns, got " + std::to_string(lanternCount), failures);
Assert(hasCube, "GPU render test: Missing physics cube geometry", failures);
// Validate all scene objects have valid shader keys (critical for rendering)
for (size_t i = 0; i < objects.size(); ++i) {
const auto& obj = objects[i];
Assert(!obj.shaderKeys.empty(),
"GPU render test: Object " + std::to_string(i) + " (" + obj.objectType + ") has no shader keys",
failures);
// Validate room geometry (floor, ceiling, walls) has expected shader keys
if (obj.objectType == "floor") {
Assert(!obj.shaderKeys.empty() && obj.shaderKeys.front() == "floor",
"GPU render test: Floor should have shader key 'floor'", failures);
Assert(obj.vertices.size() >= 100,
"GPU render test: Floor should have tessellated geometry (expected >= 100 vertices, got " +
std::to_string(obj.vertices.size()) + ")", failures);
} else if (obj.objectType == "ceiling") {
Assert(!obj.shaderKeys.empty() && obj.shaderKeys.front() == "ceiling",
"GPU render test: Ceiling should have shader key 'ceiling'", failures);
Assert(obj.vertices.size() >= 100,
"GPU render test: Ceiling should have tessellated geometry (expected >= 100 vertices, got " +
std::to_string(obj.vertices.size()) + ")", failures);
} else if (obj.objectType == "wall") {
Assert(!obj.shaderKeys.empty() && obj.shaderKeys.front() == "wall",
"GPU render test: Wall should have shader key 'wall'", failures);
}
}
// Create actual render buffers and render multiple frames to test animation
auto ecsService = std::make_shared<sdl3cpp::services::impl::EcsService>(logger);
auto sceneService = std::make_shared<sdl3cpp::services::impl::SceneService>(sceneScriptService, ecsService, logger);
sceneService->LoadScene(objects);
auto device = backend.CreateDevice();
const int testFrames = 5;
std::cout << "GPU render test: Rendering " << testFrames << " frames to validate pipeline\n";
for (int frame = 0; frame < testFrames; ++frame) {
float elapsedTime = frame * 0.016f; // ~60 FPS
auto renderCommands = sceneService->GetRenderCommands(elapsedTime);
if (renderCommands.size() != objects.size()) {
std::cerr << "GPU render test: Frame " << frame << " produced "
<< renderCommands.size() << " commands, expected " << objects.size() << '\n';
++failures;
success = false;
break;
}
backend.BeginFrame(device);
// Validate cube is animating (matrix should change between frames)
if (frame == 0) {
for (size_t i = 0; i < renderCommands.size(); ++i) {
if (objects[i].shaderKeys.empty()) continue;
const auto& key = objects[i].shaderKeys.front();
if (key == "floor" && objects[i].vertices.size() < 100) {
// This is the cube - verify it has non-identity matrix (spinning)
bool hasRotation = false;
const auto& matrix = renderCommands[i].modelMatrix;
// Check off-diagonal elements for rotation
if (std::abs(matrix[1]) > 0.01f || std::abs(matrix[2]) > 0.01f ||
std::abs(matrix[4]) > 0.01f || std::abs(matrix[6]) > 0.01f ||
std::abs(matrix[8]) > 0.01f || std::abs(matrix[9]) > 0.01f) {
hasRotation = true;
}
if (!hasRotation) {
std::cerr << "GPU render test: Cube is not spinning (rotation matrix is identity)\n";
++failures;
success = false;
}
break;
backend.BeginFrame(device);
// Validate cube is animating (matrix should change between frames)
if (frame == 0) {
for (size_t i = 0; i < renderCommands.size(); ++i) {
if (objects[i].shaderKeys.empty()) continue;
const auto& key = objects[i].shaderKeys.front();
if (key == "floor" && objects[i].vertices.size() < 100) {
// This is the cube - verify it has non-identity matrix (spinning)
bool hasRotation = false;
const auto& matrix = renderCommands[i].modelMatrix;
// Check off-diagonal elements for rotation
if (std::abs(matrix[1]) > 0.01f || std::abs(matrix[2]) > 0.01f ||
std::abs(matrix[4]) > 0.01f || std::abs(matrix[6]) > 0.01f ||
std::abs(matrix[8]) > 0.01f || std::abs(matrix[9]) > 0.01f) {
hasRotation = true;
}
if (!hasRotation) {
std::cerr << "GPU render test: Cube is not spinning (rotation matrix is identity)\n";
++failures;
success = false;
}
break;
}
}
backend.EndFrame(device);
}
backend.DestroyDevice(device);
sceneService->Shutdown();
engineService->Shutdown();
std::cout << "GPU render test: Successfully rendered and validated scene pipeline\n";
backend.EndFrame(device);
}
backend.DestroyDevice(device);
sceneService->Shutdown();
engineService->Shutdown();
std::cout << "GPU render test: Successfully rendered and validated scene pipeline\n";
}
backend.Shutdown();
@@ -552,7 +557,8 @@ void RunCubeDemoSceneTests(int& failures) {
}
auto logger = std::make_shared<sdl3cpp::services::impl::LoggerService>();
auto configService = std::make_shared<CubeDemoConfigService>(scriptPath, *configJson, "vulkan");
const std::string preferredRenderer = DeterminePreferredRenderer();
auto configService = std::make_shared<CubeDemoConfigService>(scriptPath, *configJson, preferredRenderer);
auto meshService = std::make_shared<sdl3cpp::services::impl::MeshService>(configService, logger);
auto audioService = std::make_shared<StubAudioCommandService>();
auto physicsService = std::make_shared<sdl3cpp::services::impl::PhysicsBridgeService>(logger);