Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 0 additions & 69 deletions .github/actions/setup-node-pnpm/action.yml

This file was deleted.

5 changes: 4 additions & 1 deletion .github/rulesets/main.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,10 @@
"type": "required_status_checks",
"parameters": {
"strict_required_status_checks_policy": true,
"required_status_checks": [{ "context": "🧪 check", "integration_id": 15368 }]
"required_status_checks": [
{ "context": "🧪 check", "integration_id": 15368 },
{ "context": "📊 compare benchmarks", "integration_id": 15368 }
]
}
}
]
Expand Down
58 changes: 58 additions & 0 deletions .github/workflows/benchmark.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
name: "📊 benchmark"

on:
pull_request:

permissions:
contents: read

concurrency:
group: benchmark-${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true

jobs:
benchmark:
name: "📊 compare benchmarks"
runs-on: ubuntu-24.04
timeout-minutes: 15

steps:
- name: "📥 check out head"
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
repository: ${{ github.event.pull_request.head.repo.full_name }}
ref: ${{ github.event.pull_request.head.sha }}
persist-credentials: false

- name: "⚙️ set up node and pnpm"
uses: devlsh/tools/github/setup@5a43d10d4535a7cf1368acd9313e70d417ac6235

- name: "📥 check out base"
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
repository: ${{ github.event.pull_request.base.repo.full_name }}
ref: ${{ github.event.pull_request.base.sha }}
path: benchmark-base
persist-credentials: false

- name: "📥 install base dependencies"
working-directory: benchmark-base
run: pnpm install --frozen-lockfile

- name: "📦 build base"
working-directory: benchmark-base
run: pnpm build

- name: "📂 stage base output"
run: |
mkdir -p dist
cp -R benchmark-base/dist/. dist/

- name: "📊 capture base benchmark"
run: pnpm bench:full --mode capture --reporter=default

- name: "📦 build head"
run: pnpm build

- name: "📊 compare benchmark"
run: pnpm bench:full --mode compare --reporter=default --reporter=github-actions
4 changes: 2 additions & 2 deletions .github/workflows/demo.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ jobs:
persist-credentials: false

- name: "⚙️ set up node and pnpm"
uses: ./.github/actions/setup-node-pnpm
uses: devlsh/tools/github/setup@5a43d10d4535a7cf1368acd9313e70d417ac6235

- name: "✅ run checks"
run: pnpm check
Expand Down Expand Up @@ -66,7 +66,7 @@ jobs:
persist-credentials: false

- name: "⚙️ set up node and pnpm"
uses: ./.github/actions/setup-node-pnpm
uses: devlsh/tools/github/setup@5a43d10d4535a7cf1368acd9313e70d417ac6235

- name: "📦 build demo"
run: pnpm demo build
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ jobs:
persist-credentials: false

- name: "⚙️ set up node and pnpm"
uses: ./.github/actions/setup-node-pnpm
uses: devlsh/tools/github/setup@5a43d10d4535a7cf1368acd9313e70d417ac6235

- name: "📦 publish"
run: pnpm publish --access public --tag latest --provenance --no-git-checks
4 changes: 1 addition & 3 deletions .github/workflows/validate.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,7 @@ jobs:

- name: "⚙️ set up node and pnpm"
id: setup
uses: ./.github/actions/setup-node-pnpm
with:
cache: "true"
uses: devlsh/tools/github/setup@5a43d10d4535a7cf1368acd9313e70d417ac6235

- name: "✅ run checks"
run: pnpm check
Expand Down
3 changes: 3 additions & 0 deletions .openchamber/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
*
!.gitignore
!project.json
61 changes: 61 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,67 @@ pnpm demo build

For demo behavior changes, run `pnpm demo dev` and inspect the affected controls, terrain and endpoint edits, search options, resizing, and cleanup in the browser. See the [demo guide](demo/README.md) for its controls. Library checks do not replace demo checks.

## Benchmarks

The native Vitest benchmark measures the minified package export, not transformed source. Build current source before each benchmark session:

```sh
pnpm build
```

Run either portfolio with the direct Vitest conveniences:

```sh
pnpm bench:quick
pnpm bench:full
```

Quick has 12 workloads and 48 prebuilt inputs. Full has 27 workloads and 108 prebuilt inputs. Each workload has four deterministic rotations. Benchmarks measure performance only and do not validate path correctness.

Domain files in `bench/fixtures/` declare grid factories and search options. The shared portfolio creates fresh grids and endpoint copies before it prepares the four rotations. Stable workload IDs identify reference files. A changed workload needs a new ID and a new comparison series.

Run the quick cases with an adjacent target:

```sh
pnpm bench:quick --reporter=default -t 'Adjacent target'
```

Run `pnpm test` for the existing library correctness tests. These tests use the source public export and need no build.

The initial settings are provisional minimum floors, not deadlines:

- Quick: 250 ms warmup, at least 16 warmup iterations, 250 ms measurement, and at least 32 measured iterations.
- Full: 250 ms warmup, at least 4 warmup iterations, 250 ms measurement, and at least 32 measured iterations.

The process targets are approximately 10 seconds and 60 seconds. Slow callbacks can exceed these targets to reach the iteration floors. Full uses fewer samples to reduce benchmark compute cost, with less statistical precision. These settings do not guarantee a total runtime or change any timeout. Do not reduce existing workloads or change production code to meet these targets.

Each native result measures a batch of four searches. Native `hz` counts batches per second. Native p99 describes a batch, not a single search. The four rotations are inputs, not independent statistical samples. Allocation and normal GC remain inside search timings.

Divide mean latency by four for mean latency per search. Multiply native `hz` by four for searches per second.

Capture a local reference only when you intend to create or replace it. Then display the native comparison:

```sh
pnpm bench:quick --mode capture
pnpm bench:quick --mode compare --reporter=default
```

Use `bench:full` instead of `bench:quick` for a full reference.

When full sampling settings change, capture a fresh local full reference before comparison, even when workload IDs stay unchanged.

Native reference files do not record sampling settings, so comparison cannot detect this mismatch. Quick references need no replacement for a full-only sampling change. CI captures a fresh base reference with the head harness for each paired run.

Check warmup sensitivity with the same unchanged build:

```sh
pnpm bench:quick --mode double-warmup --reporter=default
```

This mode doubles warmup time without changing measurement floors or reference files.

Use `bench:full` for the full control. Repeat ordinary runs manually on a quiet host. Inspect sample counts and spread across fresh processes.

## Pull Requests

Search existing issues and PRs before proposing duplicate work. Keep your change focused and update affected callers, tests, [README examples](README.md#usage), and contributor instructions together.
Expand Down
21 changes: 21 additions & 0 deletions bench.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { defineConfig } from 'vitest/config';

export default defineConfig(({ mode }) => ({
test: {
benchmark: { include: ['bench/**/*.bench.ts'] },
fileParallelism: false,
maxWorkers: 1,
projects: (['quick', 'full'] as const).map((name) => ({
extends: true,
test: {
name,
pool: 'forks',
testTimeout: 0,
provide: {
benchmarkSuite: name,
benchmarkMode: mode,
},
},
})),
},
}));
24 changes: 24 additions & 0 deletions bench/fixtures/corridor.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { grid } from '../helpers';
import { type CaseDefinition } from './types';

export const cases: CaseDefinition[] = [
{
id: 'corridor.128.numeric.default',
name: 'Straight corridor, end to end | 128×1 | numeric tiles | four-way | default heuristic',
quick: true,
grid: () => grid(128, 1),
options: {
from: [0, 0],
to: [127, 0],
},
},
{
id: 'corridor.1024.numeric.default',
name: 'Straight corridor, end to end | 1024×1 | numeric tiles | four-way | default heuristic',
grid: () => grid(1024, 1),
options: {
from: [0, 0],
to: [1023, 0],
},
},
];
62 changes: 62 additions & 0 deletions bench/fixtures/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import { rotate } from '../helpers';
import { type SearchOptions } from '../../src/types';
import { cases as corridor } from './corridor';
import { cases as maze } from './maze';
import { randomCases, disconnected, corners } from './obstacles';
import { cases as open } from './open';
import { cases as rooms } from './rooms';
import { cases as terrain } from './terrain';
import { cheap, representation } from './tiles';
import { type Suite, type Workload } from './types';

/**
* Stable portfolio order. A changed workload requires a new ID and a new series.
*/
const cases = [
...cheap,
...open,
...corridor,
...maze,
...rooms,
...randomCases,
...disconnected,
...corners,
...terrain,
...representation,
];

export function select(suite: Suite) {
return cases
.values()
.filter((entry) => suite === 'full' || entry.quick)
.map(({ id }) => ({ id }))
.toArray();
}

/**
* Build fresh inputs outside timing. Numeric obstacles use the package's -1 sentinel.
*/
export function fixtures(): Workload[] {
return cases.map(({ id, name, grid, options }) => {
const queries: SearchOptions[] = [
{
...options,
grid: grid(),
from: [...options.from],
to: [...options.to],
},
];

for (let rotation = 1; rotation < 4; rotation++) {
queries.push(rotate(queries[rotation - 1]));
}

return {
id,
name,
queries,
};
});
}

export { type Suite, type Workload } from './types';
Loading