Initial Brokkr scaffold

This commit is contained in:
Meta Vens 2026-06-12 11:57:33 +01:00
commit 16adc98447
16 changed files with 675 additions and 0 deletions

15
.gitignore vendored Normal file
View file

@ -0,0 +1,15 @@
target/
.brokkr/
.brokkr-smoke/
*.user
*.suo
Library/
Temp/
Obj/
Build/
Builds/
Logs/
UserSettings/
__pycache__/
*.pyc

114
Cargo.lock generated Normal file
View file

@ -0,0 +1,114 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 4
[[package]]
name = "anyhow"
version = "1.0.102"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c"
[[package]]
name = "brokkr-daemon"
version = "0.1.0"
dependencies = [
"anyhow",
"serde",
"serde_json",
]
[[package]]
name = "itoa"
version = "1.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
[[package]]
name = "memchr"
version = "2.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "88904434abc2901f197fe8cc55f0445e7ded921dba5911dad2e2b39b48e663c4"
[[package]]
name = "proc-macro2"
version = "1.0.106"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.45"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924"
dependencies = [
"proc-macro2",
]
[[package]]
name = "serde"
version = "1.0.228"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e"
dependencies = [
"serde_core",
"serde_derive",
]
[[package]]
name = "serde_core"
version = "1.0.228"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.228"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "serde_json"
version = "1.0.150"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e8014e44b4736ed0538adeecded0fce2a272f22dc9578a7eb6b2d9993c74cfb9"
dependencies = [
"itoa",
"memchr",
"serde",
"serde_core",
"zmij",
]
[[package]]
name = "syn"
version = "2.0.117"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "unicode-ident"
version = "1.0.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
[[package]]
name = "zmij"
version = "1.0.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa"

6
Cargo.toml Normal file
View file

@ -0,0 +1,6 @@
[workspace]
resolver = "2"
members = [
"brokkr-daemon"
]

44
README.md Normal file
View file

@ -0,0 +1,44 @@
# Brokkr
Brokkr is the GameCult creative-tool broker daemon: a CultMesh service that lets
Unity, Blender, and future editor runtimes expose their live authoring surfaces
to the Verse without pretending those runtimes are the same machine.
The name earns its keep twice. Brokkr is the dwarf at the forge bellows in the
Eddic treasure contest, holding the work steady while the artifact takes shape.
Brokkr is also the broker: the daemon that routes typed editor observations,
capabilities, commands, and Eve/CultUI surfaces between tool runtimes and the
Verse.
## Authority
- Brokkr owns the broker contract, provider advertisement, routing policy, and
typed command receipts.
- Unity owns Unity editor truth: scenes, assets, selection, play mode, imports,
build settings, and editor-side side effects.
- Blender owns Blender editor truth: scenes, objects, assets, operators, add-ons,
render settings, and editor-side side effects.
- Eve owns rendering of Brokkr surfaces.
- Odin owns discovery of Brokkr as a Verse provider.
- CultCache owns durable `.cc` witnesses and command receipts.
Plugins are adapters. They do not own Verse state. They connect their host editor
to Brokkr, publish editor observations, and execute admitted commands from the
broker.
## Surfaces
- `surfaces/unity/Packages/com.gamecult.brokkr`: Unity editor package scaffold.
- `surfaces/blender/brokkr_bridge`: Blender add-on scaffold.
- `brokkr-daemon`: Rust daemon skeleton that emits Brokkr's provider
advertisement and command policy.
## First Smoke
```powershell
cargo run -p brokkr-daemon -- provider
```
The first smoke prints the typed provider advertisement. That is deliberately
small: discovery shape first, live sockets second.

11
brokkr-daemon/Cargo.toml Normal file
View file

@ -0,0 +1,11 @@
[package]
name = "brokkr-daemon"
version = "0.1.0"
edition = "2024"
publish = false
[dependencies]
anyhow = "1"
serde = { version = "1", features = ["derive"] }
serde_json = "1"

170
brokkr-daemon/src/main.rs Normal file
View file

@ -0,0 +1,170 @@
use anyhow::Result;
use serde::Serialize;
const PROVIDER_SCHEMA: &str = "gamecult.brokkr.provider_advertisement.v0";
const PROVIDER_ID: &str = "brokkr.creative_tool_broker";
const CULTMESH_BASE_URI: &str = "cultmesh://brokkr";
#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
struct ProviderAdvertisement {
schema: &'static str,
provider: ProviderIdentity,
authority: Authority,
transports: Vec<Transport>,
tool_surfaces: Vec<ToolSurface>,
eve_surfaces: Vec<EveSurface>,
command_policy: CommandPolicy,
}
#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
struct ProviderIdentity {
id: &'static str,
title: &'static str,
description: &'static str,
version: &'static str,
}
#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
struct Authority {
owner: &'static str,
role: &'static str,
state_owner: &'static str,
presentation_owner: &'static str,
discovery_owner: &'static str,
}
#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
struct Transport {
kind: &'static str,
base_uri: &'static str,
status: &'static str,
}
#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
struct ToolSurface {
id: &'static str,
title: &'static str,
tool_kind: &'static str,
adapter: &'static str,
capabilities: Vec<&'static str>,
}
#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
struct EveSurface {
id: &'static str,
title: &'static str,
cult_mesh_uri: &'static str,
schema: &'static str,
}
#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
struct CommandPolicy {
mode: &'static str,
envelope_schema: &'static str,
receipt_schema: &'static str,
notes: Vec<&'static str>,
}
fn main() -> Result<()> {
let command = std::env::args()
.nth(1)
.unwrap_or_else(|| "provider".to_string());
match command.as_str() {
"provider" => print_provider(),
"smoke" => print_provider(),
_ => {
eprintln!("usage: brokkr-daemon [provider|smoke]");
std::process::exit(2);
}
}
}
fn print_provider() -> Result<()> {
let advertisement = build_provider_advertisement();
println!("{}", serde_json::to_string_pretty(&advertisement)?);
Ok(())
}
fn build_provider_advertisement() -> ProviderAdvertisement {
ProviderAdvertisement {
schema: PROVIDER_SCHEMA,
provider: ProviderIdentity {
id: PROVIDER_ID,
title: "Brokkr",
description: "GameCult creative-tool broker for Unity, Blender, and future editor runtimes.",
version: env!("CARGO_PKG_VERSION"),
},
authority: Authority {
owner: "Brokkr",
role: "CultMesh broker for creative editor observations, command admission, receipts, and Eve/CultUI projection.",
state_owner: "Brokkr typed CultCache witnesses; host editors own host-local scene and asset truth.",
presentation_owner: "Eve/CultUI lowers Brokkr surfaces without owning editor truth.",
discovery_owner: "Odin discovers Brokkr through CultMesh provider advertisements.",
},
transports: vec![Transport {
kind: "cultmesh",
base_uri: CULTMESH_BASE_URI,
status: "scaffold",
}],
tool_surfaces: vec![
ToolSurface {
id: "brokkr.unity_editor",
title: "Unity Editor",
tool_kind: "unity-editor",
adapter: "surfaces/unity/Packages/com.gamecult.brokkr",
capabilities: shared_capabilities(),
},
ToolSurface {
id: "brokkr.blender_editor",
title: "Blender Editor",
tool_kind: "blender-editor",
adapter: "surfaces/blender/brokkr_bridge",
capabilities: shared_capabilities(),
},
],
eve_surfaces: vec![
EveSurface {
id: "brokkr.eve.tool_broker.v0",
title: "Creative Tool Broker",
cult_mesh_uri: "cultmesh://brokkr/eve/tool-broker",
schema: "gamecult.eve.surface.v1",
},
EveSurface {
id: "brokkr.eve.command_receipts.v0",
title: "Command Receipts",
cult_mesh_uri: "cultmesh://brokkr/eve/command-receipts",
schema: "gamecult.eve.surface.v1",
},
],
command_policy: CommandPolicy {
mode: "policy-gated",
envelope_schema: "gamecult.brokkr.command_request.v0",
receipt_schema: "gamecult.brokkr.command_receipt.v0",
notes: vec![
"Plugins are adapters and do not own Verse provider truth.",
"All host mutations must produce command receipts.",
"Unity and Blender commands share one envelope shape.",
],
},
}
}
fn shared_capabilities() -> Vec<&'static str> {
vec![
"host.status.read",
"scene.tree.read",
"selection.read",
"asset.catalog.read",
"command.palette.read",
"command.execute",
"receipt.read",
]
}

86
docs/architecture.md Normal file
View file

@ -0,0 +1,86 @@
# Brokkr Architecture
## Objective
Expose creative editor runtimes to the Verse through one coherent CultMesh broker
daemon, beginning with Unity and Blender plugin surfaces.
## Current Mechanism
Brokkr starts as a Rust daemon skeleton that can emit a provider advertisement.
Unity and Blender each have a thin plugin scaffold with host-local metadata and
connection settings. The live transport is intentionally not claimed yet; the
first contract establishes owner, inputs, outputs, command policy, and surface
names.
## Invariants
- Editor hosts keep editor truth. Brokkr never becomes the canonical scene,
asset, object, import, or build database.
- Brokkr owns Verse-facing broker identity, command admission, routing receipts,
and durable witness publication.
- Unity and Blender commands share the same command envelope and receipt shape.
- Eve/CultUI surfaces are projections over Brokkr/provider state and host
observations, not separate dashboards with their own truth.
- JSON is tolerated only as a schema/debug/export boundary. Durable state should
become CultCache `.cc` witnesses as the runtime grows.
## Intended Change
The Unity idea is widened into a general creative-tool broker: Unity and Blender
are peers behind one broker contract. Tool-specific plugins stay boring and
replaceable. Brokkr becomes the stable Verse organ.
## Cut Line
Do not build separate Unity and Blender Verse providers unless a future invariant
proves they need independent authority. Do not give plugins their own discovery
truth, command policy, or dashboard summaries. They delegate to Brokkr.
## Owner Map
Owner: Brokkr owns tool-to-Verse brokerage.
Inputs:
- Host observations from Unity and Blender plugins.
- Operator commands admitted through Brokkr's command policy.
- Odin discovery queries.
- Eve/CultUI surface requests.
- CultCache witness stores.
Outputs:
- `gamecult.brokkr.provider_advertisement.v0`
- `gamecult.brokkr.tool_host_snapshot.v0`
- `gamecult.brokkr.command_request.v0`
- `gamecult.brokkr.command_receipt.v0`
- Eve/CultUI surface documents for host status, selection, assets, scene/object
trees, command palette, and receipt history.
Derived State:
- Unity package settings are adapter configuration, not Verse authority.
- Blender add-on preferences are adapter configuration, not Verse authority.
- Editor selection and scene summaries are observations until command receipts
confirm an accepted mutation.
Forbidden Writers:
- Unity editor callbacks cannot write Verse provider truth directly.
- Blender operators cannot write Verse provider truth directly.
- Eve renderers cannot admit commands directly to editor hosts.
- Odin discovery records cannot decide command permissions.
Shared Paths:
- Unity commands, Blender commands, programmatic commands, UI-triggered commands,
and replayed command receipts must use the same command envelope.
- Host snapshots from all tools must carry host id, tool kind, project path,
observed-at timestamp, capabilities, and authority owner.
Deletion Line:
If a plugin grows its own command policy, provider advertisement, or durable
state ledger, cut it back into an adapter and move the authority into Brokkr.

27
docs/provider-contract.md Normal file
View file

@ -0,0 +1,27 @@
# Brokkr Provider Contract
Provider id: `brokkr.creative_tool_broker`
CultMesh base URI: `cultmesh://brokkr`
Primary Eve surface id: `brokkr.eve.tool_broker.v0`
## Tool Kinds
- `unity-editor`
- `blender-editor`
## Capability Families
- `host.status.read`
- `scene.tree.read`
- `selection.read`
- `asset.catalog.read`
- `command.palette.read`
- `command.execute`
- `receipt.read`
The scaffold advertises command execution as policy-gated. No host command
should execute unless the host plugin recognizes the command family and Brokkr
has issued a command request with a receipt id.

View file

@ -0,0 +1,17 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "gamecult.brokkr.provider_advertisement.v0",
"title": "Brokkr Provider Advertisement",
"type": "object",
"required": ["schema", "provider", "authority", "transports", "toolSurfaces", "eveSurfaces", "commandPolicy"],
"properties": {
"schema": { "const": "gamecult.brokkr.provider_advertisement.v0" },
"provider": { "type": "object" },
"authority": { "type": "object" },
"transports": { "type": "array" },
"toolSurfaces": { "type": "array" },
"eveSurfaces": { "type": "array" },
"commandPolicy": { "type": "object" }
}
}

View file

@ -0,0 +1,80 @@
bl_info = {
"name": "Brokkr",
"author": "GameCult",
"version": (0, 1, 0),
"blender": (4, 0, 0),
"location": "View3D > Sidebar > Brokkr",
"description": "Blender adapter for the Brokkr CultMesh creative-tool broker.",
"category": "System",
}
import bpy
PROVIDER_ID = "brokkr.blender_editor"
TOOL_KIND = "blender-editor"
DEFAULT_BROKER_URI = "cultmesh://brokkr"
class BrokkrPreferences(bpy.types.AddonPreferences):
bl_idname = __name__
broker_uri: bpy.props.StringProperty(
name="Broker URI",
default=DEFAULT_BROKER_URI,
description="CultMesh broker URI for Brokkr",
)
def draw(self, context):
layout = self.layout
layout.prop(self, "broker_uri")
class BROKKR_PT_status(bpy.types.Panel):
bl_label = "Brokkr"
bl_idname = "BROKKR_PT_status"
bl_space_type = "VIEW_3D"
bl_region_type = "UI"
bl_category = "Brokkr"
def draw(self, context):
layout = self.layout
prefs = context.preferences.addons[__name__].preferences
layout.label(text=f"Provider: {PROVIDER_ID}")
layout.label(text=f"Tool Kind: {TOOL_KIND}")
layout.label(text=f"Broker: {prefs.broker_uri}")
layout.operator("brokkr.capture_snapshot")
class BROKKR_OT_capture_snapshot(bpy.types.Operator):
bl_idname = "brokkr.capture_snapshot"
bl_label = "Capture Host Snapshot"
bl_description = "Scaffold command: report the current Blender host snapshot to the console"
def execute(self, context):
snapshot = {
"schema": "gamecult.brokkr.tool_host_snapshot.v0",
"providerId": PROVIDER_ID,
"toolKind": TOOL_KIND,
"projectPath": bpy.data.filepath,
"scene": context.scene.name if context.scene else "",
}
print(f"Brokkr snapshot scaffold: {snapshot}")
return {"FINISHED"}
classes = (
BrokkrPreferences,
BROKKR_PT_status,
BROKKR_OT_capture_snapshot,
)
def register():
for cls in classes:
bpy.utils.register_class(cls)
def unregister():
for cls in reversed(classes):
bpy.utils.unregister_class(cls)

View file

@ -0,0 +1,10 @@
# Blender Add-on Scaffold
Install `surfaces/blender/brokkr_bridge` as a Blender add-on directory during
local development.
The scaffold adds a Brokkr panel to the 3D View sidebar. It does not open a live
transport yet. Its first job is to preserve the adapter boundary: Blender emits
host snapshots and executes admitted Brokkr commands; Brokkr owns Verse-facing
broker state and receipts.

View file

@ -0,0 +1,18 @@
using UnityEditor;
namespace GameCult.Brokkr.Editor
{
internal static class BrokkrSettings
{
internal const string ProviderId = "brokkr.unity_editor";
internal const string ToolKind = "unity-editor";
internal const string DefaultBrokerUri = "cultmesh://brokkr";
internal static string BrokerUri
{
get => EditorPrefs.GetString("GameCult.Brokkr.BrokerUri", DefaultBrokerUri);
set => EditorPrefs.SetString("GameCult.Brokkr.BrokerUri", value);
}
}
}

View file

@ -0,0 +1,39 @@
using UnityEditor;
using UnityEngine;
namespace GameCult.Brokkr.Editor
{
public sealed class BrokkrWindow : EditorWindow
{
private string brokerUri = BrokkrSettings.DefaultBrokerUri;
[MenuItem("GameCult/Brokkr")]
public static void Open()
{
GetWindow<BrokkrWindow>("Brokkr");
}
private void OnEnable()
{
brokerUri = BrokkrSettings.BrokerUri;
}
private void OnGUI()
{
EditorGUILayout.LabelField("Provider", BrokkrSettings.ProviderId);
EditorGUILayout.LabelField("Tool Kind", BrokkrSettings.ToolKind);
brokerUri = EditorGUILayout.TextField("Broker URI", brokerUri);
if (GUILayout.Button("Save Settings"))
{
BrokkrSettings.BrokerUri = brokerUri;
}
EditorGUILayout.Space();
EditorGUILayout.HelpBox(
"Scaffold only: this adapter should publish Unity editor observations to Brokkr and execute admitted broker commands with receipts.",
MessageType.Info);
}
}
}

View file

@ -0,0 +1,15 @@
using System;
namespace GameCult.Brokkr
{
[Serializable]
public sealed class BrokkrHostSnapshot
{
public string schema = "gamecult.brokkr.tool_host_snapshot.v0";
public string providerId = "brokkr.unity_editor";
public string toolKind = "unity-editor";
public string projectPath = "";
public string observedAt = "";
}
}

View file

@ -0,0 +1,11 @@
{
"name": "com.gamecult.brokkr",
"version": "0.1.0",
"displayName": "Brokkr",
"description": "Unity Editor adapter for the Brokkr CultMesh creative-tool broker.",
"unity": "2022.3",
"author": {
"name": "GameCult"
}
}

12
tools/check.ps1 Normal file
View file

@ -0,0 +1,12 @@
$ErrorActionPreference = "Stop"
Push-Location (Split-Path -Parent $PSScriptRoot)
try {
cargo fmt --check
cargo test --workspace
cargo run -p brokkr-daemon -- provider | Out-Null
}
finally {
Pop-Location
}