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
4 changes: 3 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,9 @@ jobs:
- run: bun run games:clone
env:
GAMES_CLONE_TOKEN: ${{ secrets.GAMES_CLONE_TOKEN }}
- run: bun test packages apps/dev scripts
# Browser and physics suites cold-start Chromium/WASM; on a loaded runner the first test in a
# file can pass Bun's 5 s default and cascade through the file. Tests with their own timeout keep it.
- run: bun test --timeout 20000 packages apps/dev scripts

web-build:
runs-on: ubuntu-latest
Expand Down
3 changes: 3 additions & 0 deletions changes/doctor-silent-game.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
### Added

- `jgengine doctor` flags a silent game: one that neither declares `defineGame({ audio })` cues nor calls `ctx.game.audio.play`/`loop`. Four reference games shipped without a single sound; the fix text points at procedural `SoundDef` synth patches, which need no asset files.
3 changes: 2 additions & 1 deletion packages/jgengine/src/create.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,8 @@ describe("writeGame", () => {
}

const failures = diagnose(dir).filter(
(finding) => !finding.ok && !finding.label.includes("art-direction") && !finding.label.includes("skeleton"),
(finding) =>
!finding.ok && !finding.label.includes("art-direction") && !finding.label.includes("skeleton") && !finding.label.includes("has sound"),
);
expect(failures).toEqual([]);
});
Expand Down
7 changes: 7 additions & 0 deletions packages/jgengine/src/doctor.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,13 @@ describe("diagnose", () => {
expect(failingLabels(dir)).toContain("Tailwind @source covers @jgengine/react and @jgengine/shell");
});

test("flags a silent game until it declares or plays audio cues", () => {
const dir = scaffold();
expect(failingLabels(dir)).toContain("game has sound (declares audio cues)");
writeFileSync(join(dir, "src", "game", "sfx.ts"), 'export const cue = (ctx: { game: { audio: { play(id: string): void } } }) => ctx.game.audio.play("build");\n');
expect(failingLabels(dir)).not.toContain("game has sound (declares audio cues)");
});

test("flags an aliased component folder that index.css never scans", () => {
const dir = scaffold();
const tsconfigPath = join(dir, "tsconfig.json");
Expand Down
5 changes: 5 additions & 0 deletions packages/jgengine/src/doctor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -254,6 +254,11 @@ export function diagnose(dir: string): Finding[] {
if (existsSync(modelsPath)) related.push(readFileSync(modelsPath, "utf8"));
const assetsPath = join(dir, "src", "game", "assets.ts");
if (existsSync(assetsPath)) related.push(readFileSync(assetsPath, "utf8"));
findings.push({
ok: /\baudio\s*:/.test(configText) || /\baudio\.(play|loop)\(/.test(readSourceText(join(dir, "src"))),
label: "game has sound (declares audio cues)",
fix: 'a silent game reads unfinished — declare cues as procedural SoundDef synth patches (no asset files needed) in defineGame({ audio: { sounds } }) and call ctx.game.audio.play(id) on accepted actions: build, hit, purchase, objective, win/lose',
});
const prototype = assessPrototypeLook(related);
findings.push({
ok: !prototype.isPrototype,
Expand Down
Loading