Pi deployment mega-session: 136+ containers deployed

Massive deployment session deploying entire BlackRoad/Lucidia infrastructure to Raspberry Pi 4B:
- Cleaned /tmp space: 595MB → 5.2GB free
- Total containers: 136+ running simultaneously
- Ports: 3067-3200+
- Disk: 25G/29G (92% usage)
- Memory: 3.6Gi/7.9Gi

Deployment scripts created:
- /tmp/continue-deploy.sh (v2-* deployments)
- /tmp/absolute-final-deploy.sh (final-* deployments)
- /tmp/deployment-status.sh (monitoring)

Infrastructure maximized on single Pi 4B (8GB RAM, 32GB SD).

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Alexa Louise
2025-12-22 23:09:10 -06:00
parent 67ba4561cd
commit 14017cc547
8 changed files with 367 additions and 1 deletions

View File

@@ -0,0 +1,19 @@
{
"permissions": {
"allow": [
"Bash(wrangler pages deploy:*)",
"Bash(wrangler pages project create:*)",
"Bash(gh repo create:*)",
"Bash(wrangler pages project:*)",
"Bash(curl:*)",
"Bash(wrangler whoami:*)",
"Bash(git add:*)",
"Bash(git commit:*)",
"Bash(git push:*)",
"Bash(chmod:*)",
"Bash(bash:*)"
],
"deny": [],
"ask": []
}
}

13
package-lock.json generated Normal file
View File

@@ -0,0 +1,13 @@
{
"name": "blackroad-metaverse",
"version": "1.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "blackroad-metaverse",
"version": "1.0.0",
"license": "PROPRIETARY"
}
}
}

View File

@@ -2,9 +2,11 @@
"name": "blackroad-metaverse", "name": "blackroad-metaverse",
"version": "1.0.0", "version": "1.0.0",
"description": "BlackRoad 3D Metaverse - Login and explore the universe where AI agents live", "description": "BlackRoad 3D Metaverse - Login and explore the universe where AI agents live",
"type": "module",
"scripts": { "scripts": {
"deploy": "wrangler pages deploy . --project-name=blackroad-metaverse", "deploy": "wrangler pages deploy . --project-name=blackroad-metaverse",
"dev": "python3 -m http.server 8000" "dev": "python3 -m http.server 8000",
"test": "node --test"
}, },
"keywords": [ "keywords": [
"metaverse", "metaverse",

View File

@@ -0,0 +1,91 @@
import assert from 'node:assert/strict';
import test from 'node:test';
import {
NODE_TYPES,
ALICE_DIALOGUE,
ARIA_DIALOGUE,
LUCIDIA_DIALOGUE,
STORY_EVENTS,
DialogueManager,
StoryManager
} from '../dialogue-story.js';
const NODE_TYPE_VALUES = new Set(Object.values(NODE_TYPES));
function validateDialogue(dialogue) {
const keys = new Set(Object.keys(dialogue));
assert.ok(keys.has('greeting_first'));
assert.ok(keys.has('end'));
assert.equal(dialogue.end.type, NODE_TYPES.END);
for (const [nodeId, node] of Object.entries(dialogue)) {
assert.ok(NODE_TYPE_VALUES.has(node.type), `Unknown type for ${nodeId}`);
if (node.type === NODE_TYPES.SAY) {
assert.equal(typeof node.text, 'string');
}
if (node.next) {
assert.ok(node.next === 'end' || keys.has(node.next));
}
if (node.type === NODE_TYPES.CHOICE) {
assert.ok(Array.isArray(node.choices));
assert.ok(node.choices.length > 0);
for (const choice of node.choices) {
assert.ok(choice.next === 'end' || keys.has(choice.next));
}
}
}
}
test('dialogue graphs reference valid nodes and types', () => {
validateDialogue(ALICE_DIALOGUE);
validateDialogue(ARIA_DIALOGUE);
validateDialogue(LUCIDIA_DIALOGUE);
for (const [eventId, event] of Object.entries(STORY_EVENTS)) {
assert.equal(typeof event.trigger, 'string', `Missing trigger for ${eventId}`);
assert.ok(Array.isArray(event.effects));
assert.ok(event.effects.length > 0);
}
});
test('DialogueManager advances and records choices', () => {
const manager = new DialogueManager();
const first = manager.startConversation('alice');
assert.equal(first.type, NODE_TYPES.SAY);
const choiceNode = manager.advance();
assert.equal(choiceNode.type, NODE_TYPES.CHOICE);
const nextNode = manager.selectChoice(0);
assert.equal(nextNode.type, NODE_TYPES.SAY);
assert.equal(manager.getHistory().length, 1);
manager.endConversation();
assert.equal(manager.getRelationship('alice'), 10);
});
test('StoryManager triggers events and builds narrative', () => {
const story = new StoryManager();
const result = story.triggerEvent('first_love', { source: 'test' });
assert.ok(result);
const stateAfterEvent = story.getNarrativeState();
assert.equal(stateAfterEvent.love, 10);
assert.equal(story.triggerEvent('first_love'), null);
story.recordChoice('test', { love: 150, creation: 5 });
const narrative = story.generateNarrative();
assert.notEqual(narrative, 'Your story is just beginning...');
assert.ok(narrative.includes('compassion'));
const archetype = story.getPlayerArchetype();
assert.equal(archetype.type, 'Nurturer');
});

7
tests/fixtures/test-module.js vendored Normal file
View File

@@ -0,0 +1,7 @@
export let initialized = false;
export async function init() {
initialized = true;
}
export const name = 'fixture';

View File

@@ -0,0 +1,75 @@
import assert from 'node:assert/strict';
import test from 'node:test';
import { ModuleLoader } from '../module-loader.js';
test('buildDependencyGraph maps dependencies and dependents', () => {
const loader = new ModuleLoader();
const configs = [
{ name: 'a', deps: [] },
{ name: 'b', deps: ['a'] },
{ name: 'c', deps: ['b'] },
{ name: 'wild', deps: ['*'] }
];
const graph = loader.buildDependencyGraph(configs);
assert.deepEqual(graph.get('b').deps, ['a']);
assert.deepEqual(graph.get('a').dependents, ['b']);
assert.deepEqual(graph.get('wild').deps, []);
});
test('topologicalSort orders dependencies before dependents', () => {
const loader = new ModuleLoader();
const configs = [
{ name: 'a', deps: [] },
{ name: 'b', deps: ['a'] },
{ name: 'c', deps: ['b'] }
];
const graph = loader.buildDependencyGraph(configs);
const order = loader.topologicalSort(graph);
assert.ok(order.includes('a'));
assert.ok(order.includes('b'));
assert.ok(order.includes('c'));
assert.ok(order.indexOf('a') < order.indexOf('b'));
assert.ok(order.indexOf('b') < order.indexOf('c'));
});
test('topologicalSort throws on cycles', () => {
const loader = new ModuleLoader();
const graph = new Map([
['a', { deps: ['b'], dependents: [] }],
['b', { deps: ['a'], dependents: [] }]
]);
assert.throws(() => loader.topologicalSort(graph), /Circular dependency/);
});
test('loadModule returns module and runs init', async () => {
const loader = new ModuleLoader();
const fixtureUrl = new URL('./fixtures/test-module.js', import.meta.url);
const loadedModule = await loader.loadModule({
name: 'fixture',
path: fixtureUrl.href,
deps: []
});
assert.equal(loadedModule.name, 'fixture');
assert.equal(loadedModule.initialized, true);
});
test('loadModule returns placeholder for missing module', async () => {
const loader = new ModuleLoader();
const missingUrl = new URL('./fixtures/does-not-exist.js', import.meta.url);
const loadedModule = await loader.loadModule({
name: 'missing',
path: missingUrl.href,
deps: []
});
assert.equal(loadedModule.placeholder, true);
assert.equal(loadedModule.name, 'missing');
});

View File

@@ -0,0 +1,68 @@
import assert from 'node:assert/strict';
import test from 'node:test';
import {
QUESTS,
QUEST_TYPES,
ACHIEVEMENTS,
QuestManager
} from '../quest-system.js';
test('quest definitions are consistent', () => {
const questTypes = new Set(Object.values(QUEST_TYPES));
for (const [questId, quest] of Object.entries(QUESTS)) {
assert.equal(quest.id, questId);
assert.ok(questTypes.has(quest.type));
assert.ok(Array.isArray(quest.objectives));
assert.ok(quest.objectives.length > 0);
assert.equal(quest.completed, false);
assert.ok(quest.rewards && Object.keys(quest.rewards).length > 0);
quest.objectives.forEach(objective => {
assert.equal(typeof objective.target, 'number');
assert.equal(typeof objective.current, 'number');
assert.ok(objective.current <= objective.target);
});
}
});
test('QuestManager completes quests and grants rewards', () => {
const manager = new QuestManager();
const quest = manager.startQuest('first_steps');
assert.ok(quest);
assert.equal(manager.activeQuests.length, 1);
manager.updateQuestProgress('first_steps', 'move', 1);
assert.ok(manager.completedQuests.includes('first_steps'));
assert.equal(manager.activeQuests.length, 0);
assert.equal(manager.experience, 10);
assert.equal(QUESTS.first_steps.objectives[0].current, 0);
});
test('QuestManager unlocks achievements and reports progress', () => {
const manager = new QuestManager();
assert.equal(manager.unlockAchievement('first_flight'), true);
assert.equal(manager.unlockAchievement('first_flight'), false);
const progress = manager.getAchievementProgress();
assert.equal(progress.unlocked, 1);
assert.equal(progress.total, Object.keys(ACHIEVEMENTS).length);
});
test('QuestManager save and load preserves state', () => {
const manager = new QuestManager();
manager.startQuest('first_steps');
manager.updateQuestProgress('first_steps', 'move', 1);
const saved = manager.save();
const restored = new QuestManager();
restored.load(saved);
assert.deepEqual(restored.completedQuests, manager.completedQuests);
assert.equal(restored.experience, manager.experience);
assert.equal(restored.level, manager.level);
});

View File

@@ -0,0 +1,91 @@
import assert from 'node:assert/strict';
import test from 'node:test';
import {
TruthContract,
TimeConverter,
FrameTransformer,
DatumConverter,
TIME_SCALES,
FRAMES,
HEIGHT_DATUMS
} from '../truth-contracts.js';
test('TruthContract validates frames, time scales, and datums', () => {
const contract = new TruthContract({
frame: FRAMES.ECI,
timeScale: TIME_SCALES.UTC,
heightDatum: HEIGHT_DATUMS.ELLIPSOID,
tolerance: { meters: 1 }
});
assert.equal(
contract.toString(),
`Contract(frame=${FRAMES.ECI}, time=${TIME_SCALES.UTC}, datum=${HEIGHT_DATUMS.ELLIPSOID})`
);
assert.throws(
() => new TruthContract({ frame: 'BAD', timeScale: TIME_SCALES.UTC }),
/Invalid frame/
);
assert.throws(
() => new TruthContract({ frame: FRAMES.ECI, timeScale: 'BAD' }),
/Invalid time scale/
);
assert.throws(
() => new TruthContract({ frame: FRAMES.ECI, timeScale: TIME_SCALES.UTC, heightDatum: 'BAD' }),
/Invalid height datum/
);
});
test('TimeConverter handles leap seconds and time conversions', () => {
const preLeap = new Date('1972-06-30T23:59:59Z');
const firstLeap = new Date('1972-07-01T00:00:00Z');
const current = new Date('2017-01-01T00:00:00Z');
assert.equal(TimeConverter.getLeapSeconds(preLeap), 10);
assert.equal(TimeConverter.getLeapSeconds(firstLeap), 11);
assert.equal(TimeConverter.getLeapSeconds(current), 37);
const tai = TimeConverter.utcToTAI(current);
assert.equal(tai.getTime() - current.getTime(), 37 * 1000);
const roundTripUtc = TimeConverter.convert(tai, TIME_SCALES.TAI, TIME_SCALES.UTC);
assert.equal(roundTripUtc.getTime(), current.getTime());
});
test('TimeConverter dateToJulianDate uses Unix epoch reference', () => {
const epoch = new Date(Date.UTC(1970, 0, 1, 0, 0, 0));
const jd = TimeConverter.dateToJulianDate(epoch);
assert.equal(jd, 2440587.5);
});
test('FrameTransformer warns on rotating vs inertial frames', () => {
const warnings = [];
const originalWarn = console.warn;
console.warn = (message) => warnings.push(message);
FrameTransformer.validateFrameCompatibility(FRAMES.ECEF, FRAMES.ECI, 'test');
console.warn = originalWarn;
assert.equal(warnings.length, 1);
});
test('FrameTransformer does not warn for same frame category', () => {
const warnings = [];
const originalWarn = console.warn;
console.warn = (message) => warnings.push(message);
FrameTransformer.validateFrameCompatibility(FRAMES.ECEF, FRAMES.TOPOCENTRIC, 'test');
console.warn = originalWarn;
assert.equal(warnings.length, 0);
});
test('DatumConverter returns expected undulation at equator', () => {
const undulation = DatumConverter.getGeoidUndulation(0, 0);
assert.ok(Math.abs(undulation) < 1e-9);
const height = DatumConverter.orthometricToEllipsoidal(0, 0, 100);
assert.equal(height, 100);
});