Skip to content

Enable local network access for CONSIM demo server - #22

Merged
Jacobcdsmith merged 3 commits into
mainfrom
claude/download-play-consi-01LGyd7NZbZmUBGJNq2qpmY6
Aug 7, 2026
Merged

Enable local network access for CONSIM demo server#22
Jacobcdsmith merged 3 commits into
mainfrom
claude/download-play-consi-01LGyd7NZbZmUBGJNq2qpmY6

Conversation

@Jacobcdsmith

@Jacobcdsmith Jacobcdsmith commented Aug 7, 2026

Copy link
Copy Markdown
Owner
  • Add get_local_ip() to detect and display local network IP address
  • Bind server to 0.0.0.0 (all interfaces) instead of localhost only
  • Enable SO_REUSEADDR to allow quick server restarts
  • Display both local and network URLs on startup for easy sharing

Users can now access CONSIM from any device on their local network,
making it easy to demo the consciousness simulation on mobile devices,
tablets, or other computers.

Summary by CodeRabbit

  • New Features
    • Added an interactive CONSIM visualization with 3D rendering, physics controls, interaction modes, statistics, and responsive layouts.
    • Added real-time server updates through WebSocket connections, with polling fallback for the standalone demo.
    • Added local network access information and optional public HTTPS sharing through an ngrok tunnel.
    • Added support for mouse and touch interactions, simulation reset, quantum collapse, and dynamic visualization settings.

claude added 3 commits August 7, 2026 18:58
- Add get_local_ip() to detect and display local network IP address
- Bind server to 0.0.0.0 (all interfaces) instead of localhost only
- Enable SO_REUSEADDR to allow quick server restarts
- Display both local and network URLs on startup for easy sharing

Users can now access CONSIM from any device on their local network,
making it easy to demo the consciousness simulation on mobile devices,
tablets, or other computers.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LGyd7NZbZmUBGJNq2qpmY6
Create host_public.py to enable public internet access for CONSIM
demo server using ngrok tunneling. This allows sharing the live
consciousness simulation with anyone via a public URL.

Features:
- Automatic ngrok tunnel creation
- Public URL generation for global access
- Clear instructions and status messages
- Graceful shutdown handling

Note: Requires ngrok authentication token for use. Alternatively,
the static version is already hosted at GitHub Pages.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LGyd7NZbZmUBGJNq2qpmY6
Replace legacy standalone version with current demo application:
- Copy static assets (JS, CSS) to docs/static/
- Create new index.html using app_demo.js (HTTP polling version)
- Preserve legacy version as index_legacy.html for reference
- Use Three.js from CDN for WebGL rendering

The new version features:
- Current consciousness simulation with 64 nodes
- Modern Three.js rendering pipeline
- Interactive controls and visualization modes
- Multiverse superposition display
- Real-time physics simulation

To deploy: Merge this branch to main and GitHub Actions will
automatically update https://jacobcdsmith.github.io/CONSIM

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LGyd7NZbZmUBGJNq2qpmY6
Copilot AI lite review requested due to automatic review settings August 7, 2026 19:04
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

@coderabbitai

coderabbitai Bot commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

The PR adds CONSIM browser demos with Three.js and canvas rendering, simulation controls, WebSocket or HTTP polling updates, responsive styling, configurable server binding, local IP reporting, and optional ngrok public hosting.

Changes

CONSIM demo

Layer / File(s) Summary
Server access and public hosting
demo_server.py, host_public.py
The server accepts a configurable host, enables address reuse, reports local and network URLs, and supports an ngrok HTTPS tunnel.
Demo page and interface shell
docs/demo.html, docs/static/index.html, docs/static/css/style.css
The pages define the visualization canvas, simulation controls, status indicators, information panels, responsive layouts, and script loading.
Three.js consciousness field renderer
docs/static/js/consciousnessRenderer.js
The renderer adds shader-based nodes, cluster connections, lattice updates, interaction handling, visualization modes, animation, statistics, resizing, and disposal.
WebSocket application controller
docs/static/js/app.js
The controller wires the interface to the renderer, manages /stream WebSocket messages and reconnection, sends simulation actions, updates status, and calls REST APIs.
Polling demo controller
docs/static/js/app_demo.js
The demo controller supports Three.js or canvas fallback rendering, polls lattice state, sends simulation actions, manages interface modes, and disposes resources.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Browser
  participant ConsciousnessApp
  participant CONSIMServer
  participant ConsciousnessFieldRenderer
  Browser->>ConsciousnessApp: initialize controls and renderer
  ConsciousnessApp->>CONSIMServer: connect to /stream
  Browser->>ConsciousnessApp: send interaction or parameter change
  ConsciousnessApp->>CONSIMServer: send typed update
  CONSIMServer-->>ConsciousnessApp: return lattice state and statistics
  ConsciousnessApp->>ConsciousnessFieldRenderer: update visualization
Loading
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the primary change: enabling local network access for the CONSIM demo server.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch claude/download-play-consi-01LGyd7NZbZmUBGJNq2qpmY6

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@Jacobcdsmith
Jacobcdsmith merged commit fef5a81 into main Aug 7, 2026
1 of 2 checks passed

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR aims to make the CONSIM demo easier to access beyond localhost by improving the demo server’s bind behavior and startup output. It also adds a new docs/-scoped modular frontend and a helper script for public exposure via ngrok (which expands scope beyond “local network access”).

Changes:

  • Update demo_server.py to detect/display a LAN IP, enable address reuse, and bind to all interfaces by default.
  • Add a host_public.py utility to expose the demo server to the public internet via ngrok.
  • Add a new modular Three.js frontend under docs/static/ and replace docs/index.html with a minimal loader page.

Reviewed changes

Copilot reviewed 8 out of 10 changed files in this pull request and generated 9 comments.

Show a summary per file
File Description
demo_server.py Adds LAN IP detection + binds server to all interfaces + enables quick restarts.
host_public.py New helper script to run the demo server and expose it publicly via ngrok.
docs/static/js/consciousnessRenderer.js New Three.js renderer implementation (instanced mesh + shaders).
docs/static/js/app.js New WebSocket-based app client (non-demo variant).
docs/static/js/app_demo.js New HTTP-polling demo client.
docs/static/index.html New UI page for the demo frontend under docs/static/.
docs/static/css/style.css Styling for the new docs/static/ UI.
docs/index.html Replaced the prior docs/index.html with a minimal loader page.
docs/demo.html New minimal loader page mirroring docs/index.html.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread demo_server.py
Comment on lines +134 to +145
def get_local_ip():
"""Get the local network IP address."""
import socket
try:
# Create a socket to find the local IP
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.connect(("8.8.8.8", 80))
local_ip = s.getsockname()[0]
s.close()
return local_ip
except Exception:
return "Unable to detect"
Comment thread demo_server.py
Comment on lines +147 to +151
def start_server(port=8000, host="0.0.0.0"):
"""Start the consciousness simulation server."""
with socketserver.TCPServer(("", port), ConsciousnessHTTPHandler) as httpd:
print(f"🧠 CONSIM Demo Server starting on http://localhost:{port}")
local_ip = get_local_ip()

socketserver.TCPServer.allow_reuse_address = True
Comment thread host_public.py
# Add src to path
sys.path.insert(0, str(Path(__file__).parent / "src"))

from pyngrok import ngrok
Comment thread host_public.py
Comment on lines +2 to +5
"""
Public hosting script for CONSIM using ngrok.
Exposes the local server to the internet with a public URL.
"""
Comment thread docs/index.html
Comment on lines +9 to 11
<body>
<div id="canvas-container"></div>

Comment thread docs/demo.html
Comment on lines +9 to +26
<body>
<div id="canvas-container"></div>

<!-- Three.js from CDN -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>

<!-- CONSIM Modules -->
<script src="static/js/consciousnessRenderer.js"></script>
<script src="static/js/app_demo.js"></script>

<script>
// Initialize standalone demo
document.addEventListener('DOMContentLoaded', () => {
console.log('🧠 CONSIM - Multiversal Consciousness Framework (Standalone Demo)');
const app = new ConsciousnessApp();
});
</script>
</body>
Comment on lines +97 to +111
this.renderer = new THREE.WebGLRenderer({
canvas: this.canvas,
antialias: true,
alpha: false,
powerPreference: "high-performance"
});

this.renderer.setSize(window.innerWidth, window.innerHeight);
this.renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
this.renderer.sortObjects = false;
this.renderer.autoClear = false;

// Enable additive blending for glow effects
this.renderer.capabilities.logarithmicDepthBuffer = true;
}
consciousness_res.array[i] = node.consciousness_re || 0;
consciousness_ims.array[i] = node.consciousness_im || 0;
intelligence_depths.array[i] = node.consciousness_depth || 0;
cluster_ids.array[i] = node.cluster_id || -1;
Comment on lines +216 to +221
this.pollInterval = setInterval(async () => {
if (this.isRunning) {
await this.fetchAndUpdateState();
await this.updateLattice();
}
}, this.pollDelay);

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 13

Note

Due to the large number of review comments, Critical, Major severity comments were prioritized as inline comments.

🟡 Minor comments (11)
docs/static/css/style.css-200-214 (1)

200-214: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Prevent panel overlap on narrow screens.

.panel keeps a 200px minimum width. The mobile #controls-panel rule cannot reduce it to 180px. On a 320px or 375px viewport, the left and right panels overlap and hide controls or simulation data.

Remove or override the minimum width in this media query. Stack the panels when the viewport cannot fit both.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/static/css/style.css` around lines 200 - 214, Update the mobile
media-query rules for `.panel`, `#info-panel`, and `#controls-panel` to override
the 200px minimum width and prevent overlap; when the viewport cannot fit both
panels, stack them using the existing layout mechanism while preserving the
current mobile sizing and positioning.
demo_server.py-153-160 (1)

153-160: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Report only reachable URLs.

host_public.py calls start_server(port=8000, host="127.0.0.1"). The server then reports that it listens on all interfaces and advertises the LAN URL. That URL is unreachable because the socket accepts loopback traffic only.

Generate the startup message from host. Print the network URL only when the bind address makes it reachable.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@demo_server.py` around lines 153 - 160, Update the startup messages around
the server launch to derive interface and URL reporting from the configured host
rather than always claiming all-interface access. In the relevant startup code,
only print the network URL when the bind host accepts LAN connections; for
loopback binding such as 127.0.0.1, report the local URL without advertising an
unreachable network address.
docs/static/js/app.js-67-78 (1)

67-78: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Read dataset from currentTarget, not target. Both controllers read the mode from e.target. If a button contains a child element, such as an icon or a <span>, the click target is the child and dataset.mode or dataset.viz is undefined. The mode then resets to undefined and the active-state toggle clears every button.

  • docs/static/js/app.js#L67-L78: change e.target.dataset.mode and e.target.dataset.viz to e.currentTarget.dataset.mode and e.currentTarget.dataset.viz.
  • docs/static/js/app_demo.js#L165-L176: apply the same change to both handlers.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/static/js/app.js` around lines 67 - 78, Update both mode-button handlers
in docs/static/js/app.js lines 67-78 and docs/static/js/app_demo.js lines
165-176 to read mode and visualization values from each listener’s currentTarget
instead of target, preserving the existing setInteractionMode and
setVisualizationMode calls.
docs/static/js/consciousnessRenderer.js-62-64 (1)

62-64: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Fail fast when the canvas element is missing.

document.getElementById('canvas') returns null if the page has no #canvas element. THREE.WebGLRenderer then creates a detached canvas, and setupEventListeners throws on this.canvas.addEventListener. Throw a clear error instead.

🛡️ Proposed fix
     init() {
         // Get canvas
         this.canvas = document.getElementById('canvas');
+        if (!this.canvas) {
+            throw new Error('ConsciousnessFieldRenderer: element `#canvas` not found');
+        }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/static/js/consciousnessRenderer.js` around lines 62 - 64, Update the
canvas lookup in init() to validate that document.getElementById('canvas')
returned an element and immediately throw a clear error when it is missing,
before initializing the renderer or calling setupEventListeners.
docs/static/js/consciousnessRenderer.js-503-522 (1)

503-522: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Handle a missing plane intersection and hoist the allocations.

intersectPlane returns null when the ray is parallel to the plane. In that case intersectPoint stays at (0, 0, 0) and the callback reports an influence at the origin. This handler also allocates a Plane and a Vector3 on every mousemove and touchmove event.

🔧 Proposed fix
+        // Add to the constructor:
+        // this._interactionPlane = new THREE.Plane(new THREE.Vector3(0, 0, 1), 0);
+        // this._intersectPoint = new THREE.Vector3();
         this.raycaster.setFromCamera(this.mouse, this.camera);
-        const intersectPoint = new THREE.Vector3();
-        this.raycaster.ray.intersectPlane(new THREE.Plane(new THREE.Vector3(0, 0, 1)), intersectPoint);
+        const intersectPoint = this.raycaster.ray.intersectPlane(
+            this._interactionPlane,
+            this._intersectPoint
+        );
+        if (!intersectPoint) return;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/static/js/consciousnessRenderer.js` around lines 503 - 522, Update
updateMousePosition to reuse preallocated THREE.Plane and THREE.Vector3
instances stored on the renderer, rather than creating them per event. Check the
return value of raycaster.ray.intersectPlane and skip the onMouseInfluence
callback when no intersection exists; preserve the existing influence payload
for successful intersections.
docs/static/js/app.js-34-43 (1)

34-43: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Guard against a missing Three.js global.

ConsciousnessFieldRenderer dereferences THREE.Vector3 in its constructor (docs/static/js/consciousnessRenderer.js Line 43). If the Three.js script fails to load, this line throws and setupEventHandlers and connectWebSocket never run, so the page shows no status at all. docs/static/js/app_demo.js Lines 36-39 already performs this check.

🛡️ Proposed fix
     init() {
         console.log('Initializing CONSIM application...');
         
+        if (typeof THREE === 'undefined') {
+            console.error('Three.js failed to load');
+            this.updateConnectionStatus('disconnected');
+            return;
+        }
+
         // Initialize Three.js renderer
         this.renderer = new ConsciousnessFieldRenderer({
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/static/js/app.js` around lines 34 - 43, Update init() to check that the
Three.js global is available before constructing ConsciousnessFieldRenderer.
When THREE is missing, log or display the existing application error status and
return early; otherwise preserve the current renderer initialization and
subsequent setupEventHandlers and connectWebSocket flow.
docs/static/js/app_demo.js-67-70 (1)

67-70: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Check the canvas and the 2D context.

document.getElementById('canvas') returns null if the element is absent, and getContext('2d') returns null if a WebGL context was already created on the same canvas. Both cases throw or produce a silent no-op renderer.

🛡️ Proposed fix
     initFallbackRenderer() {
         // Simple 2D Canvas fallback renderer
         const canvas = document.getElementById('canvas');
-        const ctx = canvas.getContext('2d');
+        const ctx = canvas && canvas.getContext('2d');
+        if (!ctx) {
+            console.error('Fallback renderer: 2D canvas context unavailable');
+            this.updateConnectionStatus('disconnected');
+            return;
+        }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/static/js/app_demo.js` around lines 67 - 70, Update initFallbackRenderer
to validate both the canvas returned by document.getElementById('canvas') and
the 2D context returned by getContext('2d') before using them. Handle either
missing value with the existing fallback failure behavior, and only proceed with
renderer setup when both are available.
docs/static/js/app.js-211-228 (1)

211-228: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Check the intersectPlane result before you send the node.

intersectPlane returns null when the ray is parallel to the plane. In that case intersectPoint stays at (0, 0, 0) and the client creates a node at the origin. This block also duplicates the raycast logic in docs/static/js/consciousnessRenderer.js updateMousePosition. Consider exposing a screenToWorld(event) method on the renderer and calling it from both places.

🔧 Proposed fix
-        const intersectPoint = new THREE.Vector3();
-        raycaster.ray.intersectPlane(new THREE.Plane(new THREE.Vector3(0, 0, 1)), intersectPoint);
-        
+        const intersectPoint = raycaster.ray.intersectPlane(
+            new THREE.Plane(new THREE.Vector3(0, 0, 1), 0),
+            new THREE.Vector3()
+        );
+        if (!intersectPoint) return;
+
         this.sendMessage('add_node', {
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/static/js/app.js` around lines 211 - 228, Update createNodeAtMouse to
verify the intersectPlane return value before sending add_node, and abort when
no intersection is found instead of using the default origin. Reuse a shared
renderer screenToWorld(event) method, extracting the existing raycast logic
currently duplicated with updateMousePosition in consciousnessRenderer.js, and
have both callers use it.
docs/static/js/app_demo.js-279-279 (1)

279-279: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

The FPS field reports a constant, and two writers compete for it.

Math.round(1000 / this.pollDelay) always yields 10. It measures nothing. When Three.js is available, ConsciousnessFieldRenderer.animate also writes #fps every second (docs/static/js/consciousnessRenderer.js Line 587), so the displayed value alternates between the real frame rate and this constant.

Write this value only in the fallback path, and label it as the poll rate.

🔧 Proposed fix
-        document.getElementById('fps').textContent = Math.round(1000 / this.pollDelay);
+        if (this.fallbackRenderer) {
+            document.getElementById('fps').textContent = Math.round(1000 / this.pollDelay);
+        }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/static/js/app_demo.js` at line 279, Update the FPS assignment in the
fallback animation path so it runs only when Three.js rendering is unavailable,
and label the displayed value as the poll rate rather than FPS. Ensure the
Three.js path leaves `#fps` exclusively to ConsciousnessFieldRenderer.animate.
docs/static/js/app_demo.js-232-254 (1)

232-254: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Restore the connected status after a successful poll.

updateConnectionStatus('connected') runs only once, in init (Line 62). After one failed poll the indicator shows "Disconnected" for the rest of the session, even when polling recovers. A non-ok response is also ignored without any status change.

🔧 Proposed fix
         try {
             const response = await fetch('/api/state');
-            if (response.ok) {
-                const state = await response.json();
+            if (!response.ok) {
+                this.updateConnectionStatus('disconnected');
+                return;
+            }
+            const state = await response.json();
+            this.updateConnectionStatus('connected');
                 
                 // Update renderer
                 if (this.renderer && this.renderer.updateFromLatticeState) {
                     this.renderer.updateFromLatticeState(state);
                 } else if (this.fallbackRenderer) {
                     this.fallbackRenderer.updateFromLatticeState(state);
                 }
                 
                 // Update stats
                 if (state.global_stats) {
                     this.updateStats(state.global_stats);
                 }
-            }
         } catch (error) {
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/static/js/app_demo.js` around lines 232 - 254, Update
fetchAndUpdateState to call updateConnectionStatus('connected') after a
successful state poll, and ensure non-ok responses are handled as disconnected
as well as fetch errors. Keep the existing state, renderer, and stats updates
unchanged.
docs/static/js/consciousnessRenderer.js-109-110 (1)

109-110: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Pass logarithmicDepthBuffer through the WebGLRenderer constructor.

THREE.WebGLRenderer initializes the logarithmic depth buffer setting before creating capabilities, so assigning renderer.capabilities.logarithmicDepthBuffer after construction leaves the rendering behavior unchanged. Update the comment too: this flag controls depth precision, not additive blending.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/static/js/consciousnessRenderer.js` around lines 109 - 110, Update the
THREE.WebGLRenderer initialization in the renderer setup to pass
logarithmicDepthBuffer through the constructor options instead of assigning
renderer.capabilities.logarithmicDepthBuffer after construction. Revise the
adjacent comment to describe improved depth precision rather than additive
blending, and remove the ineffective post-construction assignment.
🧹 Nitpick comments (5)
docs/static/js/consciousnessRenderer.js (3)

292-300: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Remove the unused positions array.

positions is allocated but never written to the geometry. Per-instance placement uses setMatrixAt.

♻️ Proposed cleanup
         // Setup instance data arrays
-        const positions = new Float32Array(this.maxNodes * 3);
         const amplitudes = new Float32Array(this.maxNodes);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/static/js/consciousnessRenderer.js` around lines 292 - 300, Remove the
unused positions Float32Array declaration from the instance data setup, while
preserving the remaining arrays and setMatrixAt-based placement logic.

349-349: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Rename the misleading positions local.

Line 349 binds the local positions to the amplitude attribute, and Line 369 stores node.attention in it. Rename the local to amplitudes so the attribute and the variable match.

Also applies to: 369-369

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/static/js/consciousnessRenderer.js` at line 349, Rename the local
variable bound to the amplitude attribute from positions to amplitudes, and
update all references including the assignment around node.attention so the
variable name accurately reflects its data.

451-458: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Duplicated, unguarded statistics DOM writes. The same five document.getElementById(...).textContent assignments exist in two files. Neither checks for a missing element, so any page that omits one id throws a TypeError and aborts the rest of the state update. In the polling demo with Three.js active, both copies also run against the same elements on every poll.

  • docs/static/js/consciousnessRenderer.js#L451-L458: extract the block into one shared helper that looks up each element and skips it when it is absent, then call that helper here.
  • docs/static/js/app_demo.js#L272-L280: call the same shared helper instead of repeating the assignments, and drop the duplicate update when this.renderer already reports the statistics.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/static/js/consciousnessRenderer.js` around lines 451 - 458, Extract the
statistics DOM-update logic from consciousnessRenderer.js#L451-L458 into one
shared helper that safely skips missing elements, then have updateStats call it.
In docs/static/js/app_demo.js#L272-L280, replace the duplicated assignments with
the same helper and remove the duplicate update when this.renderer already
reports the statistics.
docs/static/js/app.js (2)

81-94: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Guard the button and canvas lookups.

setupSlider checks for missing elements, but these three lookups do not. If any of collapseBtn, resetBtn, or canvas is absent, addEventListener throws and the remaining handlers are never registered.

♻️ Proposed refactor
-        document.getElementById('collapseBtn').addEventListener('click', () => {
-            this.triggerQuantumCollapse();
-        });
-        
-        document.getElementById('resetBtn').addEventListener('click', () => {
-            this.resetSimulation();
-        });
-        
-        // Mouse events for node creation
-        document.getElementById('canvas').addEventListener('click', (e) => {
-            if (!this.isMouseDown) {
-                this.createNodeAtMouse(e);
-            }
-        });
+        const on = (id, handler) => {
+            const el = document.getElementById(id);
+            if (el) el.addEventListener('click', handler);
+        };
+
+        on('collapseBtn', () => this.triggerQuantumCollapse());
+        on('resetBtn', () => this.resetSimulation());
+
+        // Mouse events for node creation
+        on('canvas', (e) => {
+            if (!this.isMouseDown) {
+                this.createNodeAtMouse(e);
+            }
+        });
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/static/js/app.js` around lines 81 - 94, Update the event-listener
registrations in the initialization method containing setupSlider to guard the
lookups for collapseBtn, resetBtn, and canvas before calling addEventListener.
Register each handler only when its corresponding element exists, preserving the
current triggerQuantumCollapse, resetSimulation, and createNodeAtMouse behavior.

183-188: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Remove the empty conditional.

The if body contains only a comment. Remove the branch, or add a TODO that states what triggers the effect.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/static/js/app.js` around lines 183 - 188, Remove the empty
consciousness_magnitude conditional from updateGlobalStats, or replace it with a
meaningful TODO describing the specific effect trigger to implement; do not
retain an if block whose body only contains a comment.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@demo_server.py`:
- Around line 147-152: Restrict ConsciousnessHTTPHandler’s root URL mapping to
an approved static-content directory instead of Path(__file__).parent, and
ensure start_server uses that directory before binding publicly. In
host_public.py lines 35-39, create the public tunnel only after the server is
configured to serve the approved static content; update both sites accordingly.

In `@docs/demo.html`:
- Around line 10-24: Add the DOM elements expected by ConsciousnessApp in
docs/demo.html, including the `#canvas` fallback target, controls consumed by
setupSlider(), and status/stat elements updated during initialization. Prefer
matching the canonical page shell’s element IDs and structure so app_demo.js can
initialize without null references.

In `@docs/static/index.html`:
- Around line 29-44: Associate each label in the slider controls with its
corresponding range input by adding matching for attributes: gravityValue with
gravitySlider, frictionValue with frictionSlider, timeValue with timeSlider, and
fieldValue with fieldSlider.
- Line 7: Update the asset references in docs/static/index.html, including the
stylesheet and scripts around lines 80–82, to use Jekyll relative_url-generated
paths (or equivalent document-relative paths) instead of root-relative
/static/... URLs, preserving the existing asset targets while honoring the
configured baseurl.

In `@docs/static/js/app_demo.js`:
- Around line 294-309: Update createNodeAtMouse to use the active Three.js
renderer camera for screen-to-world conversion when the renderer exists,
following the raycasting approach used by app.js createNodeAtMouse. Preserve the
current center/scale fallback conversion for the 2D renderer path, and ensure
the resulting world coordinates reflect the camera’s perspective and zoom.
- Around line 209-222: Replace the setInterval-based polling in startPolling
with a self-scheduling async loop that awaits fetchAndUpdateState and
updateLattice before scheduling the next tick. Ensure only one iteration runs at
a time, continue polling while isRunning remains true, and preserve the initial
updateLattice call and pollDelay timing.

In `@docs/static/js/app.js`:
- Around line 123-153: Update the WebSocket lifecycle handlers in the connection
controller: reset reconnectDelay to its initial value alongside
reconnectAttempts in onopen, and add a disposed-state guard checked by onclose
before scheduling reconnects. Ensure dispose() marks the controller disposed
before closing the socket so its onclose handler cannot create another
connection.

In `@docs/static/js/consciousnessRenderer.js`:
- Around line 398-444: Update updateClusterConnections to dispose each existing
connection’s geometry and material before removing or dropping it, including
handling material arrays if applicable, so repeated state updates release GPU
resources. Preserve the current connection-building behavior; the requested fix
is lifecycle cleanup rather than changing cluster pair generation.
- Line 375: Update the cluster_ids assignment in the node rendering flow to use
a nullish check instead of truthiness, preserving valid cluster_id value 0 while
still defaulting missing or nullish IDs to -1.
- Around line 567-602: Update the animate/dispose lifecycle in the renderer
class to store the requestAnimationFrame handle, cancel it in dispose(), and
prevent further frame work after disposal. Also guard the FPS update’s
document.getElementById('fps') result before assigning textContent so the
animation loop remains safe when the element is absent.
- Around line 157-173: Update the node vertex shader’s main position calculation
to apply each InstancedMesh instanceMatrix when USE_INSTANCING is enabled, while
preserving the consciousness displacement and existing non-instanced behavior.
Ensure gl_Position uses the instance-transformed displaced position so separate
instances retain their individual transforms.

In `@host_public.py`:
- Around line 28-39: Replace the fixed time.sleep(3) in the startup flow before
ngrok.connect with verified readiness signaling from start_demo_server, or
bounded polling of the local /api/status endpoint. Proceed to create the tunnel
only after port 8000 responds with the expected status, and fail clearly on
startup errors or timeout.
- Line 15: Add pyngrok to requirements.txt as a runtime dependency so the
host_public.py import succeeds on clean installations.

---

Minor comments:
In `@demo_server.py`:
- Around line 153-160: Update the startup messages around the server launch to
derive interface and URL reporting from the configured host rather than always
claiming all-interface access. In the relevant startup code, only print the
network URL when the bind host accepts LAN connections; for loopback binding
such as 127.0.0.1, report the local URL without advertising an unreachable
network address.

In `@docs/static/css/style.css`:
- Around line 200-214: Update the mobile media-query rules for `.panel`,
`#info-panel`, and `#controls-panel` to override the 200px minimum width and
prevent overlap; when the viewport cannot fit both panels, stack them using the
existing layout mechanism while preserving the current mobile sizing and
positioning.

In `@docs/static/js/app_demo.js`:
- Around line 67-70: Update initFallbackRenderer to validate both the canvas
returned by document.getElementById('canvas') and the 2D context returned by
getContext('2d') before using them. Handle either missing value with the
existing fallback failure behavior, and only proceed with renderer setup when
both are available.
- Line 279: Update the FPS assignment in the fallback animation path so it runs
only when Three.js rendering is unavailable, and label the displayed value as
the poll rate rather than FPS. Ensure the Three.js path leaves `#fps` exclusively
to ConsciousnessFieldRenderer.animate.
- Around line 232-254: Update fetchAndUpdateState to call
updateConnectionStatus('connected') after a successful state poll, and ensure
non-ok responses are handled as disconnected as well as fetch errors. Keep the
existing state, renderer, and stats updates unchanged.

In `@docs/static/js/app.js`:
- Around line 67-78: Update both mode-button handlers in docs/static/js/app.js
lines 67-78 and docs/static/js/app_demo.js lines 165-176 to read mode and
visualization values from each listener’s currentTarget instead of target,
preserving the existing setInteractionMode and setVisualizationMode calls.
- Around line 34-43: Update init() to check that the Three.js global is
available before constructing ConsciousnessFieldRenderer. When THREE is missing,
log or display the existing application error status and return early; otherwise
preserve the current renderer initialization and subsequent setupEventHandlers
and connectWebSocket flow.
- Around line 211-228: Update createNodeAtMouse to verify the intersectPlane
return value before sending add_node, and abort when no intersection is found
instead of using the default origin. Reuse a shared renderer
screenToWorld(event) method, extracting the existing raycast logic currently
duplicated with updateMousePosition in consciousnessRenderer.js, and have both
callers use it.

In `@docs/static/js/consciousnessRenderer.js`:
- Around line 62-64: Update the canvas lookup in init() to validate that
document.getElementById('canvas') returned an element and immediately throw a
clear error when it is missing, before initializing the renderer or calling
setupEventListeners.
- Around line 503-522: Update updateMousePosition to reuse preallocated
THREE.Plane and THREE.Vector3 instances stored on the renderer, rather than
creating them per event. Check the return value of raycaster.ray.intersectPlane
and skip the onMouseInfluence callback when no intersection exists; preserve the
existing influence payload for successful intersections.
- Around line 109-110: Update the THREE.WebGLRenderer initialization in the
renderer setup to pass logarithmicDepthBuffer through the constructor options
instead of assigning renderer.capabilities.logarithmicDepthBuffer after
construction. Revise the adjacent comment to describe improved depth precision
rather than additive blending, and remove the ineffective post-construction
assignment.

---

Nitpick comments:
In `@docs/static/js/app.js`:
- Around line 81-94: Update the event-listener registrations in the
initialization method containing setupSlider to guard the lookups for
collapseBtn, resetBtn, and canvas before calling addEventListener. Register each
handler only when its corresponding element exists, preserving the current
triggerQuantumCollapse, resetSimulation, and createNodeAtMouse behavior.
- Around line 183-188: Remove the empty consciousness_magnitude conditional from
updateGlobalStats, or replace it with a meaningful TODO describing the specific
effect trigger to implement; do not retain an if block whose body only contains
a comment.

In `@docs/static/js/consciousnessRenderer.js`:
- Around line 292-300: Remove the unused positions Float32Array declaration from
the instance data setup, while preserving the remaining arrays and
setMatrixAt-based placement logic.
- Line 349: Rename the local variable bound to the amplitude attribute from
positions to amplitudes, and update all references including the assignment
around node.attention so the variable name accurately reflects its data.
- Around line 451-458: Extract the statistics DOM-update logic from
consciousnessRenderer.js#L451-L458 into one shared helper that safely skips
missing elements, then have updateStats call it. In
docs/static/js/app_demo.js#L272-L280, replace the duplicated assignments with
the same helper and remove the duplicate update when this.renderer already
reports the statistics.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: e8c2b8cc-90d2-4e05-a697-e52ece89f91a

📥 Commits

Reviewing files that changed from the base of the PR and between 6e77f05 and 1b8f867.

📒 Files selected for processing (10)
  • demo_server.py
  • docs/demo.html
  • docs/index.html
  • docs/index_legacy.html
  • docs/static/css/style.css
  • docs/static/index.html
  • docs/static/js/app.js
  • docs/static/js/app_demo.js
  • docs/static/js/consciousnessRenderer.js
  • host_public.py

Comment thread demo_server.py
Comment on lines +147 to +152
def start_server(port=8000, host="0.0.0.0"):
"""Start the consciousness simulation server."""
with socketserver.TCPServer(("", port), ConsciousnessHTTPHandler) as httpd:
print(f"🧠 CONSIM Demo Server starting on http://localhost:{port}")
local_ip = get_local_ip()

socketserver.TCPServer.allow_reuse_address = True
with socketserver.TCPServer((host, port), ConsciousnessHTTPHandler) as httpd:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🔴 Critical | 🏗️ Heavy lift

Restrict static serving before exposing the server.

ConsciousnessHTTPHandler at demo_server.py Line 22 serves Path(__file__).parent. An unauthenticated client can request project files such as /demo_server.py directly. Binding to 0.0.0.0 exposes them on the LAN, and the ngrok tunnel exposes the same handler on the Internet.

  • demo_server.py#L147-L152: Serve only an approved static-content directory. Update the root URL mapping to that directory.
  • host_public.py#L35-L39: Create the public tunnel only after the server serves approved static content.
🧰 Tools
🪛 Ruff (0.16.1)

[error] 147-147: Possible binding to all interfaces

(S104)

📍 Affects 2 files
  • demo_server.py#L147-L152 (this comment)
  • host_public.py#L35-L39
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@demo_server.py` around lines 147 - 152, Restrict ConsciousnessHTTPHandler’s
root URL mapping to an approved static-content directory instead of
Path(__file__).parent, and ensure start_server uses that directory before
binding publicly. In host_public.py lines 35-39, create the public tunnel only
after the server is configured to serve the approved static content; update both
sites accordingly.

Comment thread docs/demo.html
Comment on lines +10 to +24
<div id="canvas-container"></div>

<!-- Three.js from CDN -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>

<!-- CONSIM Modules -->
<script src="static/js/consciousnessRenderer.js"></script>
<script src="static/js/app_demo.js"></script>

<script>
// Initialize standalone demo
document.addEventListener('DOMContentLoaded', () => {
console.log('🧠 CONSIM - Multiversal Consciousness Framework (Standalone Demo)');
const app = new ConsciousnessApp();
});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

rg -n -C 3 --glob 'app_demo.js' \
  'getElementById|querySelector|canvas-container|`#canvas`|gravitySlider|collapseBtn|statusText' .

Repository: Jacobcdsmith/CONSIM

Length of output: 13240


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '--- docs/demo.html ---\n'
cat -n docs/demo.html

printf '\n--- docs/static/js/app_demo.js outline/constructor ---\n'
wc -l docs/static/js/app_demo.js
sed -n '1,170p' docs/static/js/app_demo.js | cat -n

Repository: Jacobcdsmith/CONSIM

Length of output: 248


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '--- docs/demo.html ---'
cat -n docs/demo.html

echo
echo '--- docs/static/js/app_demo.js outline/constructor ---'
wc -l docs/static/js/app_demo.js
sed -n '1,170p' docs/static/js/app_demo.js | cat -n

Repository: Jacobcdsmith/CONSIM

Length of output: 9071


Add the missing DOM elements required by ConsciousnessApp.

docs/demo.html only defines #canvas-container, but docs/static/js/app_demo.js reads #canvas for the fallback renderer, attaches a click listener to #canvas, calls setupSlider() on several controls, and updates status/stats elements during initialization. Either include the canonical page shell’s DOM or make ConsciousnessApp use #canvas-container and handle missing controls/status UI safely.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/demo.html` around lines 10 - 24, Add the DOM elements expected by
ConsciousnessApp in docs/demo.html, including the `#canvas` fallback target,
controls consumed by setupSlider(), and status/stat elements updated during
initialization. Prefer matching the canonical page shell’s element IDs and
structure so app_demo.js can initialize without null references.

Comment thread docs/static/index.html
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>CONSIM - Consciousness Lattice Visualization</title>
<link rel="stylesheet" href="/static/css/style.css">

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

fd -a -p '.github/workflows' . -t f -x sh -c '
  echo "=== $1 ==="
  rg -n -C 3 "pages|docs|baseurl|base_path|deploy" "$1" || true
' sh {}

Repository: Jacobcdsmith/CONSIM

Length of output: 157


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== files ==="
git ls-files | rg '(^docs/static/index\.html$|docs/|\.github/workflows/)' || true

echo "=== docs/static/index.html relevant ==="
if [ -f docs/static/index.html ]; then
  wc -l docs/static/index.html
  sed -n '1,130p' docs/static/index.html | cat -n
fi

echo "=== repository marker files ==="
git ls-files | rg '(^CNAME$|(^docs/index\.html$|(^docs/.*\.html$)|(^docs/.*\.js$)|(^docs/.*\.css$)|(^Jekyll|(^Gemfile|(^_config\.yml|(^package\.json$))' || true

echo "=== all static references in docs ==="
rg -n 'href="/static|src="/static|href="./[^/]|src="./[^/]|href="\.\./|src="\.\./' docs || true

Repository: Jacobcdsmith/CONSIM

Length of output: 5333


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== .github/workflows/static.yml ==="
sed -n '1,220p' .github/workflows/static.yml | cat -n

echo "=== repo marker/config files ==="
git ls-files | rg '(^CNAME$|^docs/index\.html$|^docs/[^/]*\.html$|^docs/.*\.js$|^docs/.*\.css$|^Jekyll$|^Gemfile$|^_config\.yml$|^package\.json$)' || true

Repository: Jacobcdsmith/CONSIM

Length of output: 1959


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== _config.yml ==="
sed -n '1,120p' _config.yml | cat -n

echo "=== docs root files ==="
git ls-files docs | head -80

Repository: Jacobcdsmith/CONSIM

Length of output: 994


Do not use /static/... links in the Jekyll docs.

_config.yml sets baseurl: "/CONSIM", but docs/static/index.html uses root-relative /static/... for the stylesheet and scripts, so those assets are requested from the domain root instead of https://jacobcdsmith.github.io/CONSIM/static/....

Replace them with Jekyll {{ '/static/css/style.css' | relative_url }} links, or use paths relative to docs/static/index.html.

Also applies to lines 80-82.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/static/index.html` at line 7, Update the asset references in
docs/static/index.html, including the stylesheet and scripts around lines 80–82,
to use Jekyll relative_url-generated paths (or equivalent document-relative
paths) instead of root-relative /static/... URLs, preserving the existing asset
targets while honoring the configured baseurl.

Comment thread docs/static/index.html
Comment on lines +29 to +44
<div class="slider-container">
<label>Gravity: <span id="gravityValue">1.0</span></label>
<input type="range" id="gravitySlider" min="0" max="2" step="0.1" value="1.0">
</div>
<div class="slider-container">
<label>Friction: <span id="frictionValue">0.99</span></label>
<input type="range" id="frictionSlider" min="0.9" max="1" step="0.01" value="0.99">
</div>
<div class="slider-container">
<label>Time Dilation: <span id="timeValue">1.0</span></label>
<input type="range" id="timeSlider" min="0.1" max="2" step="0.1" value="1.0">
</div>
<div class="slider-container">
<label>Field Strength: <span id="fieldValue">1.0</span></label>
<input type="range" id="fieldSlider" min="0.2" max="3" step="0.1" value="1.0">
</div>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Associate each range input with its label.

The labels do not use for, and the inputs are not nested in the labels. Assistive technology can expose these controls without their purpose.

Set each label for value to its corresponding input ID.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/static/index.html` around lines 29 - 44, Associate each label in the
slider controls with its corresponding range input by adding matching for
attributes: gravityValue with gravitySlider, frictionValue with frictionSlider,
timeValue with timeSlider, and fieldValue with fieldSlider.

Comment on lines +209 to +222
async startPolling() {
this.isRunning = true;

// First update the lattice
await this.updateLattice();

// Start polling loop
this.pollInterval = setInterval(async () => {
if (this.isRunning) {
await this.fetchAndUpdateState();
await this.updateLattice();
}
}, this.pollDelay);
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Replace the interval with a self-scheduling loop.

setInterval does not wait for the async callback to settle. Each tick awaits two network round trips. If the server needs more than 100 ms, ticks overlap and requests queue without bound. POST /api/update advances the simulation, so overlapping ticks step the simulation more than once per interval and produce inconsistent state.

Each tick also issues two requests, so one browser tab generates about 20 requests per second.

🔧 Proposed fix
     async startPolling() {
         this.isRunning = true;
-        
-        // First update the lattice
-        await this.updateLattice();
-        
-        // Start polling loop
-        this.pollInterval = setInterval(async () => {
-            if (this.isRunning) {
-                await this.fetchAndUpdateState();
-                await this.updateLattice();
-            }
-        }, this.pollDelay);
+
+        const loop = async () => {
+            if (!this.isRunning) return;
+            await this.updateLattice();
+            await this.fetchAndUpdateState();
+            if (!this.isRunning) return;
+            this.pollTimeout = setTimeout(loop, this.pollDelay);
+        };
+
+        loop();
     }
 
     stopPolling() {
         this.isRunning = false;
-        if (this.pollInterval) {
-            clearInterval(this.pollInterval);
-            this.pollInterval = null;
-        }
+        if (this.pollTimeout) {
+            clearTimeout(this.pollTimeout);
+            this.pollTimeout = null;
+        }
     }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/static/js/app_demo.js` around lines 209 - 222, Replace the
setInterval-based polling in startPolling with a self-scheduling async loop that
awaits fetchAndUpdateState and updateLattice before scheduling the next tick.
Ensure only one iteration runs at a time, continue polling while isRunning
remains true, and preserve the initial updateLattice call and pollDelay timing.

consciousness_res.array[i] = node.consciousness_re || 0;
consciousness_ims.array[i] = node.consciousness_im || 0;
intelligence_depths.array[i] = node.consciousness_depth || 0;
cluster_ids.array[i] = node.cluster_id || -1;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Cluster id 0 is converted to -1.

node.cluster_id || -1 treats the valid id 0 as falsy. The fragment shader highlights nodes only when vCluster_id >= 0.0 (Line 208), so every node in cluster 0 loses its highlight. Use a nullish check.

🐛 Proposed fix
-            cluster_ids.array[i] = node.cluster_id || -1;
+            cluster_ids.array[i] = node.cluster_id ?? -1;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
cluster_ids.array[i] = node.cluster_id || -1;
cluster_ids.array[i] = node.cluster_id ?? -1;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/static/js/consciousnessRenderer.js` at line 375, Update the cluster_ids
assignment in the node rendering flow to use a nullish check instead of
truthiness, preserving valid cluster_id value 0 while still defaulting missing
or nullish IDs to -1.

Comment on lines +398 to +444
updateClusterConnections(clusters) {
// Remove old connections
this.clusterConnections.forEach(connection => {
this.scene.remove(connection);
});
this.clusterConnections = [];

// Add new connections
clusters.forEach(cluster => {
if (!cluster.nodes || cluster.nodes.length < 2) return;

const geometry = new THREE.BufferGeometry();
const positions = [];
const colors = [];

// Create connections between all nodes in cluster
for (let i = 0; i < cluster.nodes.length; i++) {
for (let j = i + 1; j < cluster.nodes.length; j++) {
const nodeA = cluster.nodes[i];
const nodeB = cluster.nodes[j];

positions.push(nodeA.x, nodeA.y, 0);
positions.push(nodeB.x, nodeB.y, 0);

// Color based on cluster ID
const hue = (cluster.id * 30) % 360 / 360;
const color = new THREE.Color().setHSL(hue, 1.0, 0.6);
colors.push(color.r, color.g, color.b);
colors.push(color.r, color.g, color.b);
}
}

geometry.setAttribute('position', new THREE.Float32BufferAttribute(positions, 3));
geometry.setAttribute('color', new THREE.Float32BufferAttribute(colors, 3));

const material = new THREE.LineBasicMaterial({
vertexColors: true,
transparent: true,
opacity: 0.6,
blending: THREE.AdditiveBlending
});

const connections = new THREE.LineSegments(geometry, material);
this.scene.add(connections);
this.clusterConnections.push(connections);
});
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚀 Performance & Scalability | 🟠 Major | ⚡ Quick win

Dispose cluster geometries and materials before you drop them.

This method runs on every state update. app_demo.js polls every 100 ms, so it builds new BufferGeometry and LineBasicMaterial objects about ten times per second. scene.remove() does not free GPU buffers, so WebGL memory grows without bound during a session.

Also note the inner loop creates every node pair, so a cluster of n nodes produces n*(n-1)/2 line segments. Consider capping the cluster size or reusing one geometry with a preallocated buffer.

🔧 Proposed fix
         // Remove old connections
         this.clusterConnections.forEach(connection => {
             this.scene.remove(connection);
+            connection.geometry.dispose();
+            connection.material.dispose();
         });
         this.clusterConnections = [];
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
updateClusterConnections(clusters) {
// Remove old connections
this.clusterConnections.forEach(connection => {
this.scene.remove(connection);
});
this.clusterConnections = [];
// Add new connections
clusters.forEach(cluster => {
if (!cluster.nodes || cluster.nodes.length < 2) return;
const geometry = new THREE.BufferGeometry();
const positions = [];
const colors = [];
// Create connections between all nodes in cluster
for (let i = 0; i < cluster.nodes.length; i++) {
for (let j = i + 1; j < cluster.nodes.length; j++) {
const nodeA = cluster.nodes[i];
const nodeB = cluster.nodes[j];
positions.push(nodeA.x, nodeA.y, 0);
positions.push(nodeB.x, nodeB.y, 0);
// Color based on cluster ID
const hue = (cluster.id * 30) % 360 / 360;
const color = new THREE.Color().setHSL(hue, 1.0, 0.6);
colors.push(color.r, color.g, color.b);
colors.push(color.r, color.g, color.b);
}
}
geometry.setAttribute('position', new THREE.Float32BufferAttribute(positions, 3));
geometry.setAttribute('color', new THREE.Float32BufferAttribute(colors, 3));
const material = new THREE.LineBasicMaterial({
vertexColors: true,
transparent: true,
opacity: 0.6,
blending: THREE.AdditiveBlending
});
const connections = new THREE.LineSegments(geometry, material);
this.scene.add(connections);
this.clusterConnections.push(connections);
});
}
updateClusterConnections(clusters) {
// Remove old connections
this.clusterConnections.forEach(connection => {
this.scene.remove(connection);
connection.geometry.dispose();
connection.material.dispose();
});
this.clusterConnections = [];
// Add new connections
clusters.forEach(cluster => {
if (!cluster.nodes || cluster.nodes.length < 2) return;
const geometry = new THREE.BufferGeometry();
const positions = [];
const colors = [];
// Create connections between all nodes in cluster
for (let i = 0; i < cluster.nodes.length; i++) {
for (let j = i + 1; j < cluster.nodes.length; j++) {
const nodeA = cluster.nodes[i];
const nodeB = cluster.nodes[j];
positions.push(nodeA.x, nodeA.y, 0);
positions.push(nodeB.x, nodeB.y, 0);
// Color based on cluster ID
const hue = (cluster.id * 30) % 360 / 360;
const color = new THREE.Color().setHSL(hue, 1.0, 0.6);
colors.push(color.r, color.g, color.b);
colors.push(color.r, color.g, color.b);
}
}
geometry.setAttribute('position', new THREE.Float32BufferAttribute(positions, 3));
geometry.setAttribute('color', new THREE.Float32BufferAttribute(colors, 3));
const material = new THREE.LineBasicMaterial({
vertexColors: true,
transparent: true,
opacity: 0.6,
blending: THREE.AdditiveBlending
});
const connections = new THREE.LineSegments(geometry, material);
this.scene.add(connections);
this.clusterConnections.push(connections);
});
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/static/js/consciousnessRenderer.js` around lines 398 - 444, Update
updateClusterConnections to dispose each existing connection’s geometry and
material before removing or dropping it, including handling material arrays if
applicable, so repeated state updates release GPU resources. Preserve the
current connection-building behavior; the requested fix is lifecycle cleanup
rather than changing cluster pair generation.

Comment on lines +567 to +602
animate() {
requestAnimationFrame(() => this.animate());

const time = Date.now() * 0.001;

// Update shader uniforms
this.nodeMaterial.uniforms.time.value = time;
if (this.attentionFieldMaterial) {
this.attentionFieldMaterial.uniforms.time.value = time;
}
if (this.universeMaterial) {
this.universeMaterial.uniforms.time.value = time;
}

// Update FPS counter
this.frameCount++;
if (Date.now() - this.lastFPSUpdate > 1000) {
this.fps = this.frameCount;
this.frameCount = 0;
this.lastFPSUpdate = Date.now();
document.getElementById('fps').textContent = this.fps;
}

// Render scene
this.renderer.clear();
this.renderer.render(this.scene, this.camera);
}

dispose() {
// Clean up resources
this.renderer.dispose();
this.nodeGeometry.dispose();
this.nodeMaterial.dispose();
if (this.attentionFieldMaterial) this.attentionFieldMaterial.dispose();
if (this.universeMaterial) this.universeMaterial.dispose();
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Stop the animation loop in dispose().

animate always schedules the next frame. dispose() releases the renderer, the geometry, and the materials, but the pending frame still runs this.nodeMaterial.uniforms.time and this.renderer.clear(). That produces an error on every frame after disposal. Track the frame handle and cancel it.

Line 587 also calls document.getElementById('fps') without a null check. If the page omits that element, the loop throws once per second.

🔧 Proposed fix
     animate() {
-        requestAnimationFrame(() => this.animate());
+        if (this.isDisposed) return;
+        this.animationFrameId = requestAnimationFrame(() => this.animate());
@@
         if (Date.now() - this.lastFPSUpdate > 1000) {
             this.fps = this.frameCount;
             this.frameCount = 0;
             this.lastFPSUpdate = Date.now();
-            document.getElementById('fps').textContent = this.fps;
+            const fpsEl = document.getElementById('fps');
+            if (fpsEl) fpsEl.textContent = this.fps;
         }
@@
     dispose() {
+        this.isDisposed = true;
+        if (this.animationFrameId !== undefined) {
+            cancelAnimationFrame(this.animationFrameId);
+            this.animationFrameId = undefined;
+        }
         // Clean up resources
         this.renderer.dispose();
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
animate() {
requestAnimationFrame(() => this.animate());
const time = Date.now() * 0.001;
// Update shader uniforms
this.nodeMaterial.uniforms.time.value = time;
if (this.attentionFieldMaterial) {
this.attentionFieldMaterial.uniforms.time.value = time;
}
if (this.universeMaterial) {
this.universeMaterial.uniforms.time.value = time;
}
// Update FPS counter
this.frameCount++;
if (Date.now() - this.lastFPSUpdate > 1000) {
this.fps = this.frameCount;
this.frameCount = 0;
this.lastFPSUpdate = Date.now();
document.getElementById('fps').textContent = this.fps;
}
// Render scene
this.renderer.clear();
this.renderer.render(this.scene, this.camera);
}
dispose() {
// Clean up resources
this.renderer.dispose();
this.nodeGeometry.dispose();
this.nodeMaterial.dispose();
if (this.attentionFieldMaterial) this.attentionFieldMaterial.dispose();
if (this.universeMaterial) this.universeMaterial.dispose();
}
animate() {
if (this.isDisposed) return;
this.animationFrameId = requestAnimationFrame(() => this.animate());
const time = Date.now() * 0.001;
// Update shader uniforms
this.nodeMaterial.uniforms.time.value = time;
if (this.attentionFieldMaterial) {
this.attentionFieldMaterial.uniforms.time.value = time;
}
if (this.universeMaterial) {
this.universeMaterial.uniforms.time.value = time;
}
// Update FPS counter
this.frameCount++;
if (Date.now() - this.lastFPSUpdate > 1000) {
this.fps = this.frameCount;
this.frameCount = 0;
this.lastFPSUpdate = Date.now();
const fpsEl = document.getElementById('fps');
if (fpsEl) fpsEl.textContent = this.fps;
}
// Render scene
this.renderer.clear();
this.renderer.render(this.scene, this.camera);
}
dispose() {
this.isDisposed = true;
if (this.animationFrameId !== undefined) {
cancelAnimationFrame(this.animationFrameId);
this.animationFrameId = undefined;
}
// Clean up resources
this.renderer.dispose();
this.nodeGeometry.dispose();
this.nodeMaterial.dispose();
if (this.attentionFieldMaterial) this.attentionFieldMaterial.dispose();
if (this.universeMaterial) this.universeMaterial.dispose();
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/static/js/consciousnessRenderer.js` around lines 567 - 602, Update the
animate/dispose lifecycle in the renderer class to store the
requestAnimationFrame handle, cancel it in dispose(), and prevent further frame
work after disposal. Also guard the FPS update’s document.getElementById('fps')
result before assigning textContent so the animation loop remains safe when the
element is absent.

Comment thread host_public.py
# Add src to path
sys.path.insert(0, str(Path(__file__).parent / "src"))

from pyngrok import ngrok

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

fd -a '^(requirements.*\.txt|pyproject\.toml|setup\.py|Pipfile)$' . -x sh -c '
  echo "=== $1 ==="
  rg -n -i "(pyngrok|install_requires|dependencies|requires)" "$1" || true
' sh {}

Repository: Jacobcdsmith/CONSIM

Length of output: 201


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== tracked manifest files ==="
git ls-files | grep -Ei '(^|/)(requirements.*\.txt|pyproject\.toml|setup\.py|setup\.cfg|Pipfile|poetry\.lock|Pipfile\.lock)$' || true

echo "=== pyngrok imports/usages ==="
rg -n --hidden --glob '!*.pyc' --glob '!__pycache__/**' 'pyngrok|ngrok|ngrok_api|authtoken|tunnel' . || true

echo "=== host_public.py relevant lines ==="
fd -a '^host_public\.py$' . -x sh -c 'echo "=== $1 ==="; sed -n "1,120p" "$1"' sh {}

echo "=== requirements search case-insensitive all files ==="
rg -n -i 'pyngrok|dependencies|install_requires|requires' requirements.txt setup.py pyproject.toml Pipfile 2>/dev/null || true

Repository: Jacobcdsmith/CONSIM

Length of output: 4561


Declare pyngrok as a runtime dependency.

host_public.py imports pyngrok, but requirements.txt does not list it. A clean install can fail with ModuleNotFoundError before the launcher starts.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@host_public.py` at line 15, Add pyngrok to requirements.txt as a runtime
dependency so the host_public.py import succeeds on clean installations.

Comment thread host_public.py
Comment on lines +28 to +39
server_thread = threading.Thread(target=start_demo_server, daemon=True)
server_thread.start()

# Wait for server to start
print("⏳ Starting local server...")
time.sleep(3)

# Create ngrok tunnel
print("🌐 Creating public internet tunnel...")
try:
# Open a ngrok tunnel to port 8000
public_url = ngrok.connect(8000, bind_tls=True)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Wait for verified server readiness.

time.sleep(3) does not show that start_demo_server bound port 8000. If startup fails, ngrok.connect can publish a dead target. If another process owns port 8000, it can publish the wrong service.

Signal readiness from start_server, or poll /api/status with a timeout and validate the expected response before creating the tunnel.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@host_public.py` around lines 28 - 39, Replace the fixed time.sleep(3) in the
startup flow before ngrok.connect with verified readiness signaling from
start_demo_server, or bounded polling of the local /api/status endpoint. Proceed
to create the tunnel only after port 8000 responds with the expected status, and
fail clearly on startup errors or timeout.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants