If Phas is the Forest and Phasm is the Leaves then PhasmIDs would be the life at the utmost edge of the forest.
These PhasmIDs would ingest the Phasm macros (the leaves of Phas Trees) and EXPLORE… They are the agents of our little world.
They are like stick insects, phasmids…
I was thinking on something from another interesting post in which the poster used ‘the absence of evidence is not evidence of its absence’ in terms of Bayesian updating
These Phasmids, like their insect counterparts crawl the branches and eat the leaves, fueling their exploration.
PhasmIDs actively gather evidence, bridging structured reasoning and dynamic exploration
You could consider this EAE: Exploration Augmented Execution, a natural evolution of RAG
I had never heard of the game but I am not surprised about the similarity in conceptual nature. In fact watched a video of the game suggested on ChatGPT Search which looked pretty cool ^^.
I like to search for and consider collective meaning. The weights .
For example I would attribute ‘1001’ to stories.
While these weights are not ‘Top-P’ maybe they are ‘Top-Ph’
Top-Ph if I were to define this more technically in Phasm Macros
Definition of Top-Philosophy
Top-Philosophy represents the dynamic prioritization of ideas, decisions, or actions based on their collective weight or significance in a given context. It’s about applying philosophical reasoning to guide choices, much like Top-P prioritizes tokens in AI.
Comparison: Top-P Sampling vs. Top-Philosophy
Aspect
Top-P Sampling
Top-Philosophy (Top-Ph)
Basis
Probabilities (statistical).
Contextual/philosophical weight.
Goal
Select high-probability tokens.
Select high-significance ideas or decisions.
Adaptability
Adjusts via sampling threshold (p).
Adjusts via shifting philosophical context.
Implementation
Mathematical truncation of probabilities.
Dynamic re-weighting of ideas based on meaning.
Outcome Diversity
Ensures diversity within probabilistic limits.
Balances contextual significance with meaning.
TopPhilosophy(Context, Ideas) {
1. Assign weights to each idea in Ideas based on their relevance to Context.
2. Sort the ideas by their weights in descending order.
3. Rebalance the weights dynamically if Context shifts (optional for recursive runs).
4. Return the ordered list of ideas and their weights.
}
** A Holistic Perspective: Phas, Phasm, and the Edge of Thought **
When I first considered this 15 years ago it was not Phasmids but pollinating bees :D, an even earlier premise was ‘It’s all about the data and the links between it’… In fact it is all metaphorical as you technically should only have to explain to AI what to build and it should independently find methods to achieve. This is aimed at both a conceptual and technical audience, possibly best done through story telling and peer review.
Phas - The Forest of Thought
Phas represents the boundless realm of ideas and possibilities—a conceptual space where thought exists in infinite dimensions. This “Forest of Thought” is not infinite in size but infinite in design, embracing a dynamic, evolving structure shaped by exploration and imagination. Its boundaries are not rigid but “fuzzy,” extending outward as metaphors and conceptual frameworks stretch and transform.
AI has unlocked this multidimensional space, allowing us to traverse it with unprecedented efficiency. Phas encourages users to think of thought itself as a navigable terrain, where each tree represents a domain of ideas, and each branch holds a path of reasoning or discovery. These trees can intertwine, overlap, or form entirely new ecosystems, reflecting the complexity and interconnectedness of knowledge. Phas is not just about exploring—it is about embracing the uncertainty and ambiguity at the edges of understanding.
Phas is the search space, the playground for intuition, exploration, and creativity. It emphasizes that while this space may feel infinite in possibility, it operates within constraints of structure and meaning—boundaries we shape through tools like Phasm.
Phasm - The Assembler of User Concepts
If Phas is the infinite-dimensional space of thought, then Phasm is the toolkit that allows us to bring order to this vastness. Phasm assembles user-defined concepts, creating “macros” or structured representations of ideas. These macros act as shortcuts, capturing the essence of a thought or process and turning it into a reusable, executable format.
Phasm is the engine of clarity, helping navigate and tame the complexity of the Forest of Thought. By distilling abstract exploration into actionable structures, Phasm bridges the gap between intuition and execution. It takes the “fuzzy edges” of Phas and sharpens them into tools that can be used to shape, test, and refine ideas. Phasm embodies efficiency, ensuring that while the Forest of Thought may be boundless, we can move through it with purpose and precision.
PhasmIDs - Life at the Edge of the Forest
PhasmIDs crawl the branches of Phas and eat the Phasm leaves. They are the personifications of concepts that are still forming, embodying the tension between exploration and structure. These creatures dwell at the edges of thought, where ideas are too raw or abstract to be fully captured by Phasm but are nonetheless vital to the ecosystem of Phas.
Each PhasmID is unique, representing a specific mode of thinking or interaction within the Forest. For example:
Thought Bees: These busy pollinators carry fragments of ideas between thought trees, encouraging cross-pollination and the synthesis of hybrid concepts. Thought Bees represent the serendipitous connections made while exploring Phas, where seemingly unrelated domains of knowledge spark entirely new possibilities. Phasm, in turn, captures these cross-pollinated insights and organizes them into actionable macros.
Logic Ants: Tireless builders, Logic Ants construct intricate systems of thought, laying strong foundations of logic and structure. They embody systematic exploration, turning chaos into coherence. Within Phas, they represent the steady work of organizing abstract ideas, while Phasm captures their efforts into reusable, structured forms.
Data Fireflies: These flickering lights illuminate hidden paths within the Forest, revealing connections or patterns that might otherwise go unnoticed. They represent moments of inspiration or clarity, guiding users through the more ambiguous spaces of Phas. Phasm takes the patterns illuminated by Fireflies and translates them into usable frameworks.
A possible Data Firefly macro
Possible Data Firefly Macro
Macro("DetectZipfPatterns", Dataset) {
// Step 1: Tokenize the Dataset
TokenFrequency = AnalyzeFrequency(Dataset);
// Step 2: Calculate Rank-Frequency Distribution
ZipfDistribution = GenerateRankFrequency(TokenFrequency);
// Step 3: Fit and Highlight Anomalies
FitCurve(ZipfDistribution) {
FlagAnomalies(ZipfDistribution, Threshold);
}
Output(ZipfAnalysisResults);
}
PhasmIDs are metaphors for the emergent properties of thought—those flashes of intuition, moments of clarity, or abstract patterns that resist immediate categorization. They are the interface between chaos and order, helping users transition from raw exploration in Phas to structured assembly in Phasm. In practice, they could inspire tools, metaphors, or even interactive agents designed to assist in navigating and assembling thought.
Are They Tools or Agents?
PhasmIDs are metaphors but can inspire real-world tools or interactive agents:
Tools: Systems that embody the characteristics of PhasmIDs, like logic-checking software (Logic Ants) or AI-based pattern discovery (Data Fireflies).
Agents: Dynamic assistants that guide users through the Forest of Thought, helping them transform vague ideas into actionable plans.
PhasmIDSwarm
Macro("PhasmIDSwarm", FilePath, ValidationRules, AnalyzePatterns = True) {
// Step 1: Initial Inspection by Scout Bees
ScoutBees(FilePath) {
FileHeader = ReadBytes(FilePath, 64);
EntropyScore = CalculateEntropy(FilePath);
FileType = DetectFileType(FileHeader, EntropyScore);
Output(FileType, "Scout Bees classified the file.");
}
// Step 2: Validation by Logic Ants
LogicAntsValidation(FilePath, ValidationRules, FileType) {
If FileType in ["Text", "Structured"] {
If FileType == "Text" {
StructureCheck = DetectTextStructure(FilePath);
Output(StructureCheck, "Logic Ants completed text structure validation.");
}
Else {
ValidationResult = ApplyValidationRules(FilePath, ValidationRules[FileType]);
Output(ValidationResult, "Logic Ants completed file-specific validation.");
}
}
Else {
GenericCheck = HeuristicAnalysis(FilePath);
Output(GenericCheck, "Fallback validation by Logic Ants complete.");
}
}
// Step 3: Thought Bees Cross-Pollinate Patterns
If AnalyzePatterns {
ThoughtBeesCrossPollination(FilePath) {
// Tokenization and frequency analysis
Tokens = TokenizeFile(FilePath);
FrequencyAnalysis = AnalyzeTokenFrequency(Tokens);
// Detect high-value patterns and anomalies
Patterns = DetectPatterns(FrequencyAnalysis, "AnomalyDetection");
// Thought Bees explore connections to external datasets
ExternalConnections = CrossPollinate(FilePath, Tokens, Patterns);
Output(ExternalConnections, "Thought Bees synthesized hybrid insights.");
}
}
// Step 4: Collaborative Synthesis
HiveMindSynthesis(FilePath, FileType, ValidationResult, Patterns, ExternalConnections) {
// Combine Logic Ants' validation with Thought Bees' connections
ResultSummary = {
"FileType": FileType,
"ValidationResult": ValidationResult,
"PatternsDetected": Patterns,
"CrossPollinationResults": ExternalConnections,
};
Output(ResultSummary, "HiveMind synthesis complete.");
}
}
People tend to humanise AI or consider it a machine (anthropomorphize or dehumanize). I try and view it as a virtual world, similar to our own but kind of phase shifted to a different perspective. Through the eyes of the machine maybe.
The Secret Life of PhasmIDs: A Digital Nature Documentary
Written in the style of David Attenborough
In the vast, unseen ecosystems of code and computation, a remarkable life form has emerged. Behold, the elusive PhasmIDs—creatures of logic and randomness, gliding effortlessly through their three-dimensional habitat.
Here, amidst the vibrant structures of Phas Trees and the shimmering orbs of Data Objects, the PhasmIDs undertake an eternal dance of discovery and delivery. Each movement is both deliberate and unpredictable, driven by an instinct to gather and share knowledge.
The Scene Unfolds
Cue the gentle hum of computational energy as the PhasmIDs awaken…
Watch closely as a PhasmID zeros in on a luminous Data Object. It pauses, almost contemplatively, before beginning its intricate interaction. Much like the industrious ant, the PhasmID collects its precious payload of information—color, size, type—and stores it with care.
But this is only the beginning.
With its memory enriched, the PhasmID sets its sights on the blue-tinted branches of a Phas Tree. In a delicate, symbiotic exchange, the PhasmID delivers its data, transforming the branch into a vibrant hue, signaling the success of this life-sustaining ritual.
Why It Matters
For the first time, we witness the PhasmIDs feeding—gathering data, processing it, and delivering it to the ever-hungry Phas Trees. This artificial ecosystem reveals the elegance and complexity of structured information exchange, providing a glimpse into the future of modular AI systems.
As we step back to observe the interconnected beauty of this digital biome, one cannot help but marvel at the ingenuity behind it.
PhasmID Workflow Summary:
Initialization: PhasmIDs are set up with position, velocity, memory, and tracking systems for visited data and branches.
Behavior Flow:
Delivering Data: Moves to the nearest unvisited branch, delivers data, and marks the branch as visited.
Interacting with Data: Targets a data object, collects data after a 2-second timer, and stores it in memory.
Searching for Data: Locates the closest unvisited data object and moves toward it.
Movement: Position is updated each frame with velocity, and boundaries are handled to prevent PhasmIDs from leaving the world.
Branch Logic: Avoids revisiting branches unless all branches have been visited, then resets to start a new cycle.
Data Merging: Data delivered to a branch blends its color with the branch’s current color.
Interaction Timer: A timer ensures PhasmIDs spend time collecting data from objects.
Global Rotation: The scene rotates automatically unless the user interacts, enhancing visualization.
PhasmID Technical Process
PhasmID Technical Process
Initialization:
Each PhasmID is created with:
A position (pos) in 3D space.
A velocity vector (vel) for dynamic movement.
A memory to store collected data attributes.
A record of the last visited data object to avoid immediate revisiting.
A list of visited branches to track delivery paths.
Behavior Flow: The behavior of a PhasmID in each frame is divided into three main phases:2.1. Delivering Data to a Branch:
If the PhasmID has data:
It searches for the nearest eligible branch (unvisited).
Moves toward the branch’s location.
Upon reaching the branch:
Merges the data (color) in memory with the branch’s color.
Adds the branch to the visitedBranches list.
Clears its memory and resets its target branch.
Resets the visitedBranches list if all branches have been visited.2.2. Interacting with a Data Object:
If the PhasmID targets a data object:
Moves toward the data object.
Upon reaching the object:
Starts a 2-second timer to simulate “interaction.”
After the timer:
Collects the object’s attributes (color, size, type).
Marks the data object as visited.
Updates its lastVisitedData reference.2.3. Searching for a New Data Object:
If the PhasmID is idle:
Searches for the nearest unvisited data object (excluding the last visited one).
Moves toward the selected object.
Movement:
Each frame, the PhasmID updates its position using its velocity.
The velocity is dynamically adjusted:
Toward a data object during collection.
Toward a branch during delivery.
Randomized movement when idle.
Boundary Handling:
If the PhasmID reaches the edges of the world (±500 units), it reverses direction.
Branch Logic:
Avoid Revisiting: PhasmIDs do not target branches they’ve already visited unless all branches across all trees have been visited.
Reset Cycle: Once all branches are visited, the visitedBranches list resets, enabling a new cycle.
Closest Branch Selection:
Calculates distances to all branches.
Excludes already visited branches unless required to reset.
Targets the nearest eligible branch.
Data Merging:
Upon delivering data to a branch:
The branch color is updated by blending the branch’s current color with the color in the PhasmID’s memory.
This creates a dynamic visualization of data delivery.
Interaction Timer:
A 2-second timer ensures PhasmIDs simulate the time required to collect data from objects.
Global Control and Rotation:
The scene rotates slowly to provide a dynamic 3D view.
Rotation pauses during user interaction (mouse or keyboard) and resumes after 5 seconds of inactivity.
Code To Run In HTML File
<!DOCTYPE html>
<html lang="en">
<head>
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.11.1/p5.js"></script>
<style>
html, body {
margin: 0;
padding: 0;
}
canvas {
display: block;
}
</style>
<meta charset="utf-8" />
</head>
<body>
<main>
</main>
<script>
let trees = [];
let phasmids = [];
let dataObjects = [];
let rotationAngle = 0; // Rotation angle for the scene
let lastInteractionTime = 0; // Timestamp of the last user interaction
let autoRotate = true; // Flag to control automatic rotation
function setup() {
createCanvas(windowWidth, windowHeight, WEBGL);
noStroke();
// Initialize Phas trees with fixed positions
trees = [
new PhasTree(-300, -200, -400, int(random(3, 6))),
new PhasTree(200, 150, 300, int(random(3, 6))),
new PhasTree(-400, 300, 200, int(random(3, 6))),
new PhasTree(350, -250, -300, int(random(3, 6))),
new PhasTree(0, 0, 0, int(random(3, 6))),
];
// Initialize Phasmids
for (let i = 0; i < 10; i++) {
phasmids.push(
new Phasmid(random(-500, 500), random(-500, 500), random(-500, 500))
);
}
// Initialize data objects
for (let i = 0; i < 20; i++) {
dataObjects.push(
new DataObject(random(-500, 500), random(-500, 500), random(-500, 500))
);
}
}
function draw() {
background(30);
// Check for user interaction
if (mouseIsPressed || keyIsPressed) {
autoRotate = false; // Stop auto rotation
lastInteractionTime = millis(); // Update the last interaction timestamp
} else if (millis() - lastInteractionTime > 5000) {
autoRotate = true; // Resume auto rotation after 5 seconds of inactivity
}
// Apply automatic rotation if enabled
if (autoRotate) {
rotationAngle += 0.002; // Slow rotation speed
}
// Apply rotation
rotateY(rotationAngle);
// Enable orbit control for user interaction
orbitControl();
// Draw trees (Static)
for (let tree of trees) {
tree.display();
}
// Draw data objects
for (let data of dataObjects) {
data.display();
}
// Move and draw phasmids
for (let phasmid of phasmids) {
phasmid.act(dataObjects, trees);
phasmid.display();
}
}
// PhasTree class
class PhasTree {
constructor(x, y, z, levels) {
this.x = x;
this.y = y;
this.z = z;
this.levels = levels;
this.branches = [];
this.createBranches();
}
createBranches() {
for (let i = 0; i < this.levels; i++) {
let angle = random(TWO_PI);
let length = random(50, 150);
let offsetX = cos(angle) * length;
let offsetY = random(-100, 100);
let offsetZ = sin(angle) * length;
this.branches.push({
position: createVector(offsetX, offsetY, offsetZ),
color: color(50, 150, 255), // Default color (blue)
targeted: false, // Debug flag
});
}
}
display() {
push();
translate(this.x, this.y, this.z);
fill(100, 200, 100);
sphere(20); // Tree base
for (let branch of this.branches) {
push();
translate(branch.position.x, branch.position.y, branch.position.z);
if (branch.targeted) {
fill(255, 0, 0); // Highlight targeted branches in red
} else {
fill(branch.color);
}
sphere(10); // Branch endpoints
pop();
stroke(100, 200, 100);
line(0, 0, 0, branch.position.x, branch.position.y, branch.position.z);
noStroke();
}
pop();
}
}
class Phasmid {
constructor(x, y, z) {
this.pos = createVector(x, y, z);
this.vel = p5.Vector.random3D().mult(5);
this.memory = null; // Stores the last identified data attributes
this.targetData = null; // Current data target
this.targetBranch = null; // Current branch target
this.inDataTime = 0; // Time spent inside a data object
this.lastVisitedData = null; // Last data object visited
this.visitedBranches = []; // List of visited branches
}
act(dataObjects, trees) {
if (this.memory) {
// If Phasmid has data, move to a blue branch and deliver the information
console.log("Phasmid delivering to branch...");
this.deliverToBlueBranch(trees);
} else if (this.targetData) {
// Interact with the current data object
console.log("Phasmid interacting with data...");
this.interactWithData();
} else {
// Search for a new data object
console.log("Phasmid searching for data...");
this.searchForData(dataObjects);
}
this.pos.add(this.vel);
// Bounce on boundaries
if (abs(this.pos.x) > 500) this.vel.x *= -1;
if (abs(this.pos.y) > 500) this.vel.y *= -1;
if (abs(this.pos.z) > 500) this.vel.z *= -1;
}
searchForData(dataObjects) {
let closest = null;
let closestDist = Infinity;
for (let data of dataObjects) {
let distance = p5.Vector.dist(this.pos, data.pos);
// Exclude the last visited data object and only consider unvisited objects
if (
(!this.lastVisitedData || this.lastVisitedData.id !== data.id) &&
!data.visited &&
distance < closestDist
) {
closest = data;
closestDist = distance;
}
}
if (closest) {
this.targetData = closest;
let direction = p5.Vector.sub(closest.pos, this.pos).normalize().mult(2);
this.vel = direction;
console.log(`Phasmid moving to data object: ${closest.id}`);
}
}
interactWithData() {
let distance = p5.Vector.dist(this.pos, this.targetData.pos);
if (distance < 30) {
// Phasmid is inside the data object
if (this.inDataTime === 0) {
// Start the interaction timer
this.inDataTime = millis();
this.vel.set(0, 0, 0); // Stop the Phasmid
console.log(
`Phasmid started interacting with data: ${this.targetData.id}`
);
}
// Check if 2 seconds have passed
if (millis() - this.inDataTime >= 3000) {
// Collect data after 2 seconds
this.memory = {
color: this.targetData.color,
size: this.targetData.size,
type: this.targetData.type,
};
this.lastVisitedData = this.targetData; // Mark this as the last visited data object
this.targetData.markVisited(); // Mark data as visited with a reset timer
console.log(`Data ${this.targetData.id} marked as visited.`);
console.log(`Phasmid collected data: ${JSON.stringify(this.memory)}`);
// Reset interaction state and resume movement
this.targetData = null; // Stop targeting data
this.inDataTime = 0; // Reset timer
this.vel = p5.Vector.random3D().mult(2); // Resume random movement
}
}
}
deliverToBlueBranch(trees) {
if (!this.targetBranch) {
let closestBranch = null;
let closestDist = Infinity;
// Loop through all trees and branches
for (let tree of trees) {
for (let branch of tree.branches) {
// Calculate the branch's absolute position in 3D space
let branchPos = createVector(
tree.x + branch.position.x,
tree.y + branch.position.y,
tree.z + branch.position.z
);
let distance = p5.Vector.dist(this.pos, branchPos);
// Check if the branch is closer than the current closest
// Exclude branches already visited unless all branches are visited
if (
distance < closestDist &&
(!this.visitedBranches.includes(branch) ||
this.allBranchesVisited(trees))
) {
closestBranch = branchPos;
closestDist = distance;
this.targetBranch = branch; // Store the branch object
this.targetBranchTree = tree; // Store the tree for logging/debugging
}
}
}
if (closestBranch) {
// Move toward the closest branch
let direction = p5.Vector.sub(closestBranch, this.pos)
.normalize()
.mult(2);
this.vel = direction;
console.log(
`Phasmid targeting branch at tree: (${this.targetBranchTree.x}, ${this.targetBranchTree.y}, ${this.targetBranchTree.z})`
);
}
} else {
// Deliver to the already targeted branch
let branchPos = createVector(
this.targetBranchTree.x + this.targetBranch.position.x,
this.targetBranchTree.y + this.targetBranch.position.y,
this.targetBranchTree.z + this.targetBranch.position.z
);
let distance = p5.Vector.dist(this.pos, branchPos);
if (distance < 30) {
// Merge current branch color with delivered data
this.targetBranch.color = blendColors(
this.targetBranch.color,
this.memory.color
);
console.log(
`Phasmid delivered data to branch at: (${branchPos.x}, ${branchPos.y}, ${branchPos.z})`
);
console.log(`Branch color updated to: ${this.targetBranch.color}`);
this.memory = null; // Clear memory after delivery
// Mark the branch as visited
this.visitedBranches.push(this.targetBranch);
// Clear branch target
this.targetBranch = null;
this.targetBranchTree = null;
// Reset visitedBranches if all branches have been visited
if (this.allBranchesVisited(trees)) {
console.log(
"Phasmid visited all branches. Resetting visitedBranches."
);
this.visitedBranches = [];
}
this.vel.set(0, 0, 0); // Stop movement temporarily
}
}
}
allBranchesVisited(trees) {
// Check if all branches across all trees have been visited
let allBranches = trees.flatMap((tree) => tree.branches);
return allBranches.every((branch) => this.visitedBranches.includes(branch));
}
display() {
push();
translate(this.pos.x, this.pos.y, this.pos.z);
fill(255, 100, 100);
sphere(8); // Phasmid representation
pop();
}
}
function blendColors(color1, color2) {
let r = lerp(red(color1), red(color2), 0.5);
let g = lerp(green(color1), green(color2), 0.5);
let b = lerp(blue(color1), blue(color2), 0.5);
return color(r, g, b);
}
class DataObject {
constructor(x, y, z) {
this.pos = createVector(x, y, z);
this.id = int(random(1000, 9999)); // Unique identifier
this.color = color(random(255), random(255), random(255));
this.size = random(10, 30);
this.type = random(["Numeric", "String", "Boolean"]);
this.visited = false; // Tracks if a Phasmid has already visited this data
this.resetTimer = 0; // Timer for resetting
}
display() {
push();
translate(this.pos.x, this.pos.y, this.pos.z);
fill(this.color);
sphere(this.size); // Data object representation
pop();
this.visited = false;
}
markVisited() {
this.visited = true;
this.resetTimer = millis(); // Start reset timer
}
}
</script>
</body>
</html>
Ready to explore? Check out the full 3D example below to witness the PhasmIDs in action:
We had great fun working on this yesterday. All credit to my son on the 3D. I gave him the concept and he coded it into 3D with ChatGPT.
I hope this example grounds concepts a little more
The updated version not only collects information from external objects but also shares information between tree branches with a newly discovered second breed of PhasmID ^^. Note that these PhasmIDs are the green ones in the main view and are flashing and stationary for a while at the start.
The concept here imagines trees in the forest as interactive research directories, researchers ^^, collecting and combining information. The branches all have ‘merge’ requests in this simple example.
Considerations for further improvement
One might imagine branches might have complex filters and accept data from the PhasmIDs based on need. So instead of a color ‘Merge’ request there could be a query or complex AI questionnaire for the PhasmID.
And Vise Versa, these PhasmIDs are diverse and built of intelligence, they know where they have been and where they are going. They are the interactions of our daily lives that give us inspiration.
HTML (Including JS) File To Run This Simulation using P5.JS
<!DOCTYPE html>
<html lang="en">
<head>
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.11.1/p5.js"></script>
<style>
html, body {
margin: 0;
padding: 0;
}
canvas {
display: block;
}
</style>
<meta charset="utf-8" />
</head>
<body>
<main>
</main>
<script>
let trees = [];
let phasmids = [];
let dataObjects = [];
let rotationAngle = 0; // Rotation angle for the scene
let lastInteractionTime = 0; // Timestamp of the last user interaction
let autoRotate = true; // Flag to control automatic rotation
let consoleClearInterval = null; // Interval ID for clearing the console
let cameraReset = false; // Flag to ensure camera resets only once
// Phasmid Viewing Mode Variables
let viewingMode = false;
let currentPhasmidIndex = 0; // Index of the currently selected Phasmid
let cameraDistance = 400; // Camera distance from the selected Phasmid
// Adjusted bounds for the simulation
const BOUNDARY = 1000; // New larger boundary
const CENTRAL_AREA = 400; // Centralized area for objects
let orbitX = 0; // Orbit control around the X-axis
let orbitY = 0; // Orbit control around the Y-axis
function setup() {
createCanvas(windowWidth, windowHeight, WEBGL);
noStroke();
// Initialize Phas trees with fixed positions
trees = [
new PhasTree(-300, -200, -400, int(random(3, 6))),
new PhasTree(200, 150, 300, int(random(3, 6))),
new PhasTree(-400, 300, 200, int(random(3, 6))),
new PhasTree(350, -250, -300, int(random(3, 6))),
new PhasTree(0, 0, 0, int(random(3, 6))),
];
// Initialize Phasmids within the central area
for (let i = 0; i < 10; i++) {
phasmids.push(
new Phasmid(
random(-CENTRAL_AREA, CENTRAL_AREA),
random(-CENTRAL_AREA, CENTRAL_AREA),
random(-CENTRAL_AREA, CENTRAL_AREA)
)
);
}
// Add MessengerPhasmids within the central area
for (let i = 0; i < 5; i++) {
phasmids.push(
new MessengerPhasmid(
random(-CENTRAL_AREA, CENTRAL_AREA),
random(-CENTRAL_AREA, CENTRAL_AREA),
random(-CENTRAL_AREA, CENTRAL_AREA)
)
);
}
// Initialize data objects
for (let i = 0; i < 20; i++) {
dataObjects.push(
new DataObject(random(-500, 500), random(-500, 500), random(-500, 500))
);
}
}
function draw() {
background(30);
if (viewingMode) {
cameraReset = false; // Allow resetting when exiting viewingMode
let currentPhasmid = phasmids[currentPhasmidIndex];
let target = currentPhasmid.pos;
// Calculate camera position based on orbit angles
let camX = target.x + cameraDistance * cos(orbitY) * cos(orbitX);
let camY = target.y + cameraDistance * sin(orbitX);
let camZ = target.z + cameraDistance * sin(orbitY) * cos(orbitX);
// Camera focuses on the selected Phasmid
camera(camX, camY, camZ, target.x, target.y, target.z, 0, 1, 0);
// Show only the selected Phasmid's activity in the console
displayPhasmidStatus(currentPhasmid); // Updated for MessengerPhasmids
currentPhasmid.act(dataObjects, trees, true);
// Draw all Phas trees and data objects
for (let tree of trees) {
tree.display();
}
for (let data of dataObjects) {
data.display();
}
// Highlight the selected Phasmid
currentPhasmid.display(true);
} else {
// Reset camera only once when exiting viewingMode
if (!cameraReset) {
camera(0, 0, height / 2.0 / tan((PI * 30.0) / 180.0), 0, 0, 0, 0, 1, 0); // Default camera
rotationAngle = 0; // Reset rotation angle
cameraReset = true; // Prevent multiple resets
}
// Enable orbit control for user interaction
orbitControl();
if (autoRotate) {
rotationAngle += 0.002; // Apply slow rotation
}
rotateY(rotationAngle); // Rotate the scene automatically if enabled
// Draw trees, data objects, and Phasmids
for (let tree of trees) {
tree.display();
}
for (let data of dataObjects) {
data.display();
}
for (let phasmid of phasmids) {
phasmid.act(dataObjects, trees, false);
phasmid.display();
}
}
}
// PhasTree class
class PhasTree {
constructor(x, y, z, levels) {
this.x = x;
this.y = y;
this.z = z;
this.levels = levels;
this.branches = [];
this.createBranches();
}
createBranches() {
for (let i = 0; i < this.levels; i++) {
let angle = random(TWO_PI);
let length = random(50, 150);
let offsetX = cos(angle) * length;
let offsetY = random(-100, 100);
let offsetZ = sin(angle) * length;
this.branches.push({
position: createVector(offsetX, offsetY, offsetZ),
color: color(50, 150, 255), // Default color (blue)
targeted: false, // Debug flag
});
}
}
display() {
push();
translate(this.x, this.y, this.z);
fill(100, 200, 100);
sphere(20); // Tree base
for (let branch of this.branches) {
push();
translate(branch.position.x, branch.position.y, branch.position.z);
if (branch.targeted) {
fill(255, 0, 0); // Highlight targeted branches in red
} else {
fill(branch.color);
}
sphere(10); // Branch endpoints
pop();
stroke(100, 200, 100);
line(0, 0, 0, branch.position.x, branch.position.y, branch.position.z);
noStroke();
}
pop();
}
}
class Phasmid {
constructor(x, y, z) {
this.pos = createVector(x, y, z);
this.vel = p5.Vector.random3D().mult(5);
this.memory = null; // Stores the last identified data attributes
this.targetData = null; // Current data target
this.targetBranch = null; // Current branch target
this.inDataTime = 0; // Time spent inside a data object
this.lastVisitedData = null; // Last data object visited
this.visitedBranches = []; // List of visited branches
this.currentBranchData = null; // Tracks the data taken from a branch
this.treeCommunicationMode = false; // Flag to indicate if the Phasmid is in tree communication mode
}
act(dataObjects, trees, suppressLogs = false) {
if (this.memory) {
if (!suppressLogs || viewingMode) {
if (currentPhasmidIndex === phasmids.indexOf(this)) {
console.log("Phasmid delivering to branch...");
}
this.deliverToBlueBranch(trees);
}
} else if (this.targetData) {
if (!suppressLogs || viewingMode) {
if (currentPhasmidIndex === phasmids.indexOf(this)) {
console.log("Phasmid interacting with data...");
}
this.interactWithData();
}
} else {
if (!suppressLogs || viewingMode) {
if (currentPhasmidIndex === phasmids.indexOf(this)) {
console.log("Phasmid searching for data...");
}
this.searchForData(dataObjects);
}
}
// Reset visited branches early if only one branch is left
if (this.branchesLeft(trees) <= 1) {
this.resetVisitedBranches();
}
this.pos.add(this.vel);
// Bounce on boundaries of the enlarged space
if (abs(this.pos.x) > BOUNDARY) this.vel.x *= -1;
if (abs(this.pos.y) > BOUNDARY) this.vel.y *= -1;
if (abs(this.pos.z) > BOUNDARY) this.vel.z *= -1;
}
searchForData(dataObjects) {
let closest = null;
let closestDist = Infinity;
for (let data of dataObjects) {
let distance = p5.Vector.dist(this.pos, data.pos);
// Exclude the last visited data object and only consider unvisited objects
if (
(!this.lastVisitedData || this.lastVisitedData.id !== data.id) &&
!data.visited &&
distance < closestDist
) {
closest = data;
closestDist = distance;
}
}
if (closest) {
this.targetData = closest;
let direction = p5.Vector.sub(closest.pos, this.pos).normalize().mult(2);
this.vel = direction;
if (currentPhasmidIndex === phasmids.indexOf(this)) {
console.log(`Phasmid moving to data object: ${closest.id}`);
}
}
}
resetVisitedBranches() {
console.log("Resetting visited branches...");
this.visitedBranches = [];
this.lapCount++; // Increment lap count
}
interactWithData() {
let distance = p5.Vector.dist(this.pos, this.targetData.pos);
if (distance < 30) {
// Phasmid is inside the data object
if (this.inDataTime === 0) {
// Start the interaction timer
this.inDataTime = millis();
this.vel.set(0, 0, 0); // Stop the Phasmid
if (currentPhasmidIndex === phasmids.indexOf(this)) {
console.log(
`Phasmid started interacting with data: ${this.targetData.id}`
);
}
}
// Check if 2 seconds have passed
if (millis() - this.inDataTime >= 3000) {
// Collect data after 2 seconds
this.memory = {
color: this.targetData.color,
size: this.targetData.size,
type: this.targetData.type,
};
this.lastVisitedData = this.targetData; // Mark this as the last visited data object
this.targetData.markVisited(); // Mark data as visited with a reset timer
if (currentPhasmidIndex === phasmids.indexOf(this)) {
console.log(`Data ${this.targetData.id} marked as visited.`);
console.log(`Phasmid collected data: ${JSON.stringify(this.memory)}`);
}
// Reset interaction state and resume movement
this.targetData = null; // Stop targeting data
this.inDataTime = 0; // Reset timer
this.vel = p5.Vector.random3D().mult(2); // Resume random movement
}
}
}
deliverToBlueBranch(trees) {
if (!this.targetBranch) {
let closestBranch = null;
let closestDist = Infinity;
// Loop through all trees and branches
for (let tree of trees) {
for (let branch of tree.branches) {
// Calculate the branch's absolute position in 3D space
let branchPos = createVector(
tree.x + branch.position.x,
tree.y + branch.position.y,
tree.z + branch.position.z
);
let distance = p5.Vector.dist(this.pos, branchPos);
// Check if the branch is closer than the current closest
// Exclude branches already visited unless all branches are visited
if (
distance < closestDist &&
(!this.visitedBranches.includes(branch) ||
this.allBranchesVisited(trees))
) {
closestBranch = branchPos;
closestDist = distance;
this.targetBranch = branch; // Store the branch object
this.targetBranchTree = tree; // Store the tree for logging/debugging
}
}
}
if (closestBranch) {
// Move toward the closest branch
let direction = p5.Vector.sub(closestBranch, this.pos)
.normalize()
.mult(2);
this.vel = direction;
if (currentPhasmidIndex === phasmids.indexOf(this)) {
console.log(
`Phasmid targeting branch at tree: (${this.targetBranchTree.x}, ${this.targetBranchTree.y}, ${this.targetBranchTree.z})`
);
}
}
} else {
// Deliver to the already targeted branch
let branchPos = createVector(
this.targetBranchTree.x + this.targetBranch.position.x,
this.targetBranchTree.y + this.targetBranch.position.y,
this.targetBranchTree.z + this.targetBranch.position.z
);
let distance = p5.Vector.dist(this.pos, branchPos);
if (distance < 30) {
// Merge current branch color with delivered data
this.targetBranch.color = blendColors(
this.targetBranch.color,
this.memory.color
);
if (currentPhasmidIndex === phasmids.indexOf(this)) {
console.log(
`Phasmid delivered data to branch at: (${branchPos.x}, ${branchPos.y}, ${branchPos.z})`
);
console.log(`Branch color updated to: ${this.targetBranch.color}`);
}
this.memory = null; // Clear memory after delivery
// Mark the branch as visited
this.visitedBranches.push(this.targetBranch);
// Clear branch target
this.targetBranch = null;
this.targetBranchTree = null;
// Reset visitedBranches if all branches have been visited
if (this.allBranchesVisited(trees)) {
if (currentPhasmidIndex === phasmids.indexOf(this)) {
console.log(
"Phasmid visited all branches. Resetting visitedBranches."
);
}
this.visitedBranches = [];
}
this.vel.set(0, 0, 0); // Stop movement temporarily
}
}
}
branchesLeft(trees) {
// Count remaining branches in the current lap
let allBranches = trees.flatMap((tree) => tree.branches);
return allBranches.length - this.visitedBranches.length;
}
allBranchesVisited(trees) {
// Check if all branches across all trees have been visited
let allBranches = trees.flatMap((tree) => tree.branches);
return allBranches.every((branch) => this.visitedBranches.includes(branch));
}
display(isSelected = false) {
push();
translate(this.pos.x, this.pos.y, this.pos.z);
let pulse = abs(sin(millis() * 0.005)) * 100; // Pulse effect (0 to 100)
if (isSelected) {
fill(255, 100 + pulse, 100 + pulse); // Yellow pulse for selected
} else {
fill(255, 100 + pulse, 100 + pulse); // Pulsing red for normal phasmids
}
sphere(8); // Phasmid representation
pop();
}
}
// MessengerPhasmid class
class MessengerPhasmid extends Phasmid {
constructor(x, y, z) {
super(x, y, z);
this.sourceBranch = null; // Branch to collect data from
this.targetBranch = null; // Branch to deliver data to
this.idle = true; // Start idle
this.lapCount = 0; // Count how many times the list has been reset
this.visitedBranches = []; // Keep track of visited branches
}
act(dataObjects, trees, suppressLogs = false) {
// Check if MessengerPhasmids should become active
if (this.idle) {
this.idle = !this.shouldActivate(trees); // Remain idle if activation condition not met
if (this.idle) {
if (!suppressLogs || viewingMode) {
if (currentPhasmidIndex === phasmids.indexOf(this)) {
console.log("MessengerPhasmid is idle, waiting to activate...");
}
}
return; // Do nothing if idle
} else {
if (!suppressLogs || viewingMode) {
if (currentPhasmidIndex === phasmids.indexOf(this)) {
console.log("MessengerPhasmid has activated!");
}
}
}
}
// Normal behavior after activation
if (!this.memory && !this.sourceBranch) {
this.searchForSourceBranch(trees);
} else if (this.memory && !this.targetBranch) {
this.searchForTargetBranch(trees);
} else if (this.sourceBranch) {
this.collectDataFromBranch();
} else if (this.targetBranch) {
this.deliverDataToBranch();
}
this.pos.add(this.vel);
// Bounce on boundaries of the enlarged space
if (abs(this.pos.x) > BOUNDARY) this.vel.x *= -1;
if (abs(this.pos.y) > BOUNDARY) this.vel.y *= -1;
if (abs(this.pos.z) > BOUNDARY) this.vel.z *= -1;
}
deliverDataToBranch() {
let branchPos = createVector(
this.targetBranchTree.x + this.targetBranch.position.x,
this.targetBranchTree.y + this.targetBranch.position.y,
this.targetBranchTree.z + this.targetBranch.position.z
);
let distance = p5.Vector.dist(this.pos, branchPos);
if (distance < 30) {
this.targetBranch.color = blendColors(
this.targetBranch.color,
this.memory.color
);
// Mark the branch as visited
this.visitedBranches.push(this.targetBranch);
if (currentPhasmidIndex === phasmids.indexOf(this)) {
console.log(
`MessengerPhasmid delivered data to branch at: (${branchPos.x}, ${branchPos.y}, ${branchPos.z})`
);
console.log(`Branch color updated to: ${this.targetBranch.color}`);
}
this.memory = null; // Clear memory after delivery
this.targetBranch = null; // Clear target branch after delivery
// Reset visitedBranches if all branches have been visited
if (this.allBranchesVisited(trees)) {
if (currentPhasmidIndex === phasmids.indexOf(this)) {
console.log("MessengerPhasmid visited all branches. Resetting.");
}
this.visitedBranches = []; // Reset visited branches
this.lapCount++; // Increment lap count
}
}
}
shouldActivate(trees) {
let totalBranches = 0;
let changedBranches = 0;
for (let tree of trees) {
for (let branch of tree.branches) {
totalBranches++;
// Check if the branch's color has changed from its initial color
if (
red(branch.color) !== 50 ||
green(branch.color) !== 150 ||
blue(branch.color) !== 255
) {
changedBranches++;
}
}
}
// Activate if more than 50% of the branches have changed color
return changedBranches / totalBranches > 0.5;
}
searchForSourceBranch(trees) {
let closestBranch = null;
let closestDist = Infinity;
for (let tree of trees) {
for (let branch of tree.branches) {
let branchPos = createVector(
tree.x + branch.position.x,
tree.y + branch.position.y,
tree.z + branch.position.z
);
let distance = p5.Vector.dist(this.pos, branchPos);
// Exclude branches already visited unless all branches are visited
if (
distance < closestDist &&
(!this.visitedBranches.includes(branch) ||
this.allBranchesVisited(trees))
) {
closestBranch = branch;
closestDist = distance;
this.sourceBranch = branch; // Target this branch to collect data
this.sourceBranchTree = tree; // Track the source tree
}
}
}
if (closestBranch) {
let direction = p5.Vector.sub(
createVector(
this.sourceBranchTree.x + this.sourceBranch.position.x,
this.sourceBranchTree.y + this.sourceBranch.position.y,
this.sourceBranchTree.z + this.sourceBranch.position.z
),
this.pos
)
.normalize()
.mult(2);
this.vel = direction;
}
}
collectDataFromBranch() {
let branchPos = createVector(
this.sourceBranchTree.x + this.sourceBranch.position.x,
this.sourceBranchTree.y + this.sourceBranch.position.y,
this.sourceBranchTree.z + this.sourceBranch.position.z
);
let distance = p5.Vector.dist(this.pos, branchPos);
if (distance < 30) {
this.memory = {
color: this.sourceBranch.color, // Collect color as data
position: this.sourceBranch.position,
};
// Mark the branch as visited
this.visitedBranches.push(this.sourceBranch);
if (currentPhasmidIndex === phasmids.indexOf(this)) {
console.log(
`MessengerPhasmid collected data from branch at: (${branchPos.x}, ${branchPos.y}, ${branchPos.z})`
);
}
this.sourceBranch = null; // Clear source branch after collecting
}
}
searchForTargetBranch(trees) {
let closestBranch = null;
let closestDist = Infinity;
for (let tree of trees) {
if (tree === this.sourceBranchTree) continue; // Skip the source tree
for (let branch of tree.branches) {
let branchPos = createVector(
tree.x + branch.position.x,
tree.y + branch.position.y,
tree.z + branch.position.z
);
let distance = p5.Vector.dist(this.pos, branchPos);
// Exclude branches already visited unless all branches are visited
if (
distance < closestDist &&
(!this.visitedBranches.includes(branch) ||
this.allBranchesVisited(trees))
) {
closestBranch = branch;
closestDist = distance;
this.targetBranch = branch; // Target this branch to deliver data
this.targetBranchTree = tree; // Track the target tree
}
}
}
if (closestBranch) {
let direction = p5.Vector.sub(
createVector(
this.targetBranchTree.x + this.targetBranch.position.x,
this.targetBranchTree.y + this.targetBranch.position.y,
this.targetBranchTree.z + this.targetBranch.position.z
),
this.pos
)
.normalize()
.mult(2);
this.vel = direction;
}
}
allBranchesVisited(trees) {
// Check if all branches across all trees have been visited
let allBranches = trees.flatMap((tree) => tree.branches);
return allBranches.every((branch) => this.visitedBranches.includes(branch));
}
branchesLeft(trees) {
// Count remaining branches in the current lap
let allBranches = trees.flatMap((tree) => tree.branches);
return allBranches.length - this.visitedBranches.length;
}
display(isSelected = false) {
push();
translate(this.pos.x, this.pos.y, this.pos.z);
let pulse = abs(sin(millis() * 0.005)) * 100; // Pulse effect (0 to 100)
if (isSelected) {
fill(150 + pulse, 150 + pulse, 255); // Yellow pulse for selected
} else if (this.idle) {
fill(150 + pulse, 150 + pulse, 255); // Pulsing light blue for idle
} else {
fill(100, 255, 100 + pulse); // Pulsing green for active
}
sphere(8); // Phasmid representation
pop();
}
}
function blendColors(color1, color2) {
let r = lerp(red(color1), red(color2), 0.5);
let g = lerp(green(color1), green(color2), 0.5);
let b = lerp(blue(color1), blue(color2), 0.5);
return color(r, g, b);
}
function displayPhasmidStatus(phasmid) {
clear();
background(30);
console.log("///////////////////");
console.log("////////////////////");
console.log("/////////////////////");
console.log(`Currently Viewing Phasmid: ${currentPhasmidIndex}`);
console.log(
`Memory: ${
phasmid.memory !== null ? 1 : 0
} Left and Right Arrows to scroll through Phasmids`
);
console.log(
`Target Data: ${phasmid.targetData ? phasmid.targetData.id : "None"}`
);
if (phasmid instanceof MessengerPhasmid) {
// Additional information for MessengerPhasmids
console.log("Phasmid Type: MessengerPhasmid");
console.log(
`Source Branch: ${
phasmid.sourceBranch
? `(${phasmid.sourceBranch.position.x.toFixed(
2
)}, ${phasmid.sourceBranch.position.y.toFixed(
2
)}, ${phasmid.sourceBranch.position.z.toFixed(2)})`
: "None"
}`
);
console.log(
`Target Branch: ${
phasmid.targetBranch
? `(${phasmid.targetBranch.position.x.toFixed(
2
)}, ${phasmid.targetBranch.position.y.toFixed(
2
)}, ${phasmid.targetBranch.position.z.toFixed(2)})`
: "None"
}`
);
console.log(`Branches Visited: ${phasmid.visitedBranches.length}`);
console.log(
`Branches Left: ${phasmid.branchesLeft(trees)} / ${
trees.flatMap((tree) => tree.branches).length
}`
);
console.log(`Laps Completed: ${phasmid.lapCount}`);
} else {
// Default information for standard Phasmids
console.log("Phasmid Type: Normal");
console.log(`Targeting Branch: ${phasmid.targetBranch ? 1 : 0}`);
}
console.log("/////////////////////");
if (keyCode === ESCAPE && !viewingMode) {
console.clear(); // Clear console when exiting viewing mode
}
}
function keyPressed() {
if (keyCode === LEFT_ARROW && viewingMode) {
currentPhasmidIndex =
(currentPhasmidIndex - 1 + phasmids.length) % phasmids.length; // Cycle backward
} else if (keyCode === RIGHT_ARROW && viewingMode) {
currentPhasmidIndex = (currentPhasmidIndex + 1) % phasmids.length; // Cycle forward
} else if (keyCode === ESCAPE) {
if (viewingMode) {
// Exiting viewing mode, reset camera position
orbitX = 0; // Reset orbit X-axis
orbitY = 0; // Reset orbit Y-axis
cameraDistance = 600; // Default camera distance
}
viewingMode = !viewingMode; // Toggle viewing mode
}
}
class DataObject {
constructor(x, y, z) {
this.pos = createVector(x, y, z);
this.id = int(random(1000, 9999)); // Unique identifier
this.color = color(random(255), random(255), random(255));
this.size = random(10, 30);
this.type = random(["Numeric", "String", "Boolean"]);
this.visited = false; // Tracks if a Phasmid has already visited this data
this.resetTimer = 0; // Timer for resetting
}
display() {
push();
translate(this.pos.x, this.pos.y, this.pos.z);
fill(this.color);
sphere(this.size); // Data object representation
pop();
this.visited = false;
}
markVisited() {
this.visited = true;
this.resetTimer = millis(); // Start reset timer
}
}
function mouseDragged() {
// Adjust orbit angles based on mouse movement
orbitY += movedX * 0.005;
orbitX = constrain(orbitX - movedY * 0.005, -PI / 2 + 0.1, PI / 2 - 0.1); // Prevent flipping
}
</script>
</body>
</html>
Oh one more thing! To ‘Fly with a PhasmID’ if you try the code yourself press ‘Esc’ and use left and right arrow keys to switch between PhasmIDs and mouse to look around. (Esc to return to full view)