Files
feuerwerk/tools/seed-e2e.mjs
T
rootandClaude Opus 5 86b5363a01
game-maker / suite (push) Successful in 9m10s
Fonts: a new asset type, picked from the server or dropped in, worn by text objects (#74)
A font asset holds a file and nothing else, the twin of a texture: the server keeps it
content-addressed under data/fonts/{sha256}.{ext}, with the format read from the file's own
four-byte signature (finfo reports fonts inconsistently across libmagic versions). A text
wears one through its ordinary Font property, so overrides, reset, undo and the Properties
node come for free; no font means the editor's own typeface.

The picker lists everything the server holds and renders each row in the face it offers.
The family name is gm-font-<hash16>, never the name inside the file, so two "Regular" cannot
collide. WorldViewport.syncText is the single place that draws it, which covers scene, game
preview and play at once.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-13 20:09:59 +02:00

115 lines
4.4 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env node
/**
* Writes the fixture scene tools/e2e.mjs asserts against: two top-level crates with a collider
* child each, a camera, the Mover behavior owning the red crate and a Spawner. The objects are
* uniquely named and positioned exactly where the checks expect them.
*
* Usage: node tools/seed-e2e.mjs [projectId] [baseUrl]
*/
import { edge, node, port, sceneObject, variable } from './lib/builders.mjs';
import { PROJECTS, cliArgs, uploadFont, writeProject } from './lib/projects.mjs';
import { adjustColorsShader } from './lib/shaders.mjs';
const { projectId, baseUrl } = cliArgs('seed-e2e.mjs', PROJECTS.fixture);
/**
* Two typefaces for the font step to pick and to drop. They are put on the server but not made
* into assets: the step is about the picker and the drop making them. The files come from the
* system rather than the repo – the CI image installs the same DejaVu package the host has,
* because the checks measure text widths (see tools/ci/Dockerfile).
*/
const SYSTEM_FONTS = '/usr/share/fonts/truetype/dejavu';
await uploadFont(baseUrl, `${SYSTEM_FONTS}/DejaVuSerif.ttf`);
await uploadFont(baseUrl, `${SYSTEM_FONTS}/DejaVuSansMono.ttf`);
/**
* "Mover" in five nodes: every frame, move the owner along the normalized arrow-key axis,
* scaled by the `speed` variable per second (On update's dt feeds the dt input). "Collide with
* solids" stops the owner's solid colliders at other solid colliders.
*/
const moverGraph = {
nodes: [
node('n-update', 'OnUpdate', 0, 0),
node('n-owner', 'Owner', 0, 120),
node('n-axis', 'AxisInput', 0, 200),
node('n-speed', 'Variables', 0, 400, {}, [port('speed', 'number')]),
node('n-move', 'Move', 340, 0, { collide: true }),
],
edges: [
edge('e1', 'n-update', 'exec', 'n-move', 'exec'),
edge('e6', 'n-update', 'dt', 'n-move', 'dt'),
edge('e2', 'n-owner', 'object', 'n-move', 'object'),
edge('e3', 'n-axis', 'x', 'n-move', 'dx'),
edge('e4', 'n-axis', 'y', 'n-move', 'dy'),
edge('e5', 'n-speed', 'speed', 'n-move', 'scale'),
],
};
/** "Spawner": pressing Space places a new Crate at world (0, 150) and logs its name. */
const spawnerGraph = {
nodes: [
node('s-key', 'OnKeyDown', 0, 0, { key: ' ' }),
node('s-spawn', 'Spawn', 260, 0, { asset: 'crate', y: 150 }),
node('s-log', 'Log', 520, 0),
],
edges: [
edge('s-e1', 's-key', 'exec', 's-spawn', 'exec'),
edge('s-e2', 's-spawn', 'exec', 's-log', 'exec'),
edge('s-e3', 's-spawn', 'object', 's-log', 'message'),
],
};
const project = {
formatVersion: 2,
id: projectId,
name: 'E2E fixture',
activeSceneId: 'scene1',
assets: [
{
id: 'scene1',
type: 'scene',
name: 'Level 1',
props: { backgroundColor: 0x22252c },
objects: [
sceneObject('o1', 'crate', { x: -150 }),
sceneObject('o2', 'crate', {
name: 'Blue crate',
x: 120,
y: -80,
overrides: { width: 140, color: 0x4488ff },
}),
sceneObject('o3', 'cam'),
// The Mover is parented to the red crate (its owner); its position is local to it.
sceneObject('o4', 'mover', { y: 110, parentId: 'o1' }),
// Both crates carry the shared collider as a centred child; the blue one is widened to
// match its 140-wide sprite override.
sceneObject('oc1', 'collider', { parentId: 'o1' }),
sceneObject('oc2', 'collider', { parentId: 'o2', overrides: { width: 140 } }),
// Top-level spawner (no owner) left of the red crate: Space spawns a crate.
sceneObject('o5', 'spawner', { x: -300 }),
],
},
{ id: 'crate', type: 'sprite', name: 'Crate', props: { width: 100, height: 60, color: 0xe0553f } },
{ id: 'cam', type: 'camera', name: 'Main camera', props: { width: 640, height: 360, zoom: 1 } },
{
id: 'collider',
type: 'collider',
name: 'Crate collider',
props: { shape: 'box', width: 100, height: 60, radius: 50, isTrigger: false },
},
{
id: 'mover',
type: 'behavior',
name: 'Mover',
props: { speed: 200 },
variables: [variable('speed', 'number', 0)],
graph: moverGraph,
},
{ id: 'spawner', type: 'behavior', name: 'Spawner', props: {}, variables: [], graph: spawnerGraph },
// Worn by nothing here; the shader step assigns it and reads the pixels.
adjustColorsShader(),
],
};
await writeProject(projectId, baseUrl, project);