Blog

  • Self-Hosted Colyseus WebGL Game Server On Raspberry Pi Zero W

    Self-Hosted Colyseus WebGL Game Server On Raspberry Pi Zero W


    Introduction

    Building game servers is fun for many developers. You can host your games on small hardware.

    Small Scale Hosting Benefits

    The Raspberry Pi Zero W is very affordable. It is a great choice for local hosting.

    Colyseus is a powerful server engine for games. It uses Node.js to manage multiple players.

    Open Source Freedom and Licensing

    The engine uses the highly permissive MIT license. This open source license matters for your freedom.

    You can modify the code for any project. There are no fees to use this software.

    Open source tools help you learn very quickly. You can read the source code to understand logic.

    Development Environment Setup

    Fedora Linux provides a stable base for development. You can manage your Pi server from Fedora.

    Fedora includes modern tools to handle remote servers. You use SSH to connect to the Raspberry Pi.

    Installing Node.js and Software

    Start by installing Node.js 22 on your Pi. Use the terminal to update your system software.

    Node.js allows the server to handle many connections. It uses an event loop to stay fast.

    The Pi Zero W has limited memory resources. Colyseus runs efficiently on this small device.

    Resource Management and Memory

    You only have 512MB of RAM available here. This forces you to write very clean code.

    Networking is handled through the WebSocket protocol. This allows for fast real-time player updates.

    WebSockets keep a constant connection between players. This reduces the lag during your game play.

    Data Storage and Persistence

    You should use SQLite 3 for your database. It is lightweight and perfect for player stats.

    SQLite saves data into a single local file. This is much faster than running heavy databases.

    Server Logic and Authority

    The server handles game logic for all players. This prevents cheating in your multiplayer WebGL games.

    An authoritative server makes all the final decisions. It calculates the movements and scores for everyone.

    Process Management and Stability

    PM2 helps keep your server running constantly. It will restart the service if errors occur.

    This process manager ensures high availability for players. Your server stays online even after a crash.

    Connect your Fedora machine to the same network. You can test your game server using browsers.

    Testing on a local network is very fast. It helps you find bugs before going live.

    Conclusion and Skill Building

    The setup is perfect for small indie games. It teaches you about backend logic and networking.

    You learn how to handle data synchronization today. These skills are valuable for any software job.

    Always monitor the CPU usage on your Pi. Keep your code clean to save system power.

    Small optimizations make a big difference on Pi. Your players will enjoy a much smoother experience.

    📷 Screenshots

    Colyseus Install
    Command Line Colyseus Installation

    Create Room
    Web Browser Displaying Colyseus Create Room Option

    Join Room
    Web Browser Displaying Colyseus Join Room Option

    Send Message
    Web Browser Displaying Colyseus Send Message Option

    Received Message
    Command Line Displaying Colyseus Recevied Message Results

    🎬 Live YouTube Screencast

    Video Displaying The Installation Colyseus Game Server For Raspberry Pi

    Take Your Skills Further

  • AI Image Generation With Flux.2 Klein On AMD Instinct MI60

    AI Image Generation With Flux.2 Klein On AMD Instinct MI60


    Introduction

    AI image generation is changing fast for Linux users. You can now run Flux.2 Klein locally today.

    High Performance Hardware For AI

    The AMD Instinct MI60 is a powerful server GPU. It features 32GB of high speed HBM2 memory.

    Fedora 43 provides the best environment for this task. It includes the latest ROCm 6.4 software stack.

    Modern Transformer Models

    Flux.2 Klein is a very fast transformer model. It unifies image generation and editing in one tool.

    The model fits perfectly into the MI60 memory. Using GGUF quantization helps maintain high output quality.

    Open Source Licensing Benefits

    The stable-diffusion.cpp application uses the MIT license. This open source license allows for total freedom.

    You can modify and distribute the software easily. There are no hidden fees for commercial usage.

    Open source code ensures your privacy remains safe. You can verify how your data is processed locally.

    Compiling On Fedora 43 With ROCm

    Compiling for AMD GPUs is a straightforward process. First you must install the hipblas-devel system packages.

    Use the dnf command to install development tools. You also need the git and cmake packages.

    Building From Source

    Clone the stable-diffusion.cpp repository from the web. Create a new build directory inside the folder.

    Run the cmake command with the HIP option. Set the SD_HIPBLAS flag to the ON position.

    Target the gfx906 architecture for the MI60 GPU. This matches the specific hardware version of your card.

    Start the compilation process using the make command. Use multiple processor cores to speed up the build.

    Performance And Hardware Optimization

    The MI60 lacks modern matrix cores for AI. However the 1TB per second bandwidth is amazing.

    This bandwidth allows Flux.2 Klein to generate images quickly. You can create high resolution art in seconds.

    The C++ code runs much faster than Python. It removes heavy dependencies for a lightweight local setup.

    Always monitor your hardware temperatures during heavy generation. The MI60 requires active cooling for long sessions.

    Licensing and Commercial Use

    The FLUX.2 [klein] 4B model is released under the Apache 2.0 License, which is a significant departure from the more restrictive “Non-Commercial” licenses found on the larger 9B and 32B models. This permissive license means you are legally free to use the 4B model for commercial purposes, including building paid applications, generating marketing assets for clients, or integrating the model into a business workflow without paying royalties. You are also encouraged to modify the weights, train custom LoRAs, or redistribute the model, provided you include the original license and copyright notice.

    Tips for Best Results

    To get the most out of the Klein 4B architecture, shift away from “tag-based” prompting and adopt a prose-style approach. Describe your scene as if you are writing a brief passage in a novel; the model is highly sensitive to word order, so place your primary subject at the beginning. Additionally, FLUX.2 [klein] excels at following hex color codes and rendering exact text placed within single quotes.

    On the technical side, aim for 4 to 6 inference steps for the Distilled version. Increasing the step count beyond 10 often degrades quality. Keep your Guidance Scale (CFG) low—around 1.0 for Distilled and 3.5 to 5.0 for the Base model. Finally, ensure you are using the specific Klein VAE and the Qwen3-4B text encoder to avoid tensor shape mismatch errors during loading.

    Screenshot

    Flux.2-klein-4B 4 Steps
    4 Steps Flux.2-klein-4B Results From stable-diffusion.cpp

    Flux.2-klein-4B 20 Steps
    20 Steps Flux.2-klein-4B Results From stable-diffusion.cpp

    Flux.2-klein-4B Hold Sign
    Sign Holding Flux.2-klein-4B Results From stable-diffusion.cpp

    Live Screencast

    Screencast Of AI Generated Images Using Flux.2-klein-4B And stable-diffusion.cpp On AMD GPU

    Addedum Fixing AI Black Images Using Flex.2-Klein And stable-diffusion.cpp

    Take Your Skills Further

  • Optimizing Megaminx Geometry with Threejs and Qwen3

    Optimizing Megaminx Geometry with Threejs and Qwen3

    Introduction to 3D Geometry Optimization

    Generating a 3D Megaminx requires precise geometric math. Last time the AI model hallucinated some code.

    The first output used four sides for pentagons. This caused the 3D model to look broken.

    Fixing the Mathematical Foundation

    We must fix the vertex logic for accuracy. A dodecahedron uses specific golden ratio coordinates.

    The new code uses the Threejs CircleGeometry class. Setting the segments to five creates perfect pentagons.

    Using Normal Vectors for Face Placement

    We now use face normals for correct placement. Normals are vectors pointing away from the center.

    The lookAt function rotates faces toward these vectors. This eliminates hundreds of lines of manual coordinates.

    Deep Dive into Mathematical Concepts

    A Megaminx has much more complexity than a cube. It features twelve faces instead of the usual six.

    Each face is a pentagon with five equal sides. The internal angles are exactly one hundred eight degrees.

    The code defines a constant called PHI first. This represents the golden ratio of 1.618 approx.

    This number determines the positions of the vertices. It ensures all twelve faces meet at perfect edges.

    Looping Through the Dodecahedron Faces

    The loop iterates through twelve unique normal vectors. Each vector represents the center of a specific face.

    The normalize function sets the vector length to one. This allows us to scale the puzzle size easily.

    We use multiplyScalar to move faces from the center. This creates the physical distance needed for the shape.

    The lookAt method is the most important part here. It aligns the flat pentagon to the radial vector.

    Without lookAt every face would point the same way. The puzzle would look like a stack of pancakes.

    Open Source Licensing and AI Tools

    The Apache 2.0 license protects your creative work. It allows commercial use and protects against patent claims.

    This permissive license is ideal for open source projects. You can share your 3D puzzle code without fear.

    Qwen3 provides open weights for your local development. The model helps you debug complex 3D math problems.

    Using AI as a partner speeds up your workflow. It suggests efficient ways to handle 3D vector math.

    Conclusion and Final Results

    The final script is much shorter and faster. It runs smoothly on your local Fedora system.

    Optimizing AI code is a vital developer skill.
    Always verify the math when using generated scripts.

    
    
    
    const scene = new THREE.Scene();
    const camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 100);
    camera.position.set(8, 8, 8);
    
    const renderer = new THREE.WebGLRenderer({ antialias: true });
    renderer.setSize(window.innerWidth, window.innerHeight);
    document.body.appendChild(renderer.domElement);
    
    const controls = new THREE.OrbitControls(camera, renderer.domElement);
    scene.add(new THREE.AmbientLight(0xffffff, 1));
    
    // Create Geometry & Vertex Colors
    const geo = new THREE.DodecahedronGeometry(4, 0);
    const colors = [0xffffff, 0x0046ad, 0xb71234, 0x009b48, 0xffd500, 0xff5800, 0x800080, 0x00ff00, 0x00ffff, 0xff00ff, 0x808080, 0x4B2C20];
    const colAttr = [];
    colors.forEach(color => {
        const c = new THREE.Color(color);
        for (let j = 0; j < 9; j++) colAttr.push(c.r, c.g, c.b);
    });
    geo.setAttribute('color', new THREE.Float32BufferAttribute(colAttr, 3));
    
    const mesh = new THREE.Mesh(geo, new THREE.MeshPhongMaterial({ vertexColors: true, flatShading: true }));
    const wire = new THREE.LineSegments(new THREE.EdgesGeometry(geo), new THREE.LineBasicMaterial({ color: 0x000000 }));
    
    const puzzle = new THREE.Group();
    puzzle.add(mesh, wire);
    scene.add(puzzle);
    
    

    Consolidated Demo

    HTML5 Optimized Megaminx Cube

    Screenshot

    Original vs Optimized
    Web Browser Showing Original And Optimized Megaminx Cube

    Optimized Megaminx Cube
    Web Browser Showing Optimized Megaminx Cube Results

    Live Screencast

    Screencast Of Optimized Megaminx Cube Code

    Take Your Skills Further

  • Generating a Megaminx with Qwen3 and HTML5

    Generating a Megaminx with Qwen3 and HTML5

    Introduction to Megaminx Generation

    Creating a Megaminx with HTML5 code is fun. We will use Qwen3 on Fedora Linux today.

    The Megaminx is a puzzle with twelve sides. It looks like a complex 3d dodecahedron shape.

    Understanding the Megaminx Puzzle

    This puzzle has fifty different movable plastic pieces. A standard 3×3 cube only has twenty pieces.

    Each face is a pentagon with five edges. You solve it using logic similar to cubes.

    The geometry requires precise vertex math for rendering. Modern web browsers handle these 3d shapes well.

    The Qwen3 Model and Open Source Benefits

    Alibaba released the Qwen3 model as open weights. It uses the flexible Apache 2.0 software license.

    This license allows for free commercial software use. Developers can modify the model for their projects.

    Open source models promote transparency for all users. You can run this AI privately on Linux.

    Users do not need to pay monthly fees. The community improves the model through shared feedback.

    Model Specifications and Quantization

    The Qwen3-30B model has thirty billion total parameters. It is a large model requiring significant video memory.

    The A3B label means three billion active parameters. This Mixture of Experts architecture keeps inference fast.

    The UD stands for the Unsloth Dynamic format. This optimizes the model for better local performance.

    The Q5_K_XL suffix describes the high quantization level. It uses five bits to represent the model weights.

    The XL version maintains very high output accuracy. It balances file size with professional code generation.

    Setting Up llama.cpp on Fedora Linux

    You have several ways to install llama.cpp here. The easiest way is using the dnf manager.

    Many Fedora users prefer building from the source. Clone the official repository from GitHub to begin.

    You can also use Docker for quick setup. This keeps your system clean and very organized.

    Building from source allows for specific hardware optimizations. Ensure you have the latest GCC compiler installed.

    Running the Local AI Server

    Open your Fedora terminal to start the server. Run the specific command to load the model.

    llama-server -m /mnt/AI/models/chat_models/Qwen3-30B-A3B-Instruct-2507-UD-Q5_K_XL.gguf --jinja -c 24576 --port 8081 -ngl 999

    This command starts the local web server backend. It allows for high speed code generation tasks.

    Explaining the Server Flags

    The -m flag points to your model file. We use the UD-Q5_K_XL version for high quality.

    The –jinja flag enables the chat template logic. This helps the model understand complex user prompts.

    The -c option sets the context window size. We use 24576 to handle large code blocks.

    The –port 8081 flag sets the network address. You can access the interface via your browser.

    The -ngl 999 flag enables full GPU acceleration. This moves all model layers to your VRAM.

    Generating the Digital Puzzle

    The model generates the HTML5 and CSS code. It uses math to create twelve pentagon faces.

    The output shows a colorful digital puzzle. Copy the code into a new index file.

    
    
    
    <style>
        body {
            margin: 0;
            overflow: hidden;
            background-color: #f0f0f0;
            font-family: Arial, sans-serif;
        }
        #container {
            position: absolute;
            width: 100%;
            height: 100%;
        }
        #controls {
            position: absolute;
            top: 20px;
            left: 20px;
            background-color: rgba(255, 255, 255, 0.8);
            padding: 15px;
            border-radius: 8px;
            box-shadow: 0 0 10px rgba(0, 0, 0, 0.2);
            z-index: 100;
        }
        button {
            margin: 5px;
            padding: 8px 12px;
            font-size: 14px;
            background-color: #4CAF50;
            color: white;
            border: none;
            border-radius: 4px;
            cursor: pointer;
            transition: background-color 0.3s;
        }
        button:hover {
            background-color: #45a049;
        }
        button:disabled {
            background-color: #cccccc;
            cursor: not-allowed;
        }
        #scramble {
            background-color: #2196F3;
        }
        #scramble:hover {
            background-color: #1976D2;
        }
        #reset {
            background-color: #f44336;
        }
        #reset:hover {
            background-color: #d32f2f;
        }
        #info {
            position: absolute;
            bottom: 20px;
            left: 20px;
            background-color: rgba(255, 255, 255, 0.8);
            padding: 10px;
            border-radius: 8px;
            box-shadow: 0 0 10px rgba(0, 0, 0, 0.2);
            max-width: 300px;
        }
        .face-label {
            font-size: 12px;
            font-weight: bold;
            margin-bottom: 2px;
        }
        .face-color {
            display: inline-block;
            width: 15px;
            height: 15px;
            border: 1px solid #333;
            margin-right: 5px;
        }
    </style>
    <div id="container"></div>
    
    <div id="controls">
        <button id="scramble">Scramble</button>
        <button id="reset">Reset</button>
        <div>
            <div class="face-label">Rotation:</div>
            <button id="rotate-left">←</button>
            <button id="rotate-right">→</button>
        </div>
    </div>
    
    <div id="info">
        <h3>3D Megaminx Puzzle</h3>
        <p>Click and drag to rotate the entire puzzle.</p>
        <p>Use the buttons to rotate individual faces.</p>
        <p>Scramble the puzzle or reset it to solve.</p>
        <div>
            <div class="face-label">Face Colors:</div>
            <div><span class="face-color" style="background-color: #FF0000;"></span> Red</div>
            <div><span class="face-color" style="background-color: #00FF00;"></span> Green</div>
            <div><span class="face-color" style="background-color: #0000FF;"></span> Blue</div>
            <div><span class="face-color" style="background-color: #FFFF00;"></span> Yellow</div>
            <div><span class="face-color" style="background-color: #FFA500;"></span> Orange</div>
            <div><span class="face-color" style="background-color: #FFC0CB;"></span> Pink</div>
            <div><span class="face-color" style="background-color: #800080;"></span> Purple</div>
            <div><span class="face-color" style="background-color: #008000;"></span> Lime</div>
            <div><span class="face-color" style="background-color: #000080;"></span> Navy</div>
            <div><span class="face-color" style="background-color: #808080;"></span> Gray</div>
            <div><span class="face-color" style="background-color: #FF00FF;"></span> Magenta</div>
            <div><span class="face-color" style="background-color: #00FFFF;"></span> Cyan</div>
        </div>
    </div>
    
    <!-- Load Three.js from CDN -->
    <script src="https://cdn.jsdelivr.net/npm/three@0.152.2/build/three.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/three@0.152.2/examples/js/controls/OrbitControls.js"></script>
    
    <script>
        // Main Megaminx class
        class Megaminx {
            constructor() {
                this.scene = new THREE.Scene();
                this.scene.background = new THREE.Color(0xf0f0f0);
                
                this.camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
                this.camera.position.set(0, 0, 10);
                
                this.renderer = new THREE.WebGLRenderer({ antialias: true });
                this.renderer.setSize(window.innerWidth, window.innerHeight);
                this.renderer.setPixelRatio(window.devicePixelRatio);
                document.getElementById('container').appendChild(this.renderer.domElement);
                
                this.controls = new THREE.OrbitControls(this.camera, this.renderer.domElement);
                this.controls.enableDamping = true;
                this.controls.dampingFactor = 0.05;
                
                // Colors for the faces
                this.colors = [
                    '#FF0000', '#00FF00', '#0000FF', '#FFFF00', 
                    '#FFA500', '#FFC0CB', '#800080', '#008000', 
                    '#000080', '#808080', '#FF00FF', '#00FFFF'
                ];
                
                // Define the 12 faces of the dodecahedron
                this.faces = [];
                this.facePositions = [];
                this.faceNormals = [];
                
                // Create the dodecahedron geometry
                this.createMegaminx();
                
                // Add lights
                this.addLights();
                
                // Add event listeners
                this.addEventListeners();
                
                // Start animation loop
                this.animate();
            }
            
            createMegaminx() {
                // Create a dodecahedron with 12 faces
                const radius = 5;
                const phi = (1 + Math.sqrt(5)) / 2; // Golden ratio
                
                // Generate the 20 vertices of a dodecahedron
                const vertices = [
                    [-1, -1, -1], [1, -1, -1], [1, 1, -1], [-1, 1, -1],
                    [-1, -1, 1], [1, -1, 1], [1, 1, 1], [-1, 1, 1],
                    [0, -1/phi, -phi], [0, 1/phi, -phi], [0, -1/phi, phi], [0, 1/phi, phi],
                    [-1/phi, -phi, 0], [1/phi, -phi, 0], [-1/phi, phi, 0], [1/phi, phi, 0],
                    [-phi, 0, -1/phi], [-phi, 0, 1/phi], [phi, 0, -1/phi], [phi, 0, 1/phi]
                ];
                
                // Scale vertices to radius
                const scaledVertices = vertices.map(v => [
                    v[0] * radius,
                    v[1] * radius,
                    v[2] * radius
                ]);
                
                // Define the 12 faces of the dodecahedron
                const faceIndices = [
                    [0, 1, 5, 4], [1, 2, 6, 5], [2, 3, 7, 6], [3, 0, 4, 7],
                    [4, 5, 10, 9], [5, 6, 11, 10], [6, 7, 12, 11], [7, 4, 9, 12],
                    [8, 9, 10, 13], [9, 10, 11, 14], [10, 11, 12, 15], [11, 12, 13, 16],
                    [12, 13, 14, 17], [13, 14, 15, 18], [14, 15, 16, 19], [15, 16, 17, 8],
                    [16, 17, 18, 1], [17, 18, 19, 2], [18, 19, 8, 3], [19, 8, 9, 0]
                ];
                
                // Create the faces
                for (let i = 0; i < 12; i++) {
                    const face = new THREE.Group();
                    face.userData = { index: i, rotation: 0 };
                    
                    // Get the vertices for this face
                    const indices = faceIndices[i];
                    const faceVertices = indices.map(idx => scaledVertices[idx]);
                    
                    // Calculate the center of the face
                    const center = faceVertices.reduce((sum, v) => [
                        sum[0] + v[0],
                        sum[1] + v[1],
                        sum[2] + v[2]
                    ], [0, 0, 0]).map(coord => coord / faceVertices.length);
                    
                    // Calculate the normal vector
                    const normal = this.calculateFaceNormal(faceVertices);
                    
                    // Store face information
                    this.facePositions.push(center);
                    this.faceNormals.push(normal);
                    
                    // Create the face geometry
                    const geometry = new THREE.BufferGeometry();
                    const positions = [];
                    const colors = [];
                    
                    // Add vertices for the face
                    faceVertices.forEach(v => {
                        positions.push(v[0], v[1], v[2]);
                        // Add a color for this face
                        colors.push(
                            parseInt(this.colors[i].substring(1, 3), 16) / 255,
                            parseInt(this.colors[i].substring(3, 5), 16) / 255,
                            parseInt(this.colors[i].substring(5, 7), 16) / 255
                        );
                    });
                    
                    geometry.setAttribute('position', new THREE.Float32BufferAttribute(positions, 3));
                    geometry.setAttribute('color', new THREE.Float32BufferAttribute(colors, 3));
                    
                    // Create material with face color
                    const material = new THREE.MeshBasicMaterial({
                        vertexColors: true,
                        transparent: true,
                        opacity: 0.8
                    });
                    
                    // Create mesh for the face
                    const mesh = new THREE.Mesh(geometry, material);
                    mesh.userData = { faceIndex: i };
                    face.add(mesh);
                    
                    // Add the face to the scene
                    this.scene.add(face);
                    this.faces.push(face);
                }
                
                // Create the center pieces (one for each face)
                for (let i = 0; i < 12; i++) {
                    const centerGeometry = new THREE.SphereGeometry(0.5, 16, 16);
                    const centerMaterial = new THREE.MeshBasicMaterial({
                        color: this.colors[i]
                    });
                    const center = new THREE.Mesh(centerGeometry, centerMaterial);
                    center.position.set(
                        this.facePositions[i][0],
                        this.facePositions[i][1],
                        this.facePositions[i][2]
                    );
                    center.userData = { type: 'center', faceIndex: i };
                    this.scene.add(center);
                }
                
                // Create the edge pieces (one for each edge)
                // This is a simplified version - in a real Megaminx, there are more pieces
                // For simplicity, we'll just create a few edge pieces
                for (let i = 0; i < 30; i++) {
                    const edgeGeometry = new THREE.SphereGeometry(0.3, 12, 12);
                    const edgeMaterial = new THREE.MeshBasicMaterial({
                        color: 0x888888
                    });
                    const edge = new THREE.Mesh(edgeGeometry, edgeMaterial);
                    // Position edge pieces along the edges
                    edge.position.set(
                        (Math.random() - 0.5) * 2,
                        (Math.random() - 0.5) * 2,
                        (Math.random() - 0.5) * 2
                    );
                    edge.userData = { type: 'edge', index: i };
                    this.scene.add(edge);
                }
                
                // Create the corner pieces (one for each corner)
                for (let i = 0; i < 20; i++) {
                    const cornerGeometry = new THREE.SphereGeometry(0.25, 12, 12);
                    const cornerMaterial = new THREE.MeshBasicMaterial({
                        color: 0x333333
                    });
                    const corner = new THREE.Mesh(cornerGeometry, cornerMaterial);
                    // Position corner pieces at vertices
                    corner.position.set(
                        (Math.random() - 0.5) * 2,
                        (Math.random() - 0.5) * 2,
                        (Math.random() - 0.5) * 2
                    );
                    corner.userData = { type: 'corner', index: i };
                    this.scene.add(corner);
                }
            }
            
            calculateFaceNormal(vertices) {
                // Calculate the normal vector of a face using cross product
                const v1 = vertices[0];
                const v2 = vertices[1];
                const v3 = vertices[2];
                
                const edge1 = [v2[0] - v1[0], v2[1] - v1[1], v2[2] - v1[2]];
                const edge2 = [v3[0] - v1[0], v3[1] - v1[1], v3[2] - v1[2]];
                
                // Cross product
                const normal = [
                    edge1[1] * edge2[2] - edge1[2] * edge2[1],
                    edge1[2] * edge2[0] - edge1[0] * edge2[2],
                    edge1[0] * edge2[1] - edge1[1] * edge2[0]
                ];
                
                // Normalize
                const length = Math.sqrt(normal[0] * normal[0] + normal[1] * normal[1] + normal[2] * normal[2]);
                return [
                    normal[0] / length,
                    normal[1] / length,
                    normal[2] / length
                ];
            }
            
            addLights() {
                // Add ambient light
                const ambientLight = new THREE.AmbientLight(0xffffff, 0.6);
                this.scene.add(ambientLight);
                
                // Add directional light
                const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8);
                directionalLight.position.set(1, 1, 1);
                this.scene.add(directionalLight);
                
                // Add another directional light from the opposite direction
                const directionalLight2 = new THREE.DirectionalLight(0xffffff, 0.4);
                directionalLight2.position.set(-1, -1, -1);
                this.scene.add(directionalLight2);
            }
            
            addEventListeners() {
                // Scramble button
                document.getElementById('scramble').addEventListener('click', () => {
                    this.scramble();
                });
                
                // Reset button
                document.getElementById('reset').addEventListener('click', () => {
                    this.reset();
                });
                
                // Rotate left button
                document.getElementById('rotate-left').addEventListener('click', () => {
                    this.rotateFace(0, -1);
                });
                
                // Rotate right button
                document.getElementById('rotate-right').addEventListener('click', () => {
                    this.rotateFace(0, 1);
                });
                
                // Handle window resize
                window.addEventListener('resize', () => {
                    this.camera.aspect = window.innerWidth / window.innerHeight;
                    this.camera.updateProjectionMatrix();
                    this.renderer.setSize(window.innerWidth, window.innerHeight);
                });
            }
            
            scramble() {
                // Randomly rotate faces to scramble the puzzle
                const numRotations = 20;
                for (let i = 0; i < numRotations; i++) {
                    const faceIndex = Math.floor(Math.random() * 12);
                    const direction = Math.random() > 0.5 ? 1 : -1;
                    this.rotateFace(faceIndex, direction);
                }
            }
            
            reset() {
                // Reset the puzzle to solved state
                // In a real implementation, this would reset all pieces to their original positions
                // For now, we'll just reset the face rotations
                this.faces.forEach(face => {
                    face.userData.rotation = 0;
                });
            }
            
            rotateFace(faceIndex, direction) {
                // Rotate a face by 72 degrees (1/5 of a full rotation)
                const face = this.faces[faceIndex];
                const rotation = direction * Math.PI / 5; // 72 degrees in radians
                
                // Rotate the face itself
                face.userData.rotation += rotation;
                face.rotation.y += rotation;
                
                // In a real Megaminx, we would also rotate the adjacent pieces
                // This is a simplified version - in a full implementation,
                // we would need to track all the pieces and their positions
                console.log(`Rotated face ${faceIndex} by ${direction > 0 ? 'clockwise' : 'counterclockwise'}`);
            }
            
            animate() {
                requestAnimationFrame(() => this.animate());
                
                // Update controls
                this.controls.update();
                
                // Render the scene
                this.renderer.render(this.scene, this.camera);
            }
        }
        
        // Initialize the Megaminx when the page loads
        window.addEventListener('load', () => {
            new Megaminx();
        });
    </script>
    
    

    Open the file in your favorite web browser. You can now see the 3d Megaminx live.

    Future posts will focus on optimizing this code. Keep practicing your programming skills every day.

    Consolidated Demo

    HTML5 AI-Generated Megaminx Puzzle

    Screenshot

    AI 3D Megaminx
    Web Browser Showing llama.cpp And Generated Megaminx Cube

    AI HTML5 Code
    Web Browser Showing AI Code And Generated Pyraminx Cube

    Live Screencast

    Screencast Of AI Generated Megaminx Cube Code

    Take Your Skills Further

  • Understanding Local AI Architecture GGUF And Quantization

    Understanding Local AI Architecture GGUF And Quantization


    Introduction

    Local AI development is becoming very popular for Linux users. You can run powerful language models on your own computer.

    This guide explains the core concepts of local AI. You do not need to write any complex code.

    Fedora Linux provides a stable base for these tools. Most beginners start with a tool called llama.cpp today.

    Common AI Model File Formats

    Large language models are usually saved in two formats. You will often see safetensors or GGUF file types.

    Safetensors files are the original and secure model weights. They are commonly used by researchers for training models.

    The safetensors format prevents malicious code execution during loading. This makes it the standard for sharing raw models.

    However these files often require massive amounts of memory. They are not always optimized for home computer hardware.

    The GGUF Standard For Local Use

    GGUF is a newer format designed for local inference. It stores everything needed to run a model easily.

    These files include model weights and important metadata together. This makes sharing and loading models much more reliable.

    GGUF was created specifically for the llama.cpp ecosystem. It allows for single file distribution of complex models.

    You can download one file and start chatting immediately. This simplicity is perfect for those new to Linux.

    Explaining Model Quantization

    Quantization is a key technique for home computer users. It shrinks the size of massive AI model files.

    Think of it like compressing a high resolution image. You lose a little detail but save much space.

    Digital weights are usually stored as large floating numbers. Quantization rounds these numbers to smaller integer values.

    A model using 16-bit precision requires huge video memory. A 4-bit quantized model uses much less space.

    Model Weights
    Precision Level VRAM Usage (7B Model) Speed Benefit
    16-bit (Half) 14 GB to 15 GB Baseline speed
    8-bit (Integer) 7 GB to 8 GB 1.8x faster
    4-bit (Compressed) 3.5 GB to 5 GB 2.4x faster
    Precision Level VRAM Usage (7B Model) Speed Benefit

    Smaller models run much faster on standard CPU hardware. You can chat with AI without expensive server cards.

    Running Models With Llama.cpp

    Llama.cpp acts as the engine for these local models. It is written in C++ for maximum execution speed.

    The software works without any heavy Python library dependencies. It runs smoothly on Fedora using simple terminal commands.

    Llama.cpp supports various hardware backends like CUDA and ROCm. This makes it compatible with both Nvidia and AMD.

    The engine handles the math required for model predictions. It translates your text prompts into numerical data quickly.

    Hardware Optimization And Privacy

    You can offload specific tasks to your graphics card. This improves the speed of generating long text responses.

    If your GPU is small you can use RAM. This flexibility is the core strength of local AI.

    Privacy is the biggest benefit of this local setup. Your data never leaves your computer or local network.

    You do not need an internet connection to work. This ensures your private conversations remain completely confidential always.

    Screenshot

    GGUF Quantization
    Banner Representing GGUF And Quantization

    Live Screencast

    Screencast Of GGUF Quantization Explanation

    Addedum Video Of Beginner Local AI Installation

    Take Your Skills Further

  • Automate 3D Web Charts With Blender Python And CSV

    Automate 3D Web Charts With Blender Python And CSV


    Introduction

    Learn to build 3D charts with Python. Fedora Linux makes this process very smooth.

    Getting Started on Fedora Linux

    First install Blender on your Fedora system. Use the dnf command in your terminal.

    Using the Blender Python API

    Blender features a powerful Python scripting environment. This API automates every 3D modeling step.

    Create a CSV file for your chart data. Save it inside your main project folder.

    
    
    
    import bpy
    import csv
    import os
    
    # --- CONFIGURATION ---
    CSV_FILE_PATH = "path/to/your/data.csv"  # Update this!
    EXPORT_PATH = "path/to/your/chart.glb"
    GAP = 1.5  # Space between bars
    
    def create_bar(name, value, index):
        # Create a cube
        bpy.ops.mesh.primitive_cube_add(size=1)
        bar = bpy.context.active_object
        bar.name = name
        
        # Scale and position (Blender cubes are 2 units tall by default)
        bar.scale = (0.5, 0.5, value / 2)
        bar.location = (index * GAP, 0, value / 2)
        
        # Create Material
        mat = bpy.data.materials.new(name=f"Mat_{name}")
        mat.use_nodes = True
        nodes = mat.node_tree.nodes
        # Set color based on height (Simple Green to Red logic)
        color_node = nodes.get("Principled BSDF")
        color_node.inputs[0].default_value = (value/10, 0.1, 0.8, 1) 
        bar.data.materials.append(mat)
    
    def generate_chart():
        # Clear existing objects
        bpy.ops.object.select_all(action='SELECT')
        bpy.ops.object.delete()
    
        with open(CSV_FILE_PATH, mode='r') as f:
            reader = csv.DictReader(f)
            for i, row in enumerate(reader):
                label = row['Label']
                val = float(row['Value'])
                create_bar(label, val, i)
    
        # Export to GLB for Web
        bpy.ops.export_scene.gltf(filepath=EXPORT_PATH, export_format='GLB')
        print(f"Chart exported to {EXPORT_PATH}")
    
    if __name__ == "__main__":
        generate_chart()
    
    

    Data Processing and Mesh Generation

    The script reads your specific data values. It creates a unique bar for each.

    Height scales automatically based on your numbers. Materials change color to reflect different values.

    
    
    
    <script type="importmap">
        {
            "imports": {
                "three": "https://unpkg.com/three@0.160.0/build/three.module.js",
                "three/addons/": "https://unpkg.com/three@0.160.0/examples/jsm/"
            }
        }
    </script>
    
    <script type="module">
        import * as THREE from 'three';
        import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
        import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
        import { EXRLoader } from 'three/addons/loaders/EXRLoader.js';
    
        let scene, camera, renderer, controls;
    
        init();
    
        function init() {
            // 1. Scene & Camera
            scene = new THREE.Scene();
            camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
            camera.position.set(5, 5, 10);
    
            // 2. Renderer Setup
            renderer = new THREE.WebGLRenderer({ antialias: true });
            renderer.setPixelRatio(window.devicePixelRatio);
            renderer.setSize(window.innerWidth, window.innerHeight);
            renderer.toneMapping = THREE.ReinhardToneMapping; // Better for HDR/EXR
            renderer.toneMappingExposure = 1.0;
            document.body.appendChild(renderer.domElement);
    
            // 3. Orbit Controls
            controls = new OrbitControls(camera, renderer.domElement);
            controls.enableDamping = true;
    
            // 4. Load EXR Environment Map
            new EXRLoader()
                .load('courtyard.exr', function (texture) {
                    texture.mapping = THREE.EquirectangularReflectionMapping;
    
                    scene.background = texture;
                    scene.environment = texture; // This provides the lighting
    
                    // 5. Load the Blender GLB Chart
                    const loader = new GLTFLoader();
                    loader.load('interactive_chart.glb', function (gltf) {
                        scene.add(gltf.scene);
                        
                        // Center the model based on its bounding box
                        const box = new THREE.Box3().setFromObject(gltf.scene);
                        const center = box.getCenter(new THREE.Vector3());
                        gltf.scene.position.x += (gltf.scene.position.x - center.x);
                        
                        animate();
                    }, undefined, function (error) {
                        console.error('Error loading GLB:', error);
                    });
                });
    
            window.addEventListener('resize', onWindowResize);
        }
    
        function onWindowResize() {
            camera.aspect = window.innerWidth / window.innerHeight;
            camera.updateProjectionMatrix();
            renderer.setSize(window.innerWidth, window.innerHeight);
        }
    
        function animate() {
            requestAnimationFrame(animate);
            controls.update();
            renderer.render(scene, camera);
        }
    </script>
    
    

    Exporting for Web Deployment

    Export the final scene to a GLB file. ThreeJS displays the 3D model online.

    Add a courtyard EXR for natural lighting. This makes your web chart look realistic.

    📸 Screenshots & Screencast

    3D Web Charts Python code
    Blender Scripting Workspace Displaying 3D Web Charts Python Code

    3D Web Charts in Blender
    Blender Layout Workspace Displaying 3D Web Charts

    3D Web Charts in Blender Shading
    Blender Shading Workspace Displaying 3D Web Charts

    3D Web Charts in Web browser
    Web Browser Displaying Rendered 3D Web Charts

    Screencast For Blender Python API 3D Web Charts

    Take Your Skills Further

  • Modern Shell for Developers Nushell on Fedora Linux

    Modern Shell for Developers Nushell on Fedora Linux


    Introduction

    Nushell is a new type of shell for programmers. It treats all your data as structured tables.

    Structured Data Versus Plain Text

    Most shells like Bash only use plain text strings. Nushell understands columns and rows like a database.

    Installing Nushell on Fedora Linux is very simple today. Use the dnf command to grab the latest version.

    Cross Platform Compatibility and Installation

    The shell works perfectly on Windows and macOS too. This makes it a great cross platform choice.

    Type ls to see a beautiful colored table. You can filter files easily using the where command.

    Powerful Pipelines and Commands

    Standard Linux commands feel much more powerful here. Pipelines pass real data objects instead of raw text.

    You do not need to use grep or awk. Simply select the columns you want to see.

    Beginners will love the helpful error messages provided. The shell points exactly where your syntax failed.

    Customizing your prompt is also a fun experience. You can use the built in configuration files.

    Bash users are used to manipulating messy strings. Nushell eliminates the need for complex regular expressions.

    Data Export and Performance

    Every command output is a structured data object. You can export tables directly to JSON or CSV.

    The shell is written entirely in the Rust language. This makes it very fast and memory efficient.

    Native support for SQLite is built directly into Nushell. You can query databases without leaving your prompt.

    Open Source MIT License

    The project is fully open source for all users. It uses the permissive MIT License for its code.

    This license allows you to use it very freely. Community members can contribute to the GitHub repository easily.

    Advanced File Handling and Plugins

    Opening a directory feels like opening a spreadsheet. Each file attribute has its own searchable column.

    Use the get command to extract specific values. This is much easier than using cut or sed.

    Pipelines in Nushell are extremely logical and clean. Data flows from one command to another smoothly.

    The plugin system allows for massive community extensions. You can add new capabilities with minimal effort.

    Try using the open command on a JSON file. Nushell converts the file into an interactive table instantly.

    You can sort lists by size or date easily. Use the sort-by command to organize your file views.

    Learning Nushell will change how you view the terminal. It brings modern data science tools to the shell.

    This tool is perfect for developers working on Fedora. It bridges the gap between scripting and data.

    📷 Screenshots

    Nushell Configuraton
    Command Line Displaying Nushell Initial Configuration

    Nushell Sort Filter
    Command Line Displaying Nushell Filter Sort By Size

    Nushell JSON Table
    Command Line Displaying Nushell Viewing JSON File As Table

    Nushell REST API
    Command Line Displaying Nushell Querying A REST API

    Gnome Terminal Preferences
    Gnome Terminal Preferences Custom Command Dialog

    🎬 Live YouTube Screencast

    Video Displaying The Installation And Use Of Nushell

    Take Your Skills Further