Skip to content

Add DRC Destroyer arcade game for routing visualization#9773

Closed
oharboe wants to merge 1 commit intoThe-OpenROAD-Project:masterfrom
Pinata-Consulting:drc-destroyer-2
Closed

Add DRC Destroyer arcade game for routing visualization#9773
oharboe wants to merge 1 commit intoThe-OpenROAD-Project:masterfrom
Pinata-Consulting:drc-destroyer-2

Conversation

@oharboe
Copy link
Copy Markdown
Collaborator

@oharboe oharboe commented Mar 16, 2026

A fun demo with a serious undertone: all of this was done automatically with zero programming in ca. 30 minutes. The trick is now to identify use cases, stakeholders, and minimize the cognitive load for maintainers and users.

This PR is just a self contained demo, an example of a use-case specific emphemral code that has little value in the ORFS repository.

image

This also demonstrates the democratization of the EDA build flow. Do what you need to do for your chip and use-case. The source code for the OpenROAD project is the
user-interface to Claude.

The grander vision (see #9770): today the Qt GUI stands between you and what you want to do. The Qt GUI is an OpenROAD developer debugging tool. The user will use Claude to generate directly what they need — whether that's a standalone HTML visualization, a custom report, or an interactive tool tailored to their specific use-case. No generic GUI can anticipate every user's needs, but an AI that reads the source code can build exactly what's needed on the fly.

The serious undertone to generating a standalone static HTML: we've identified and addressed the deployment use-case — a single file with zero dependencies that anyone can open in a browser, share as a link, or attach to a PR. No servers, no installs, no runtime dependencies. This is how you ship tooling that people actually use.

The unit tests close the feedback loop so Claude can quickly add extension points: write extension, add test, run bazelisk test, iterate — all within a single conversation.

This is just a fun feature and has no practical value.

Also fix Resizer.cc makeScenes call to match updated STA API (StringSeq passed by pointer).

How to run:

Demo mode (instant, no build needed)

python3 test/orfs/mock-array/game.py --demo

With real routing data (builds MockArray, ca. 30 min)

bazelisk run //test/orfs/mock-array:game

Unit tests (49 tests, headless)

bazelisk test //test/orfs/mock-array:test_game

How to release (anyone can play by clicking the link):

bazelisk run //test/orfs/mock-array:game
gh release create game-v1.0
--title "DRC Destroyer"
--notes "Download drc_destroyer.html and open in any browser."
/tmp/drc_destroyer.html

A fun demo with a serious undertone: all of this was done
automatically with zero programming in ca. 30 minutes.
The trick is now to identify use cases, stakeholders, and
minimize the cognitive load for maintainers and users.

This also demonstrates the democratization of the EDA build
flow. Do what you need to do for your chip and use-case.
The source code for the OpenROAD project is the
user-interface to Claude.

The grander vision (see The-OpenROAD-Project#9770): today the Qt GUI stands
between you and what you want to do. The Qt GUI is an
OpenROAD developer debugging tool. The user will use Claude
to generate directly what they need — whether that's a
standalone HTML visualization, a custom report, or an
interactive tool tailored to their specific use-case. No
generic GUI can anticipate every user's needs, but an AI
that reads the source code can build exactly what's needed
on the fly.

The serious undertone to generating a standalone static HTML:
we've identified and addressed the deployment use-case — a
single file with zero dependencies that anyone can open in a
browser, share as a link, or attach to a PR. No servers, no
installs, no runtime dependencies. This is how you ship
tooling that people actually use.

The unit tests close the feedback loop so Claude can quickly
add extension points: write extension, add test, run bazelisk
test, iterate — all within a single conversation.

This is just a fun feature and has no practical value.

Also fix Resizer.cc makeScenes call to match updated STA API
(StringSeq passed by pointer).

How to run:

  # Demo mode (instant, no build needed)
  python3 test/orfs/mock-array/game.py --demo

  # With real routing data (builds MockArray, ca. 30 min)
  bazelisk run //test/orfs/mock-array:game

  # Unit tests (49 tests, headless)
  bazelisk test //test/orfs/mock-array:test_game

How to release (anyone can play by clicking the link):

  bazelisk run //test/orfs/mock-array:game
  gh release create game-v1.0 \
    --title "DRC Destroyer" \
    --notes "Download drc_destroyer.html and open in any browser." \
    /tmp/drc_destroyer.html

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: Øyvind Harboe <oyvind.harboe@zylin.com>
@github-actions
Copy link
Copy Markdown
Contributor

clang-tidy review says "All clean, LGTM! 👍"

Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request introduces a creative and fun "DRC Destroyer" arcade game for visualizing routing DRCs. The implementation, which includes a Python script to generate a standalone HTML/JS game from ODB data, the game logic itself, and comprehensive unit tests, is a great demonstration of the project's capabilities. The addition of documentation and a fix for the Resizer API are also appreciated.

My review focuses on the new code, with a few suggestions to improve robustness, performance, and code cleanliness in the JavaScript and Python files. These are minor points in an otherwise impressive and enjoyable feature.

Comment on lines +37 to +45
function layerZ(name) {
for (const k in LAYER_Z) if (name.includes(k)) return LAYER_Z[k];
return 0;
}

function layerColor(name) {
for (const k in LAYER_COLORS) if (name.includes(k)) return LAYER_COLORS[k];
return "#888";
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

The current implementation of layerZ and layerColor using name.includes(k) can lead to incorrect matches if layer names are substrings of one another (e.g., 'M1' matching 'M10'). To make this more robust, you can iterate through the layer keys sorted by length in descending order. This ensures that longer, more specific keys are checked first.

Suggested change
function layerZ(name) {
for (const k in LAYER_Z) if (name.includes(k)) return LAYER_Z[k];
return 0;
}
function layerColor(name) {
for (const k in LAYER_COLORS) if (name.includes(k)) return LAYER_COLORS[k];
return "#888";
}
function layerZ(name) {
const sortedKeys = Object.keys(LAYER_Z).sort((a, b) => b.length - a.length);
for (const k of sortedKeys) if (name.includes(k)) return LAYER_Z[k];
return 0;
}
function layerColor(name) {
const sortedKeys = Object.keys(LAYER_COLORS).sort((a, b) => b.length - a.length);
for (const k of sortedKeys) if (name.includes(k)) return LAYER_COLORS[k];
return "#888";
}

Comment on lines +239 to +257
function detonate(bx, by) {
const blastR = 0.04;
for (const t of targets) {
if (!t.alive) continue;
const dx = t.nx - bx, dy = t.ny - by;
if (Math.sqrt(dx*dx + dy*dy) < blastR) {
t.alive = false;
score += bonusActive ? 20 : 10;
}
}
for (const w of warningMarkers) {
if (!w.alive) continue;
const dx = w.nx - bx, dy = w.ny - by;
if (Math.sqrt(dx*dx + dy*dy) < blastR) {
w.alive = false;
score += bonusActive ? 20 : 10;
}
}
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

For performance, it's better to compare squared distances to avoid using Math.sqrt in a loop. This is a common optimization in game development.

Suggested change
function detonate(bx, by) {
const blastR = 0.04;
for (const t of targets) {
if (!t.alive) continue;
const dx = t.nx - bx, dy = t.ny - by;
if (Math.sqrt(dx*dx + dy*dy) < blastR) {
t.alive = false;
score += bonusActive ? 20 : 10;
}
}
for (const w of warningMarkers) {
if (!w.alive) continue;
const dx = w.nx - bx, dy = w.ny - by;
if (Math.sqrt(dx*dx + dy*dy) < blastR) {
w.alive = false;
score += bonusActive ? 20 : 10;
}
}
}
function detonate(bx, by) {
const blastR = 0.04;
const blastR2 = blastR * blastR;
for (const t of targets) {
if (!t.alive) continue;
const dx = t.nx - bx, dy = t.ny - by;
if (dx*dx + dy*dy < blastR2) {
t.alive = false;
score += bonusActive ? 20 : 10;
}
}
for (const w of warningMarkers) {
if (!w.alive) continue;
const dx = w.nx - bx, dy = w.ny - by;
if (dx*dx + dy*dy < blastR2) {
w.alive = false;
score += bonusActive ? 20 : 10;
}
}
}

Comment on lines +23 to +24
import subprocess
import sys
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

The subprocess and sys modules are imported but not used in this file. They can be removed.

return villains


def parse_drc_log(log_dir):
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

The total variable is assigned but never used. It can be removed.

@github-actions
Copy link
Copy Markdown
Contributor

clang-tidy review says "All clean, LGTM! 👍"

@oharboe oharboe closed this Mar 16, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant