Files
feuerwerk/tests/core/camera.test.ts
T
rootandClaude Opus 5 37fff5dabf Play through the first camera that is switched on
The game view took whatever camera came first in the scene, ignoring the
switch, so `enabled = false` on a camera did nothing and a second camera
never got its turn. `World.activeCamera()` now decides: of the cameras
switched on – the whole parent chain counted, as for any object – the
first in hierarchy order, and none on renders nothing at all. It is asked
per frame, so a graph switching cameras hands the view over immediately.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-09 16:13:30 +02:00

83 lines
3.2 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { describe, expect, it } from 'vitest';
import { CreateAssetCommand } from '../../src/core/commands/assetCommands';
import { SetOverrideCommand } from '../../src/core/commands/sceneObjectCommands';
import { treeOrder } from '../../src/core/model/hierarchy';
import { makeSpriteScene, place, startWorld } from '../helpers';
/** The fixture's scene with a camera asset, and switching a placed object off the way the inspector does. */
function sceneWithCamera() {
const fixture = makeSpriteScene();
const camera = new CreateAssetCommand('camera', 'Cam');
fixture.stack.execute(camera);
return {
...fixture,
cameraId: camera.asset.id,
switchOff: (objectId: string) =>
fixture.stack.execute(new SetOverrideCommand(fixture.sceneId, objectId, 'enabled', false)),
};
}
describe('the camera the game is seen through', () => {
it('skips a camera that is switched off, so nothing is rendered while it is the only one', () => {
const f = sceneWithCamera();
const cam = place(f, f.cameraId, { x: 0, y: 0 });
const { world } = startWorld(f);
expect(world.activeCamera()?.objectId).toBe(cam);
// Switching it off reaches the running world through the same sync an inspector edit uses.
f.switchOff(cam);
world.syncFromModel();
expect(world.activeCamera()).toBeUndefined();
f.stack.undo();
world.syncFromModel();
expect(world.activeCamera()?.objectId).toBe(cam);
});
it('is off as well when a parent above it is off', () => {
const f = sceneWithCamera();
const holder = place(f, f.spriteId, { x: 0, y: 0 });
const cam = place(f, f.cameraId, { x: 0, y: 0 }, holder);
const { world } = startWorld(f);
expect(world.activeCamera()?.objectId).toBe(cam);
f.switchOff(holder);
world.syncFromModel();
expect(world.activeCamera()).toBeUndefined();
});
it('takes the first switched-on one, and hands the view over when that one goes off', () => {
const f = sceneWithCamera();
const first = place(f, f.cameraId, { x: 0, y: 0 });
const second = place(f, f.cameraId, { x: 500, y: 0 });
const { world } = startWorld(f);
expect(world.activeCamera()?.objectId).toBe(first);
f.switchOff(first);
world.syncFromModel();
expect(world.activeCamera()?.objectId).toBe(second);
// And back: the order decides again as soon as both are on.
f.stack.undo();
world.syncFromModel();
expect(world.activeCamera()?.objectId).toBe(first);
f.switchOff(second);
world.syncFromModel();
expect(world.activeCamera()?.objectId).toBe(first);
});
it('reads "first" in hierarchy order – what the panel shows – not in storage order', () => {
const f = sceneWithCamera();
const holder = place(f, f.spriteId, { x: 0, y: 0 });
const stored = place(f, f.cameraId, { x: 500, y: 0 });
const nested = place(f, f.cameraId, { x: 0, y: 0 }, holder);
// The nested camera is stored last but listed first, right below its parent.
expect(f.sceneOf().objects.map((o) => o.id)).toEqual([holder, stored, nested]);
expect(treeOrder(f.sceneOf()).map(({ object }) => object.id)).toEqual([holder, nested, stored]);
const { world } = startWorld(f);
expect(world.activeCamera()?.objectId).toBe(nested);
});
});