From: Svjatoslav Agejenko Date: Sat, 12 Sep 2026 09:58:20 +0000 (+0300) Subject: feat(env): add pluggable world environments X-Git-Url: http://www2.svjatoslav.eu/gitweb/?a=commitdiff_plain;h=ff37193e27da88a84377471ef1ea1ad852a4f5bc;p=sixth.git feat(env): add pluggable world environments Add an EnvironmentProvider contract so the workspace can open a pluggable 3D world by name, with discovery through a registry that pairs built-in providers with ServiceLoader classpath plug-ins. The --env= flag resolves a provider, reports unknown or unavailable environments, and lets the workspace own the provider lifecycle. A built-in grid provider validates the contract without external data. Test-driver flags are recognized anywhere on the command line so they can coexist with --env, and start.sh rebuilds the modules before launching the Fallout 4 environment. --- diff --git a/TODO.org b/TODO.org index 6063160..80b3e5d 100644 --- a/TODO.org +++ b/TODO.org @@ -1,3 +1,4 @@ + * Put agents in 3D world - Agent has body that resembles human @@ -20,3 +21,17 @@ - Kimi K3 + + +* World is built around intent + +- you start with the world and you name intent for it + +- AI is monitoring what you are doing and is always ready to help + - rename space to better capture intent + +- you create new (optionally linked) spaces with (sub intents) + - you can inherit copy of existing apps into new space or star with empty space + + + diff --git a/src/main/java/eu/svjatoslav/sixth/core/Main.java b/src/main/java/eu/svjatoslav/sixth/core/Main.java index b0d232e..c93fc1e 100644 --- a/src/main/java/eu/svjatoslav/sixth/core/Main.java +++ b/src/main/java/eu/svjatoslav/sixth/core/Main.java @@ -24,49 +24,96 @@ public class Main { private static final long SELFTEST_TIMEOUT_MS = 15_000; public static void main(final String[] args) throws Exception { - if (args.length > 0 && "--clicktest".equals(args[0])) + String environmentName = null; + for (final String arg : args) + if (arg.startsWith("--env=")) + environmentName = arg.substring("--env=".length()); + + if (hasArg(args, "--clicktest")) prepareClickTestPage(); - if (args.length > 0 && "--keytest".equals(args[0])) + if (hasArg(args, "--keytest")) prepareKeyTestPage(); - if (args.length > 0 && "--scrolltest".equals(args[0])) + if (hasArg(args, "--scrolltest")) prepareScrollTestPage(); - if (args.length > 0 && "--hovertest".equals(args[0])) + if (hasArg(args, "--hovertest")) prepareHoverTestPage(); final Workspace workspace = new Workspace(); workspace.open(); - if (args.length > 0 && "--selftest".equals(args[0])) + if (environmentName != null) + openRequestedEnvironment(workspace, environmentName); + + if (hasArg(args, "--selftest")) System.exit(runSelfTest(workspace)); - if (args.length > 0 && "--guitest".equals(args[0])) + if (hasArg(args, "--guitest")) System.exit(runGuiTest(workspace)); - if (args.length > 0 && "--clicktest".equals(args[0])) + if (hasArg(args, "--clicktest")) System.exit(runClickTest(workspace)); - if (args.length > 0 && "--keytest".equals(args[0])) + if (hasArg(args, "--keytest")) System.exit(runKeyTest(workspace)); - if (args.length > 0 && "--scrolltest".equals(args[0])) + if (hasArg(args, "--scrolltest")) System.exit(runScrollTest(workspace)); - if (args.length > 0 && "--hovertest".equals(args[0])) + if (hasArg(args, "--hovertest")) System.exit(runHoverTest(workspace)); - if (args.length > 0 && "--headtest".equals(args[0])) + if (hasArg(args, "--headtest")) System.exit(runHeadTest(workspace)); - if (args.length > 0 && "--headdebug".equals(args[0])) + if (hasArg(args, "--headdebug")) startHeadDebugLogger(workspace); Runtime.getRuntime().addShutdownHook( new Thread(workspace::close)); } + /** + * True when the exact flag appears anywhere on the command line; + * test drivers must not be position-sensitive now that --env= can + * occupy args[0]. + */ + private static boolean hasArg(final String[] args, final String flag) { + for (final String arg : args) + if (flag.equals(arg)) + return true; + return false; + } + + /** + * Resolves the --env= provider, checks availability, and opens + * it into the running workspace. Exits with status 1 when the name + * is unknown or the provider's data files are missing. + */ + private static void openRequestedEnvironment(final Workspace workspace, + final String name) + throws Exception { + final var provider + = eu.svjatoslav.sixth.env.EnvironmentRegistry.findByName(name); + if (provider == null) { + System.out.println("no environment named '" + name + "'"); + eu.svjatoslav.sixth.env.EnvironmentRegistry + .printAvailableEnvironments(); + System.exit(1); + } + if (!provider.isAvailable()) { + System.out.println("environment '" + name + "' (" + + provider.getDisplayName() + ") is not available on" + + " this machine — its data files were not found"); + System.exit(1); + } + System.out.println("opening environment: " + + provider.getDisplayName()); + workspace.openEnvironment(provider); + } + /** * Drives the terminal without AWT events: injects bytes straight into * the PTY, exactly what a focused keystroke would send. diff --git a/src/main/java/eu/svjatoslav/sixth/env/EnvironmentContext.java b/src/main/java/eu/svjatoslav/sixth/env/EnvironmentContext.java new file mode 100644 index 0000000..a38319b --- /dev/null +++ b/src/main/java/eu/svjatoslav/sixth/env/EnvironmentContext.java @@ -0,0 +1,71 @@ +/* + * Sixth spatial computing environment. Author: Svjatoslav Agejenko. + * This project is released under Creative Commons Zero (CC0) license. + */ +package eu.svjatoslav.sixth.env; + +import eu.svjatoslav.sixth.e3d.gui.Camera; +import eu.svjatoslav.sixth.e3d.gui.FrameListener; +import eu.svjatoslav.sixth.e3d.gui.ViewPanel; +import eu.svjatoslav.sixth.e3d.renderer.raster.ShapeCollection; + +/** + * Host services handed to an {@link EnvironmentProvider} when it is + * opened. This is deliberately a narrow window into the workspace: the + * scene to add shapes to, the camera to place at the world's spawn + * point, and the per-frame tick for streaming worlds. + */ +public final class EnvironmentContext { + + private final ViewPanel viewPanel; + + public EnvironmentContext(final ViewPanel viewPanel) { + this.viewPanel = viewPanel; + } + + /** + * The root scene graph. The environment adds its shapes here and + * keeps references to whatever it must later remove in close(). + */ + public ShapeCollection getScene() { + return viewPanel.getRootShapeCollection(); + } + + /** + * The workspace camera. An environment typically moves it to its + * spawn point in open(); afterwards the user owns it (fly controls, + * head tracking, SpaceNavigator). + */ + public Camera getCamera() { + return viewPanel.getCamera(); + } + + /** + * Registers a per-frame callback — the streaming world's heartbeat: + * "recompute what should exist around the camera." Return true from + * the callback when the scene changed and needs a repaint. + */ + public void addFrameListener(final FrameListener listener) { + viewPanel.addFrameListener(listener); + } + + public void removeFrameListener(final FrameListener listener) { + viewPanel.removeFrameListener(listener); + } + + /** + * Requests a repaint after scene changes made outside a frame + * callback (e.g. from a background loader thread). + */ + public void requestRepaint() { + viewPanel.repaintDuringNextViewUpdate(); + } + + /** + * Escape hatch to the underlying view panel. Prefer the narrower + * methods above; this exists for things like reading viewport size. + */ + public ViewPanel getViewPanel() { + return viewPanel; + } +} diff --git a/src/main/java/eu/svjatoslav/sixth/env/EnvironmentProvider.java b/src/main/java/eu/svjatoslav/sixth/env/EnvironmentProvider.java new file mode 100644 index 0000000..13881b8 --- /dev/null +++ b/src/main/java/eu/svjatoslav/sixth/env/EnvironmentProvider.java @@ -0,0 +1,77 @@ +/* + * Sixth spatial computing environment. Author: Svjatoslav Agejenko. + * This project is released under Creative Commons Zero (CC0) license. + */ +package eu.svjatoslav.sixth.env; + +/** + * A pluggable 3D environment ("world provider") for the Sixth workspace: + * a game world, a map, or a procedural scene that the workspace panels + * live inside of. Today the wallpaper is a Fallout 4 level, tomorrow a + * Quake map, the day after a 3D fractal — the workspace does not care, + * it only knows this interface. + * + *

Implementations are discovered in two ways:

+ *
    + *
  • built-in providers, registered directly in + * {@link EnvironmentRegistry};
  • + *
  • external providers on the classpath, discovered via + * {@link java.util.ServiceLoader} — a provider jar declares itself in + * {@code META-INF/services/eu.svjatoslav.sixth.env.EnvironmentProvider}. + * This is how separate environment projects (e.g. + * sixth-environment-fo4) plug in without Sixth depending on them.
  • + *
+ * + *

Selection: {@code sixth --env=} picks a provider by + * {@link #getName()}.

+ * + *

Lifecycle: {@link #open(EnvironmentContext)} is called once after the + * workspace window exists. The provider builds its initial scene into + * {@link EnvironmentContext#getScene()}, positions the camera via + * {@link EnvironmentContext#getCamera()}, and — for streaming worlds — + * registers a per-frame callback via + * {@link EnvironmentContext#addFrameListener} whose job is "recompute what + * should exist around the camera, add/remove shapes accordingly." + * {@link #close()} must unregister listeners and release resources.

+ * + *

What an environment is NOT: it never touches the engine internals, + * never creates windows, and never interferes with workspace panels — + * it only adds shapes to the shared scene and moves nothing it does not + * own.

+ */ +public interface EnvironmentProvider { + + /** + * Short unique selector used on the command line, e.g. "fallout4", + * "quake1", "fractal". Lower case, no spaces. + */ + String getName(); + + /** + * Human-readable name, e.g. "Fallout 4 — the Commonwealth". + */ + String getDisplayName(); + + /** + * True when this provider can actually run on this machine — for + * game worlds this means the game data files were found. Providers + * that need no external data (procedural worlds) always return true. + */ + boolean isAvailable(); + + /** + * Builds the initial scene and starts any background streaming. + * Called once, after the workspace window exists. + * + * @param context host services: the scene, the camera, frame ticks + * @throws Exception if the environment cannot be built (for game + * worlds: data files unreadable/corrupt) + */ + void open(EnvironmentContext context) throws Exception; + + /** + * Stops background work and removes the environment's shapes from + * the scene. Must tolerate being called after a failed open(). + */ + void close(); +} diff --git a/src/main/java/eu/svjatoslav/sixth/env/EnvironmentRegistry.java b/src/main/java/eu/svjatoslav/sixth/env/EnvironmentRegistry.java new file mode 100644 index 0000000..14993e8 --- /dev/null +++ b/src/main/java/eu/svjatoslav/sixth/env/EnvironmentRegistry.java @@ -0,0 +1,61 @@ +/* + * Sixth spatial computing environment. Author: Svjatoslav Agejenko. + * This project is released under Creative Commons Zero (CC0) license. + */ +package eu.svjatoslav.sixth.env; + +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.ServiceLoader; + +/** + * Finds the environments that can run on this machine: built-in + * providers plus any declared on the classpath via + * {@code META-INF/services/eu.svjatoslav.sixth.env.EnvironmentProvider} + * (the plug-in mechanism used by separate environment projects such as + * sixth-environment-fo4). + */ +public final class EnvironmentRegistry { + + private EnvironmentRegistry() { + } + + /** + * All discovered providers, available or not. Built-ins first, then + * classpath plug-ins; keyed by name, later duplicates lose. + */ + public static List discoverProviders() { + final Map providers + = new LinkedHashMap<>(); + providers.put("grid", new GridEnvironmentProvider()); + for (final EnvironmentProvider provider + : ServiceLoader.load(EnvironmentProvider.class)) + providers.putIfAbsent(provider.getName(), provider); + return new ArrayList<>(providers.values()); + } + + /** + * Looks up a provider by its command-line name; null when no + * provider with that name is on the classpath. + */ + public static EnvironmentProvider findByName(final String name) { + for (final EnvironmentProvider provider : discoverProviders()) + if (provider.getName().equals(name)) + return provider; + return null; + } + + /** + * Prints the known providers and their availability, for CLI help + * and for the error message when a requested provider is missing. + */ + public static void printAvailableEnvironments() { + System.out.println("known environments:"); + for (final EnvironmentProvider provider : discoverProviders()) + System.out.println(" " + provider.getName() + + (provider.isAvailable() ? "" : " (unavailable)") + + " — " + provider.getDisplayName()); + } +} diff --git a/src/main/java/eu/svjatoslav/sixth/env/GridEnvironmentProvider.java b/src/main/java/eu/svjatoslav/sixth/env/GridEnvironmentProvider.java new file mode 100644 index 0000000..164b602 --- /dev/null +++ b/src/main/java/eu/svjatoslav/sixth/env/GridEnvironmentProvider.java @@ -0,0 +1,54 @@ +/* + * Sixth spatial computing environment. Author: Svjatoslav Agejenko. + * This project is released under Creative Commons Zero (CC0) license. + */ +package eu.svjatoslav.sixth.env; + +import eu.svjatoslav.sixth.e3d.geometry.Rectangle; +import eu.svjatoslav.sixth.e3d.math.Transform; +import eu.svjatoslav.sixth.e3d.renderer.raster.shapes.basic.line.LineAppearance; +import eu.svjatoslav.sixth.e3d.renderer.raster.shapes.composite.wireframe.Grid2D; + +import static eu.svjatoslav.sixth.e3d.renderer.raster.Color.hex; + +/** + * Built-in proof environment: a huge ground grid under the workspace. + * Exists to validate the provider contract (discovery, lifecycle, camera + * placement) without any external data — and as the reference + * implementation for how little a working provider can be. + */ +public class GridEnvironmentProvider implements EnvironmentProvider { + + @Override + public String getName() { + return "grid"; + } + + @Override + public String getDisplayName() { + return "Ground grid (built-in test environment)"; + } + + @Override + public boolean isAvailable() { + return true; + } + + @Override + public void open(final EnvironmentContext context) { + // ground plane at y=0, camera 150 units above it (negative Y is + // up in engine coordinates), looking slightly down + final Transform transform = Transform.fromAngles(0, 0, 0, 0, + Math.PI / 2, 0); + context.getScene().addShape(new Grid2D(transform, + new Rectangle(20000), 40, 40, + new LineAppearance(10, hex("5a5a3a")))); + context.getCamera().getTransform().set(0, -150, -300, 0, -0.4, 0); + context.requestRepaint(); + } + + @Override + public void close() { + // keeps no references and no background work — nothing to undo + } +} diff --git a/src/main/java/eu/svjatoslav/sixth/workspace/Workspace.java b/src/main/java/eu/svjatoslav/sixth/workspace/Workspace.java index 6468596..ed98493 100644 --- a/src/main/java/eu/svjatoslav/sixth/workspace/Workspace.java +++ b/src/main/java/eu/svjatoslav/sixth/workspace/Workspace.java @@ -48,11 +48,24 @@ public class Workspace { private final ViewFrame viewFrame; private final List terminalPanels = new ArrayList<>(); private FirefoxPanel firefoxPanel; + private eu.svjatoslav.sixth.env.EnvironmentProvider environment; public Workspace() { viewFrame = new ViewFrame("Sixth"); } + /** + * Attaches a pluggable environment (game world, procedural scene) + * after open(); the workspace then owns its lifecycle. + */ + public void openEnvironment( + final eu.svjatoslav.sixth.env.EnvironmentProvider provider) + throws Exception { + environment = provider; + provider.open(new eu.svjatoslav.sixth.env.EnvironmentContext( + viewFrame.getViewPanel())); + } + /** * Builds the scene and shows the window. * @@ -158,6 +171,8 @@ public class Workspace { * Shuts down all background shell processes. */ public void close() { + if (environment != null) + environment.close(); for (final TerminalPanel terminalPanel : terminalPanels) terminalPanel.getSession().stop(); if (firefoxPanel != null) diff --git a/start.sh b/start.sh new file mode 100755 index 0000000..e3591c2 --- /dev/null +++ b/start.sh @@ -0,0 +1,34 @@ +#!/usr/bin/env bash +# Start the Sixth workspace with the Fallout 4 environment. +# +# Rebuilds every module in dependency order first, so the classpath is +# always at the latest source state (the FO4 provider resolves `sixth` +# and `sixth-3d` from the local Maven repository — stale installs there +# are the classic reason the world silently doesn't show up). +# +# Extra args are forwarded to the JVM launcher, e.g.: +# ./start.sh -Dsixth.fo4.cell=ConcordExt +# Actually: args go to Main. To pass -D properties, edit JAVA_OPTS below +# or export them before calling. + +set -euo pipefail + +ROOT=/home/n0/workspace/svjatoslav +FO4="$ROOT/sixth-environment-fo4" + +echo "==> building sixth-3d" +mvn -f "$ROOT/sixth-3d/pom.xml" install -q -DskipTests -Dmaven.javadoc.skip=true + +echo "==> building sixth" +mvn -f "$ROOT/sixth/pom.xml" install -q -DskipTests -Dmaven.javadoc.skip=true + +echo "==> building sixth-environment-fo4" +mvn -f "$FO4/pom.xml" package -q -DskipTests -Dmaven.javadoc.skip=true +mvn -f "$FO4/pom.xml" dependency:build-classpath -q -Dmdep.outputFile=target/cp.txt + +echo "==> starting Sixth with --env=fallout4" +cd "$FO4" +exec java -Xmx8g --enable-native-access=ALL-UNNAMED \ + ${JAVA_OPTS:-} \ + -cp "target/classes:$(cat target/cp.txt)" \ + eu.svjatoslav.sixth.core.Main --env=fallout4 "$@"