From 9299ada1863d7aa3deeaea2695db78c7a218d6a9 Mon Sep 17 00:00:00 2001 From: David Ali Date: Wed, 4 Feb 2026 13:05:01 +0100 Subject: [PATCH] first commit --- .gitignore | 5 ++ LICENSE | 21 ++++++ Makefile | 12 ++++ README.md | 39 +++++++++++ preview.png | Bin 0 -> 43165 bytes src/NervousSystem.hpp | 99 ++++++++++++++++++++++++++++ src/Spider.hpp | 147 ++++++++++++++++++++++++++++++++++++++++++ src/World.hpp | 49 ++++++++++++++ src/main.cpp | 81 +++++++++++++++++++++++ 9 files changed, 453 insertions(+) create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 Makefile create mode 100644 README.md create mode 100644 preview.png create mode 100644 src/NervousSystem.hpp create mode 100644 src/Spider.hpp create mode 100644 src/World.hpp create mode 100644 src/main.cpp diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..8ddc329 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +arachne + +# C-Flat ignores +cflat +cflat-*.txt diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..2e2989f --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 Dávid Ali + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..d3e1575 --- /dev/null +++ b/Makefile @@ -0,0 +1,12 @@ +CXX = g++ +CXXFLAGS = -std=c++17 -O3 -Wall +LDFLAGS = -lraylib + +SRC = src/main.cpp +TARGET = arachne + +all: + $(CXX) $(SRC) -o $(TARGET) $(CXXFLAGS) $(LDFLAGS) + +clean: + rm -f $(TARGET) \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..3a84768 --- /dev/null +++ b/README.md @@ -0,0 +1,39 @@ +# Arachne: An Experiment in Digital Instinct + +**Arachne** /əˈræk.ni/ brings a digital creature to life. It is not a standard game character: it is a biological simulation driven by a **Spiking Neural Network (SNN)**. This project explores the boundary between code and instinct. It demonstrates how raw sensory data transforms into movement through a web of artificial neurons. + +![Simulation View](preview.png) + +## The Anatomy of a Hunter + +The simulation centers on a spider called Arachne. It does not follow scripted paths. It hunts using a sophisticated nervous system. The creature possesses a multi-faceted visual system: different "eyes" cover specific angles and ranges to detect prey. + +Vision is not its only sense. The spider feels the world through its legs. Specialized vibration sensors detect the movement of flies nearby. These sensory inputs flow directly into the neural network, they stimulate specific neurons based on proximity and angle. The output is not just data, it is an action. Motor units fire to rotate the body, move forward, or freeze in response to stimuli. + +## The Neuromorphic Brain + +The core of this project is the **NervousSystem** class. It mimics biological processing using a **Leaky Integrate-and-Fire (LIF)** model. Neurons in this system do not pass continuous numbers like deep learning models. Instead: they accumulate an electric charge over time. + +This charge decays if no input is received: this mimics biological recovery. When the potential hits a critical threshold: the neuron "spikes". It fires a single discrete signal to its neighbors. Excitatory synapses increase the charge of target neurons, while inhibitory synapses suppress them. This creates a complex dynamic where behavior emerges from the timing of these spikes. + +## Simulation vs. Reality + +This project pushes the limits of standard computing to mimic biology. However, it remains an approximation. A true biological brain operates asynchronously in continuous time. Arachne currently lives within a synchronous loop: its nervous system updates in lockstep with the 60 FPS frame rate. + +The current network is also static. The synaptic weights are hardcoded to define behavior: the spider is born with its instincts already formed. It does not yet learn from its failures. + +## The Path Forward + +The evolution of Arachne is just beginning. The next major step is to break free from the frame rate. I plan to implement an **Event-Based Neural Engine**. This will allow neurons to fire asynchronously via a priority queue, which will create true time independence. + +I also aim to introduce plasticity. By implementing **STDP (Spike-Timing-Dependent Plasticity)** the spider will gain the ability to learn. It will strengthen connections that lead to a successful catch. The simulation will eventually support complex morphology and interaction with dangerous environments. + +## Technical Foundation + +* **Language**: C++17 +* **Engine**: raylib +* **Structure**: Simple Makefile system + +## License + +MIT \ No newline at end of file diff --git a/preview.png b/preview.png new file mode 100644 index 0000000000000000000000000000000000000000..1a43f29135631fff28aa123f30d174611e5fd7c6 GIT binary patch literal 43165 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 +#include +#include "raylib.h" +#include "raymath.h" + +class NervousSystem { + public: + struct Neuron { + private: + friend class NervousSystem; + float potential = 0.0f; + float recovery = 0.92f; + float threshold = 1.0f; + bool fired = false; + + bool update(float input) { + potential = (potential * recovery) + input; + if (potential < 0.0f) potential = 0.0f; + if (potential >= threshold) { + potential = 0.0f; + fired = true; + return true; + } + fired = false; + return false; + } + + public: + float getCharge() const { return potential / threshold; } + bool hasFired() const { return fired; } + }; + + private: + struct Synapse { + int from; + int to; + float weight; + }; + + std::vector units; + std::vector connections; + std::vector incomingSignals; + + public: + int addUnit() { + units.push_back(Neuron()); + incomingSignals.push_back(0.0f); + return static_cast(units.size()) - 1; + } + + void connect(int fromIdx, int toIdx, float weight) { + if (fromIdx < (int)units.size() && toIdx < (int)units.size()) { + connections.push_back({fromIdx, toIdx, weight}); + } + } + + void tick() { + for (std::size_t i = 0; i < units.size(); ++i) { + units[i].update(incomingSignals[i]); + incomingSignals[i] = 0.0f; + } + + for (const auto& syn : connections) { + if (units[syn.from].fired) { + incomingSignals[syn.to] += syn.weight; + } + } + } + + void stimulate(int idx, float power) { + if (idx >= 0 && idx < (int)incomingSignals.size()) { + incomingSignals[idx] += power; + } + } + + void draw(Vector2 pos, int rows, float spacing) const { + for (const auto& syn : connections) { + Vector2 p1 = {pos.x + (syn.from / rows) * spacing * 2, pos.y + (syn.from % rows) * spacing}; + Vector2 p2 = {pos.x + (syn.to / rows) * spacing * 2, pos.y + (syn.to % rows) * spacing}; + + Color synColor = units[syn.from].fired ? YELLOW : Fade(GRAY, 0.3f); + DrawLineEx(p1, p2, 1.0f, synColor); + } + + for (std::size_t i = 0; i < units.size(); ++i) { + Vector2 nodePos = {pos.x + (i / rows) * spacing * 2, pos.y + (i % rows) * spacing}; + + float charge = units[i].getCharge(); + Color nodeColor = ColorLerp(DARKGRAY, LIME, charge); + if (units[i].fired) nodeColor = WHITE; + + DrawCircleV(nodePos, 8, nodeColor); + DrawCircleLinesV(nodePos, 8, units[i].fired ? YELLOW : DARKGRAY); + } + } + + const Neuron& getUnit(int idx) const { return units[idx]; } +}; \ No newline at end of file diff --git a/src/Spider.hpp b/src/Spider.hpp new file mode 100644 index 0000000..08b1bab --- /dev/null +++ b/src/Spider.hpp @@ -0,0 +1,147 @@ +#pragma once +#include "raylib.h" +#include "raymath.h" +#include "NervousSystem.hpp" +#include +#include + +struct Eye { + int unitIdx; + float angleOffset; + float fov; + float range; +}; + +class Spider { + public: + NervousSystem brain; + Vector2 position; + float angle; + + std::vector eyes; + std::vector vibrationUnits; + std::vector motorUnits; + int freezeUnit; + Spider(Vector2 startPos) : position(startPos), angle(0.0f) { + auto addEye = [&](float offset, float fov, float range) { + int id = brain.addUnit(); + eyes.push_back({id, offset, fov, range}); + }; + + addEye(-5.0f, 30.0f, 600.0f); + addEye(5.0f, 30.0f, 600.0f); + + addEye(-20.0f, 60.0f, 500.0f); + addEye(20.0f, 60.0f, 500.0f); + + addEye(-45.0f, 120.0f, 350.0f); + addEye(45.0f, 120.0f, 350.0f); + + addEye(-60.0f, 180.0f, 200.0f); + addEye(60.0f, 180.0f, 200.0f); + + for (int i = 0; i < 8; i++) { + vibrationUnits.push_back(brain.addUnit()); + } + + for (int i = 0; i < 8; i++) { + motorUnits.push_back(brain.addUnit()); + } + + freezeUnit = brain.addUnit(); + + for (int m : motorUnits) { + brain.connect(freezeUnit, m, -5.0f); + } + + for (int i = 0; i < 8; i++) { + for (int m = 0; m < 8; m++) { + if ((i < 4 && m >= 4) || (i >= 4 && m < 4)) { + brain.connect(vibrationUnits[i], motorUnits[m], 2.2f); + } + + if (i == m) { + brain.connect(vibrationUnits[i], motorUnits[m], 0.4f); + } + } + } + + for (int e = 0; e < 8; e++) { + float eyeAng = eyes[e].angleOffset; + float focusMultiplier = (std::abs(eyeAng) <= 30.0f) ? 2.8f : 1.5f; + + int targetLegIdx = -1; + + if (eyeAng < -15.0f) { + targetLegIdx = 7; + } else if (eyeAng > 15.0f) { + targetLegIdx = 0; + } else if (std::abs(eyeAng) <= 15.0f) { + brain.connect(eyes[e].unitIdx, motorUnits[0], focusMultiplier * 0.8f); + brain.connect(eyes[e].unitIdx, motorUnits[7], focusMultiplier * 0.8f); + } + + if (targetLegIdx != -1) { + brain.connect(eyes[e].unitIdx, motorUnits[targetLegIdx], focusMultiplier); + } + } + } + + void update() { + brain.tick(); + + float turnStep = 5.0f * DEG2RAD; + float moveStep = 3.0f; + + for (int i = 0; i < 8; i++) { + if (brain.getUnit(motorUnits[i]).hasFired()) { + if (i < 4) + angle += turnStep; + else + angle -= turnStep; + + Vector2 forward = {cosf(angle), sinf(angle)}; + position = Vector2Add(position, Vector2Scale(forward, moveStep)); + } + } + } + + void draw() { + DrawCircleV(position, 15, (Color){30, 30, 30, 255}); + Vector2 headPos = Vector2Add(position, Vector2Rotate({12, 0}, angle)); + DrawCircleV(headPos, 10, BLACK); + + for (const auto& eye : eyes) { + float totalAngle = angle * RAD2DEG + eye.angleOffset; + + DrawCircleSector(headPos, eye.range, totalAngle - eye.fov / 2, totalAngle + eye.fov / 2, 10, Fade(RED, 0.1f)); + + DrawCircleSectorLines(headPos, eye.range, totalAngle - eye.fov / 2, totalAngle + eye.fov / 2, 10, + Fade(RED, 0.2f)); + } + + for (int side : {-1, 1}) { + Vector2 eyePos = Vector2Add(headPos, Vector2Rotate({6, (float)side * 4}, angle)); + DrawCircleV(eyePos, 2, RED); + } + + for (int i = 0; i < 8; i++) { + float legOffset = (i < 4) ? (-135.0f + i * 30.0f) : (45.0f + (i - 4) * 30.0f); + float legAngle = angle + legOffset * DEG2RAD; + + const auto& unit = brain.getUnit(motorUnits[i]); + float length = 35.0f + (unit.hasFired() ? 15.0f : unit.getCharge() * 5.0f); + + Vector2 legEnd = Vector2Add(position, Vector2Rotate({length, 0}, legAngle)); + + Color legColor = ColorLerp(BLACK, RED, unit.getCharge()); + if (unit.hasFired()) legColor = MAROON; + + DrawLineEx(position, legEnd, 2.0f, legColor); + DrawCircleV(legEnd, 3, legColor); + } + if (brain.getUnit(freezeUnit).hasFired()) { + DrawText("!!! STRACH !!!", position.x - 40, position.y - 40, 10, RED); + } + } +}; \ No newline at end of file diff --git a/src/World.hpp b/src/World.hpp new file mode 100644 index 0000000..3758ceb --- /dev/null +++ b/src/World.hpp @@ -0,0 +1,49 @@ +#pragma once +#include "raylib.h" +#include + +struct Fly { + Vector2 position; + Vector2 velocity; + bool isAlive = true; +}; + +class World { + public: + std::vector flies; + World(int count, int w, int h) { + for (int i = 0; i < count; i++) { + flies.push_back({{(float)GetRandomValue(0, w), (float)GetRandomValue(0, h)}, + {(float)GetRandomValue(-100, 100) / 40.0f, (float)GetRandomValue(-100, 100) / 40.0f}}); + } + } + + void update() { + for (auto& f : flies) { + if (!f.isAlive) continue; + f.position = Vector2Add(f.position, f.velocity); + if (f.position.x < 0 || f.position.x > 1200) f.velocity.x *= -1; + if (f.position.y < 0 || f.position.y > 800) f.velocity.y *= -1; + } + } + + void draw() { + for (const auto& f : flies) { + if (f.isAlive) DrawCircleV(f.position, 3, LIME); + } + } + + void respawn(int targetCount) { + int currentAlive = 0; + for (const auto& f : flies) { + if (f.isAlive) currentAlive++; + } + + if (currentAlive < targetCount) { + if (rand() % 100 == 0) + flies.push_back({{(float)GetRandomValue(0, 1200), (float)GetRandomValue(0, 800)}, + {(float)GetRandomValue(-100, 100) / 40.0f, (float)GetRandomValue(-100, 100) / 40.0f}, + true}); + } + } +}; \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 0000000..d9ce3f2 --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,81 @@ +#include "raylib.h" +#include "raymath.h" +#include "Spider.hpp" +#include "World.hpp" +#include + +int main() { + const int screenWidth = 1200; + const int screenHeight = 800; + InitWindow(screenWidth, screenHeight, "Arachne"); + SetTargetFPS(60); + + Spider arachne({(float)screenWidth / 2, (float)screenHeight / 2}); + + World world(15, screenWidth, screenHeight); + + while (!WindowShouldClose()) { + world.update(); + world.respawn(15); + + for (auto& fly : world.flies) { + if (!fly.isAlive) continue; + + Vector2 toFly = Vector2Subtract(fly.position, arachne.position); + float dist = Vector2Length(toFly); + + if (dist < 20.0f) { + fly.isAlive = false; + continue; + } + + for (const auto& eye : arachne.eyes) { + if (dist < eye.range) { + float absEyeAng = arachne.angle + eye.angleOffset * DEG2RAD; + Vector2 viewDir = {cosf(absEyeAng), sinf(absEyeAng)}; + Vector2 toFlyNorm = Vector2Normalize(toFly); + + float dot = Vector2DotProduct(toFlyNorm, viewDir); + float angleToFly = acosf(fmaxf(-1.0f, fminf(1.0f, dot))) * RAD2DEG; + + if (angleToFly < eye.fov / 2.0f) { + float intensity = (1.0f - dist / eye.range) * 0.5f; + arachne.brain.stimulate(eye.unitIdx, intensity); + } + } + } + } + + for (const auto& fly : world.flies) { + if (!fly.isAlive) continue; + + for (int i = 0; i < 8; i++) { + float legOff = (i < 4) ? (-135.0f + i * 30.0f) : (45.0f + (i - 4) * 30.0f); + Vector2 legPos = Vector2Add(arachne.position, Vector2Rotate({40, 0}, arachne.angle + legOff * DEG2RAD)); + float distToLeg = Vector2Distance(fly.position, legPos); + + if (distToLeg < 150.0f) { + float vibPower = 0.3f * (1.0f - distToLeg / 150.0f); + arachne.brain.stimulate(arachne.vibrationUnits[i], vibPower); + } + } + } + + arachne.update(); + + BeginDrawing(); + ClearBackground((Color){30, 30, 40, 255}); + + arachne.draw(); + + DrawText("NEURAL NETWORK ACTIVITY", 950, 20, 10, GRAY); + arachne.brain.draw({1000, 50}, 8, 30.0f); + world.draw(); + + DrawFPS(10, 10); + EndDrawing(); + } + + CloseWindow(); + return 0; +} \ No newline at end of file