feat(runtime): add Bevy 0.20 live editor M0 spike

- new bevy_forge_runtime crate with EditorPlugin, EditorState, Selection
- native Bevy UI shell + official bevy_ui_widgets 0.20 (no egui in runtime)
- live hierarchy, reflection inspector, translate gizmo, editor camera
- deterministic 1440x900/1000x640 screenshot mode
- demo example, docs and screenshots; v1 crates untouched
- interactive computer-use test fixed inspector command-queue panic
This commit is contained in:
Emil Shanaty
2026-10-27 22:14:21 +03:00
parent 1cc727f890
commit 59212c952f
17 changed files with 6315 additions and 64 deletions
@@ -0,0 +1,155 @@
//! M0 spike demo: a small hardcoded 3D scene plus the Bevy Forge editor plugin.
//!
//! Run:
//! cargo run -p bevy_forge_runtime --example editor_spike
//!
//! Deterministic screenshot:
//! FORGE_SCREENSHOT=$PWD/.swarm/m0/evidence/m0-1440x900.png \
//! FORGE_SCREENSHOT_SIZE=1440x900 \
//! cargo run -p bevy_forge_runtime --example editor_spike
use bevy::{prelude::*, render::view::Tonemapping, window::WindowResolution};
use bevy_forge_runtime::{EditorPlugin, Selection};
fn main() {
App::new()
.add_plugins(DefaultPlugins.set(WindowPlugin {
primary_window: Some(Window {
title: "Bevy Forge — M0 Spike".into(),
resolution: WindowResolution::new(1440, 900).with_scale_factor_override(1.0),
..default()
}),
..default()
}))
.add_plugins(EditorPlugin)
.add_systems(Startup, demo_scene)
.run();
}
fn demo_scene(
mut commands: Commands,
mut meshes: ResMut<Assets<Mesh>>,
mut materials: ResMut<Assets<StandardMaterial>>,
mut selection: ResMut<Selection>,
) {
commands.insert_resource(GlobalAmbientLight {
color: Color::WHITE,
brightness: 600.0,
..default()
});
let ground = materials.add(StandardMaterial {
base_color: Color::srgb(0.16, 0.20, 0.17),
perceptual_roughness: 0.95,
metallic: 0.0,
..default()
});
commands.spawn((
Name::new("Ground"),
Mesh3d(meshes.add(Plane3d::default().mesh().size(14.0, 14.0))),
MeshMaterial3d(ground),
Transform::from_xyz(0.0, -0.02, 0.0),
));
let hero_mat = materials.add(StandardMaterial {
base_color: Color::srgb(0.85, 0.55, 0.25),
perceptual_roughness: 0.65,
metallic: 0.0,
..default()
});
let hat_mat = materials.add(StandardMaterial {
base_color: Color::srgb(0.25, 0.45, 0.75),
perceptual_roughness: 0.5,
metallic: 0.0,
..default()
});
let hero = commands
.spawn((
Name::new("Hero Cube"),
Mesh3d(meshes.add(Cuboid::new(1.0, 1.0, 1.0))),
MeshMaterial3d(hero_mat),
Transform::from_xyz(0.0, 0.5, 0.0),
))
.with_children(|parent| {
parent.spawn((
Name::new("Hat"),
Mesh3d(meshes.add(Cuboid::new(0.55, 0.18, 0.55))),
MeshMaterial3d(hat_mat),
Transform::from_xyz(0.0, 0.62, 0.0),
));
})
.id();
let red_mat = materials.add(StandardMaterial {
base_color: Color::srgb(0.75, 0.18, 0.16),
perceptual_roughness: 0.7,
..default()
});
let blue_mat = materials.add(StandardMaterial {
base_color: Color::srgb(0.18, 0.35, 0.78),
perceptual_roughness: 0.7,
..default()
});
commands.spawn((
Name::new("Red Cube"),
Mesh3d(meshes.add(Cuboid::new(0.8, 0.8, 0.8))),
MeshMaterial3d(red_mat),
Transform::from_xyz(-2.6, 0.4, 0.6),
));
commands.spawn((
Name::new("Blue Cube"),
Mesh3d(meshes.add(Cuboid::new(0.8, 0.8, 0.8))),
MeshMaterial3d(blue_mat),
Transform::from_xyz(2.6, 0.4, -0.6),
));
let crate_mat = materials.add(StandardMaterial {
base_color: Color::srgb(0.55, 0.38, 0.20),
perceptual_roughness: 0.9,
..default()
});
let stack = commands
.spawn((
Name::new("Crate Stack"),
Transform::from_xyz(0.0, 0.0, -2.8),
Visibility::default(),
))
.with_children(|parent| {
parent.spawn((
Name::new("Crate A"),
Mesh3d(meshes.add(Cuboid::new(0.8, 0.8, 0.8))),
MeshMaterial3d(crate_mat.clone()),
Transform::from_xyz(0.0, 0.4, 0.0),
));
parent.spawn((
Name::new("Crate B"),
Mesh3d(meshes.add(Cuboid::new(0.8, 0.8, 0.8))),
MeshMaterial3d(crate_mat),
Transform::from_xyz(0.0, 1.2, 0.0),
));
})
.id();
commands.spawn((
Name::new("Sun"),
DirectionalLight {
illuminance: 12_000.0,
..default()
},
Transform::from_xyz(4.0, 8.0, 4.0).looking_at(Vec3::ZERO, Vec3::Y),
));
// Placeholder camera. T04 adopts it for the editor orbit/pan/zoom controls.
commands.spawn((
Name::new("Editor Camera"),
Camera3d::default(),
Tonemapping::AcesFitted,
Transform::from_xyz(6.0, 5.0, 10.0).looking_at(Vec3::new(0.0, 0.5, 0.0), Vec3::Y),
));
selection.0 = Some(hero);
info!(
"demo scene ready: hero={hero:?}, stack={stack:?}, selected={:?}",
selection.0
);
}