Initial commit

moved from private version control
This commit is contained in:
2025-10-19 21:12:41 +11:00
parent 5e84773bbe
commit e64f3e881e
187 changed files with 25372 additions and 0 deletions

View File

@@ -0,0 +1,296 @@
import { BlueprintNode } from "../core/BlueprintNode.js";
import { nodeModules } from "./library/index.js";
/** @typedef {import('./nodeTypes.js').NodeDefinition} NodeDefinition */
/** @typedef {import('./nodeTypes.js').NodeBehavior} NodeBehavior */
/** @typedef {import('./nodeTypes.js').NodeModule} NodeModule */
/** @typedef {{ value: string, weight: number }} SearchField */
/**
* Central registry providing node definitions, behavior hooks, and factory helpers.
*/
export class NodeRegistry {
constructor() {
/** @type {Map<string, NodeDefinition>} */
this.definitions = new Map();
/** @type {Map<string, NodeBehavior>} */
this.behaviors = new Map();
/** @type {Set<string>} */
this.entryNodeTypes = new Set();
/** @type {Map<string, '_init'|'_update'|'_draw'|'_update60'>} */
this.entryPointEvents = new Map();
nodeModules.forEach((module) => this.registerModule(module));
}
/**
* Normalizes search inputs for case-insensitive comparisons.
*
* @param {string} query Raw query string.
* @returns {string}
*/
#normalizeQuery(query) {
return (query ?? "").trim().toLowerCase();
}
/**
* Determines whether a node definition matches the provided query.
*
* @param {NodeDefinition} definition Definition to inspect.
* @param {string} query Search query.
* @returns {boolean}
*/
matchesDefinition(definition, query) {
const normalized = this.#normalizeQuery(query);
if (!normalized) {
return true;
}
const score = this.#scoreDefinition(definition, normalized);
return Number.isFinite(score);
}
/**
* Enumerates every registered definition.
*
* @returns {Array<NodeDefinition>}
*/
list() {
return [...this.definitions.values()];
}
/**
* Retrieves a single node definition.
*
* @param {string} id Definition identifier.
* @returns {NodeDefinition | undefined}
*/
get(id) {
return this.definitions.get(id);
}
/**
* Performs a fuzzy search across node metadata and returns results sorted by distance.
*
* @param {string} query Search input.
* @returns {Array<NodeDefinition>}
*/
search(query) {
const normalized = this.#normalizeQuery(query);
if (!normalized) {
return this.list().sort((a, b) => a.title.localeCompare(b.title));
}
/** @type {Array<{ definition: NodeDefinition, score: number }>} */
const ranked = [];
this.definitions.forEach((definition) => {
const score = this.#scoreDefinition(definition, normalized);
if (Number.isFinite(score)) {
ranked.push({ definition, score });
}
});
ranked.sort((a, b) => {
if (a.score === b.score) {
return a.definition.title.localeCompare(b.definition.title);
}
return a.score - b.score;
});
return ranked.map((entry) => entry.definition);
}
/**
* Aggregates searchable fields for a node definition and assigns weighting.
*
* @param {NodeDefinition} definition Definition to inspect.
* @returns {Array<SearchField>} Normalized field collection with weights.
*/
#collectSearchFields(definition) {
const fields = /** @type {Array<SearchField>} */ ([
{ value: definition.title, weight: 0.8 },
{ value: definition.category, weight: 1 },
]);
if (definition.description) {
fields.push({ value: definition.description, weight: 1 });
}
if (Array.isArray(definition.searchTags)) {
definition.searchTags.forEach((tag) => {
fields.push({ value: tag, weight: 1 });
});
}
return fields
.filter((field) => typeof field.value === "string" && field.value.trim().length)
.map((field) => ({
value: field.value.toLowerCase(),
weight: field.weight,
}));
}
/**
* Computes a fuzzy score representing how closely a definition matches a query.
*
* @param {NodeDefinition} definition Target definition.
* @param {string} normalizedQuery Lowercase trimmed query string.
* @returns {number} Matching score where lower values are closer; Infinity means no match.
*/
#scoreDefinition(definition, normalizedQuery) {
const fields = this.#collectSearchFields(definition);
if (!fields.length) {
return Number.POSITIVE_INFINITY;
}
let best = Number.POSITIVE_INFINITY;
for (const field of fields) {
const score = this.#computeFuzzyScore(normalizedQuery, field.value);
if (!Number.isFinite(score)) {
continue;
}
const weighted = score * field.weight;
if (weighted < best) {
best = weighted;
}
if (best === 0) {
break;
}
}
return best;
}
/**
* Produces a fuzzy matching distance between the query and a candidate string.
*
* @param {string} query Normalized query string.
* @param {string} candidate Normalized candidate string.
* @returns {number} Distance score; Infinity indicates the query is not a subsequence of the candidate.
*/
#computeFuzzyScore(query, candidate) {
if (!candidate) {
return Number.POSITIVE_INFINITY;
}
const qLength = query.length;
const cLength = candidate.length;
if (!qLength) {
return 0;
}
let qIndex = 0;
let score = 0;
let lastMatch = -1;
for (let cIndex = 0; cIndex < cLength; cIndex += 1) {
if (candidate[cIndex] !== query[qIndex]) {
continue;
}
if (lastMatch === -1) {
score += cIndex;
} else {
score += cIndex - lastMatch - 1;
}
lastMatch = cIndex;
qIndex += 1;
if (qIndex === qLength) {
break;
}
}
if (qIndex !== qLength) {
return Number.POSITIVE_INFINITY;
}
score += cLength - lastMatch - 1;
return score;
}
/**
* Instantiates a blueprint node from a definition.
*
* @param {string} definitionId Definition identifier.
* @param {{id: string, position: {x:number,y:number}}} options Initialization options.
* @returns {BlueprintNode}
*/
createNode(definitionId, options) {
const definition = this.get(definitionId);
if (!definition) {
throw new Error(`Unknown node definition: ${definitionId}`);
}
const properties = {};
definition.properties.forEach((schema) => {
const value = schema.defaultValue ?? null;
properties[schema.key] = value;
});
definition.initializeProperties?.(properties);
return new BlueprintNode({
id: options.id,
type: definition.id,
title: definition.title,
position: { ...options.position },
inputs: definition.inputs.map((pin) => ({
...pin,
})),
outputs: definition.outputs.map((pin) => ({
...pin,
})),
properties,
});
}
/**
* Registers a node module exposing editor metadata and Lua behavior.
*
* @param {NodeModule} module Node module to register.
*/
registerModule(module) {
const { definition, behavior } = module;
this.definitions.set(definition.id, definition);
if (behavior) {
this.behaviors.set(definition.id, behavior);
if (behavior.isEntryPoint) {
this.entryNodeTypes.add(definition.id);
if (behavior.eventName) {
this.entryPointEvents.set(definition.id, behavior.eventName);
}
}
}
}
/**
* Retrieves behavior metadata for a node type.
*
* @param {string} id Node identifier.
* @returns {NodeBehavior | undefined}
*/
getBehavior(id) {
return this.behaviors.get(id);
}
/**
* Enumerates node types flagged as entry points.
*
* @returns {Array<string>}
*/
getEntryNodeTypes() {
return [...this.entryNodeTypes];
}
/**
* Exposes lifecycle event bindings for entry nodes.
*
* @returns {Map<string, '_init'|'_update'|'_draw'|'_update60'>}
*/
getEntryPointEvents() {
return new Map(this.entryPointEvents);
}
}

View File

@@ -0,0 +1,41 @@
import { createNodeModule } from "../nodeTypes.js";
/**
* Adds two numeric values.
*/
export const addNumberNode = createNodeModule(
{
id: "add_number",
title: "Add",
category: "Math",
description: "Add two numbers.",
searchTags: ["add", "math", "sum", "number"],
inputs: [
{
id: "a",
name: "A",
direction: "input",
kind: "number",
defaultValue: 0,
},
{
id: "b",
name: "B",
direction: "input",
kind: "number",
defaultValue: 0,
},
],
outputs: [
{ id: "res", name: "Result", direction: "output", kind: "number" },
],
properties: [],
},
{
evaluateValue: ({ resolveValueInput }) => {
const a = resolveValueInput("a", "0");
const b = resolveValueInput("b", "0");
return `(${a}) + (${b})`;
},
}
);

View File

@@ -0,0 +1,16 @@
import { sfxNode } from "./sfx.js";
import { musicNode } from "./music.js";
/**
* All audio-related node modules backed by PICO-8 helpers.
*/
export const audioNodes = [sfxNode, musicNode];
/**
* Checklist tracking coverage of documented audio helpers.
* @type {Array<{ function: string, nodeId: string, implemented: boolean }>}
*/
export const audioFunctionChecklist = [
{ function: "SFX", nodeId: "audio_sfx", implemented: true },
{ function: "MUSIC", nodeId: "audio_music", implemented: true },
];

View File

@@ -0,0 +1,64 @@
import { createNodeModule } from "../../nodeTypes.js";
/**
* Plays or stops music using the PICO-8 music helper.
*/
export const musicNode = createNodeModule(
{
id: "audio_music",
title: "Play Music",
category: "Audio",
description:
"Start, fade, or stop music playback using MUSIC().",
searchTags: ["music", "audio", "song", "pattern"],
inputs: [
{ id: "exec_in", name: "Exec", direction: "input", kind: "exec" },
{
id: "pattern",
name: "Pattern",
direction: "input",
kind: "number",
description: "Pattern index or command",
defaultValue: 0,
},
{
id: "fade",
name: "Fade (ms)",
direction: "input",
kind: "number",
description: "Fade duration in milliseconds",
},
{
id: "mask",
name: "Channel Mask",
direction: "input",
kind: "number",
description: "Bitmask reserving channels",
},
],
outputs: [
{ id: "exec_out", name: "Exec", direction: "output", kind: "exec" },
],
properties: [],
},
{
emitExec: ({ indent, indentLevel, resolveValueInput, emitNextExec }) => {
const OMIT = "__pg_omit__";
const pattern = resolveValueInput("pattern", "0");
const fade = resolveValueInput("fade", OMIT);
const mask = resolveValueInput("mask", OMIT);
const args = [pattern];
if (fade !== OMIT || mask !== OMIT) {
const fadeArg = fade === OMIT ? "nil" : fade;
args.push(fadeArg);
if (mask !== OMIT) {
args.push(mask);
}
}
const line = `${indent(indentLevel)}music(${args.join(", ")})`;
return [line, ...emitNextExec("exec_out")];
},
}
);

View File

@@ -0,0 +1,76 @@
import { createNodeModule } from "../../nodeTypes.js";
/**
* Plays or controls a sound effect using the PICO-8 sfx helper.
*/
export const sfxNode = createNodeModule(
{
id: "audio_sfx",
title: "Play SFX",
category: "Audio",
description:
"Trigger a sound effect, control looping, or stop playback via SFX().",
searchTags: ["sfx", "audio", "sound", "effect"],
inputs: [
{ id: "exec_in", name: "Exec", direction: "input", kind: "exec" },
{
id: "id",
name: "SFX",
direction: "input",
kind: "number",
description: "Sound effect slot or command",
defaultValue: 0,
},
{
id: "channel",
name: "Channel",
direction: "input",
kind: "number",
description: "Playback channel (-1 auto)",
},
{
id: "offset",
name: "Offset",
direction: "input",
kind: "number",
description: "Note offset",
},
{
id: "length",
name: "Length",
direction: "input",
kind: "number",
description: "Number of notes to play",
},
],
outputs: [
{ id: "exec_out", name: "Exec", direction: "output", kind: "exec" },
],
properties: [],
},
{
emitExec: ({ indent, indentLevel, resolveValueInput, emitNextExec }) => {
const OMIT = "__pg_omit__";
const id = resolveValueInput("id", "0");
const channel = resolveValueInput("channel", OMIT);
const offset = resolveValueInput("offset", OMIT);
const length = resolveValueInput("length", OMIT);
const args = [id];
if (channel !== OMIT || offset !== OMIT || length !== OMIT) {
const channelArg = channel === OMIT ? "nil" : channel;
args.push(channelArg);
if (offset !== OMIT || length !== OMIT) {
const offsetArg = offset === OMIT ? "nil" : offset;
args.push(offsetArg);
if (length !== OMIT) {
args.push(length);
}
}
}
const line = `${indent(indentLevel)}sfx(${args.join(", ")})`;
return [line, ...emitNextExec("exec_out")];
},
}
);

View File

@@ -0,0 +1,26 @@
import { createNodeModule } from "../nodeTypes.js";
/**
* Constant boolean literal.
*/
export const booleanLiteralNode = createNodeModule(
{
id: "boolean_literal",
title: "Boolean",
category: "Values",
description: "Constant boolean literal.",
searchTags: ["boolean", "literal", "true", "false"],
inputs: [],
outputs: [
{ id: "value", name: "Value", direction: "output", kind: "boolean" },
],
properties: [
{ key: "value", label: "Value", type: "boolean", defaultValue: true },
],
},
{
evaluateValue: ({ node, formatLiteral }) => {
return formatLiteral("boolean", node.properties.value ?? true);
},
}
);

View File

@@ -0,0 +1,32 @@
import { createNodeModule } from "../nodeTypes.js";
/**
* Invokes a custom event by reference.
* @type {import('../nodeTypes.js').NodeModule}
*/
export const callCustomEventNode = createNodeModule(
{
id: "call_custom_event",
title: "Call Custom Event",
category: "Events",
description: "Invokes a custom event defined elsewhere in the graph.",
searchTags: ["event", "call", "trigger", "custom"],
inputs: [{ id: "exec_in", name: "Exec", direction: "input", kind: "exec" }],
outputs: [
{ id: "exec_out", name: "Exec", direction: "output", kind: "exec" },
],
properties: [],
initializeProperties: (properties) => {
properties.eventId =
typeof properties.eventId === "string" ? properties.eventId : "";
if (
!properties.arguments ||
typeof properties.arguments !== "object" ||
Array.isArray(properties.arguments)
) {
properties.arguments = {};
}
},
},
null
);

View File

@@ -0,0 +1,47 @@
import { createNodeModule } from "../nodeTypes.js";
/**
* Compares two values using a selected operator.
*/
export const compareNode = createNodeModule(
{
id: "compare",
title: "Compare",
category: "Logic",
description: "Compare two values with a selected operator.",
searchTags: ["compare", "logic", "condition", "branch"],
inputs: [
{ id: "a", name: "A", direction: "input", kind: "any" },
{ id: "b", name: "B", direction: "input", kind: "any" },
],
outputs: [
{ id: "res", name: "Result", direction: "output", kind: "boolean" },
],
properties: [
{
key: "operator",
label: "Operator",
type: "enum",
defaultValue: "==",
options: [
{ label: "Equal", value: "==" },
{ label: "Not Equal", value: "!=" },
{ label: "Greater", value: ">" },
{ label: "Less", value: "<" },
{ label: "Greater Or Equal", value: ">=" },
{ label: "Less Or Equal", value: "<=" },
],
},
],
},
{
evaluateValue: ({ node, resolveValueInput, sanitizeOperator }) => {
const a = resolveValueInput("a", "0");
const b = resolveValueInput("b", "0");
const operator = sanitizeOperator(
String(node.properties.operator ?? "==")
);
return `(${a}) ${operator} (${b})`;
},
}
);

View File

@@ -0,0 +1,44 @@
import { createNodeModule } from "../nodeTypes.js";
/**
* Defines a reusable custom event entry point.
* @type {import('../nodeTypes.js').NodeModule}
*/
export const customEventNode = createNodeModule(
{
id: "custom_event",
title: "Custom Event",
category: "Events",
description: "Defines a custom event that can be triggered elsewhere.",
searchTags: ["event", "custom", "broadcast", "trigger"],
inputs: [],
outputs: [
{ id: "exec_out", name: "Exec", direction: "output", kind: "exec" },
],
properties: [
{
key: "name",
label: "Event Name",
type: "string",
defaultValue: "CustomEvent",
placeholder: "Enter event name",
},
],
initializeProperties: (properties) => {
if (typeof properties.name !== "string" || !properties.name.trim()) {
properties.name = "CustomEvent";
}
if (!Array.isArray(properties.parameters)) {
properties.parameters = [];
}
const counter = Number.isFinite(properties.parameterCounter)
? Number(properties.parameterCounter)
: 0;
properties.parameterCounter = counter;
},
},
{
isEntryPoint: true,
emitExec: ({ emitNextExec }) => emitNextExec("exec_out"),
}
);

View File

@@ -0,0 +1,33 @@
import { createNodeModule } from "../../nodeTypes.js";
/**
* Opens persistent cartridge storage using cartdata.
*/
export const cartdataNode = createNodeModule(
{
id: "data_cartdata",
title: "Cart Data Init",
category: "Data",
description: "Setup persistent storage for the cartridge using CARTDATA().",
searchTags: ["cartdata", "save", "persistent", "data"],
inputs: [
{
id: "id",
name: "Identifier",
direction: "input",
kind: "string",
defaultValue: "\"my_cart\"",
},
],
outputs: [
{ id: "loaded", name: "Loaded", direction: "output", kind: "boolean" },
],
properties: [],
},
{
evaluateValue: ({ resolveValueInput }) => {
const id = resolveValueInput("id", '"pico_cart"');
return `cartdata(${id})`;
},
}
);

View File

@@ -0,0 +1,33 @@
import { createNodeModule } from "../../nodeTypes.js";
/**
* Reads a persistent value using dget.
*/
export const dgetNode = createNodeModule(
{
id: "data_dget",
title: "Data Get",
category: "Data",
description: "Retrieve a persistent number using DGET().",
searchTags: ["dget", "load", "data", "persistent"],
inputs: [
{
id: "index",
name: "Index",
direction: "input",
kind: "number",
defaultValue: 0,
},
],
outputs: [
{ id: "value", name: "Value", direction: "output", kind: "number" },
],
properties: [],
},
{
evaluateValue: ({ resolveValueInput }) => {
const index = resolveValueInput("index", "0");
return `dget(${index})`;
},
}
);

View File

@@ -0,0 +1,43 @@
import { createNodeModule } from "../../nodeTypes.js";
/**
* Writes a persistent value using dset.
*/
export const dsetNode = createNodeModule(
{
id: "data_dset",
title: "Data Set",
category: "Data",
description: "Store a persistent number using DSET().",
searchTags: ["dset", "save", "data", "persistent"],
inputs: [
{ id: "exec_in", name: "Exec", direction: "input", kind: "exec" },
{
id: "index",
name: "Index",
direction: "input",
kind: "number",
defaultValue: 0,
},
{
id: "value",
name: "Value",
direction: "input",
kind: "number",
defaultValue: 0,
},
],
outputs: [
{ id: "exec_out", name: "Exec", direction: "output", kind: "exec" },
],
properties: [],
},
{
emitExec: ({ indent, indentLevel, resolveValueInput, emitNextExec }) => {
const index = resolveValueInput("index", "0");
const value = resolveValueInput("value", "0");
const line = `${indent(indentLevel)}dset(${index}, ${value})`;
return [line, ...emitNextExec("exec_out")];
},
}
);

View File

@@ -0,0 +1,18 @@
import { cartdataNode } from "./cartdata.js";
import { dsetNode } from "./dset.js";
import { dgetNode } from "./dget.js";
/**
* All data persistence node modules backed by PICO-8 helpers.
*/
export const dataNodes = [cartdataNode, dsetNode, dgetNode];
/**
* Checklist tracking coverage of documented cart data helpers.
* @type {Array<{ function: string, nodeId: string, implemented: boolean }>}
*/
export const dataFunctionChecklist = [
{ function: "CARTDATA", nodeId: "data_cartdata", implemented: true },
{ function: "DSET", nodeId: "data_dset", implemented: true },
{ function: "DGET", nodeId: "data_dget", implemented: true },
];

View File

@@ -0,0 +1,35 @@
import { createNodeModule } from "../../nodeTypes.js";
/**
* Applies host configuration flags via devkit_config().
*/
export const devkitConfigNode = createNodeModule(
{
id: "devkit_config",
title: "Devkit Config",
category: "Devkit",
description: "Send a configuration string to devkit_config().",
searchTags: ["devkit", "config", "host"],
inputs: [
{ id: "exec_in", name: "Exec", direction: "input", kind: "exec" },
{
id: "config",
name: "Config",
direction: "input",
kind: "string",
defaultValue: "",
},
],
outputs: [
{ id: "exec_out", name: "Exec", direction: "output", kind: "exec" },
],
properties: [],
},
{
emitExec: ({ indent, indentLevel, resolveValueInput, emitNextExec }) => {
const config = resolveValueInput("config", '""');
const line = `${indent(indentLevel)}devkit_config(${config})`;
return [line, ...emitNextExec("exec_out")];
},
}
);

View File

@@ -0,0 +1,33 @@
import { createNodeModule } from "../../nodeTypes.js";
/**
* Reads values from devkit-provided inputs via devkit_input().
*/
export const devkitInputReadNode = createNodeModule(
{
id: "devkit_input_read",
title: "Devkit Input Read",
category: "Devkit",
description: "Fetch the value of a devkit input index.",
searchTags: ["devkit", "input", "read"],
inputs: [
{
id: "index",
name: "Index",
direction: "input",
kind: "number",
defaultValue: 0,
},
],
outputs: [
{ id: "value", name: "Value", direction: "output", kind: "any" },
],
properties: [],
},
{
evaluateValue: ({ resolveValueInput }) => {
const index = resolveValueInput("index", "0");
return `devkit_input(${index})`;
},
}
);

View File

@@ -0,0 +1,43 @@
import { createNodeModule } from "../../nodeTypes.js";
/**
* Sends values to devkit inputs via devkit_input().
*/
export const devkitInputWriteNode = createNodeModule(
{
id: "devkit_input_write",
title: "Devkit Input Write",
category: "Devkit",
description: "Push a value to a devkit input index.",
searchTags: ["devkit", "input", "write"],
inputs: [
{ id: "exec_in", name: "Exec", direction: "input", kind: "exec" },
{
id: "index",
name: "Index",
direction: "input",
kind: "number",
defaultValue: 0,
},
{
id: "value",
name: "Value",
direction: "input",
kind: "number",
defaultValue: 0,
},
],
outputs: [
{ id: "exec_out", name: "Exec", direction: "output", kind: "exec" },
],
properties: [],
},
{
emitExec: ({ indent, indentLevel, resolveValueInput, emitNextExec }) => {
const index = resolveValueInput("index", "0");
const value = resolveValueInput("value", "0");
const line = `${indent(indentLevel)}devkit_input(${index}, ${value})`;
return [line, ...emitNextExec("exec_out")];
},
}
);

View File

@@ -0,0 +1,22 @@
import { devkitConfigNode } from "./devkitConfig.js";
import { devkitInputReadNode } from "./devkitInputRead.js";
import { devkitInputWriteNode } from "./devkitInputWrite.js";
/**
* Node modules covering devkit helpers.
*/
export const devkitNodes = [
devkitConfigNode,
devkitInputReadNode,
devkitInputWriteNode,
];
/**
* Checklist tracking coverage of devkit helpers.
* @type {Array<{ function: string, nodeId: string, implemented: boolean }>}
*/
export const devkitFunctionChecklist = [
{ function: "DEVKIT_CONFIG", nodeId: "devkit_config", implemented: true },
{ function: "DEVKIT_INPUT (read)", nodeId: "devkit_input_read", implemented: true },
{ function: "DEVKIT_INPUT (write)", nodeId: "devkit_input_write", implemented: true },
];

View File

@@ -0,0 +1,26 @@
import { createNodeModule } from "../nodeTypes.js";
/**
* Lifecycle event triggered once per visible frame.
* @type {import('../nodeTypes.js').NodeModule}
*/
export const eventDrawNode = createNodeModule(
{
id: "event_draw",
title: "Event Draw",
category: "Events",
description: "Called once per visible frame.",
searchTags: ["draw", "render", "frame", "loop"],
unique: true,
inputs: [],
outputs: [
{ id: "exec_out", name: "Exec", direction: "output", kind: "exec" },
],
properties: [],
},
{
isEntryPoint: true,
eventName: "_draw",
emitExec: ({ emitNextExec }) => emitNextExec("exec_out"),
}
);

View File

@@ -0,0 +1,26 @@
import { createNodeModule } from "../nodeTypes.js";
/**
* Lifecycle entry triggered once when the cart boots.
* @type {import('../nodeTypes.js').NodeModule}
*/
export const eventInitNode = createNodeModule(
{
id: "event_start",
title: "Event Init",
category: "Events",
description: "Called once on cart startup.",
searchTags: ["start", "init", "boot", "lifecycle"],
unique: true,
inputs: [],
outputs: [
{ id: "exec_out", name: "Exec", direction: "output", kind: "exec" },
],
properties: [],
},
{
isEntryPoint: true,
eventName: "_init",
emitExec: ({ emitNextExec }) => emitNextExec("exec_out"),
}
);

View File

@@ -0,0 +1,25 @@
import { createNodeModule } from "../nodeTypes.js";
/**
* @type {import('../nodeTypes.js').NodeModule}
*/
export const eventUpdateNode = createNodeModule(
{
id: "event_update",
title: "Event Update",
category: "Events",
description: "Called once per update",
searchTags: ["update", "loop", "frame", "tick"],
unique: true,
inputs: [],
outputs: [
{ id: "exec_out", name: "Exec", direction: "output", kind: "exec" },
],
properties: [],
},
{
isEntryPoint: true,
eventName: "_update",
emitExec: ({ emitNextExec }) => emitNextExec("exec_out"),
}
);

View File

@@ -0,0 +1,92 @@
import { createNodeModule } from "../nodeTypes.js";
/**
* Iterates from start to end inclusive using PICO-8 for semantics.
*/
export const forLoopNode = createNodeModule(
{
id: "for_loop",
title: "For Loop",
category: "Logic",
description:
"Iterate from start to end inclusive using PICO-8 for semantics.",
searchTags: ["loop", "for", "iterate", "counter"],
inputs: [
{ id: "exec_in", name: "Exec", direction: "input", kind: "exec" },
{
id: "start",
name: "Start",
direction: "input",
kind: "number",
defaultValue: 0,
},
{
id: "end",
name: "End",
direction: "input",
kind: "number",
defaultValue: 10,
},
{
id: "step",
name: "Step",
direction: "input",
kind: "number",
defaultValue: 1,
},
],
outputs: [
{ id: "loop", name: "Loop", direction: "output", kind: "exec" },
{ id: "completed", name: "Completed", direction: "output", kind: "exec" },
],
properties: [
{
key: "index",
label: "Index Variable",
type: "string",
defaultValue: "i",
},
],
},
{
emitExec: ({
node,
indent,
indentLevel,
resolveValueInput,
sanitizeIdentifier,
emitBranch,
path,
}) => {
const index = sanitizeIdentifier(String(node.properties.index ?? "i"));
const startValue = resolveValueInput("start", "0");
const endValue = resolveValueInput("end", "0");
const stepValue = resolveValueInput("step", "1");
const lines = [
`${indent(
indentLevel
)}for ${index} = ${startValue}, ${endValue}, ${stepValue} do`,
];
const loopLines = emitBranch("loop", {
indentLevel: indentLevel + 1,
path: new Set(path),
});
if (!loopLines.length) {
lines.push(`${indent(indentLevel + 1)}-- loop body`);
} else {
lines.push(...loopLines);
}
lines.push(`${indent(indentLevel)}end`);
lines.push(
...emitBranch("completed", {
indentLevel,
path,
})
);
return lines;
},
}
);

View File

@@ -0,0 +1,30 @@
import { createNodeModule } from "../nodeTypes.js";
/**
* Exposes the current value of a variable.
*/
export const getVariableNode = createNodeModule(
{
id: "get_var",
title: "Get Variable",
category: "Logic",
description: "Expose the current value of a variable.",
searchTags: ["get", "read", "variable", "access"],
inputs: [],
outputs: [{ id: "value", name: "Value", direction: "output", kind: "any" }],
properties: [
{
key: "name",
label: "Variable Name",
type: "string",
defaultValue: "score",
},
],
},
{
evaluateValue: ({ node, sanitizeIdentifier }) => {
const name = sanitizeIdentifier(String(node.properties.name ?? "var"));
return name;
},
}
);

View File

@@ -0,0 +1,33 @@
import { createNodeModule } from "../../nodeTypes.js";
/**
* Reads a value from a GPIO pin using gpio().
*/
export const gpioReadNode = createNodeModule(
{
id: "gpio_read",
title: "GPIO Read",
category: "IO",
description: "Fetch the current value of a GPIO pin.",
searchTags: ["gpio", "hardware", "input"],
inputs: [
{
id: "index",
name: "Pin",
direction: "input",
kind: "number",
defaultValue: 0,
},
],
outputs: [
{ id: "value", name: "Value", direction: "output", kind: "number" },
],
properties: [],
},
{
evaluateValue: ({ resolveValueInput }) => {
const pin = resolveValueInput("index", "0");
return `gpio(${pin})`;
},
}
);

View File

@@ -0,0 +1,43 @@
import { createNodeModule } from "../../nodeTypes.js";
/**
* Writes a value to a GPIO pin using gpio().
*/
export const gpioWriteNode = createNodeModule(
{
id: "gpio_write",
title: "GPIO Write",
category: "IO",
description: "Set the value for a GPIO pin.",
searchTags: ["gpio", "hardware", "output"],
inputs: [
{ id: "exec_in", name: "Exec", direction: "input", kind: "exec" },
{
id: "index",
name: "Pin",
direction: "input",
kind: "number",
defaultValue: 0,
},
{
id: "value",
name: "Value",
direction: "input",
kind: "number",
defaultValue: 0,
},
],
outputs: [
{ id: "exec_out", name: "Exec", direction: "output", kind: "exec" },
],
properties: [],
},
{
emitExec: ({ indent, indentLevel, resolveValueInput, emitNextExec }) => {
const pin = resolveValueInput("index", "0");
const value = resolveValueInput("value", "0");
const line = `${indent(indentLevel)}gpio(${pin}, ${value})`;
return [line, ...emitNextExec("exec_out")];
},
}
);

View File

@@ -0,0 +1,16 @@
import { gpioReadNode } from "./gpioRead.js";
import { gpioWriteNode } from "./gpioWrite.js";
/**
* Node modules covering GPIO helpers.
*/
export const gpioNodes = [gpioReadNode, gpioWriteNode];
/**
* Checklist tracking coverage of GPIO read/write helpers.
* @type {Array<{ function: string, nodeId: string, implemented: boolean }>}
*/
export const gpioFunctionChecklist = [
{ function: "GPIO (read)", nodeId: "gpio_read", implemented: true },
{ function: "GPIO (write)", nodeId: "gpio_write", implemented: true },
];

View File

@@ -0,0 +1,37 @@
import { createNodeModule } from "../../nodeTypes.js";
/**
* Applies a camera offset to subsequent draw calls.
*/
export const cameraNode = createNodeModule(
{
id: "graphics_camera",
title: "Add Camera Offset",
category: "Graphics",
description: "Shift the draw origin by the specified camera offset.",
searchTags: ["camera", "offset", "scroll", "view"],
inputs: [
{ id: "exec_in", name: "Exec", direction: "input", kind: "exec" },
{ id: "x", name: "X", direction: "input", kind: "number" },
{ id: "y", name: "Y", direction: "input", kind: "number" },
],
outputs: [
{ id: "exec_out", name: "Exec", direction: "output", kind: "exec" },
],
properties: [],
},
{
emitExec: ({ indent, indentLevel, resolveValueInput, emitNextExec }) => {
const OMIT = "__pg_omit__";
const x = resolveValueInput("x", OMIT);
const y = resolveValueInput("y", OMIT);
let call = "camera()";
if (x !== OMIT) {
call = `camera(${x}, ${y === OMIT ? "0" : y})`;
}
const lines = [`${indent(indentLevel)}${call}`];
lines.push(...emitNextExec("exec_out"));
return lines;
},
}
);

View File

@@ -0,0 +1,57 @@
import { createNodeModule } from "../../nodeTypes.js";
/**
* Draws an outlined circle.
*/
export const circNode = createNodeModule(
{
id: "graphics_circ",
title: "Draw Circle",
category: "Graphics",
description: "Render an outlined circle at the specified position.",
searchTags: ["circle", "outline", "shape", "draw"],
inputs: [
{ id: "exec_in", name: "Exec", direction: "input", kind: "exec" },
{
id: "x",
name: "X",
direction: "input",
kind: "number",
defaultValue: 0,
},
{
id: "y",
name: "Y",
direction: "input",
kind: "number",
defaultValue: 0,
},
{
id: "radius",
name: "Radius",
direction: "input",
kind: "number",
defaultValue: 4,
},
{ id: "color", name: "Color", direction: "input", kind: "number" },
],
outputs: [
{ id: "exec_out", name: "Exec", direction: "output", kind: "exec" },
],
properties: [],
},
{
emitExec: ({ indent, indentLevel, resolveValueInput, emitNextExec }) => {
const x = resolveValueInput("x", "0");
const y = resolveValueInput("y", "0");
const radius = resolveValueInput("radius", "4");
const color = resolveValueInput("color", "__pg_omit__");
const args = [x, y, radius];
if (color !== "__pg_omit__") {
args.push(color);
}
const line = `${indent(indentLevel)}circ(${args.join(", ")})`;
return [line, ...emitNextExec("exec_out")];
},
}
);

View File

@@ -0,0 +1,57 @@
import { createNodeModule } from "../../nodeTypes.js";
/**
* Draws a filled circle.
*/
export const circfillNode = createNodeModule(
{
id: "graphics_circfill",
title: "Draw Filled Circle",
category: "Graphics",
description: "Render a filled circle at the specified position.",
searchTags: ["circle", "filled", "shape", "draw"],
inputs: [
{ id: "exec_in", name: "Exec", direction: "input", kind: "exec" },
{
id: "x",
name: "X",
direction: "input",
kind: "number",
defaultValue: 0,
},
{
id: "y",
name: "Y",
direction: "input",
kind: "number",
defaultValue: 0,
},
{
id: "radius",
name: "Radius",
direction: "input",
kind: "number",
defaultValue: 4,
},
{ id: "color", name: "Color", direction: "input", kind: "number" },
],
outputs: [
{ id: "exec_out", name: "Exec", direction: "output", kind: "exec" },
],
properties: [],
},
{
emitExec: ({ indent, indentLevel, resolveValueInput, emitNextExec }) => {
const x = resolveValueInput("x", "0");
const y = resolveValueInput("y", "0");
const radius = resolveValueInput("radius", "4");
const color = resolveValueInput("color", "__pg_omit__");
const args = [x, y, radius];
if (color !== "__pg_omit__") {
args.push(color);
}
const line = `${indent(indentLevel)}circfill(${args.join(", ")})`;
return [line, ...emitNextExec("exec_out")];
},
}
);

View File

@@ -0,0 +1,63 @@
import { createNodeModule } from "../../nodeTypes.js";
/**
* Adjusts the active clipping rectangle for subsequent draw calls.
*/
export const clipNode = createNodeModule(
{
id: "graphics_clip",
title: "Set Clipping Rectangle",
category: "Graphics",
description:
"Set or reset the clipping rectangle that constrains all draw operations.",
searchTags: ["clip", "clipping", "graphics", "viewport"],
inputs: [
{ id: "exec_in", name: "Exec", direction: "input", kind: "exec" },
{ id: "x", name: "X", direction: "input", kind: "number" },
{ id: "y", name: "Y", direction: "input", kind: "number" },
{ id: "w", name: "Width", direction: "input", kind: "number" },
{ id: "h", name: "Height", direction: "input", kind: "number" },
{
id: "clip_previous",
name: "Clip Previous",
direction: "input",
kind: "boolean",
defaultValue: false,
},
],
outputs: [
{ id: "exec_out", name: "Exec", direction: "output", kind: "exec" },
],
properties: [],
},
{
emitExec: ({ indent, indentLevel, resolveValueInput, emitNextExec }) => {
const OMIT = "__pg_omit__";
const x = resolveValueInput("x", OMIT);
const y = resolveValueInput("y", OMIT);
const w = resolveValueInput("w", OMIT);
const h = resolveValueInput("h", OMIT);
const clipPrevious = resolveValueInput("clip_previous", OMIT);
let call;
if (x === OMIT && y === OMIT && w === OMIT && h === OMIT) {
call = "clip()";
} else {
const args = [
x === OMIT ? "0" : x,
y === OMIT ? "0" : y,
w === OMIT ? "128" : w,
h === OMIT ? "128" : h,
];
if (clipPrevious !== OMIT) {
args.push(clipPrevious);
}
call = `clip(${args.join(", ")})`;
}
const lines = [`${indent(indentLevel)}${call}`];
lines.push(...emitNextExec("exec_out"));
return lines;
},
}
);

View File

@@ -0,0 +1,32 @@
import { createNodeModule } from "../../nodeTypes.js";
/**
* Clears the screen and resets clipping.
*/
export const clsNode = createNodeModule(
{
id: "graphics_cls",
title: "Clear Screen",
category: "Graphics",
description: "Clear the display, optionally filling with a specific color.",
searchTags: ["cls", "clear", "screen", "background"],
inputs: [
{ id: "exec_in", name: "Exec", direction: "input", kind: "exec" },
{ id: "color", name: "Color", direction: "input", kind: "number" },
],
outputs: [
{ id: "exec_out", name: "Exec", direction: "output", kind: "exec" },
],
properties: [],
},
{
emitExec: ({ indent, indentLevel, resolveValueInput, emitNextExec }) => {
const OMIT = "__pg_omit__";
const color = resolveValueInput("color", OMIT);
const call = color === OMIT ? "cls()" : `cls(${color})`;
const lines = [`${indent(indentLevel)}${call}`];
lines.push(...emitNextExec("exec_out"));
return lines;
},
}
);

View File

@@ -0,0 +1,32 @@
import { createNodeModule } from "../../nodeTypes.js";
/**
* Updates the current draw color.
*/
export const colorNode = createNodeModule(
{
id: "graphics_color",
title: "Set Active Color",
category: "Graphics",
description: "Set the active draw color used by subsequent graphics calls.",
searchTags: ["color", "color", "ink", "draw"],
inputs: [
{ id: "exec_in", name: "Exec", direction: "input", kind: "exec" },
{ id: "color", name: "Color", direction: "input", kind: "number" },
],
outputs: [
{ id: "exec_out", name: "Exec", direction: "output", kind: "exec" },
],
properties: [],
},
{
emitExec: ({ indent, indentLevel, resolveValueInput, emitNextExec }) => {
const OMIT = "__pg_omit__";
const color = resolveValueInput("color", OMIT);
const call = color === OMIT ? "color()" : `color(${color})`;
const lines = [`${indent(indentLevel)}${call}`];
lines.push(...emitNextExec("exec_out"));
return lines;
},
}
);

View File

@@ -0,0 +1,46 @@
import { createNodeModule } from "../../nodeTypes.js";
/**
* Positions the print cursor and optionally sets the draw color.
*/
export const cursorNode = createNodeModule(
{
id: "graphics_cursor",
title: "Set Cursor",
category: "Graphics",
description:
"Move the print cursor and optionally change the active color.",
searchTags: ["cursor", "print", "text", "position"],
inputs: [
{ id: "exec_in", name: "Exec", direction: "input", kind: "exec" },
{ id: "x", name: "X", direction: "input", kind: "number" },
{ id: "y", name: "Y", direction: "input", kind: "number" },
{ id: "color", name: "Color", direction: "input", kind: "number" },
],
outputs: [
{ id: "exec_out", name: "Exec", direction: "output", kind: "exec" },
],
properties: [],
},
{
emitExec: ({ indent, indentLevel, resolveValueInput, emitNextExec }) => {
const OMIT = "__pg_omit__";
const x = resolveValueInput("x", OMIT);
const y = resolveValueInput("y", OMIT);
const color = resolveValueInput("color", OMIT);
let call = "cursor()";
if (x !== OMIT) {
const args = [x, y === OMIT ? "0" : y];
if (color !== OMIT) {
args.push(color);
}
call = `cursor(${args.join(", ")})`;
}
const lines = [`${indent(indentLevel)}${call}`];
lines.push(...emitNextExec("exec_out"));
return lines;
},
}
);

View File

@@ -0,0 +1,39 @@
import { createNodeModule } from "../../nodeTypes.js";
/**
* Reads sprite flag data.
*/
export const fgetNode = createNodeModule(
{
id: "graphics_fget",
title: "Get Sprite Flag",
category: "Graphics",
description: "Retrieve the bitfield or specific flag value for a sprite.",
searchTags: ["fget", "flag", "sprite", "meta"],
inputs: [
{
id: "sprite",
name: "Sprite",
direction: "input",
kind: "number",
defaultValue: 0,
},
{ id: "flag", name: "Flag", direction: "input", kind: "number" },
],
outputs: [
{ id: "value", name: "Value", direction: "output", kind: "number" },
],
properties: [],
},
{
evaluateValue: ({ resolveValueInput }) => {
const OMIT = "__pg_omit__";
const sprite = resolveValueInput("sprite", "0");
const flag = resolveValueInput("flag", OMIT);
if (flag === OMIT) {
return `fget(${sprite})`;
}
return `fget(${sprite}, ${flag})`;
},
}
);

View File

@@ -0,0 +1,31 @@
import { createNodeModule } from "../../nodeTypes.js";
/**
* Configures the active fill pattern.
*/
export const fillpNode = createNodeModule(
{
id: "graphics_fillp",
title: "Set Fill Pattern",
category: "Graphics",
description: "Set the 4x4 fill pattern bitfield used by many draw calls.",
searchTags: ["fillp", "pattern", "fill", "hatch"],
inputs: [
{ id: "exec_in", name: "Exec", direction: "input", kind: "exec" },
{ id: "pattern", name: "Pattern", direction: "input", kind: "number" },
],
outputs: [
{ id: "exec_out", name: "Exec", direction: "output", kind: "exec" },
],
properties: [],
},
{
emitExec: ({ indent, indentLevel, resolveValueInput, emitNextExec }) => {
const pattern = resolveValueInput("pattern", "__pg_omit__");
const call = pattern === "__pg_omit__" ? "fillp()" : `fillp(${pattern})`;
const lines = [`${indent(indentLevel)}${call}`];
lines.push(...emitNextExec("exec_out"));
return lines;
},
}
);

View File

@@ -0,0 +1,52 @@
import { createNodeModule } from "../../nodeTypes.js";
/**
* Updates sprite flag values.
*/
export const fsetNode = createNodeModule(
{
id: "graphics_fset",
title: "Set Sprite Flag",
category: "Graphics",
description: "Set a sprite's flag bitfield or a specific flag value.",
searchTags: ["fset", "flag", "sprite", "meta"],
inputs: [
{ id: "exec_in", name: "Exec", direction: "input", kind: "exec" },
{
id: "sprite",
name: "Sprite",
direction: "input",
kind: "number",
defaultValue: 0,
},
{ id: "flag", name: "Flag", direction: "input", kind: "number" },
{
id: "value",
name: "Value",
direction: "input",
kind: "any",
defaultValue: true,
},
],
outputs: [
{ id: "exec_out", name: "Exec", direction: "output", kind: "exec" },
],
properties: [],
},
{
emitExec: ({ indent, indentLevel, resolveValueInput, emitNextExec }) => {
const OMIT = "__pg_omit__";
const sprite = resolveValueInput("sprite", "0");
const flag = resolveValueInput("flag", OMIT);
const value = resolveValueInput("value", "false");
const args = [sprite];
if (flag === OMIT) {
args.push(value);
} else {
args.push(flag, value);
}
const line = `${indent(indentLevel)}fset(${args.join(", ")})`;
return [line, ...emitNextExec("exec_out")];
},
}
);

View File

@@ -0,0 +1,88 @@
import { clipNode } from "./clip.js";
import { psetNode } from "./pset.js";
import { pgetNode } from "./pget.js";
import { sgetNode } from "./sget.js";
import { ssetNode } from "./sset.js";
import { fgetNode } from "./fget.js";
import { fsetNode } from "./fset.js";
import { cursorNode } from "./cursor.js";
import { colorNode } from "./color.js";
import { clsNode } from "./cls.js";
import { cameraNode } from "./camera.js";
import { circNode } from "./circ.js";
import { circfillNode } from "./circfill.js";
import { ovalNode } from "./oval.js";
import { ovalfillNode } from "./ovalfill.js";
import { lineNode } from "./line.js";
import { rectNode } from "./rect.js";
import { rectfillNode } from "./rectfill.js";
import { rrectNode } from "./rrect.js";
import { rrectfillNode } from "./rrectfill.js";
import { palNode } from "./pal.js";
import { paltNode } from "./palt.js";
import { sprNode } from "./spr.js";
import { ssprNode } from "./sspr.js";
import { fillpNode } from "./fillp.js";
/**
* All graphics related node modules backed by PICO-8 draw helpers.
*/
export const graphicsNodes = [
clipNode,
psetNode,
pgetNode,
sgetNode,
ssetNode,
fgetNode,
fsetNode,
cursorNode,
colorNode,
clsNode,
cameraNode,
circNode,
circfillNode,
ovalNode,
ovalfillNode,
lineNode,
rectNode,
rectfillNode,
rrectNode,
rrectfillNode,
palNode,
paltNode,
sprNode,
ssprNode,
fillpNode,
];
/**
* Checklist tracking coverage of documented graphics helpers.
* @type {Array<{ function: string, nodeId: string, implemented: boolean }>}
*/
export const graphicsFunctionChecklist = [
{ function: "CLIP", nodeId: "graphics_clip", implemented: true },
{ function: "PSET", nodeId: "graphics_pset", implemented: true },
{ function: "PGET", nodeId: "graphics_pget", implemented: true },
{ function: "SGET", nodeId: "graphics_sget", implemented: true },
{ function: "SSET", nodeId: "graphics_sset", implemented: true },
{ function: "FGET", nodeId: "graphics_fget", implemented: true },
{ function: "FSET", nodeId: "graphics_fset", implemented: true },
{ function: "CURSOR", nodeId: "graphics_cursor", implemented: true },
{ function: "COLOR", nodeId: "graphics_color", implemented: true },
{ function: "CLS", nodeId: "graphics_cls", implemented: true },
{ function: "CAMERA", nodeId: "graphics_camera", implemented: true },
{ function: "CIRC", nodeId: "graphics_circ", implemented: true },
{ function: "CIRCFILL", nodeId: "graphics_circfill", implemented: true },
{ function: "OVAL", nodeId: "graphics_oval", implemented: true },
{ function: "OVALFILL", nodeId: "graphics_ovalfill", implemented: true },
{ function: "LINE", nodeId: "graphics_line", implemented: true },
{ function: "RECT", nodeId: "graphics_rect", implemented: true },
{ function: "RECTFILL", nodeId: "graphics_rectfill", implemented: true },
{ function: "RRECT", nodeId: "graphics_rrect", implemented: true },
{ function: "RRECTFILL", nodeId: "graphics_rrectfill", implemented: true },
{ function: "PAL", nodeId: "graphics_pal", implemented: true },
{ function: "PALT", nodeId: "graphics_palt", implemented: true },
{ function: "SPR", nodeId: "graphics_spr", implemented: true },
{ function: "SSPR", nodeId: "graphics_sspr", implemented: true },
{ function: "FILLP", nodeId: "graphics_fillp", implemented: true },
];

View File

@@ -0,0 +1,60 @@
import { createNodeModule } from "../../nodeTypes.js";
/**
* Draws a line segment.
*/
export const lineNode = createNodeModule(
{
id: "graphics_line",
title: "Draw Line",
category: "Graphics",
description:
"Draw a line between two points or update the current pen position.",
searchTags: ["line", "draw", "segment", "stroke"],
inputs: [
{ id: "exec_in", name: "Exec", direction: "input", kind: "exec" },
{
id: "x0",
name: "X0",
direction: "input",
kind: "number",
defaultValue: 0,
},
{
id: "y0",
name: "Y0",
direction: "input",
kind: "number",
defaultValue: 0,
},
{ id: "x1", name: "X1", direction: "input", kind: "number" },
{ id: "y1", name: "Y1", direction: "input", kind: "number" },
{ id: "color", name: "Color", direction: "input", kind: "number" },
],
outputs: [
{ id: "exec_out", name: "Exec", direction: "output", kind: "exec" },
],
properties: [],
},
{
emitExec: ({ indent, indentLevel, resolveValueInput, emitNextExec }) => {
const OMIT = "__pg_omit__";
const x0 = resolveValueInput("x0", "0");
const y0 = resolveValueInput("y0", "0");
const x1 = resolveValueInput("x1", OMIT);
const y1 = resolveValueInput("y1", OMIT);
const color = resolveValueInput("color", OMIT);
const args = [x0, y0];
if (x1 !== OMIT && y1 !== OMIT) {
args.push(x1, y1);
if (color !== OMIT) {
args.push(color);
}
}
const line = `${indent(indentLevel)}line(${args.join(", ")})`;
return [line, ...emitNextExec("exec_out")];
},
}
);

View File

@@ -0,0 +1,65 @@
import { createNodeModule } from "../../nodeTypes.js";
/**
* Draws an outlined oval.
*/
export const ovalNode = createNodeModule(
{
id: "graphics_oval",
title: "Draw Oval",
category: "Graphics",
description: "Render an ellipse using the provided bounding box.",
searchTags: ["oval", "ellipse", "shape", "outline"],
inputs: [
{ id: "exec_in", name: "Exec", direction: "input", kind: "exec" },
{
id: "x0",
name: "X0",
direction: "input",
kind: "number",
defaultValue: 0,
},
{
id: "y0",
name: "Y0",
direction: "input",
kind: "number",
defaultValue: 0,
},
{
id: "x1",
name: "X1",
direction: "input",
kind: "number",
defaultValue: 8,
},
{
id: "y1",
name: "Y1",
direction: "input",
kind: "number",
defaultValue: 8,
},
{ id: "color", name: "Color", direction: "input", kind: "number" },
],
outputs: [
{ id: "exec_out", name: "Exec", direction: "output", kind: "exec" },
],
properties: [],
},
{
emitExec: ({ indent, indentLevel, resolveValueInput, emitNextExec }) => {
const x0 = resolveValueInput("x0", "0");
const y0 = resolveValueInput("y0", "0");
const x1 = resolveValueInput("x1", "8");
const y1 = resolveValueInput("y1", "8");
const color = resolveValueInput("color", "__pg_omit__");
const args = [x0, y0, x1, y1];
if (color !== "__pg_omit__") {
args.push(color);
}
const line = `${indent(indentLevel)}oval(${args.join(", ")})`;
return [line, ...emitNextExec("exec_out")];
},
}
);

View File

@@ -0,0 +1,65 @@
import { createNodeModule } from "../../nodeTypes.js";
/**
* Draws a filled oval.
*/
export const ovalfillNode = createNodeModule(
{
id: "graphics_ovalfill",
title: "Draw Filled Oval",
category: "Graphics",
description: "Render a filled ellipse using the provided bounding box.",
searchTags: ["oval", "ellipse", "filled", "shape"],
inputs: [
{ id: "exec_in", name: "Exec", direction: "input", kind: "exec" },
{
id: "x0",
name: "X0",
direction: "input",
kind: "number",
defaultValue: 0,
},
{
id: "y0",
name: "Y0",
direction: "input",
kind: "number",
defaultValue: 0,
},
{
id: "x1",
name: "X1",
direction: "input",
kind: "number",
defaultValue: 8,
},
{
id: "y1",
name: "Y1",
direction: "input",
kind: "number",
defaultValue: 8,
},
{ id: "color", name: "Color", direction: "input", kind: "number" },
],
outputs: [
{ id: "exec_out", name: "Exec", direction: "output", kind: "exec" },
],
properties: [],
},
{
emitExec: ({ indent, indentLevel, resolveValueInput, emitNextExec }) => {
const x0 = resolveValueInput("x0", "0");
const y0 = resolveValueInput("y0", "0");
const x1 = resolveValueInput("x1", "8");
const y1 = resolveValueInput("y1", "8");
const color = resolveValueInput("color", "__pg_omit__");
const args = [x0, y0, x1, y1];
if (color !== "__pg_omit__") {
args.push(color);
}
const line = `${indent(indentLevel)}ovalfill(${args.join(", ")})`;
return [line, ...emitNextExec("exec_out")];
},
}
);

View File

@@ -0,0 +1,51 @@
import { createNodeModule } from "../../nodeTypes.js";
/**
* Remaps palette colors.
*/
export const palNode = createNodeModule(
{
id: "graphics_pal",
title: "Remap Palette Color",
category: "Graphics",
description: "Swap one palette color for another or reset the palette.",
searchTags: ["pal", "palette", "color", "remap"],
inputs: [
{ id: "exec_in", name: "Exec", direction: "input", kind: "exec" },
{ id: "c0", name: "Color 0", direction: "input", kind: "number" },
{
id: "c1",
name: "Color 1",
direction: "input",
kind: "number",
defaultValue: 0,
},
{ id: "p", name: "Palette", direction: "input", kind: "number" },
],
outputs: [
{ id: "exec_out", name: "Exec", direction: "output", kind: "exec" },
],
properties: [],
},
{
emitExec: ({ indent, indentLevel, resolveValueInput, emitNextExec }) => {
const OMIT = "__pg_omit__";
const c0 = resolveValueInput("c0", OMIT);
const c1 = resolveValueInput("c1", "0");
const p = resolveValueInput("p", OMIT);
let call = "pal()";
if (c0 !== OMIT) {
const args = [c0, c1];
if (p !== OMIT) {
args.push(p);
}
call = `pal(${args.join(", ")})`;
}
const lines = [`${indent(indentLevel)}${call}`];
lines.push(...emitNextExec("exec_out"));
return lines;
},
}
);

View File

@@ -0,0 +1,47 @@
import { createNodeModule } from "../../nodeTypes.js";
/**
* Adjusts sprite transparency settings.
*/
export const paltNode = createNodeModule(
{
id: "graphics_palt",
title: "Set Sprite Transparency",
category: "Graphics",
description: "Set or reset sprite transparency flags.",
searchTags: ["palt", "transparency", "palette", "sprite"],
inputs: [
{ id: "exec_in", name: "Exec", direction: "input", kind: "exec" },
{ id: "color", name: "Color", direction: "input", kind: "number" },
{
id: "transparent",
name: "Transparent",
direction: "input",
kind: "boolean",
},
],
outputs: [
{ id: "exec_out", name: "Exec", direction: "output", kind: "exec" },
],
properties: [],
},
{
emitExec: ({ indent, indentLevel, resolveValueInput, emitNextExec }) => {
const OMIT = "__pg_omit__";
const color = resolveValueInput("color", OMIT);
const transparent = resolveValueInput("transparent", OMIT);
let call = "palt()";
if (color !== OMIT) {
if (transparent === OMIT) {
call = `palt(${color})`;
} else {
call = `palt(${color}, ${transparent})`;
}
}
const lines = [`${indent(indentLevel)}${call}`];
lines.push(...emitNextExec("exec_out"));
return lines;
},
}
);

View File

@@ -0,0 +1,41 @@
import { createNodeModule } from "../../nodeTypes.js";
/**
* Reads the color of a screen pixel.
*/
export const pgetNode = createNodeModule(
{
id: "graphics_pget",
title: "Get Pixel Color",
category: "Graphics",
description: "Fetch the color index at the given screen coordinate.",
searchTags: ["pget", "pixel", "read", "sample"],
inputs: [
{
id: "x",
name: "X",
direction: "input",
kind: "number",
defaultValue: 0,
},
{
id: "y",
name: "Y",
direction: "input",
kind: "number",
defaultValue: 0,
},
],
outputs: [
{ id: "value", name: "Color", direction: "output", kind: "number" },
],
properties: [],
},
{
evaluateValue: ({ resolveValueInput }) => {
const x = resolveValueInput("x", "0");
const y = resolveValueInput("y", "0");
return `pget(${x}, ${y})`;
},
}
);

View File

@@ -0,0 +1,50 @@
import { createNodeModule } from "../../nodeTypes.js";
/**
* Draws a single pixel using the current or specified color.
*/
export const psetNode = createNodeModule(
{
id: "graphics_pset",
title: "Set Pixel Color",
category: "Graphics",
description: "Set a pixel on screen to a color index.",
searchTags: ["pset", "pixel", "draw", "plot"],
inputs: [
{ id: "exec_in", name: "Exec", direction: "input", kind: "exec" },
{
id: "x",
name: "X",
direction: "input",
kind: "number",
defaultValue: 0,
},
{
id: "y",
name: "Y",
direction: "input",
kind: "number",
defaultValue: 0,
},
{ id: "color", name: "Color", direction: "input", kind: "number" },
],
outputs: [
{ id: "exec_out", name: "Exec", direction: "output", kind: "exec" },
],
properties: [],
},
{
emitExec: ({ indent, indentLevel, resolveValueInput, emitNextExec }) => {
const OMIT = "__pg_omit__";
const x = resolveValueInput("x", "0");
const y = resolveValueInput("y", "0");
const color = resolveValueInput("color", OMIT);
const args = [x, y];
if (color !== OMIT) {
args.push(color);
}
const line = `${indent(indentLevel)}pset(${args.join(", ")})`;
return [line, ...emitNextExec("exec_out")];
},
}
);

View File

@@ -0,0 +1,65 @@
import { createNodeModule } from "../../nodeTypes.js";
/**
* Draws an outlined rectangle.
*/
export const rectNode = createNodeModule(
{
id: "graphics_rect",
title: "Draw Rectangle",
category: "Graphics",
description: "Render a rectangle outline defined by two corners.",
searchTags: ["rect", "rectangle", "outline", "shape"],
inputs: [
{ id: "exec_in", name: "Exec", direction: "input", kind: "exec" },
{
id: "x0",
name: "X0",
direction: "input",
kind: "number",
defaultValue: 0,
},
{
id: "y0",
name: "Y0",
direction: "input",
kind: "number",
defaultValue: 0,
},
{
id: "x1",
name: "X1",
direction: "input",
kind: "number",
defaultValue: 8,
},
{
id: "y1",
name: "Y1",
direction: "input",
kind: "number",
defaultValue: 8,
},
{ id: "color", name: "Color", direction: "input", kind: "number" },
],
outputs: [
{ id: "exec_out", name: "Exec", direction: "output", kind: "exec" },
],
properties: [],
},
{
emitExec: ({ indent, indentLevel, resolveValueInput, emitNextExec }) => {
const x0 = resolveValueInput("x0", "0");
const y0 = resolveValueInput("y0", "0");
const x1 = resolveValueInput("x1", "8");
const y1 = resolveValueInput("y1", "8");
const color = resolveValueInput("color", "__pg_omit__");
const args = [x0, y0, x1, y1];
if (color !== "__pg_omit__") {
args.push(color);
}
const line = `${indent(indentLevel)}rect(${args.join(", ")})`;
return [line, ...emitNextExec("exec_out")];
},
}
);

View File

@@ -0,0 +1,65 @@
import { createNodeModule } from "../../nodeTypes.js";
/**
* Draws a filled rectangle.
*/
export const rectfillNode = createNodeModule(
{
id: "graphics_rectfill",
title: "Draw Filled Rectangle",
category: "Graphics",
description: "Render a filled rectangle defined by two corners.",
searchTags: ["rect", "rectangle", "filled", "shape"],
inputs: [
{ id: "exec_in", name: "Exec", direction: "input", kind: "exec" },
{
id: "x0",
name: "X0",
direction: "input",
kind: "number",
defaultValue: 0,
},
{
id: "y0",
name: "Y0",
direction: "input",
kind: "number",
defaultValue: 0,
},
{
id: "x1",
name: "X1",
direction: "input",
kind: "number",
defaultValue: 8,
},
{
id: "y1",
name: "Y1",
direction: "input",
kind: "number",
defaultValue: 8,
},
{ id: "color", name: "Color", direction: "input", kind: "number" },
],
outputs: [
{ id: "exec_out", name: "Exec", direction: "output", kind: "exec" },
],
properties: [],
},
{
emitExec: ({ indent, indentLevel, resolveValueInput, emitNextExec }) => {
const x0 = resolveValueInput("x0", "0");
const y0 = resolveValueInput("y0", "0");
const x1 = resolveValueInput("x1", "8");
const y1 = resolveValueInput("y1", "8");
const color = resolveValueInput("color", "__pg_omit__");
const args = [x0, y0, x1, y1];
if (color !== "__pg_omit__") {
args.push(color);
}
const line = `${indent(indentLevel)}rectfill(${args.join(", ")})`;
return [line, ...emitNextExec("exec_out")];
},
}
);

View File

@@ -0,0 +1,73 @@
import { createNodeModule } from "../../nodeTypes.js";
/**
* Draws an outlined rounded rectangle.
*/
export const rrectNode = createNodeModule(
{
id: "graphics_rrect",
title: "Draw Rounded Rectangle",
category: "Graphics",
description: "Render a rounded rectangle outline with the supplied radius.",
searchTags: ["rounded", "rectangle", "outline", "shape"],
inputs: [
{ id: "exec_in", name: "Exec", direction: "input", kind: "exec" },
{
id: "x",
name: "X",
direction: "input",
kind: "number",
defaultValue: 0,
},
{
id: "y",
name: "Y",
direction: "input",
kind: "number",
defaultValue: 0,
},
{
id: "w",
name: "Width",
direction: "input",
kind: "number",
defaultValue: 8,
},
{
id: "h",
name: "Height",
direction: "input",
kind: "number",
defaultValue: 8,
},
{
id: "r",
name: "Radius",
direction: "input",
kind: "number",
defaultValue: 2,
},
{ id: "color", name: "Color", direction: "input", kind: "number" },
],
outputs: [
{ id: "exec_out", name: "Exec", direction: "output", kind: "exec" },
],
properties: [],
},
{
emitExec: ({ indent, indentLevel, resolveValueInput, emitNextExec }) => {
const x = resolveValueInput("x", "0");
const y = resolveValueInput("y", "0");
const w = resolveValueInput("w", "8");
const h = resolveValueInput("h", "8");
const r = resolveValueInput("r", "2");
const color = resolveValueInput("color", "__pg_omit__");
const args = [x, y, w, h, r];
if (color !== "__pg_omit__") {
args.push(color);
}
const line = `${indent(indentLevel)}rrect(${args.join(", ")})`;
return [line, ...emitNextExec("exec_out")];
},
}
);

View File

@@ -0,0 +1,73 @@
import { createNodeModule } from "../../nodeTypes.js";
/**
* Draws a filled rounded rectangle.
*/
export const rrectfillNode = createNodeModule(
{
id: "graphics_rrectfill",
title: "Draw Filled Rounded Rectangle",
category: "Graphics",
description: "Render a filled rounded rectangle with the supplied radius.",
searchTags: ["rounded", "rectangle", "filled", "shape"],
inputs: [
{ id: "exec_in", name: "Exec", direction: "input", kind: "exec" },
{
id: "x",
name: "X",
direction: "input",
kind: "number",
defaultValue: 0,
},
{
id: "y",
name: "Y",
direction: "input",
kind: "number",
defaultValue: 0,
},
{
id: "w",
name: "Width",
direction: "input",
kind: "number",
defaultValue: 8,
},
{
id: "h",
name: "Height",
direction: "input",
kind: "number",
defaultValue: 8,
},
{
id: "r",
name: "Radius",
direction: "input",
kind: "number",
defaultValue: 2,
},
{ id: "color", name: "Color", direction: "input", kind: "number" },
],
outputs: [
{ id: "exec_out", name: "Exec", direction: "output", kind: "exec" },
],
properties: [],
},
{
emitExec: ({ indent, indentLevel, resolveValueInput, emitNextExec }) => {
const x = resolveValueInput("x", "0");
const y = resolveValueInput("y", "0");
const w = resolveValueInput("w", "8");
const h = resolveValueInput("h", "8");
const r = resolveValueInput("r", "2");
const color = resolveValueInput("color", "__pg_omit__");
const args = [x, y, w, h, r];
if (color !== "__pg_omit__") {
args.push(color);
}
const line = `${indent(indentLevel)}rrectfill(${args.join(", ")})`;
return [line, ...emitNextExec("exec_out")];
},
}
);

View File

@@ -0,0 +1,42 @@
import { createNodeModule } from "../../nodeTypes.js";
/**
* Reads a pixel from the sprite sheet.
*/
export const sgetNode = createNodeModule(
{
id: "graphics_sget",
title: "Get Sprite Pixel",
category: "Graphics",
description:
"Retrieve the color index from the sprite sheet at the given coordinate.",
searchTags: ["sget", "sprite", "sheet", "read"],
inputs: [
{
id: "x",
name: "X",
direction: "input",
kind: "number",
defaultValue: 0,
},
{
id: "y",
name: "Y",
direction: "input",
kind: "number",
defaultValue: 0,
},
],
outputs: [
{ id: "value", name: "Color", direction: "output", kind: "number" },
],
properties: [],
},
{
evaluateValue: ({ resolveValueInput }) => {
const x = resolveValueInput("x", "0");
const y = resolveValueInput("y", "0");
return `sget(${x}, ${y})`;
},
}
);

View File

@@ -0,0 +1,76 @@
import { createNodeModule } from "../../nodeTypes.js";
/**
* Draws sprites from the sprite sheet.
*/
export const sprNode = createNodeModule(
{
id: "graphics_spr",
title: "Draw Sprite",
category: "Graphics",
description: "Blit one or more sprites at the given screen position.",
searchTags: ["spr", "sprite", "draw", "blit"],
inputs: [
{ id: "exec_in", name: "Exec", direction: "input", kind: "exec" },
{
id: "sprite",
name: "Sprite",
direction: "input",
kind: "number",
defaultValue: 0,
},
{
id: "x",
name: "X",
direction: "input",
kind: "number",
defaultValue: 0,
},
{
id: "y",
name: "Y",
direction: "input",
kind: "number",
defaultValue: 0,
},
{ id: "w", name: "Width", direction: "input", kind: "number" },
{ id: "h", name: "Height", direction: "input", kind: "number" },
{ id: "flip_x", name: "Flip X", direction: "input", kind: "boolean" },
{ id: "flip_y", name: "Flip Y", direction: "input", kind: "boolean" },
],
outputs: [
{ id: "exec_out", name: "Exec", direction: "output", kind: "exec" },
],
properties: [],
},
{
emitExec: ({ indent, indentLevel, resolveValueInput, emitNextExec }) => {
const OMIT = "__pg_omit__";
const sprite = resolveValueInput("sprite", "0");
const x = resolveValueInput("x", "0");
const y = resolveValueInput("y", "0");
const w = resolveValueInput("w", OMIT);
const h = resolveValueInput("h", OMIT);
const flipX = resolveValueInput("flip_x", OMIT);
const flipY = resolveValueInput("flip_y", OMIT);
const args = [sprite, x, y];
const includeSize =
w !== OMIT || h !== OMIT || flipX !== OMIT || flipY !== OMIT;
if (includeSize) {
args.push(w === OMIT ? "1" : w, h === OMIT ? "1" : h);
}
if (flipX !== OMIT || flipY !== OMIT) {
args.push(
flipX === OMIT ? "false" : flipX,
flipY === OMIT ? "false" : flipY
);
}
const line = `${indent(indentLevel)}spr(${args.join(", ")})`;
return [line, ...emitNextExec("exec_out")];
},
}
);

View File

@@ -0,0 +1,51 @@
import { createNodeModule } from "../../nodeTypes.js";
/**
* Writes a pixel to the sprite sheet.
*/
export const ssetNode = createNodeModule(
{
id: "graphics_sset",
title: "Set Sprite Pixel",
category: "Graphics",
description: "Assign a color to the sprite sheet at the given coordinate.",
searchTags: ["sset", "sprite", "sheet", "write"],
inputs: [
{ id: "exec_in", name: "Exec", direction: "input", kind: "exec" },
{
id: "x",
name: "X",
direction: "input",
kind: "number",
defaultValue: 0,
},
{
id: "y",
name: "Y",
direction: "input",
kind: "number",
defaultValue: 0,
},
{
id: "color",
name: "Color",
direction: "input",
kind: "number",
defaultValue: 0,
},
],
outputs: [
{ id: "exec_out", name: "Exec", direction: "output", kind: "exec" },
],
properties: [],
},
{
emitExec: ({ indent, indentLevel, resolveValueInput, emitNextExec }) => {
const x = resolveValueInput("x", "0");
const y = resolveValueInput("y", "0");
const color = resolveValueInput("color", "0");
const line = `${indent(indentLevel)}sset(${x}, ${y}, ${color})`;
return [line, ...emitNextExec("exec_out")];
},
}
);

View File

@@ -0,0 +1,100 @@
import { createNodeModule } from "../../nodeTypes.js";
/**
* Draws a rectangular region of the sprite sheet with scaling support.
*/
export const ssprNode = createNodeModule(
{
id: "graphics_sspr",
title: "Draw Scaled Sprite",
category: "Graphics",
description: "Stretch or flip a sprite sheet region to the screen.",
searchTags: ["sspr", "sprite", "scale", "blit"],
inputs: [
{ id: "exec_in", name: "Exec", direction: "input", kind: "exec" },
{
id: "sx",
name: "SX",
direction: "input",
kind: "number",
defaultValue: 0,
},
{
id: "sy",
name: "SY",
direction: "input",
kind: "number",
defaultValue: 0,
},
{
id: "sw",
name: "SW",
direction: "input",
kind: "number",
defaultValue: 8,
},
{
id: "sh",
name: "SH",
direction: "input",
kind: "number",
defaultValue: 8,
},
{
id: "dx",
name: "DX",
direction: "input",
kind: "number",
defaultValue: 0,
},
{
id: "dy",
name: "DY",
direction: "input",
kind: "number",
defaultValue: 0,
},
{ id: "dw", name: "DW", direction: "input", kind: "number" },
{ id: "dh", name: "DH", direction: "input", kind: "number" },
{ id: "flip_x", name: "Flip X", direction: "input", kind: "boolean" },
{ id: "flip_y", name: "Flip Y", direction: "input", kind: "boolean" },
],
outputs: [
{ id: "exec_out", name: "Exec", direction: "output", kind: "exec" },
],
properties: [],
},
{
emitExec: ({ indent, indentLevel, resolveValueInput, emitNextExec }) => {
const OMIT = "__pg_omit__";
const sx = resolveValueInput("sx", "0");
const sy = resolveValueInput("sy", "0");
const sw = resolveValueInput("sw", "8");
const sh = resolveValueInput("sh", "8");
const dx = resolveValueInput("dx", "0");
const dy = resolveValueInput("dy", "0");
const dw = resolveValueInput("dw", OMIT);
const dh = resolveValueInput("dh", OMIT);
const flipX = resolveValueInput("flip_x", OMIT);
const flipY = resolveValueInput("flip_y", OMIT);
const args = [sx, sy, sw, sh, dx, dy];
const includeDestSize =
dw !== OMIT || dh !== OMIT || flipX !== OMIT || flipY !== OMIT;
if (includeDestSize) {
args.push(dw === OMIT ? sw : dw, dh === OMIT ? sh : dh);
}
if (flipX !== OMIT || flipY !== OMIT) {
args.push(
flipX === OMIT ? "false" : flipX,
flipY === OMIT ? "false" : flipY
);
}
const line = `${indent(indentLevel)}sspr(${args.join(", ")})`;
return [line, ...emitNextExec("exec_out")];
},
}
);

View File

@@ -0,0 +1,67 @@
import { createNodeModule } from "../nodeTypes.js";
/**
* Branches execution based on a boolean condition.
*/
export const ifNode = createNodeModule(
{
id: "if",
title: "If",
category: "Logic",
description: "Branch execution based on a boolean condition.",
searchTags: ["if", "condition", "branch", "logic"],
inputs: [
{ id: "exec_in", name: "Exec", direction: "input", kind: "exec" },
{
id: "condition",
name: "Condition",
direction: "input",
kind: "boolean",
},
],
outputs: [
{ id: "then", name: "Then", direction: "output", kind: "exec" },
{ id: "else", name: "Else", direction: "output", kind: "exec" },
],
properties: [],
},
{
emitExec: ({
indent,
indentLevel,
resolveValueInput,
emitBranch,
findExecTargets,
path,
}) => {
const condition = resolveValueInput("condition", "false");
const lines = [`${indent(indentLevel)}if ${condition} then`];
const thenLines = emitBranch("then", {
indentLevel: indentLevel + 1,
path: new Set(path),
});
if (!thenLines.length) {
lines.push(`${indent(indentLevel + 1)}-- then branch`);
} else {
lines.push(...thenLines);
}
if (findExecTargets("else").length) {
lines.push(`${indent(indentLevel)}else`);
const elseLines = emitBranch("else", {
indentLevel: indentLevel + 1,
path: new Set(path),
});
if (!elseLines.length) {
lines.push(`${indent(indentLevel + 1)}-- else branch`);
} else {
lines.push(...elseLines);
}
}
lines.push(`${indent(indentLevel)}end`);
return lines;
},
}
);

View File

@@ -0,0 +1,87 @@
import { eventInitNode } from "./eventStart.js";
import { eventUpdateNode } from "./eventUpdate.js";
import { eventDrawNode } from "./eventDraw.js";
import { printNode } from "./print.js";
import { setVariableNode } from "./setVariable.js";
import { getVariableNode } from "./getVariable.js";
import { numberLiteralNode } from "./numberLiteral.js";
import { stringLiteralNode } from "./stringLiteral.js";
import { booleanLiteralNode } from "./booleanLiteral.js";
import { addNumberNode } from "./addNumber.js";
import { multiplyNumberNode } from "./multiplyNumber.js";
import { compareNode } from "./compare.js";
import { sequenceNode } from "./sequence.js";
import { ifNode } from "./ifNode.js";
import { forLoopNode } from "./forLoop.js";
import { customEventNode } from "./customEvent.js";
import { callCustomEventNode } from "./callCustomEvent.js";
import { graphicsNodes, graphicsFunctionChecklist } from "./graphics/index.js";
import { systemNodes, systemFunctionChecklist } from "./system/index.js";
import { tableNodes, tableFunctionChecklist } from "./table/index.js";
import { inputNodes, inputFunctionChecklist } from "./input/index.js";
import { audioNodes, audioFunctionChecklist } from "./audio/index.js";
import { mapNodes, mapFunctionChecklist } from "./map/index.js";
import { memoryNodes, memoryFunctionChecklist } from "./memory/index.js";
import { mathNodes, mathFunctionChecklist } from "./math/index.js";
import { menuNodes, menuFunctionChecklist } from "./menu/index.js";
import { stringNodes, stringFunctionChecklist } from "./strings/index.js";
import { dataNodes, dataFunctionChecklist } from "./data/index.js";
import { gpioNodes, gpioFunctionChecklist } from "./gpio/index.js";
import { serialNodes, serialFunctionChecklist } from "./serial/index.js";
import { devkitNodes, devkitFunctionChecklist } from "./devkit/index.js";
import { luaNodes, luaFunctionChecklist } from "./lua/index.js";
import { localVariableNodes } from "./localVariables.js";
export const nodeModules = [
eventInitNode,
eventUpdateNode,
eventDrawNode,
printNode,
setVariableNode,
getVariableNode,
numberLiteralNode,
stringLiteralNode,
booleanLiteralNode,
addNumberNode,
multiplyNumberNode,
compareNode,
sequenceNode,
ifNode,
forLoopNode,
customEventNode,
callCustomEventNode,
...localVariableNodes,
...graphicsNodes,
...systemNodes,
...tableNodes,
...inputNodes,
...audioNodes,
...mapNodes,
...memoryNodes,
...mathNodes,
...menuNodes,
...stringNodes,
...dataNodes,
...gpioNodes,
...serialNodes,
...devkitNodes,
...luaNodes,
];
export {
systemFunctionChecklist,
graphicsFunctionChecklist,
tableFunctionChecklist,
inputFunctionChecklist,
audioFunctionChecklist,
mapFunctionChecklist,
memoryFunctionChecklist,
mathFunctionChecklist,
menuFunctionChecklist,
stringFunctionChecklist,
dataFunctionChecklist,
gpioFunctionChecklist,
serialFunctionChecklist,
devkitFunctionChecklist,
luaFunctionChecklist,
};

View File

@@ -0,0 +1,55 @@
import { createNodeModule } from "../../nodeTypes.js";
/**
* Reads the state of a PICO-8 button using BTN.
*/
export const btnNode = createNodeModule(
{
id: "input_btn",
title: "Button State",
category: "Input",
description: "Read the current pressed state for a controller button.",
searchTags: ["btn", "input", "button", "press"],
inputs: [
{
id: "button",
name: "Button",
direction: "input",
kind: "number",
description: "Button index or glyph code",
},
{
id: "player",
name: "Player",
direction: "input",
kind: "number",
description: "Optional player index",
},
],
outputs: [
{ id: "value", name: "Pressed", direction: "output", kind: "boolean" },
],
properties: [],
},
{
evaluateValue: ({ resolveValueInput }) => {
const OMIT = "__pg_omit__";
const button = resolveValueInput("button", OMIT);
const player = resolveValueInput("player", OMIT);
if (button === OMIT && player === OMIT) {
return "btn()";
}
if (button === OMIT) {
return `btn(nil, ${player})`;
}
if (player === OMIT) {
return `btn(${button})`;
}
return `btn(${button}, ${player})`;
},
}
);

View File

@@ -0,0 +1,56 @@
import { createNodeModule } from "../../nodeTypes.js";
/**
* Reads button press transitions using BTNP.
*/
export const btnpNode = createNodeModule(
{
id: "input_btnp",
title: "Button Press",
category: "Input",
description:
"Detect when a button is initially pressed or repeats using BTNP.",
searchTags: ["btnp", "input", "button", "press", "repeat"],
inputs: [
{
id: "button",
name: "Button",
direction: "input",
kind: "number",
description: "Button index or glyph code",
},
{
id: "player",
name: "Player",
direction: "input",
kind: "number",
description: "Optional player index",
},
],
outputs: [
{ id: "value", name: "Pressed", direction: "output", kind: "boolean" },
],
properties: [],
},
{
evaluateValue: ({ resolveValueInput }) => {
const OMIT = "__pg_omit__";
const button = resolveValueInput("button", OMIT);
const player = resolveValueInput("player", OMIT);
if (button === OMIT && player === OMIT) {
return "btnp()";
}
if (button === OMIT) {
return `btnp(nil, ${player})`;
}
if (player === OMIT) {
return `btnp(${button})`;
}
return `btnp(${button}, ${player})`;
},
}
);

View File

@@ -0,0 +1,175 @@
import { createNodeModule } from "../../nodeTypes.js";
const DEFAULT_PLAYER_VALUE = "0";
/**
* Normalizes the stored player property to a valid controller index.
*
* @param {unknown} value Raw inspector value.
* @returns {number}
*/
const resolvePlayerIndex = (value) => {
const numeric = Number.parseInt(
typeof value === "string" ? value : String(value ?? DEFAULT_PLAYER_VALUE),
10
);
if (!Number.isFinite(numeric) || numeric < 0) {
return 0;
}
if (numeric > 7) {
return 7;
}
return numeric;
};
const BUTTON_OPTIONS = [
{ value: "0", label: "Left" },
{ value: "1", label: "Right" },
{ value: "2", label: "Up" },
{ value: "3", label: "Down" },
{ value: "4", label: "O" },
{ value: "5", label: "X" },
];
const BUTTON_VALUE_SET = new Set(BUTTON_OPTIONS.map((option) => option.value));
const DEFAULT_BUTTON_VALUE = BUTTON_OPTIONS[0]?.value ?? "0";
/**
* Normalizes button property values to a valid BTN index represented as a string.
*
* @param {unknown} value Raw inspector value.
* @returns {string}
*/
const resolveButtonValue = (value) => {
const numeric = Number.parseInt(
typeof value === "string" ? value : String(value ?? DEFAULT_BUTTON_VALUE),
10
);
const candidate = Number.isFinite(numeric) ? String(numeric) : DEFAULT_BUTTON_VALUE;
if (BUTTON_VALUE_SET.has(candidate)) {
return candidate;
}
return DEFAULT_BUTTON_VALUE;
};
const PLAYER_OPTIONS = [
{ value: "0", label: "Player 0 (Cursors / Z X)" },
{ value: "1", label: "Player 1 (SFED / Tab Q W A)" },
{ value: "2", label: "Player 2" },
{ value: "3", label: "Player 3" },
{ value: "4", label: "Player 4" },
{ value: "5", label: "Player 5" },
{ value: "6", label: "Player 6" },
{ value: "7", label: "Player 7" },
];
/**
* Evaluates whether the configured button is currently pressed.
*/
export const controllerButtonsNode = createNodeModule(
{
id: "input_button_constants",
title: "Named Button",
category: "Input",
description:
"Read a specific controller button with a dropdown selector and optional player override.",
searchTags: [
"button",
"controller",
"dpad",
"input",
"player",
"named",
"press",
],
inputs: [
{
id: "button",
name: "Button",
direction: "input",
kind: "number",
description: "Optional button index override",
},
{
id: "player",
name: "Player",
direction: "input",
kind: "number",
description: "Optional player index override",
},
],
outputs: [
{
id: "pressed",
name: "Pressed",
direction: "output",
kind: "boolean",
},
],
properties: [
{
key: "button",
label: "Button",
type: "enum",
defaultValue: DEFAULT_BUTTON_VALUE,
options: BUTTON_OPTIONS,
},
{
key: "player",
label: "Default Player",
type: "enum",
defaultValue: DEFAULT_PLAYER_VALUE,
options: PLAYER_OPTIONS,
},
],
initializeProperties: (properties) => {
properties.button = resolveButtonValue(properties.button);
const normalizedPlayer = resolvePlayerIndex(properties.player);
properties.player = String(normalizedPlayer);
const buttonInlineKey = "pin:button";
const playerInlineKey = "pin:player";
const numericButton = Number.parseInt(properties.button, 10);
const numericPlayer = normalizedPlayer;
properties[buttonInlineKey] = Number.isFinite(numericButton)
? numericButton
: 0;
properties[playerInlineKey] = Number.isFinite(numericPlayer)
? numericPlayer
: 0;
},
onPropertiesChanged: (properties) => {
properties.button = resolveButtonValue(properties.button);
const normalizedPlayer = resolvePlayerIndex(properties.player);
properties.player = String(normalizedPlayer);
const buttonInlineKey = "pin:button";
const playerInlineKey = "pin:player";
const numericButton = Number.parseInt(properties.button, 10);
const numericPlayer = normalizedPlayer;
properties[buttonInlineKey] = Number.isFinite(numericButton)
? numericButton
: 0;
properties[playerInlineKey] = Number.isFinite(numericPlayer)
? numericPlayer
: 0;
},
},
{
evaluateValue: ({ node, resolveValueInput, formatLiteral }) => {
const buttonValue = resolveButtonValue(node.properties.button);
const parsedButton = Number.parseInt(buttonValue, 10);
const buttonIndex = Number.isFinite(parsedButton) ? parsedButton : 0;
const fallbackButton = formatLiteral("number", buttonIndex);
const buttonInput = resolveValueInput("button", fallbackButton);
const fallbackPlayer = formatLiteral(
"number",
resolvePlayerIndex(node.properties.player)
);
const playerInput = resolveValueInput("player", fallbackPlayer);
return `btn(${buttonInput}, ${playerInput})`;
},
}
);

View File

@@ -0,0 +1,17 @@
import { btnNode } from "./btn.js";
import { btnpNode } from "./btnp.js";
import { controllerButtonsNode } from "./controllerButtons.js";
/**
* All input-related node modules backed by PICO-8 helpers.
*/
export const inputNodes = [controllerButtonsNode, btnNode, btnpNode];
/**
* Checklist tracking coverage of documented input helpers.
* @type {Array<{ function: string, nodeId: string, implemented: boolean }>}
*/
export const inputFunctionChecklist = [
{ function: "BTN", nodeId: "input_btn", implemented: true },
{ function: "BTNP", nodeId: "input_btnp", implemented: true },
];

View File

View File

@@ -0,0 +1,169 @@
import { createNodeModule } from "../nodeTypes.js";
const LOCAL_TYPES = [
{ value: "number", label: "Number" },
{ value: "string", label: "String" },
{ value: "boolean", label: "Boolean" },
{ value: "table", label: "Table" },
];
const DEFAULT_LOCAL_TYPE = LOCAL_TYPES[0]?.value ?? "number";
const sanitizeType = (raw) => {
const candidate = typeof raw === "string" ? raw.toLowerCase() : DEFAULT_LOCAL_TYPE;
return LOCAL_TYPES.some((entry) => entry.value === candidate)
? candidate
: DEFAULT_LOCAL_TYPE;
};
const ensureName = (raw, fallback = "localVar") => {
const value = typeof raw === "string" ? raw.trim() : "";
return value.length ? value : fallback;
};
const resolveValuePropertyKey = (type) => {
switch (type) {
case "string":
return "valueString";
case "boolean":
return "valueBoolean";
case "table":
return "valueTable";
case "number":
default:
return "valueNumber";
}
};
const defaultValueForType = (type) => {
switch (type) {
case "string":
return "";
case "boolean":
return false;
case "table":
return "{}";
case "number":
default:
return 0;
}
};
const initializeLocalProperties = (properties, { includeValue }) => {
const type = sanitizeType(properties.variableType);
properties.variableType = type;
properties.name = ensureName(properties.name);
if (typeof properties.valueNumber !== "number") {
properties.valueNumber = Number.isFinite(Number(properties.valueNumber))
? Number(properties.valueNumber)
: 0;
}
if (typeof properties.valueString !== "string") {
properties.valueString = String(properties.valueString ?? "");
}
if (typeof properties.valueBoolean !== "boolean") {
properties.valueBoolean = Boolean(properties.valueBoolean);
}
if (typeof properties.valueTable !== "string") {
properties.valueTable = String(properties.valueTable ?? "{}");
}
if (includeValue) {
const key = resolveValuePropertyKey(type);
const value = properties[key] ?? defaultValueForType(type);
properties[key] = value;
}
};
const normalizeLocalProperties = (properties, { includeValue }) => {
const type = sanitizeType(properties.variableType);
properties.variableType = type;
properties.name = ensureName(properties.name);
if (includeValue) {
const key = resolveValuePropertyKey(type);
const value = properties[key] ?? defaultValueForType(type);
properties[key] = value;
}
};
export const setLocalVariableNode = createNodeModule(
{
id: "set_local_var",
title: "Set Local",
category: "Logic",
description: "Declare or assign a local variable with an inline literal.",
searchTags: ["local", "set", "assign", "variable"],
inputs: [
{ id: "exec_in", name: "Exec", direction: "input", kind: "exec" },
{
id: "value",
name: "Value",
direction: "input",
kind: "any",
description: "Optional override for the inline literal",
},
],
outputs: [
{ id: "exec_out", name: "Exec", direction: "output", kind: "exec" },
],
properties: [],
initializeProperties: (properties) => {
initializeLocalProperties(properties, { includeValue: true });
},
onPropertiesChanged: (properties) => {
normalizeLocalProperties(properties, { includeValue: true });
},
},
{
emitExec: ({
node,
indent,
indentLevel,
resolveValueInput,
sanitizeIdentifier,
emitNextExec,
formatLiteral,
}) => {
const type = sanitizeType(node.properties.variableType);
const name = sanitizeIdentifier(ensureName(node.properties.name));
const valueKey = resolveValuePropertyKey(type);
const inlineValue = node.properties[valueKey] ?? defaultValueForType(type);
const fallbackLiteral =
type === "table"
? String(inlineValue ?? "{}") || "{}"
: formatLiteral(type, inlineValue);
const valueExpression = resolveValueInput("value", fallbackLiteral);
const statement = `${indent(indentLevel)}local ${name} = ${valueExpression}`;
return [statement, ...emitNextExec("exec_out")];
},
}
);
export const getLocalVariableNode = createNodeModule(
{
id: "get_local_var",
title: "Get Local",
category: "Logic",
description: "Access a local variable declared earlier in the flow.",
searchTags: ["local", "get", "variable", "read"],
inputs: [],
outputs: [
{ id: "value", name: "Value", direction: "output", kind: "any" },
],
properties: [],
initializeProperties: (properties) => {
initializeLocalProperties(properties, { includeValue: false });
},
onPropertiesChanged: (properties) => {
normalizeLocalProperties(properties, { includeValue: false });
},
},
{
evaluateValue: ({ node, sanitizeIdentifier }) => {
return sanitizeIdentifier(ensureName(node.properties.name));
},
}
);
export const localVariableNodes = [setLocalVariableNode, getLocalVariableNode];

View File

@@ -0,0 +1,38 @@
import { createNodeModule } from "../../nodeTypes.js";
/**
* Creates a coroutine from a function using coroutine.create().
*/
export const luaCoroutineCreateNode = createNodeModule(
{
id: "lua_coroutine_create",
title: "Coroutine Create",
category: "Lua",
description: "Wrap a function in a coroutine via coroutine.create().",
searchTags: ["coroutine", "create", "lua"],
inputs: [
{
id: "fn",
name: "Function",
direction: "input",
kind: "any",
description: "Function to run inside the coroutine",
},
],
outputs: [
{
id: "coroutine",
name: "Coroutine",
direction: "output",
kind: "any",
},
],
properties: [],
},
{
evaluateValue: ({ resolveValueInput }) => {
const fn = resolveValueInput("fn", "function() end");
return `coroutine.create(${fn})`;
},
}
);

View File

@@ -0,0 +1,91 @@
import { createNodeModule } from "../../nodeTypes.js";
/**
* Resumes a coroutine and exposes the success flag and first result.
*/
export const luaCoroutineResumeNode = createNodeModule(
{
id: "lua_coroutine_resume",
title: "Coroutine Resume",
category: "Lua",
description:
"Resume a coroutine and capture the success flag alongside the first returned value.",
searchTags: ["coroutine", "resume", "lua"],
inputs: [
{ id: "exec_in", name: "Exec", direction: "input", kind: "exec" },
{
id: "coroutine",
name: "Coroutine",
direction: "input",
kind: "any",
},
{
id: "arg1",
name: "Arg 1",
direction: "input",
kind: "any",
description: "Optional first value passed to coroutine",
},
{
id: "arg2",
name: "Arg 2",
direction: "input",
kind: "any",
description: "Optional second value passed to coroutine",
},
],
outputs: [
{ id: "exec_out", name: "Exec", direction: "output", kind: "exec" },
{
id: "success",
name: "Success",
direction: "output",
kind: "boolean",
},
{
id: "result",
name: "Result",
direction: "output",
kind: "any",
},
],
properties: [],
},
{
emitExec: ({
node,
indent,
indentLevel,
resolveValueInput,
sanitizeIdentifier,
emitNextExec,
}) => {
const OMIT = "__pg_omit__";
const target = resolveValueInput("coroutine", "nil");
const arg1 = resolveValueInput("arg1", OMIT);
const arg2 = resolveValueInput("arg2", OMIT);
const args = [target];
if (arg1 !== OMIT) {
args.push(arg1);
}
if (arg2 !== OMIT) {
args.push(arg2);
}
const successName = sanitizeIdentifier(`__pg_${node.id}_co_success`);
const resultName = sanitizeIdentifier(`__pg_${node.id}_co_result`);
const line = `${indent(indentLevel)}local ${successName}, ${resultName} = coroutine.resume(${args.join(", ")})`;
return [line, ...emitNextExec("exec_out")];
},
evaluateValue: ({ node, pinId, sanitizeIdentifier }) => {
const successName = sanitizeIdentifier(`__pg_${node.id}_co_success`);
const resultName = sanitizeIdentifier(`__pg_${node.id}_co_result`);
if (pinId === "success") {
return successName;
}
if (pinId === "result") {
return resultName;
}
return "nil";
},
}
);

View File

@@ -0,0 +1,27 @@
import { createNodeModule } from "../../nodeTypes.js";
/**
* Returns the currently running coroutine via coroutine.running().
*/
export const luaCoroutineRunningNode = createNodeModule(
{
id: "lua_coroutine_running",
title: "Coroutine Running",
category: "Lua",
description: "Access the currently running coroutine reference.",
searchTags: ["coroutine", "running", "lua"],
inputs: [],
outputs: [
{
id: "coroutine",
name: "Coroutine",
direction: "output",
kind: "any",
},
],
properties: [],
},
{
evaluateValue: () => "coroutine.running()",
}
);

View File

@@ -0,0 +1,32 @@
import { createNodeModule } from "../../nodeTypes.js";
/**
* Reports a coroutine's status via coroutine.status().
*/
export const luaCoroutineStatusNode = createNodeModule(
{
id: "lua_coroutine_status",
title: "Coroutine Status",
category: "Lua",
description: "Retrieve a coroutine's status string using coroutine.status().",
searchTags: ["coroutine", "status", "lua"],
inputs: [
{
id: "coroutine",
name: "Coroutine",
direction: "input",
kind: "any",
},
],
outputs: [
{ id: "status", name: "Status", direction: "output", kind: "string" },
],
properties: [],
},
{
evaluateValue: ({ resolveValueInput }) => {
const target = resolveValueInput("coroutine", "nil");
return `coroutine.status(${target})`;
},
}
);

View File

@@ -0,0 +1,53 @@
import { createNodeModule } from "../../nodeTypes.js";
/**
* Yields from within a coroutine using coroutine.yield().
*/
export const luaCoroutineYieldNode = createNodeModule(
{
id: "lua_coroutine_yield",
title: "Coroutine Yield",
category: "Lua",
description: "Yield execution from within a coroutine and optionally return values.",
searchTags: ["coroutine", "yield", "lua"],
inputs: [
{ id: "exec_in", name: "Exec", direction: "input", kind: "exec" },
{
id: "value1",
name: "Value 1",
direction: "input",
kind: "any",
description: "Optional first value yielded to the caller",
},
{
id: "value2",
name: "Value 2",
direction: "input",
kind: "any",
description: "Optional second value yielded to the caller",
},
],
outputs: [
{ id: "exec_out", name: "Exec", direction: "output", kind: "exec" },
],
properties: [],
},
{
emitExec: ({ indent, indentLevel, resolveValueInput, emitNextExec }) => {
const OMIT = "__pg_omit__";
const value1 = resolveValueInput("value1", OMIT);
const value2 = resolveValueInput("value2", OMIT);
const args = [];
if (value1 !== OMIT) {
args.push(value1);
}
if (value2 !== OMIT) {
args.push(value2);
}
const call = args.length
? `${indent(indentLevel)}coroutine.yield(${args.join(", ")})`
: `${indent(indentLevel)}coroutine.yield()`;
return [call, ...emitNextExec("exec_out")];
},
}
);

View File

@@ -0,0 +1,27 @@
import { createNodeModule } from "../../nodeTypes.js";
/**
* Retrieves the metatable associated with a table via getmetatable().
*/
export const luaGetMetatableNode = createNodeModule(
{
id: "lua_getmetatable",
title: "Get Metatable",
category: "Lua",
description: "Fetch a table's metatable using getmetatable().",
searchTags: ["getmetatable", "metatable", "lua"],
inputs: [
{ id: "table", name: "Table", direction: "input", kind: "table" },
],
outputs: [
{ id: "metatable", name: "Metatable", direction: "output", kind: "table" },
],
properties: [],
},
{
evaluateValue: ({ resolveValueInput }) => {
const tableValue = resolveValueInput("table", "{}");
return `getmetatable(${tableValue})`;
},
}
);

View File

@@ -0,0 +1,63 @@
import { luaSetMetatableNode } from "./setMetatable.js";
import { luaGetMetatableNode } from "./getMetatable.js";
import { luaRawGetNode } from "./rawGet.js";
import { luaRawSetNode } from "./rawSet.js";
import { luaRawEqualNode } from "./rawEqual.js";
import { luaCoroutineCreateNode } from "./coroutineCreate.js";
import { luaCoroutineResumeNode } from "./coroutineResume.js";
import { luaCoroutineYieldNode } from "./coroutineYield.js";
import { luaCoroutineStatusNode } from "./coroutineStatus.js";
import { luaCoroutineRunningNode } from "./coroutineRunning.js";
/**
* Node modules covering Lua metatable and coroutine helpers.
*/
export const luaNodes = [
luaSetMetatableNode,
luaGetMetatableNode,
luaRawGetNode,
luaRawSetNode,
luaRawEqualNode,
luaCoroutineCreateNode,
luaCoroutineResumeNode,
luaCoroutineYieldNode,
luaCoroutineStatusNode,
luaCoroutineRunningNode,
];
/**
* Checklist tracking coverage of Lua metatable and coroutine helpers.
* @type {Array<{ function: string, nodeId: string, implemented: boolean }>}
*/
export const luaFunctionChecklist = [
{ function: "SETMETATABLE", nodeId: "lua_setmetatable", implemented: true },
{ function: "GETMETATABLE", nodeId: "lua_getmetatable", implemented: true },
{ function: "RAWGET", nodeId: "lua_rawget", implemented: true },
{ function: "RAWSET", nodeId: "lua_rawset", implemented: true },
{ function: "RAWEQUAL", nodeId: "lua_rawequal", implemented: true },
{
function: "COROUTINE.CREATE",
nodeId: "lua_coroutine_create",
implemented: true,
},
{
function: "COROUTINE.RESUME",
nodeId: "lua_coroutine_resume",
implemented: true,
},
{
function: "COROUTINE.YIELD",
nodeId: "lua_coroutine_yield",
implemented: true,
},
{
function: "COROUTINE.STATUS",
nodeId: "lua_coroutine_status",
implemented: true,
},
{
function: "COROUTINE.RUNNING",
nodeId: "lua_coroutine_running",
implemented: true,
},
];

View File

@@ -0,0 +1,29 @@
import { createNodeModule } from "../../nodeTypes.js";
/**
* Compares two values using rawequal().
*/
export const luaRawEqualNode = createNodeModule(
{
id: "lua_rawequal",
title: "Raw Equal",
category: "Lua",
description: "Compare values without metamethods using rawequal().",
searchTags: ["rawequal", "compare", "lua"],
inputs: [
{ id: "a", name: "A", direction: "input", kind: "any" },
{ id: "b", name: "B", direction: "input", kind: "any" },
],
outputs: [
{ id: "equal", name: "Equal", direction: "output", kind: "boolean" },
],
properties: [],
},
{
evaluateValue: ({ resolveValueInput }) => {
const a = resolveValueInput("a", "nil");
const b = resolveValueInput("b", "nil");
return `rawequal(${a}, ${b})`;
},
}
);

View File

@@ -0,0 +1,29 @@
import { createNodeModule } from "../../nodeTypes.js";
/**
* Retrieves a raw table entry using rawget().
*/
export const luaRawGetNode = createNodeModule(
{
id: "lua_rawget",
title: "Raw Get",
category: "Lua",
description: "Access a table entry without metamethods using rawget().",
searchTags: ["rawget", "table", "lua"],
inputs: [
{ id: "table", name: "Table", direction: "input", kind: "table" },
{ id: "key", name: "Key", direction: "input", kind: "any" },
],
outputs: [
{ id: "value", name: "Value", direction: "output", kind: "any" },
],
properties: [],
},
{
evaluateValue: ({ resolveValueInput }) => {
const tableValue = resolveValueInput("table", "{}");
const key = resolveValueInput("key", "nil");
return `rawget(${tableValue}, ${key})`;
},
}
);

View File

@@ -0,0 +1,33 @@
import { createNodeModule } from "../../nodeTypes.js";
/**
* Writes a raw table entry using rawset().
*/
export const luaRawSetNode = createNodeModule(
{
id: "lua_rawset",
title: "Raw Set",
category: "Lua",
description: "Assign a table entry without metamethods using rawset().",
searchTags: ["rawset", "table", "lua"],
inputs: [
{ id: "exec_in", name: "Exec", direction: "input", kind: "exec" },
{ id: "table", name: "Table", direction: "input", kind: "table" },
{ id: "key", name: "Key", direction: "input", kind: "any" },
{ id: "value", name: "Value", direction: "input", kind: "any" },
],
outputs: [
{ id: "exec_out", name: "Exec", direction: "output", kind: "exec" },
],
properties: [],
},
{
emitExec: ({ indent, indentLevel, resolveValueInput, emitNextExec }) => {
const tableValue = resolveValueInput("table", "{}");
const key = resolveValueInput("key", "nil");
const value = resolveValueInput("value", "nil");
const line = `${indent(indentLevel)}rawset(${tableValue}, ${key}, ${value})`;
return [line, ...emitNextExec("exec_out")];
},
}
);

View File

@@ -0,0 +1,35 @@
import { createNodeModule } from "../../nodeTypes.js";
/**
* Applies a metatable to a table via setmetatable().
*/
export const luaSetMetatableNode = createNodeModule(
{
id: "lua_setmetatable",
title: "Set Metatable",
category: "Lua",
description: "Assign a metatable to a table using setmetatable().",
searchTags: ["setmetatable", "metatable", "lua"],
inputs: [
{ id: "table", name: "Table", direction: "input", kind: "table" },
{
id: "metatable",
name: "Metatable",
direction: "input",
kind: "table",
defaultValue: "nil",
},
],
outputs: [
{ id: "table_out", name: "Table", direction: "output", kind: "table" },
],
properties: [],
},
{
evaluateValue: ({ resolveValueInput }) => {
const tableValue = resolveValueInput("table", "{}");
const metatable = resolveValueInput("metatable", "nil");
return `setmetatable(${tableValue}, ${metatable})`;
},
}
);

View File

@@ -0,0 +1,20 @@
import { mapMgetNode } from "./mget.js";
import { mapMsetNode } from "./mset.js";
import { mapDrawNode } from "./map.js";
import { mapTlineNode } from "./tline.js";
/**
* All map-related node modules backed by PICO-8 helpers.
*/
export const mapNodes = [mapMgetNode, mapMsetNode, mapDrawNode, mapTlineNode];
/**
* Checklist tracking coverage of documented map helpers.
* @type {Array<{ function: string, nodeId: string, implemented: boolean }>}
*/
export const mapFunctionChecklist = [
{ function: "MGET", nodeId: "map_mget", implemented: true },
{ function: "MSET", nodeId: "map_mset", implemented: true },
{ function: "MAP", nodeId: "map_map", implemented: true },
{ function: "TLINE", nodeId: "map_tline", implemented: true },
];

View File

@@ -0,0 +1,108 @@
import { createNodeModule } from "../../nodeTypes.js";
/**
* Draws map tiles using PICO-8's map helper.
*/
export const mapDrawNode = createNodeModule(
{
id: "map_map",
title: "Draw Map",
category: "Map",
description:
"Render a section of the map to the screen with optional layers.",
searchTags: ["map", "draw", "tiles", "layer"],
inputs: [
{ id: "exec_in", name: "Exec", direction: "input", kind: "exec" },
{
id: "tile_x",
name: "Tile X",
direction: "input",
kind: "number",
description: "Starting tile X coordinate",
},
{
id: "tile_y",
name: "Tile Y",
direction: "input",
kind: "number",
description: "Starting tile Y coordinate",
},
{
id: "screen_x",
name: "Screen X",
direction: "input",
kind: "number",
description: "Screen X position in pixels",
},
{
id: "screen_y",
name: "Screen Y",
direction: "input",
kind: "number",
description: "Screen Y position in pixels",
},
{
id: "tile_w",
name: "Tile Width",
direction: "input",
kind: "number",
description: "Number of tiles across",
},
{
id: "tile_h",
name: "Tile Height",
direction: "input",
kind: "number",
description: "Number of tiles down",
},
{
id: "layers",
name: "Layers",
direction: "input",
kind: "number",
description: "Sprite flag bitmask",
},
],
outputs: [
{ id: "exec_out", name: "Exec", direction: "output", kind: "exec" },
],
properties: [],
},
{
emitExec: ({ indent, indentLevel, resolveValueInput, emitNextExec }) => {
const OMIT = "__pg_omit__";
const tileX = resolveValueInput("tile_x", OMIT);
const tileY = resolveValueInput("tile_y", OMIT);
const screenX = resolveValueInput("screen_x", OMIT);
const screenY = resolveValueInput("screen_y", OMIT);
const tileW = resolveValueInput("tile_w", OMIT);
const tileH = resolveValueInput("tile_h", OMIT);
const layers = resolveValueInput("layers", OMIT);
const values = [
{ value: tileX, placeholder: "0" },
{ value: tileY, placeholder: "0" },
{ value: screenX, placeholder: "0" },
{ value: screenY, placeholder: "0" },
{ value: tileW, placeholder: "nil" },
{ value: tileH, placeholder: "nil" },
{ value: layers, placeholder: "nil" },
];
let lastIndex = values.length - 1;
while (lastIndex >= 0 && values[lastIndex].value === OMIT) {
lastIndex -= 1;
}
const args = [];
for (let index = 0; index <= lastIndex; index += 1) {
const entry = values[index];
args.push(entry.value === OMIT ? entry.placeholder : entry.value);
}
const call = args.length ? `map(${args.join(", ")})` : "map()";
const line = `${indent(indentLevel)}${call}`;
return [line, ...emitNextExec("exec_out")];
},
}
);

View File

@@ -0,0 +1,41 @@
import { createNodeModule } from "../../nodeTypes.js";
/**
* Reads a map tile value using PICO-8's mget helper.
*/
export const mapMgetNode = createNodeModule(
{
id: "map_mget",
title: "Get Map Tile",
category: "Map",
description: "Fetch the numeric map value at a tile coordinate.",
searchTags: ["mget", "map", "tile", "read"],
inputs: [
{
id: "x",
name: "Tile X",
direction: "input",
kind: "number",
defaultValue: 0,
},
{
id: "y",
name: "Tile Y",
direction: "input",
kind: "number",
defaultValue: 0,
},
],
outputs: [
{ id: "value", name: "Value", direction: "output", kind: "number" },
],
properties: [],
},
{
evaluateValue: ({ resolveValueInput }) => {
const x = resolveValueInput("x", "0");
const y = resolveValueInput("y", "0");
return `mget(${x}, ${y})`;
},
}
);

View File

@@ -0,0 +1,51 @@
import { createNodeModule } from "../../nodeTypes.js";
/**
* Writes a map tile value using PICO-8's mset helper.
*/
export const mapMsetNode = createNodeModule(
{
id: "map_mset",
title: "Set Map Tile",
category: "Map",
description: "Store a numeric value at a tile coordinate.",
searchTags: ["mset", "map", "tile", "write"],
inputs: [
{ id: "exec_in", name: "Exec", direction: "input", kind: "exec" },
{
id: "x",
name: "Tile X",
direction: "input",
kind: "number",
defaultValue: 0,
},
{
id: "y",
name: "Tile Y",
direction: "input",
kind: "number",
defaultValue: 0,
},
{
id: "value",
name: "Value",
direction: "input",
kind: "number",
defaultValue: 0,
},
],
outputs: [
{ id: "exec_out", name: "Exec", direction: "output", kind: "exec" },
],
properties: [],
},
{
emitExec: ({ indent, indentLevel, resolveValueInput, emitNextExec }) => {
const x = resolveValueInput("x", "0");
const y = resolveValueInput("y", "0");
const value = resolveValueInput("value", "0");
const line = `${indent(indentLevel)}mset(${x}, ${y}, ${value})`;
return [line, ...emitNextExec("exec_out")];
},
}
);

View File

@@ -0,0 +1,157 @@
import { createNodeModule } from "../../nodeTypes.js";
/**
* Draws textured lines or configures precision using PICO-8's tline helper.
*/
export const mapTlineNode = createNodeModule(
{
id: "map_tline",
title: "Draw Textured Line",
category: "Map",
description:
"Render a textured line from map data or adjust tline precision.",
searchTags: ["tline", "map", "texture", "line"],
inputs: [
{ id: "exec_in", name: "Exec", direction: "input", kind: "exec" },
{
id: "x0",
name: "X0",
direction: "input",
kind: "number",
description: "Line start X",
},
{
id: "y0",
name: "Y0",
direction: "input",
kind: "number",
description: "Line start Y",
},
{
id: "x1",
name: "X1",
direction: "input",
kind: "number",
description: "Line end X",
},
{
id: "y1",
name: "Y1",
direction: "input",
kind: "number",
description: "Line end Y",
},
{
id: "mx",
name: "Map X",
direction: "input",
kind: "number",
description: "Map sample X (tiles)",
},
{
id: "my",
name: "Map Y",
direction: "input",
kind: "number",
description: "Map sample Y (tiles)",
},
{
id: "mdx",
name: "MDX",
direction: "input",
kind: "number",
description: "Map X delta",
},
{
id: "mdy",
name: "MDY",
direction: "input",
kind: "number",
description: "Map Y delta",
},
{
id: "layers",
name: "Layers",
direction: "input",
kind: "number",
description: "Sprite flag bitmask",
},
{
id: "precision",
name: "Precision",
direction: "input",
kind: "number",
description: "Precision override when setting mode",
},
],
outputs: [
{ id: "exec_out", name: "Exec", direction: "output", kind: "exec" },
],
properties: [
{
key: "mode",
label: "Mode",
type: "enum",
defaultValue: "draw",
options: [
{ label: "Draw Line", value: "draw" },
{ label: "Set Precision", value: "precision" },
],
},
],
},
{
emitExec: ({
node,
indent,
indentLevel,
resolveValueInput,
emitNextExec,
}) => {
const mode = String(node.properties.mode ?? "draw");
if (mode === "precision") {
const precision = resolveValueInput("precision", "16");
const line = `${indent(indentLevel)}tline(${precision})`;
return [line, ...emitNextExec("exec_out")];
}
const OMIT = "__pg_omit__";
const x0 = resolveValueInput("x0", OMIT);
const y0 = resolveValueInput("y0", OMIT);
const x1 = resolveValueInput("x1", OMIT);
const y1 = resolveValueInput("y1", OMIT);
const mx = resolveValueInput("mx", OMIT);
const my = resolveValueInput("my", OMIT);
const mdx = resolveValueInput("mdx", OMIT);
const mdy = resolveValueInput("mdy", OMIT);
const layers = resolveValueInput("layers", OMIT);
const values = [
{ value: x0, placeholder: "0" },
{ value: y0, placeholder: "0" },
{ value: x1, placeholder: "0" },
{ value: y1, placeholder: "0" },
{ value: mx, placeholder: "0" },
{ value: my, placeholder: "0" },
{ value: mdx, placeholder: "0.125" },
{ value: mdy, placeholder: "0" },
{ value: layers, placeholder: "nil" },
];
let lastIndex = values.length - 1;
while (lastIndex >= 0 && values[lastIndex].value === OMIT) {
lastIndex -= 1;
}
const args = [];
for (let index = 0; index <= lastIndex; index += 1) {
const entry = values[index];
args.push(entry.value === OMIT ? entry.placeholder : entry.value);
}
const call = args.length ? `tline(${args.join(", ")})` : "tline()";
const line = `${indent(indentLevel)}${call}`;
return [line, ...emitNextExec("exec_out")];
},
}
);

View File

@@ -0,0 +1,25 @@
import { createNodeModule } from "../../nodeTypes.js";
/**
* Computes the absolute value using abs.
*/
export const mathAbsNode = createNodeModule(
{
id: "math_abs",
title: "Abs",
category: "Math",
description: "Return the absolute value of a number.",
searchTags: ["abs", "absolute", "math"],
inputs: [{ id: "value", name: "Value", direction: "input", kind: "number" }],
outputs: [
{ id: "result", name: "Result", direction: "output", kind: "number" },
],
properties: [],
},
{
evaluateValue: ({ resolveValueInput }) => {
const value = resolveValueInput("value", "0");
return `abs(${value})`;
},
}
);

View File

@@ -0,0 +1,30 @@
import { createNodeModule } from "../../nodeTypes.js";
/**
* Computes an angle from delta components using atan2.
*/
export const mathAtan2Node = createNodeModule(
{
id: "math_atan2",
title: "Atan2",
category: "Math",
description:
"Return the screenspace angle for a delta X and Y pair.",
searchTags: ["atan2", "angle", "math", "trig"],
inputs: [
{ id: "dx", name: "DX", direction: "input", kind: "number" },
{ id: "dy", name: "DY", direction: "input", kind: "number" },
],
outputs: [
{ id: "result", name: "Result", direction: "output", kind: "number" },
],
properties: [],
},
{
evaluateValue: ({ resolveValueInput }) => {
const dx = resolveValueInput("dx", "0");
const dy = resolveValueInput("dy", "0");
return `atan2(${dx}, ${dy})`;
},
}
);

View File

@@ -0,0 +1,29 @@
import { createNodeModule } from "../../nodeTypes.js";
/**
* Performs bitwise and using band.
*/
export const mathBandNode = createNodeModule(
{
id: "math_band",
title: "Bitwise AND",
category: "Math",
description: "Return bits set in both operands using BAND().",
searchTags: ["band", "bitwise", "and", "math"],
inputs: [
{ id: "a", name: "A", direction: "input", kind: "number" },
{ id: "b", name: "B", direction: "input", kind: "number" },
],
outputs: [
{ id: "result", name: "Result", direction: "output", kind: "number" },
],
properties: [],
},
{
evaluateValue: ({ resolveValueInput }) => {
const a = resolveValueInput("a", "0");
const b = resolveValueInput("b", "0");
return `band(${a}, ${b})`;
},
}
);

View File

@@ -0,0 +1,25 @@
import { createNodeModule } from "../../nodeTypes.js";
/**
* Performs bitwise not using bnot.
*/
export const mathBnotNode = createNodeModule(
{
id: "math_bnot",
title: "Bitwise NOT",
category: "Math",
description: "Invert every bit in a value using BNOT().",
searchTags: ["bnot", "bitwise", "not", "math"],
inputs: [{ id: "value", name: "Value", direction: "input", kind: "number" }],
outputs: [
{ id: "result", name: "Result", direction: "output", kind: "number" },
],
properties: [],
},
{
evaluateValue: ({ resolveValueInput }) => {
const value = resolveValueInput("value", "0");
return `bnot(${value})`;
},
}
);

View File

@@ -0,0 +1,29 @@
import { createNodeModule } from "../../nodeTypes.js";
/**
* Performs bitwise or using bor.
*/
export const mathBorNode = createNodeModule(
{
id: "math_bor",
title: "Bitwise OR",
category: "Math",
description: "Return bits set in either operand using BOR().",
searchTags: ["bor", "bitwise", "or", "math"],
inputs: [
{ id: "a", name: "A", direction: "input", kind: "number" },
{ id: "b", name: "B", direction: "input", kind: "number" },
],
outputs: [
{ id: "result", name: "Result", direction: "output", kind: "number" },
],
properties: [],
},
{
evaluateValue: ({ resolveValueInput }) => {
const a = resolveValueInput("a", "0");
const b = resolveValueInput("b", "0");
return `bor(${a}, ${b})`;
},
}
);

View File

@@ -0,0 +1,29 @@
import { createNodeModule } from "../../nodeTypes.js";
/**
* Performs bitwise exclusive-or using bxor.
*/
export const mathBxorNode = createNodeModule(
{
id: "math_bxor",
title: "Bitwise XOR",
category: "Math",
description: "Return bits set in exactly one operand using BXOR().",
searchTags: ["bxor", "bitwise", "xor", "math"],
inputs: [
{ id: "a", name: "A", direction: "input", kind: "number" },
{ id: "b", name: "B", direction: "input", kind: "number" },
],
outputs: [
{ id: "result", name: "Result", direction: "output", kind: "number" },
],
properties: [],
},
{
evaluateValue: ({ resolveValueInput }) => {
const a = resolveValueInput("a", "0");
const b = resolveValueInput("b", "0");
return `bxor(${a}, ${b})`;
},
}
);

View File

@@ -0,0 +1,25 @@
import { createNodeModule } from "../../nodeTypes.js";
/**
* Ceils a numeric value using ceil.
*/
export const mathCeilNode = createNodeModule(
{
id: "math_ceil",
title: "Ceil",
category: "Math",
description: "Round a value up to the nearest integer.",
searchTags: ["ceil", "ceiling", "math", "round"],
inputs: [{ id: "value", name: "Value", direction: "input", kind: "number" }],
outputs: [
{ id: "result", name: "Result", direction: "output", kind: "number" },
],
properties: [],
},
{
evaluateValue: ({ resolveValueInput }) => {
const value = resolveValueInput("value", "0");
return `ceil(${value})`;
},
}
);

View File

@@ -0,0 +1,26 @@
import { createNodeModule } from "../../nodeTypes.js";
/**
* Computes the cosine of a value using cos.
*/
export const mathCosNode = createNodeModule(
{
id: "math_cos",
title: "Cos",
category: "Math",
description:
"Return the cosine of an angle where 1.0 represents a full turn.",
searchTags: ["cos", "cosine", "math", "trig"],
inputs: [{ id: "value", name: "Value", direction: "input", kind: "number" }],
outputs: [
{ id: "result", name: "Result", direction: "output", kind: "number" },
],
properties: [],
},
{
evaluateValue: ({ resolveValueInput }) => {
const value = resolveValueInput("value", "0");
return `cos(${value})`;
},
}
);

View File

@@ -0,0 +1,29 @@
import { createNodeModule } from "../../nodeTypes.js";
/**
* Performs integer division using the floor operation.
*/
export const mathDivNode = createNodeModule(
{
id: "math_div",
title: "Integer Divide",
category: "Math",
description: "Return the floored quotient of two numbers (A \\ B).",
searchTags: ["div", "integer", "divide", "math"],
inputs: [
{ id: "a", name: "A", direction: "input", kind: "number" },
{ id: "b", name: "B", direction: "input", kind: "number" },
],
outputs: [
{ id: "result", name: "Result", direction: "output", kind: "number" },
],
properties: [],
},
{
evaluateValue: ({ resolveValueInput }) => {
const a = resolveValueInput("a", "0");
const b = resolveValueInput("b", "1");
return `(${a}) \\ (${b})`;
},
}
);

View File

@@ -0,0 +1,25 @@
import { createNodeModule } from "../../nodeTypes.js";
/**
* Floors a numeric value using flr.
*/
export const mathFlrNode = createNodeModule(
{
id: "math_flr",
title: "Floor",
category: "Math",
description: "Round a value down to the nearest integer.",
searchTags: ["flr", "floor", "math", "round"],
inputs: [{ id: "value", name: "Value", direction: "input", kind: "number" }],
outputs: [
{ id: "result", name: "Result", direction: "output", kind: "number" },
],
properties: [],
},
{
evaluateValue: ({ resolveValueInput }) => {
const value = resolveValueInput("value", "0");
return `flr(${value})`;
},
}
);

View File

@@ -0,0 +1,79 @@
import { mathMaxNode } from "./max.js";
import { mathMinNode } from "./min.js";
import { mathMidNode } from "./mid.js";
import { mathFlrNode } from "./flr.js";
import { mathCeilNode } from "./ceil.js";
import { mathCosNode } from "./cos.js";
import { mathSinNode } from "./sin.js";
import { mathAtan2Node } from "./atan2.js";
import { mathSqrtNode } from "./sqrt.js";
import { mathAbsNode } from "./abs.js";
import { mathRndNode } from "./rnd.js";
import { mathSrandNode } from "./srand.js";
import { mathBandNode } from "./band.js";
import { mathBorNode } from "./bor.js";
import { mathBxorNode } from "./bxor.js";
import { mathBnotNode } from "./bnot.js";
import { mathShlNode } from "./shl.js";
import { mathShrNode } from "./shr.js";
import { mathLshrNode } from "./lshr.js";
import { mathRotlNode } from "./rotl.js";
import { mathRotrNode } from "./rotr.js";
import { mathDivNode } from "./div.js";
/**
* All math-related node modules backed by PICO-8 helpers.
*/
export const mathNodes = [
mathMaxNode,
mathMinNode,
mathMidNode,
mathFlrNode,
mathCeilNode,
mathCosNode,
mathSinNode,
mathAtan2Node,
mathSqrtNode,
mathAbsNode,
mathRndNode,
mathSrandNode,
mathBandNode,
mathBorNode,
mathBxorNode,
mathBnotNode,
mathShlNode,
mathShrNode,
mathLshrNode,
mathRotlNode,
mathRotrNode,
mathDivNode,
];
/**
* Checklist tracking coverage of documented math helpers.
* @type {Array<{ function: string, nodeId: string, implemented: boolean }>}
*/
export const mathFunctionChecklist = [
{ function: "MAX", nodeId: "math_max", implemented: true },
{ function: "MIN", nodeId: "math_min", implemented: true },
{ function: "MID", nodeId: "math_mid", implemented: true },
{ function: "FLR", nodeId: "math_flr", implemented: true },
{ function: "CEIL", nodeId: "math_ceil", implemented: true },
{ function: "COS", nodeId: "math_cos", implemented: true },
{ function: "SIN", nodeId: "math_sin", implemented: true },
{ function: "ATAN2", nodeId: "math_atan2", implemented: true },
{ function: "SQRT", nodeId: "math_sqrt", implemented: true },
{ function: "ABS", nodeId: "math_abs", implemented: true },
{ function: "RND", nodeId: "math_rnd", implemented: true },
{ function: "SRAND", nodeId: "math_srand", implemented: true },
{ function: "BAND", nodeId: "math_band", implemented: true },
{ function: "BOR", nodeId: "math_bor", implemented: true },
{ function: "BXOR", nodeId: "math_bxor", implemented: true },
{ function: "BNOT", nodeId: "math_bnot", implemented: true },
{ function: "SHL", nodeId: "math_shl", implemented: true },
{ function: "SHR", nodeId: "math_shr", implemented: true },
{ function: "LSHR", nodeId: "math_lshr", implemented: true },
{ function: "ROTL", nodeId: "math_rotl", implemented: true },
{ function: "ROTR", nodeId: "math_rotr", implemented: true },
{ function: "\\", nodeId: "math_div", implemented: true },
];

View File

@@ -0,0 +1,29 @@
import { createNodeModule } from "../../nodeTypes.js";
/**
* Performs a logical right bit shift using lshr.
*/
export const mathLshrNode = createNodeModule(
{
id: "math_lshr",
title: "Logical Shift Right",
category: "Math",
description: "Shift bits right with zeros entering from the left.",
searchTags: ["lshr", "shift", "bitwise", "math"],
inputs: [
{ id: "value", name: "Value", direction: "input", kind: "number" },
{ id: "amount", name: "Amount", direction: "input", kind: "number" },
],
outputs: [
{ id: "result", name: "Result", direction: "output", kind: "number" },
],
properties: [],
},
{
evaluateValue: ({ resolveValueInput }) => {
const value = resolveValueInput("value", "0");
const amount = resolveValueInput("amount", "0");
return `lshr(${value}, ${amount})`;
},
}
);

View File

@@ -0,0 +1,29 @@
import { createNodeModule } from "../../nodeTypes.js";
/**
* Returns the maximum of two values using max.
*/
export const mathMaxNode = createNodeModule(
{
id: "math_max",
title: "Max",
category: "Math",
description: "Return the greater of two numeric values.",
searchTags: ["max", "maximum", "math", "compare"],
inputs: [
{ id: "a", name: "A", direction: "input", kind: "number" },
{ id: "b", name: "B", direction: "input", kind: "number" },
],
outputs: [
{ id: "value", name: "Result", direction: "output", kind: "number" },
],
properties: [],
},
{
evaluateValue: ({ resolveValueInput }) => {
const a = resolveValueInput("a", "0");
const b = resolveValueInput("b", "0");
return `max(${a}, ${b})`;
},
}
);

View File

@@ -0,0 +1,31 @@
import { createNodeModule } from "../../nodeTypes.js";
/**
* Returns the median of three values using mid.
*/
export const mathMidNode = createNodeModule(
{
id: "math_mid",
title: "Mid",
category: "Math",
description: "Return the middle value from three numeric inputs.",
searchTags: ["mid", "median", "math", "compare"],
inputs: [
{ id: "a", name: "A", direction: "input", kind: "number" },
{ id: "b", name: "B", direction: "input", kind: "number" },
{ id: "c", name: "C", direction: "input", kind: "number" },
],
outputs: [
{ id: "value", name: "Result", direction: "output", kind: "number" },
],
properties: [],
},
{
evaluateValue: ({ resolveValueInput }) => {
const a = resolveValueInput("a", "0");
const b = resolveValueInput("b", "0");
const c = resolveValueInput("c", "0");
return `mid(${a}, ${b}, ${c})`;
},
}
);

View File

@@ -0,0 +1,29 @@
import { createNodeModule } from "../../nodeTypes.js";
/**
* Returns the minimum of two values using min.
*/
export const mathMinNode = createNodeModule(
{
id: "math_min",
title: "Min",
category: "Math",
description: "Return the lesser of two numeric values.",
searchTags: ["min", "minimum", "math", "compare"],
inputs: [
{ id: "a", name: "A", direction: "input", kind: "number" },
{ id: "b", name: "B", direction: "input", kind: "number" },
],
outputs: [
{ id: "value", name: "Result", direction: "output", kind: "number" },
],
properties: [],
},
{
evaluateValue: ({ resolveValueInput }) => {
const a = resolveValueInput("a", "0");
const b = resolveValueInput("b", "0");
return `min(${a}, ${b})`;
},
}
);

View File

@@ -0,0 +1,25 @@
import { createNodeModule } from "../../nodeTypes.js";
/**
* Generates random numbers using rnd.
*/
export const mathRndNode = createNodeModule(
{
id: "math_rnd",
title: "Random",
category: "Math",
description: "Return a random number in the range [0, x).",
searchTags: ["rnd", "random", "math"],
inputs: [{ id: "value", name: "Range", direction: "input", kind: "number" }],
outputs: [
{ id: "result", name: "Result", direction: "output", kind: "number" },
],
properties: [],
},
{
evaluateValue: ({ resolveValueInput }) => {
const value = resolveValueInput("value", "1");
return `rnd(${value})`;
},
}
);

View File

@@ -0,0 +1,29 @@
import { createNodeModule } from "../../nodeTypes.js";
/**
* Performs a left bit rotation using rotl.
*/
export const mathRotlNode = createNodeModule(
{
id: "math_rotl",
title: "Rotate Left",
category: "Math",
description: "Rotate bits left by a given amount.",
searchTags: ["rotl", "rotate", "bitwise", "math"],
inputs: [
{ id: "value", name: "Value", direction: "input", kind: "number" },
{ id: "amount", name: "Amount", direction: "input", kind: "number" },
],
outputs: [
{ id: "result", name: "Result", direction: "output", kind: "number" },
],
properties: [],
},
{
evaluateValue: ({ resolveValueInput }) => {
const value = resolveValueInput("value", "0");
const amount = resolveValueInput("amount", "0");
return `rotl(${value}, ${amount})`;
},
}
);

View File

@@ -0,0 +1,29 @@
import { createNodeModule } from "../../nodeTypes.js";
/**
* Performs a right bit rotation using rotr.
*/
export const mathRotrNode = createNodeModule(
{
id: "math_rotr",
title: "Rotate Right",
category: "Math",
description: "Rotate bits right by a given amount.",
searchTags: ["rotr", "rotate", "bitwise", "math"],
inputs: [
{ id: "value", name: "Value", direction: "input", kind: "number" },
{ id: "amount", name: "Amount", direction: "input", kind: "number" },
],
outputs: [
{ id: "result", name: "Result", direction: "output", kind: "number" },
],
properties: [],
},
{
evaluateValue: ({ resolveValueInput }) => {
const value = resolveValueInput("value", "0");
const amount = resolveValueInput("amount", "0");
return `rotr(${value}, ${amount})`;
},
}
);

View File

@@ -0,0 +1,29 @@
import { createNodeModule } from "../../nodeTypes.js";
/**
* Performs a left bit shift using shl.
*/
export const mathShlNode = createNodeModule(
{
id: "math_shl",
title: "Shift Left",
category: "Math",
description: "Shift bits left with zeros entering from the right.",
searchTags: ["shl", "shift", "bitwise", "math"],
inputs: [
{ id: "value", name: "Value", direction: "input", kind: "number" },
{ id: "amount", name: "Amount", direction: "input", kind: "number" },
],
outputs: [
{ id: "result", name: "Result", direction: "output", kind: "number" },
],
properties: [],
},
{
evaluateValue: ({ resolveValueInput }) => {
const value = resolveValueInput("value", "0");
const amount = resolveValueInput("amount", "0");
return `shl(${value}, ${amount})`;
},
}
);

View File

@@ -0,0 +1,29 @@
import { createNodeModule } from "../../nodeTypes.js";
/**
* Performs an arithmetic right bit shift using shr.
*/
export const mathShrNode = createNodeModule(
{
id: "math_shr",
title: "Shift Right",
category: "Math",
description: "Shift bits right while extending the sign bit.",
searchTags: ["shr", "shift", "bitwise", "math"],
inputs: [
{ id: "value", name: "Value", direction: "input", kind: "number" },
{ id: "amount", name: "Amount", direction: "input", kind: "number" },
],
outputs: [
{ id: "result", name: "Result", direction: "output", kind: "number" },
],
properties: [],
},
{
evaluateValue: ({ resolveValueInput }) => {
const value = resolveValueInput("value", "0");
const amount = resolveValueInput("amount", "0");
return `shr(${value}, ${amount})`;
},
}
);

View File

@@ -0,0 +1,26 @@
import { createNodeModule } from "../../nodeTypes.js";
/**
* Computes the sine of a value using sin.
*/
export const mathSinNode = createNodeModule(
{
id: "math_sin",
title: "Sin",
category: "Math",
description:
"Return the sine of an angle where 1.0 represents a full turn.",
searchTags: ["sin", "sine", "math", "trig"],
inputs: [{ id: "value", name: "Value", direction: "input", kind: "number" }],
outputs: [
{ id: "result", name: "Result", direction: "output", kind: "number" },
],
properties: [],
},
{
evaluateValue: ({ resolveValueInput }) => {
const value = resolveValueInput("value", "0");
return `sin(${value})`;
},
}
);

View File

@@ -0,0 +1,25 @@
import { createNodeModule } from "../../nodeTypes.js";
/**
* Computes a square root using sqrt.
*/
export const mathSqrtNode = createNodeModule(
{
id: "math_sqrt",
title: "Sqrt",
category: "Math",
description: "Return the square root of a numeric value.",
searchTags: ["sqrt", "square root", "math"],
inputs: [{ id: "value", name: "Value", direction: "input", kind: "number" }],
outputs: [
{ id: "result", name: "Result", direction: "output", kind: "number" },
],
properties: [],
},
{
evaluateValue: ({ resolveValueInput }) => {
const value = resolveValueInput("value", "0");
return `sqrt(${value})`;
},
}
);

View File

@@ -0,0 +1,29 @@
import { createNodeModule } from "../../nodeTypes.js";
/**
* Seeds the random number generator using srand.
*/
export const mathSrandNode = createNodeModule(
{
id: "math_srand",
title: "Seed Random",
category: "Math",
description: "Set the random number generator seed.",
searchTags: ["srand", "random", "seed", "math"],
inputs: [
{ id: "exec_in", name: "Exec", direction: "input", kind: "exec" },
{ id: "value", name: "Seed", direction: "input", kind: "number" },
],
outputs: [
{ id: "exec_out", name: "Exec", direction: "output", kind: "exec" },
],
properties: [],
},
{
emitExec: ({ indent, indentLevel, resolveValueInput, emitNextExec }) => {
const value = resolveValueInput("value", "0");
const line = `${indent(indentLevel)}srand(${value})`;
return [line, ...emitNextExec("exec_out")];
},
}
);

View File

@@ -0,0 +1,67 @@
import { createNodeModule } from "../../nodeTypes.js";
/**
* Copies data from base RAM to cartridge ROM using cstore.
*/
export const memoryCstoreNode = createNodeModule(
{
id: "memory_cstore",
title: "Store To Cart",
category: "Memory",
description:
"Copy base RAM into cart ROM, optionally targeting another cartridge.",
searchTags: ["cstore", "memory", "cart", "save"],
inputs: [
{ id: "exec_in", name: "Exec", direction: "input", kind: "exec" },
{
id: "dest",
name: "Dest",
direction: "input",
kind: "number",
defaultValue: 0,
},
{
id: "source",
name: "Source",
direction: "input",
kind: "number",
defaultValue: 0,
},
{
id: "length",
name: "Length",
direction: "input",
kind: "number",
defaultValue: 0,
},
{
id: "filename",
name: "Filename",
direction: "input",
kind: "string",
description: "Optional cartridge filename",
},
],
outputs: [
{ id: "exec_out", name: "Exec", direction: "output", kind: "exec" },
],
properties: [],
},
{
emitExec: ({ indent, indentLevel, resolveValueInput, emitNextExec }) => {
const OMIT = "__pg_omit__";
const dest = resolveValueInput("dest", "0");
const source = resolveValueInput("source", "0");
const length = resolveValueInput("length", "0");
const filename = resolveValueInput("filename", OMIT);
const args = [dest, source, length];
if (filename !== OMIT) {
args.push(filename);
}
const line = `${indent(indentLevel)}cstore(${args.join(", ")})`;
return [line, ...emitNextExec("exec_out")];
},
}
);

Some files were not shown because too many files have changed in this diff Show More