This document provides comprehensive technical information for developers who want to understand, modify, or extend the Doomberg Machine codebase.
Doomberg Machine is a browser-based 2D physics puzzle game built with vanilla JavaScript and the Matter.js physics engine. Players create Rube Goldberg machines to doom an NPC character through physics-based interactions.
┌─────────────────────────────────────┐
│ User Interface │
│ (HTML/CSS - Controls & Canvas) │
└────────────┬────────────────────────┘
│
┌────────────▼────────────────────────┐
│ Game Controller (game.js) │
│ - Event Handling │
│ - State Management │
│ - Object Placement │
└────────────┬────────────────────────┘
│
┌────────────▼────────────────────────┐
│ Physics Engine (Matter.js) │
│ - Body Simulation │
│ - Collision Detection │
│ - Constraint System │
└─────────────────────────────────────┘
placeObject()Doomberg-Machine/
├── index.html # Main HTML structure
├── style.css # All styling and animations
├── game.js # Game logic and physics integration
├── matter.min.js # Matter.js physics engine (minified)
├── package.json # Project metadata and dependencies
├── README.md # User-facing documentation
└── docs/ # GitHub Pages documentation
├── gameplay.md # Player guide
├── technical.md # This file
├── architecture.md # Design documentation
└── gameplan.md # Enhancement roadmap
index.htmlstyle.css#gameCanvas) should NOT have CSS background propertygame.jsfunction init() {
// Engine creation
// Renderer setup
// World initialization
// Static objects (ground, walls)
// NPC creation
// Event listener setup
// Collision detection registration
}
Key Responsibilities:
function placeObject(type, x, y) {
// Factory pattern for creating different object types
// Store original position for reset functionality
// Add to physics world
// Track in placedObjects array
}
Supported Object Types:
ball: Circle body with high restitutionbox: Rectangle body with medium densitydomino: Tall thin rectangle, easily toppledramp: Static angled platform (rotatable)platform: Static horizontal ledgeseesaw: Compound body with pivot constraintspring: Circle body with extreme restitution (1.5) for launching objectsexplosive: Circle body with detonation capability on impact, applies radial force to nearby objectsGame States:
State Variables:
let isRunning = false; // Physics simulation status
let isPaused = false; // Pause state (timeScale = 0)
let isSlowMotion = false; // Slow-motion state (timeScale = 0.25)
let isGridEnabled = false; // Grid overlay and snap-to-grid state
let npcDoomed = false; // Victory condition flag
let selectedTool = null; // Currently selected object type
let placedObjects = []; // Array of placed body references
let placedConstraints = []; // Array of constraint references
// Scoring system state
let gameStartTime = 0; // Timestamp when machine starts running
let doomTime = 0; // Time in seconds from start to doom
let collisionCount = 0; // Total collisions during simulation
let currentScore = 0; // Most recent calculated score
let currentStars = 0; // Most recent star rating (1-3)
Events.on(engine, 'collisionStart', (event) => {
// Check if NPC is involved in collision
// Verify sufficient velocity for "doom"
// Trigger doom sequence if conditions met
});
Doom Criteria:
Velocity Calculation:
const velocity = Math.abs(otherBody.velocity.x) + Math.abs(otherBody.velocity.y);
Simulation Speed Control:
engine.timing.timeScale to control simulation speedisPaused and isSlowMotion boolean flagsSpeed Calculation:
function applyTimeScale() {
if (isPaused) {
engine.timing.timeScale = 0; // Paused
} else if (isSlowMotion) {
engine.timing.timeScale = 0.25; // 25% speed
} else {
engine.timing.timeScale = 1.0; // Normal speed
}
}
Features:
Score Calculation: The scoring system evaluates player performance across four metrics, with a combo multiplier applied to the total.
Metrics:
max(0, 500 - ((objectCount - 1) × 20))max(0, 500 - (doomTime × 50))doomTime reduces the bonus by 50 points (e.g., 0.8s → 460, 1s → 450, 2s → 400)1.1 + min((collisionCount - 5) × 0.05, 0.5)Star Rating Calculation:
let stars = 1;
if (score >= 2000) stars = 2;
if (score >= 2800) stars = 3;
Tracking:
gameStartTime: Set when runMachine() is calleddoomTime: Calculated as (Date.now() - gameStartTime) / 1000 when NPC is doomedcollisionCount: Incremented on every collision during gameplayplacedObjects array at scoring time by examining object propertiesDisplay:
Reset Functionality:
Original Position Storage:
body.originalPosition = { x: body.position.x, y: body.position.y };
body.originalAngle = body.angle;
Audio Architecture: The game uses the Web Audio API to generate simple sound effects programmatically without requiring audio files. This provides a lightweight solution with zero external dependencies.
Sound Types:
Implementation:
const audioContext = new (window.AudioContext || window.webkitAudioContext)();
function playSound(type) {
if (!soundEnabled) return;
const oscillator = audioContext.createOscillator();
const gainNode = audioContext.createGain();
// Configure based on sound type
// Create tones using oscillators with frequency/gain envelopes
}
Sound Events:
playSound('place')playSound('collision')playSound('doom')playSound('ui')playSound('ui')User Preferences:
doomberg_sound_enabledloadSoundPreference()Engine Configuration:
engine = Engine.create();
world = engine.world;
world.gravity.y = 1; // Standard gravity
Renderer Configuration:
render = Render.create({
canvas: canvas,
engine: engine,
options: {
width: 1200, // CANVAS_WIDTH
height: 600, // CANVAS_HEIGHT
wireframes: false,
background: '#87CEEB' // Matter.js handles background fill
}
});
Grid Constants:
GRID_SIZE: 40 pixels - Grid cell size for overlay and snap-to-gridImportant: The canvas element should NOT have a CSS background as it will render over the canvas drawing context. Matter.js’s background option fills the canvas properly before rendering physics bodies.
The NPC is positioned to stand on the ground from initialization to prevent physics phase-through issues:
NPC_LEG_OFFSET: 35 pixels - Distance from NPC body center to leg centerNPC_HALF_LEG_HEIGHT: 10 pixels - Half the height of NPC legsconst groundTop = CANVAS_HEIGHT - GROUND_HEIGHT; // 580
const npcY = groundTop - NPC_LEG_OFFSET - NPC_HALF_LEG_HEIGHT; // 535
Architecture:
Save Data Structure:
{
version: 1, // Format version for future compatibility
timestamp: 1708034567890, // Unix timestamp (ms)
name: "My Awesome Machine", // User-provided name
objects: [
{
type: "ball",
x: 259.5,
y: 190.2,
angle: 0
},
{
type: "ramp",
x: 358.0,
y: 289.5,
angle: -0.297 // Radians
},
{
type: "seesaw",
x: 557.0,
y: 388.0,
angle: 0,
seesawId: 0 // For complex objects
}
// ... more objects
]
}
Storage Keys:
doomberg_{designName}doomberg_Test Machine 1Features:
Complex Object Handling:
seesawId is saved in the JSON data and used to avoid duplication during serializationseesawId values are restored when loading and seesawIdCounter is synchronized to prevent ID collisionsError Handling:
Limitations:
Seesaw Constraint:
const constraint = Matter.Constraint.create({
bodyA: pivot, // Static pivot point
bodyB: plank, // Dynamic plank
length: 0, // Zero length = fixed rotation point
stiffness: 0.9 // High stiffness for realistic pivot
});
Matter.js handles the game loop internally through the Runner and Renderer:
// Continuous rendering (always active)
Render.run(render);
// Physics simulation (activated on "Run Machine")
Runner.run(runner, engine);
Frame Rate: 60 FPS (Matter.js default)
Update Order:
init()Initializes the game engine, renderer, world, and event listeners.
createNPC()Creates the NPC as a compound body with multiple parts positioned on the ground.
npc variableCANVAS_HEIGHT - GROUND_HEIGHT = 580groundTop - NPC_LEG_OFFSET - NPC_HALF_LEG_HEIGHT = 545placeObject(type, x, y)Factory function for creating and placing game objects.
type (string): Object type identifierx (number): X coordinatey (number): Y coordinatedeleteObjectAtPosition(x, y)Deletes object at specified position using Matter.js Query.
x (number): X coordinatey (number): Y coordinateapplyExplosionForce(explosionX, explosionY, explosionRadius, explosionForce)Applies radial force to all nearby objects from an explosion center.
explosionX (number): X coordinate of explosion centerexplosionY (number): Y coordinate of explosion centerexplosionRadius (number, optional): Radius of explosion effect (default: 150)explosionForce (number, optional): Force magnitude (default: 0.08)undo()Reverts the most recent action in history.
!isRunning && historyIndex >= 0redo()Re-applies a previously undone action.
!isRunning && historyIndex < actionHistory.length - 1recordAction(action)Records an action in the history for undo/redo.
action (object): Action object with type, objectType, position, etc.updateUndoRedoButtons()Updates the enabled/disabled state of undo/redo buttons.
runMachine()Activates physics simulation and makes NPC dynamic.
!isRunningresetMachine()Restores all objects to original positions.
isRunningclearAll()Removes all placed objects from the world.
deleteObjectAtPosition(x, y)Finds and deletes the object at the specified canvas position.
x (number): Canvas x-coordinatey (number): Canvas y-coordinatedeleteObject() if body founddeleteObject(body)Removes a body from the world, handling compound objects (seesaws).
body (Body): Matter.js body to deleteseesawIddoomNPC()Triggers the victory condition when NPC is hit.
!npcDoomedcalculateAndDisplayScore()Calculates and displays the final score based on performance metrics.
npcDoomed === truecurrentScore and currentStars, displays score modalmax(0, 500 - ((objectCount - 1) × 20))max(0, 500 - (doomTime × 50))uniqueTypes × 1001.1 + min((collisionCount - 5) × 0.05, 0.5) (if collisions > 5)showScoreModal(score, stars, breakdown)Displays the score modal with detailed breakdown.
score (number): Final calculated scorestars (number): Star rating (1-3)breakdown (array): Array of scoring breakdown objectstogglePause()Toggles simulation pause state.
isRunningisPaused, applies timeScale, updates UIapplyTimeScale() to set engine.timing.timeScale to 0 (paused) or appropriate speedtoggleSlowMotion()Toggles slow-motion mode (25% speed).
isRunningisSlowMotion, applies timeScale if not paused, updates UIapplyTimeScale()Applies appropriate timeScale based on current state.
engine.timing.timeScale based on isPaused and isSlowMotion00.251.0toggleGrid()Toggles the grid overlay and snap-to-grid functionality.
isGridEnabled statesaveContraption(name)Saves current contraption design to localStorage.
name (string): Name for the saved design (max 30 chars)doomberg_{name}loadContraption(name)Loads a contraption design from localStorage.
name (string): Name of saved design to loadplaceObject() for each saved object, restores angles for rampslistSavedContraptions()Gets list of all saved contraption names.
doomberg_deleteContraption(name)Deletes a saved contraption from localStorage.
name (string): Name of design to deleterefreshSavedList()Updates the saved designs dropdown menu.
<select id="savedList"> with saved design namesgetObjectType(body)Helper function to determine object type from a Matter.js body.
body (Body): Matter.js bodynormalizeAngle(angle)Normalizes an angle to the range [0, 2π).
angle (number): Angle in radianssnapToGrid(x, y)Snaps coordinates to the nearest grid intersection when grid is enabled.
x (number): X coordinatey (number): Y coordinate{x: number, y: number} with snapped or original coordinatesisGridEnabled is false, returns original coordinates unchangedGRID_SIZE constant (40px)drawGrid(context)Draws the grid overlay on the canvas.
context (CanvasRenderingContext2D): Canvas 2D contextisGridEnabled is false, does nothingGRID_SIZE intervalsrotateRamp(angleChange)Updates the current ramp rotation angle.
angleChange (number): Angle delta in radianscurrentRampAngle, displays statusupdateStatus(message)Updates the status display text.
message (string): Status messagecanvas.addEventListener('click', (event) => {
// Convert screen coordinates to canvas coordinates
// Apply snap-to-grid if enabled
// Call placeObject() with selected tool
});
document.addEventListener('keydown', (event) => {
// Q: Rotate ramp counter-clockwise
// E: Rotate ramp clockwise
});
selectedToolrunMachine()resetMachine()clearAll()git clone https://github.com/MW-GC/Doomberg-Machine.git
cd Doomberg-Machine
npm install
python3 -m http.server 8080
npx http-server -p 8080
php -S localhost:8080
4. **Open in browser**:
Navigate to `http://localhost:8080`
### Adding New Object Types
To add a new object type, follow this pattern:
1. **Add button to HTML** (`index.html`):
```html
<button class="tool-btn" data-tool="spring">🌀 Spring</button>
game.js):
```javascript
case ‘spring’:
body = Bodies.circle(x, y, 15, {
restitution: 1.5, // Super bouncy!
density: 0.02,
render: {
fillStyle: ‘#9B59B6’ // Purple
}
});
break;case ‘explosive’: body = Bodies.circle(x, y, 18, { restitution: 0.3, density: 0.06, label: ‘explosive’, // For collision detection render: { fillStyle: ‘#E74C3C’ // Red } }); break;
3. **Test thoroughly**:
- Place multiple instances
- Test physics interactions
- Verify reset functionality
- Check collision detection
### Modifying Physics Parameters
**Gravity Adjustment**:
```javascript
world.gravity.y = 1.5; // Increase for stronger gravity
Doom Threshold:
if (velocity > 5) { // Require faster impact
npcDoomed = true;
doomNPC();
}
Object Properties:
// Make balls heavier and less bouncy
body = Bodies.circle(x, y, 20, {
restitution: 0.5, // Reduce from 0.8
density: 0.08, // Increase from 0.04
render: {
fillStyle: '#FF6B6B'
}
});
Naming Conventions:
camelCaseUPPER_SNAKE_CASECode Style:
State Management:
Supported Browsers:
Required Features:
Object Placement:
Ramp Rotation:
Physics Simulation:
Collision Detection:
Reset/Clear:
Metrics to Monitor:
Performance Tips:
main)_config.yml:
theme: jekyll-theme-cayman
title: Doomberg Machine
description: Build Rube Goldberg machines of doom!
https://username.github.io/repository-nameRecommended Optimizations:
Build Script Example:
{
"scripts": {
"build": "uglifyjs game.js -o game.min.js",
"deploy": "npm run build && git push"
}
}
Issue: Canvas shows background gradient but no game objects
background propertyrender.options.background should be set for proper renderingbackground from #gameCanvasIssue: NPC disappears when simulation starts
createNPC() uses NPC_LEG_OFFSET and NPC_HALF_LEG_HEIGHT constantsIssue: Objects fall through ground
Issue: NPC doesn’t doom
npcDoomed flag isn’t stuckIssue: Reset doesn’t work
Issue: Canvas coordinates incorrect
Enable Matter.js Debug Renderer:
render = Render.create({
// ... other options
options: {
wireframes: true, // Show collision boundaries
showAngleIndicator: true,
showVelocity: true
}
});
Console Logging:
// Log collision events
Events.on(engine, 'collisionStart', (event) => {
console.log('Collision:', event.pairs);
});
// Log object placement
function placeObject(type, x, y) {
console.log(`Placing ${type} at (${x}, ${y})`);
// ... rest of function
}
Version: 1.0.0
Last Updated: February 2026
Maintainers: MW-GC Team
For questions or contributions, please open an issue on GitHub!