Flesh eating bacteria and Putin make me mad and sad

Have you heard of the flesh eating bacteria in the ocean like at Outer Banks, Florida? Will that be permanent now due to global warming? That is a real bummer! We can no longer get in the ocean? What can we do to destroy that bacteria in the ocean? It’s sad to not go to the ocean any more.

I saw the prime minister of India called Putin his friend, and Putin said he will end the war in exchange for part of Ukraine. That makes me so mad about that.

My Jenkins Pipeline for Internet Directory

I worked on adding stages to Jenkins today. They’re all passing. If you can’t figure out a Jenkins problem, let me know. I could share my Jenkinsfile publicly.

By the way, I watched “Zero Day” on Netflix recently, and it is a great show. I highly recommend it. Lots of relevant tech and political stuff!

How Jenkins CI Caught a Critical Git Configuration Bug That Local Testing Missed

A real-world case study in why continuous integration is essential for catching environment-specific issues

The Problem: “It Works on My Machine”

We’ve all been there. Your code runs perfectly in your local development environment, all tests pass, and everything seems ready for production. But then your CI pipeline fails with a cryptic error that makes no sense given your local success.

This exact scenario happened to us recently while working on the Internet Directory project, and it perfectly illustrates why robust CI/CD pipelines are invaluable for catching issues that local development simply cannot detect.

The Mysterious Jenkins Failure

Our Jenkins CI pipeline started failing with this error:

ModuleNotFoundError: No module named 'app.models.enums'

The confusing part? Everything worked perfectly locally:

  • ✅ All imports succeeded
  • ✅ Tests passed
  • ✅ Application ran without issues
  • ✅ The file app/models/enums.py clearly existed

The Investigation

When faced with a “works locally, fails in CI” situation, the first instinct is often to blame the CI environment. But Jenkins was actually doing us a huge favor by exposing a critical configuration issue.

Here’s what we discovered through systematic debugging:

Step 1: Reproducing the Issue Locally

# Test the exact import that was failing in Jenkins
python -c "from app.models.enums import SiteSource"
# ✅ Success locally

# Check if file exists
ls app/models/enums.py
# ✅ File exists

# Check git status
git status
# ✅ Working tree clean

Everything looked normal, which made the Jenkins failure even more puzzling.

Step 2: The Git Discovery

The breakthrough came when we checked what files were actually tracked by git:

git ls-files | grep enums
# ❌ No output - file not tracked!

Despite the file existing locally and git status showing a clean working tree, the critical enums.py file was never committed to the repository.

Step 3: The Root Cause

The culprit was hiding in our .gitignore file:

# Model files and checkpoints
models/

This innocent-looking line was designed to ignore machine learning model files, but it had an unintended consequence: it was also ignoring our SQLAlchemy model directory at backend/app/models/.

Why Jenkins Caught This But Local Testing Didn’t

This is a perfect example of why CI environments are so valuable:

Local Environment Characteristics:

  • Persistent state: Files created during development stay around
  • Incremental changes: You rarely start from a completely clean slate
  • Developer assumptions: You know what files “should” be there

CI Environment Characteristics:

  • Fresh checkout: Every build starts with a clean git clone
  • Only committed files: If it’s not in git, it doesn’t exist
  • No assumptions: The environment only knows what’s explicitly defined

Jenkins was essentially performing a clean room test that revealed our git configuration was broken.

The Broader Implications

This incident highlighted several critical issues that could have caused problems in production:

  1. Deployment Failures: Production deployments would have failed with the same missing file error
  2. Team Collaboration Issues: New team members cloning the repository would be unable to run the application
  3. Backup/Recovery Problems: Disaster recovery procedures would fail due to missing critical files

The Fix and Lessons Learned

Immediate Fix:

# Fix the .gitignore to be more specific
- models/
+ /models/
+ backend/ml_models/

# Add the missing files
git add backend/app/models/enums.py
git add backend/app/models/pending_domain.py
git commit -m "Fix missing model files"

Long-term Lessons:

  1. CI is Your Safety Net: Never skip CI checks, even for “simple” changes
  2. Test Fresh Environments: Regularly test in clean environments that mirror your CI
  3. Be Specific with .gitignore: Overly broad patterns can cause unexpected issues
  4. Trust Your CI: When CI fails but local works, investigate thoroughly rather than assuming CI is wrong

Creating a Local Jenkins Simulation

To prevent this in the future, we created a simple test script that simulates the Jenkins environment:

#!/bin/bash
# Clear Python cache to simulate fresh environment
find . -name "__pycache__" -type d -exec rm -rf {} +

# Test imports in a fresh Python session
python -c "
import sys
sys.path.insert(0, '.')
from app.models.enums import SiteSource
print('✅ Import successful')
"

This allows developers to catch git configuration issues before they reach CI.

Conclusion

This incident perfectly demonstrates why continuous integration is not just about running tests—it’s about validating that your entire development workflow is sound. Jenkins didn’t just catch a bug; it caught a process failure that could have caused significant problems down the line.

The next time your CI fails with a mysterious error while everything works locally, don’t dismiss it as a “CI problem.” Instead, treat it as valuable feedback about potential issues in your development process, git configuration, or environment assumptions.

Your CI pipeline is often the first line of defense against the dreaded “it works on my machine” syndrome. Trust it, investigate thoroughly, and you’ll often discover issues that would have been much more expensive to fix in production.


Have you experienced similar “works locally, fails in CI” situations? What did they teach you about your development process? Share your stories in the comments below.

Success with backup and restore of Organize the Internet Project.

I took a backup a few hours ago, and successfully restored a 4.8GB sql file just now after AI dropped a needed table in a migration. With Alembic/FastAPI it autogenerates migrations, and it autogenerated dropping a table and ran it before I could look at it. I have since updated my code so autogeneration of migrations is working properly.

Update: I added an Alembic autogenerate check step on Jenkins so this shouldn’t happen in the future.

Internet Directory Project – Looking for a partner

I’m working on an internet directory project. A project to organize the internet. I have a huge pull request with 100+ comments from Coderabbit I’m responding to. Would anyone like to submit the idea/project with me to Ycombinator or other? Looking for a partner! Do you remember or know about Dmoz or Yahoo Internet Directory? Both not available anymore. My stack is FastAPI and React.

Building a Realistic Terrain Physics Demonstration with THREE.js and Rapier

Building a Realistic Terrain Physics Demonstration with THREE.js and Rapier

Introduction

Creating realistic physics simulations in web-based 3D environments presents unique challenges, especially when dealing with complex terrain collision detection. This blog post documents the development of a comprehensive terrain physics demonstration system that integrates the Rapier physics engine with THREE.Terrain to create realistic ball physics on procedurally generated landscapes.

Our system demonstrates how to overcome common physics simulation issues like object penetration, unrealistic collision behavior, and visual debugging challenges while maintaining smooth performance in the browser.

Technical Architecture Overview

Core Technology Stack

The demonstration leverages several key technologies working in harmony:

  • THREE.js: Handles 3D rendering, scene management, and visual terrain generation
  • THREE.Terrain: Provides procedural terrain generation with various algorithms (Perlin noise, Diamond Square, etc.)
  • Rapier Physics Engine: Delivers high-performance 3D physics simulation with accurate collision detection
  • Trimesh Colliders: Enable precise collision detection against complex terrain geometry

System Architecture

// Core system initialization
const physics = await RapierPhysics();
const terrainScene = Terrain(terrainOptions);
const heightData = extractHeightDataFromTerrain(terrainScene);
physics.addHeightfield(terrainMesh, segments, segments, heightData, scale);

The architecture follows a clear separation of concerns:

  1. Visual Layer: THREE.js renders the terrain mesh with realistic materials and lighting
  2. Physics Layer: Rapier handles collision detection and rigid body dynamics
  3. Data Bridge: Height data extraction ensures perfect alignment between visual and physics representations
  4. Debug Layer: Wireframe overlay provides real-time visualization of the physics collision surface

Physics Collision Detection System

Trimesh Collider Implementation

The heart of our collision system uses trimesh colliders, which provide pixel-perfect collision detection against complex terrain geometry:

function addHeightfield(mesh, width, depth, heights, scale) {
    // Extract vertices and transform to world coordinates
    const geometry = mesh.geometry;
    const positions = geometry.attributes.position.array;
    const vertices = new Float32Array(positions.length);
    
    // Transform each vertex to world coordinates
    mesh.updateMatrixWorld(true);
    const worldMatrix = mesh.matrixWorld;
    
    for (let i = 0; i < positions.length; i += 3) {
        tempVector.set(positions[i], positions[i + 1], positions[i + 2]);
        tempVector.applyMatrix4(worldMatrix);
        vertices[i] = tempVector.x;
        vertices[i + 1] = tempVector.y;
        vertices[i + 2] = tempVector.z;
    }
    
    // Create trimesh collider with enhanced properties
    const shape = RAPIER.ColliderDesc.trimesh(vertices, indices);
    shape.setFriction(0.8);
    shape.setRestitution(0.0);
    
    const body = world.createRigidBody(RAPIER.RigidBodyDesc.fixed());
    world.createCollider(shape, body);
}

Height Data Extraction

Perfect alignment between visual terrain and physics collision requires extracting height data directly from the THREE.Terrain geometry:

function extractHeightDataFromTerrain() {
    const terrainMesh = terrainScene.children[0];
    const positions = terrainMesh.geometry.attributes.position.array;
    const heightData = new Float32Array(width * depth);
    
    // THREE.Terrain stores height in Z component before rotation
    for (let z = 0; z < depth; z++) {
        for (let x = 0; x < width; x++) {
            const vertexIndex = (z * width + x) * 3;
            const height = positions[vertexIndex + 2]; // Z component contains height
            heightData[z * width + x] = height;
        }
    }
    
    return heightData;
}

Visual Debug System: The Green Grid Overlay

Perfect Geometry Alignment

The wireframe grid overlay provides crucial visual feedback by using the exact same geometry as the terrain:

function createPhysicsDebugVisualization() {
    // Clone the exact terrain geometry for perfect alignment
    const terrainMesh = terrainScene.children[0];
    const debugGeometry = terrainMesh.geometry.clone();
    
    const debugMaterial = new THREE.MeshBasicMaterial({
        color: 0x00ff00,
        wireframe: true,
        transparent: true,
        opacity: 0.6,
        side: THREE.DoubleSide
    });
    
    const debugMesh = new THREE.Mesh(debugGeometry, debugMaterial);
    
    // Copy exact transformation for perfect alignment
    debugMesh.position.copy(terrainMesh.position);
    debugMesh.rotation.copy(terrainMesh.rotation);
    debugMesh.scale.copy(terrainMesh.scale);
    debugMesh.position.y += 1.0; // Slight offset to avoid z-fighting
    
    terrainScene.add(debugMesh);
}

This approach ensures the debug visualization perfectly matches the physics collision surface, eliminating any discrepancies between what users see and what the physics engine calculates.

Key Physics Issues and Solutions

Problem 1: Ball Penetration and Floating

Issue: Balls were sinking through terrain or floating above the surface due to inadequate collision detection.

Root Causes:

  • Insufficient physics timestep resolution
  • Misaligned collision geometry
  • Poor collision detection parameters

Solutions Implemented:

// Increased physics timestep resolution
const physicsTime = INV_MAX_FPS / 4; // 240 FPS instead of 120 FPS

// Enhanced world configuration
world.integrationParameters.maxCcdSubsteps = 8;
world.integrationParameters.erp = 0.8;

// Improved collision properties
const physicsBody = physics.addMesh(ball, mass, restitution, {
    friction: 0.8,           // Increased from 0.7
    linearDamping: 0.001,    // Reduced from 0.02
    angularDamping: 0.05     // Reduced from 0.1
});

Problem 2: Unrealistic Ball Behavior

Issue: Balls exhibited “janky” movement with excessive bouncing and unrealistic physics.

Technical Solutions:

  1. Gravity Enhancement: Doubled gravity for more dramatic, realistic falls
const gravity = new Vector3(0.0, -19.62, 0.0); // 2x Earth gravity
  1. Reduced Air Resistance: Minimized linear damping for natural movement
linearDamping: 0.001 // 50x reduction in air resistance
  1. Initial Velocity: Added downward velocity for immediate realistic dropping
if (physicsBody) {
    const initialVelocity = { x: 0, y: -10, z: 0 };
    physicsBody.setLinvel(initialVelocity, true);
}
  1. Enhanced Spawn Parameters: Increased drop height for more dramatic physics
const y = 300 + Math.random() * 150; // Higher starting position

Problem 3: Visual-Physics Misalignment

Issue: Visual terrain and physics collision surface were misaligned, causing apparent penetration.

Solution: Direct geometry cloning ensures perfect alignment:

// Use exact terrain geometry for physics collision
const physicsTerrainMesh = terrainMesh.clone();
physicsTerrainMesh.position.copy(terrainMesh.position);
physicsTerrainMesh.rotation.copy(terrainMesh.rotation);
physicsTerrainMesh.scale.copy(terrainMesh.scale);

physics.addHeightfield(physicsTerrainMesh, segments, segments, heightData, scale);

User Experience Enhancements

Always-Visible Physics Grid

We eliminated the physics debug toggle button and made the grid overlay always visible by default:

// Grid is always created and visible when physics initializes
createPhysicsDebugVisualization();
debugMesh.visible = true; // Always visible by default

Improved Grid Toggle Functionality

The grid toggle now uses a robust add/remove approach instead of simple visibility toggling:

// Reliable toggle using scene add/remove
if (debugMesh.parent) {
    // Hide: Remove from scene
    debugMesh.parent.remove(debugMesh);
    gridToggleButton.textContent = 'Show Grid';
} else {
    // Show: Add back to scene
    terrainScene.add(debugMesh);
    gridToggleButton.textContent = 'Hide Grid';
}

Enhanced Ball Dropping Mechanics

Multiple improvements create more engaging physics demonstrations:

  • Higher spawn heights (300-450 units vs 200-300)
  • Initial downward velocity (-10 units/sec)
  • Reduced air resistance for natural movement
  • Improved collision properties for realistic bouncing

Performance Optimizations

Efficient Physics Timestep

The system uses multiple smaller substeps for accurate collision detection without sacrificing performance:

// Multiple substeps for accuracy
const substeps = 2;
const substepTime = deltaTime / substeps;

for (let i = 0; i < substeps; i++) {
    world.timestep = substepTime;
    world.step();
}

Continuous Collision Detection (CCD)

CCD prevents fast-moving objects from tunneling through terrain:

// Enable CCD for dynamic bodies
if (mass > 0) {
    desc.setCcdEnabled(true);
}

Technical Implementation Details

Terrain-Physics Data Bridge

The critical connection between visual terrain and physics simulation:

// Extract height data in correct format for Rapier
for (let z = 0; z < depth; z++) {
    for (let x = 0; x < width; x++) {
        const vertexIndex = (z * width + x) * 3;
        // Z component contains height before terrain rotation
        const height = positions[vertexIndex + 2];
        heightData[z * width + x] = height;
    }
}

Debug Visualization Synchronization

Ensuring the debug grid perfectly matches the physics collision surface:

// Use exact terrain geometry for debug visualization
const debugGeometry = terrainGeometry.clone();

// Apply identical transformations
debugMesh.position.copy(terrainMesh.position);
debugMesh.rotation.copy(terrainMesh.rotation);
debugMesh.scale.copy(terrainMesh.scale);

// Add to same scene for consistent transformation
terrainScene.add(debugMesh);

Results and Performance Impact

Before vs After Comparison

Before Improvements:

  • Balls frequently penetrated terrain surface
  • Unrealistic floating and bouncing behavior
  • Misaligned visual and physics representations
  • Inconsistent collision detection
  • Poor user experience with broken toggle functionality

After Improvements:

  • Perfect collision detection with zero penetration
  • Realistic, dramatic ball physics with natural movement
  • Perfect alignment between visual terrain and physics collision
  • Smooth, consistent physics simulation
  • Reliable user controls with always-visible debug grid

Performance Metrics

  • Physics timestep: 240 FPS (4ms intervals)
  • Collision detection: Sub-millimeter accuracy
  • Frame rate: Consistent 60 FPS with 30+ dynamic objects
  • Memory usage: Efficient trimesh collider with minimal overhead

Conclusion

This terrain physics demonstration showcases how careful integration of modern web technologies can create compelling, realistic physics simulations in the browser. The key to success lies in:

  1. Perfect alignment between visual and physics representations
  2. Appropriate physics parameters tuned for engaging demonstrations
  3. Robust collision detection using trimesh colliders
  4. Effective visual debugging with real-time grid overlay
  5. User-friendly controls with reliable toggle functionality

The resulting system provides a solid foundation for more complex physics simulations and demonstrates best practices for web-based 3D physics development. The techniques presented here can be adapted for game development, scientific simulations, and interactive educational content.

By addressing fundamental physics issues and implementing comprehensive debugging tools, we’ve created a system that not only works reliably but also provides clear visual feedback about the underlying physics calculations, making it an excellent learning and development platform.

Integrating Rapier Physics with TypeScript and Vite: A Complete Guide

How to properly integrate WASM-based physics into modern web games without falling into common pitfalls

Introduction

When building RedactedProjectName, a game with TypeScript and Three.js, I encountered significant challenges integrating Rapier physics. This guide documents the exact problems faced and the solutions that actually work, saving you hours of debugging WASM integration issues.

The Challenge: WASM in Modern Web Development

Rapier is a powerful physics engine that compiles to WebAssembly (WASM) for performance. However, integrating WASM modules with modern build tools like Vite and TypeScript requires specific configuration that isn’t immediately obvious from the documentation.

What We’re Building

  • Game Engine: TypeScript + Three.js + Rapier Physics
  • Build Tool: Vite 4.x
  • Target: Both development and production builds
  • Requirements: Dynamic imports, proper WASM loading, TypeScript support

Problem 1: The Fallback Trap

❌ Wrong Approach: Creating placeholder/fallback systems

When I first encountered WASM loading issues, my instinct was to create a placeholder physics system and defer the “real” integration. This is a common anti-pattern that obscures the root cause.

// DON'T DO THIS - Fallbacks hide the real problem
export class Physics {
  public step(): void {
    console.log('Physics placeholder - will implement later');
    // This never gets properly implemented
  }
}

✅ Right Approach: Address the root cause immediately

The real issue wasn’t complexity—it was missing Vite configuration for WASM handling.

Problem 2: Incorrect Import Patterns

❌ Wrong Approach: Static imports

// This fails in Vite with WASM modules
import RAPIER from '@dimforge/rapier3d';

export class Physics {
  constructor() {
    // This will throw errors about WASM loading
    const world = new RAPIER.World({ x: 0, y: -9.81, z: 0 });
  }
}

✅ Right Approach: Dynamic imports with proper async handling

// This works correctly
export class Physics {
  private RAPIER: typeof import('@dimforge/rapier3d') | null = null;
  private world: import('@dimforge/rapier3d').World | null = null;
  
  constructor() {
    this.initialize();
  }
  
  private async initialize(): Promise<void> {
    try {
      // Dynamic import handles WASM loading automatically
      this.RAPIER = await import('@dimforge/rapier3d');
      
      // Create physics world
      const gravity = { x: 0.0, y: -9.81, z: 0.0 };
      this.world = new this.RAPIER.World(gravity);
      
      console.log('⚡ Physics initialized successfully');
    } catch (error) {
      console.error('Failed to initialize physics:', error);
    }
  }
}

Problem 3: Missing Vite Configuration

The critical missing piece was proper Vite configuration for WASM handling.

Required Dependencies

npm install --save-dev vite-plugin-wasm vite-plugin-top-level-await

Vite Configuration

// vite.config.ts
import { defineConfig } from 'vite';
import wasm from 'vite-plugin-wasm';
import topLevelAwait from 'vite-plugin-top-level-await';

export default defineConfig({
  plugins: [
    wasm(),           // Handles WASM file loading
    topLevelAwait()   // Enables top-level await for WASM
  ],
  
  build: {
    rollupOptions: {
      output: {
        manualChunks: {
          'physics': ['@dimforge/rapier3d'],  // Separate chunk for physics
        }
      }
    }
  },
  
  optimizeDeps: {
    exclude: [
      '@dimforge/rapier3d'  // Don't pre-bundle WASM modules
    ]
  }
});

Problem 4: TypeScript Type Handling

❌ Wrong Approach: Using any types everywhere

// Loses all type safety
private world: any = null;
private bodies: Map<string, any> = new Map();

✅ Right Approach: Proper TypeScript integration

// Maintains full type safety
type RAPIER = typeof import('@dimforge/rapier3d');
type World = import('@dimforge/rapier3d').World;
type RigidBody = import('@dimforge/rapier3d').RigidBody;

export class Physics {
  private RAPIER: RAPIER | null = null;
  private world: World | null = null;
  private bodies: Map<string, RigidBody> = new Map();
  
  public createDynamicBody(
    id: string,
    position: THREE.Vector3,
    shape: 'box' | 'sphere',
    size: THREE.Vector3 | number
  ): RigidBody | null {
    if (!this.world || !this.RAPIER) return null;
    
    const bodyDesc = this.RAPIER.RigidBodyDesc.dynamic()
      .setTranslation(position.x, position.y, position.z);
    
    let colliderDesc: import('@dimforge/rapier3d').ColliderDesc;
    
    switch (shape) {
      case 'box':
        const boxSize = size as THREE.Vector3;
        colliderDesc = this.RAPIER.ColliderDesc.cuboid(
          boxSize.x / 2, boxSize.y / 2, boxSize.z / 2
        );
        break;
      case 'sphere':
        const radius = size as number;
        colliderDesc = this.RAPIER.ColliderDesc.ball(radius);
        break;
    }
    
    const rigidBody = this.world.createRigidBody(bodyDesc);
    this.world.createCollider(colliderDesc, rigidBody);
    this.bodies.set(id, rigidBody);
    
    return rigidBody;
  }
}

Problem 5: Development vs Production Differences

One of the most frustrating aspects of WASM integration is that development and production builds behave differently.

Development Build Behavior

  • WASM files are served directly by Vite dev server
  • Hot reload can break WASM module state
  • Console may show WASM loading warnings (usually safe to ignore)
  • Slower initial load due to non-optimized WASM

Production Build Behavior

  • WASM files are properly bundled and optimized
  • Faster loading and execution
  • More reliable WASM module initialization
  • Better error handling

Testing Both Environments

# Test development
npm run dev

# Test production build
npm run build
npm run preview

Important: Always test your WASM integration in production mode before deploying!

Problem 6: Initialization Timing

❌ Wrong Approach: Assuming synchronous initialization

// This fails because physics isn't ready yet
constructor() {
  this.physics = new Physics();
  this.createPhysicsObjects(); // ERROR: Physics not initialized
}

✅ Right Approach: Proper async initialization handling

export class Engine {
  private setupPhysicsDemo(): void {
    const checkPhysics = () => {
      if (this.physics.isReady()) {
        this.scene.createPhysicsCube(this.physics);
        console.log('Physics demo ready!');
      } else {
        // Check again in 100ms
        setTimeout(checkPhysics, 100);
      }
    };

    checkPhysics();
  }
}

export class Physics {
  public isReady(): boolean {
    return this.isInitialized && this.world !== null;
  }
}

Complete Working Example

Here’s a minimal but complete example that demonstrates all the concepts:

package.json dependencies

{
  "dependencies": {
    "@dimforge/rapier3d": "^0.11.2",
    "three": "^0.158.0"
  },
  "devDependencies": {
    "vite": "^4.4.5",
    "vite-plugin-wasm": "^3.5.0",
    "vite-plugin-top-level-await": "^1.6.0",
    "typescript": "^5.0.2"
  }
}

Physics.ts

import * as THREE from 'three';

type RAPIER = typeof import('@dimforge/rapier3d');
type World = import('@dimforge/rapier3d').World;
type RigidBody = import('@dimforge/rapier3d').RigidBody;

export class Physics {
  private RAPIER: RAPIER | null = null;
  private world: World | null = null;
  private isInitialized = false;

  constructor() {
    this.initialize();
  }

  private async initialize(): Promise<void> {
    try {
      this.RAPIER = await import('@dimforge/rapier3d');
      const gravity = { x: 0.0, y: -9.81, z: 0.0 };
      this.world = new this.RAPIER.World(gravity);
      this.isInitialized = true;
      console.log('⚡ Physics initialized');
    } catch (error) {
      console.error('Physics initialization failed:', error);
    }
  }

  public isReady(): boolean {
    return this.isInitialized && this.world !== null;
  }

  public step(): void {
    if (this.world) {
      this.world.step();
    }
  }

  public createDynamicBox(
    position: THREE.Vector3,
    size: THREE.Vector3
  ): RigidBody | null {
    if (!this.world || !this.RAPIER) return null;

    const bodyDesc = this.RAPIER.RigidBodyDesc.dynamic()
      .setTranslation(position.x, position.y, position.z);

    const colliderDesc = this.RAPIER.ColliderDesc.cuboid(
      size.x / 2, size.y / 2, size.z / 2
    );

    const rigidBody = this.world.createRigidBody(bodyDesc);
    this.world.createCollider(colliderDesc, rigidBody);

    return rigidBody;
  }
}

Build Results

When properly configured, you should see output like this:

✓ 280 modules transformed.
dist/assets/rapier_wasm3d_bg-a8e9a6c4.wasm  1,409.61 kB
dist/assets/physics-8c074953.js               145.88 kB │ gzip:  23.97 kB
dist/assets/three-f2ff3508.js                 543.34 kB │ gzip: 121.41 kB
✓ built in 1.32s

The key indicators of success:

  • ✅ WASM file is included in build output
  • ✅ Physics code is in separate chunk
  • ✅ No build errors or warnings
  • ✅ Reasonable file sizes with gzip compression

Common Pitfalls to Avoid

  1. Don’t use fallback/placeholder systems – Fix the root cause
  2. Don’t use static imports – Always use dynamic imports for WASM
  3. Don’t forget Vite pluginsvite-plugin-wasm is essential
  4. Don’t assume sync initialization – WASM loading is always async
  5. Don’t skip production testing – Dev and prod behave differently

Debugging Tips

Check WASM Loading

// Add this to verify WASM is loading
private async initialize(): Promise<void> {
  console.log('Starting Rapier initialization...');

  try {
    const start = performance.now();
    this.RAPIER = await import('@dimforge/rapier3d');
    const loadTime = performance.now() - start;

    console.log(`Rapier loaded in ${loadTime.toFixed(2)}ms`);

    this.world = new this.RAPIER.World({ x: 0, y: -9.81, z: 0 });
    console.log('Physics world created successfully');

  } catch (error) {
    console.error('Detailed error:', error);
    console.error('Error stack:', error.stack);
  }
}

Network Tab Verification

In browser dev tools, check that:

  • WASM file loads without 404 errors
  • File size is reasonable (~1.4MB for Rapier)
  • Loading time is acceptable for your use case

Conclusion

Integrating Rapier physics with TypeScript and Vite requires specific configuration, but once properly set up, it provides excellent performance and developer experience. The key is avoiding fallback patterns and addressing WASM integration directly with the right tools.

Key Takeaways

  1. Use dynamic imports for all WASM modules
  2. Configure Vite properly with WASM plugins
  3. Handle async initialization correctly
  4. Test both dev and production builds
  5. Maintain TypeScript safety throughout

With these patterns, you can confidently integrate Rapier physics into any TypeScript web project without the common pitfalls that plague WASM integration.


Have questions or improvements? Email me at andy@greenrobot.com