openvibers/openvibe.games Commits

Apply Prettier formatting to drifted sources, tests and CI workflow

OP OpenVibers committed 4 Oct 2026 · f9fb616c27 · 22 files, +275 −93

Asked: zod (npm) is installed on this machine now. Earlier work in OpenVibe.Games (job games-t8-steps-4-5-openvibe-games-075246) could not build or test without it: "restore the repository’s locked pnpm workspace dependencies, including workspace links, to unblock typecheck and ov test". Build OpenVibe.Ga…
Harness job after-install-npm-zod-games-192514.

Changes

ddiff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
iindex 3078bcb..7bd8baf 100644
---- a/.github/workflows/ci.yml
++++ b/.github/workflows/ci.yml
@@@ -8,10 +8,10 @@ jobs:
test:
uses: OpenVibers/OpenVibe.Shared/.github/workflows/test.yml@66b76f868ea8228aad7811309eafef3726b036f3
with:
-- package-manager: pnpm # version from package.json "packageManager"
++ package-manager: pnpm # version from package.json "packageManager"
install: pnpm install --frozen-lockfile
test: pnpm typecheck && pnpm lint && pnpm test
-- syntax-dirs: '' # TypeScript: typecheck covers it
++ syntax-dirs: '' # TypeScript: typecheck covers it
extra: cd apps/server && npx openvibe-contracts-check --service games --src src
# Secret scan (gitleaks) and dependency audit, shared by every OpenVibers repo.
security:
ddiff --git a/apps/client/src/game/placementGhost.ts b/apps/client/src/game/placementGhost.ts
iindex 952256a..26e47b3 100644
---- a/apps/client/src/game/placementGhost.ts
++++ b/apps/client/src/game/placementGhost.ts
@@@ -4,7 +4,12 @@ import type { Mesh } from '@babylonjs/core/Meshes/mesh.js'
import type { Scene } from '@babylonjs/core/scene.js'
import { CollisionLayer, type PhysicsWorld } from '@openvibe/physics'
import { ZoneIndex, type ZoneRules } from '@openvibe/gameplay'
--import { getMapOverride, terrainHeight, type ContentRegistry, type WorldShape } from '@openvibe/content'
++import {
++ getMapOverride,
++ terrainHeight,
++ type ContentRegistry,
++ type WorldShape,
++} from '@openvibe/content'
import { v3addScaled, vec3 } from '@openvibe/shared'
import { meshForShape } from '../render/sceneSetup.js'
ddiff --git a/apps/client/src/main.ts b/apps/client/src/main.ts
iindex f7c193e..1527ff5 100644
---- a/apps/client/src/main.ts
++++ b/apps/client/src/main.ts
@@@ -431,7 +431,15 @@ async function start(): Promise<void> {
hud.setStatus('connecting…')
try {
-- await connection.connect(gameSocketUrl(), identity.token, name, appearance, content.digest, slot, sso)
++ await connection.connect(
++ gameSocketUrl(),
++ identity.token,
++ name,
++ appearance,
++ content.digest,
++ slot,
++ sso,
++ )
} catch {
// Refused or timed out (a page opened during a restart): wait for the server, then reload.
hud.setStatus('could not reach the server — retrying automatically…')
ddiff --git a/apps/client/src/net/connection.test.ts b/apps/client/src/net/connection.test.ts
iindex af54a17..756ed1c 100644
---- a/apps/client/src/net/connection.test.ts
++++ b/apps/client/src/net/connection.test.ts
@@@ -14,14 +14,18 @@ describe('client content handshake', () => {
it('sends the local definition digest and protocol version in hello', () => {
expect(helloForContent('Player', defaultAppearance(), 0, digest)).toMatchObject({
-- t: 'hello', v: PROTOCOL_VERSION, contentDigest: digest,
++ t: 'hello',
++ v: PROTOCOL_VERSION,
++ contentDigest: digest,
})
})
it('turns a mismatched welcome into a typed reject before state applies', () => {
expect(welcomeContentMismatch({ ...welcome, contentDigest: 'v2-00000000' }, digest)).toEqual({
-- t: 'reject', reason: 'content_mismatch',
-- clientDigest: digest, serverDigest: 'v2-00000000',
++ t: 'reject',
++ reason: 'content_mismatch',
++ clientDigest: digest,
++ serverDigest: 'v2-00000000',
})
})
})
ddiff --git a/apps/client/src/pages/pageIcon.test.ts b/apps/client/src/pages/pageIcon.test.ts
iindex 484c3a6..29b1d84 100644
---- a/apps/client/src/pages/pageIcon.test.ts
++++ b/apps/client/src/pages/pageIcon.test.ts
@@@ -19,6 +19,8 @@ describe('page icon', () => {
it('public/favicon.svg exists and is an SVG', () => {
expect(existsSync(client('public/favicon.svg'))).toBe(true)
-- expect(readFileSync(client('public/favicon.svg'), 'utf8')).toMatch(/^<svg xmlns="http:\/\/www\.w3\.org\/2000\/svg"/)
++ expect(readFileSync(client('public/favicon.svg'), 'utf8')).toMatch(
++ /^<svg xmlns="http:\/\/www\.w3\.org\/2000\/svg"/,
++ )
})
})
ddiff --git a/apps/server/src/architectureAudit.audit.ts b/apps/server/src/architectureAudit.audit.ts
iindex 30c4d54..3c9c9cf 100644
---- a/apps/server/src/architectureAudit.audit.ts
++++ b/apps/server/src/architectureAudit.audit.ts
@@@ -77,7 +77,7 @@ describe('architecture audit: the server is Babylon-free (ADR-0007)', () => {
})
it('imports no @babylonjs/* anywhere in the server', () => {
-- expect(report(activeHits(/@babylonjs\//))) .toBe('')
++ expect(report(activeHits(/@babylonjs\//))).toBe('')
})
it('drives Rapier (not Havok) and has no Havok loader', () => {
ddiff --git a/apps/server/src/game/interactions.ts b/apps/server/src/game/interactions.ts
iindex e635f90..26453bb 100644
---- a/apps/server/src/game/interactions.ts
++++ b/apps/server/src/game/interactions.ts
@@@ -25,7 +25,15 @@ import {
} from '@openvibe/gameplay'
import type { ItemDef } from '@openvibe/content'
import { CollisionLayer } from '@openvibe/physics'
--import { asEntityId, qfromYaw, qrotateVecInv, quat, v3dist, vec3, type Vec3 } from '@openvibe/shared'
++import {
++ asEntityId,
++ qfromYaw,
++ qrotateVecInv,
++ quat,
++ v3dist,
++ vec3,
++ type Vec3,
++} from '@openvibe/shared'
import { viewDirection } from './playerSession.js'
import type { ConstraintRecord, GameWorld } from './gameWorld.js'
import { eyePosition, type PlayerSession } from './playerSession.js'
ddiff --git a/apps/server/src/game/platformIntegration.test.ts b/apps/server/src/game/platformIntegration.test.ts
iindex f85ca48..a216867 100644
---- a/apps/server/src/game/platformIntegration.test.ts
++++ b/apps/server/src/game/platformIntegration.test.ts
@@@ -211,13 +211,19 @@ describe('Games on platform identity, events and mods (real server, restart)', (
const stale = connect(a.game)
stale.conn.identity = { guestKeyHash: hashGuestKey(`stale-${offered ?? 'missing'}`) }
a.game.onMessage(stale.conn, {
-- t: 'hello', v: PROTOCOL_VERSION, slot: 0, name: 'Stale',
-- appearance: defaultAppearance(), ...(offered ? { contentDigest: offered } : {}),
++ t: 'hello',
++ v: PROTOCOL_VERSION,
++ slot: 0,
++ name: 'Stale',
++ appearance: defaultAppearance(),
++ ...(offered ? { contentDigest: offered } : {}),
})
await new Promise((r) => setTimeout(r, 0))
expect(stale.inbox).toContainEqual({
-- t: 'reject', reason: 'content_mismatch',
-- clientDigest: offered ?? null, serverDigest: a.world.content.digest,
++ t: 'reject',
++ reason: 'content_mismatch',
++ clientDigest: offered ?? null,
++ serverDigest: a.world.content.digest,
})
expect(stale.inbox.some((m) => m.t === 'welcome')).toBe(false)
}
@@@ -226,7 +232,11 @@ describe('Games on platform identity, events and mods (real server, restart)', (
const oldBundle = connect(a.game)
oldBundle.conn.identity = { guestKeyHash: hashGuestKey('old-bundle') }
a.game.onMessage(oldBundle.conn, {
-- t: 'hello', v: PROTOCOL_VERSION - 1, slot: 0, name: 'Old', appearance: defaultAppearance(),
++ t: 'hello',
++ v: PROTOCOL_VERSION - 1,
++ slot: 0,
++ name: 'Old',
++ appearance: defaultAppearance(),
})
await new Promise((r) => setTimeout(r, 0))
expect(oldBundle.inbox).toContainEqual({ t: 'reject', reason: 'protocol_mismatch' })
ddiff --git a/apps/server/src/game/systems/sessions.ts b/apps/server/src/game/systems/sessions.ts
iindex cb5144f..3242660 100644
---- a/apps/server/src/game/systems/sessions.ts
++++ b/apps/server/src/game/systems/sessions.ts
@@@ -94,10 +94,14 @@ export class SessionsSystem implements System {
return
}
if (msg.contentDigest !== serverDigest) {
-- conn.send(encodeServerMessage({
-- t: 'reject', reason: 'content_mismatch',
-- clientDigest: msg.contentDigest ?? null, serverDigest,
-- }))
++ conn.send(
++ encodeServerMessage({
++ t: 'reject',
++ reason: 'content_mismatch',
++ clientDigest: msg.contentDigest ?? null,
++ serverDigest,
++ }),
++ )
conn.close(4013, 'content_mismatch')
return
}
ddiff --git a/apps/server/src/mods/contentPack.test.ts b/apps/server/src/mods/contentPack.test.ts
iindex 0937b0c..0b8751c 100644
---- a/apps/server/src/mods/contentPack.test.ts
++++ b/apps/server/src/mods/contentPack.test.ts
@@@ -46,7 +46,9 @@ describe('games-content@2 definitions', () => {
expect(capabilitiesUsedBy(mapPack)).toEqual([CAP_DEFINE])
const result = validateContentPack(mapPack, createContent())
expect(result.ok).toBe(false)
-- expect(!result.ok && result.errors).toContain('map is defined by the built-in pack and cannot be replaced by a mod')
++ expect(!result.ok && result.errors).toContain(
++ 'map is defined by the built-in pack and cannot be replaced by a mod',
++ )
expect(validateContentPack({ map: { id: 'broken' } }, createContent()).ok).toBe(false)
})
it('needs games.def.define for any non-empty defs section', () => {
ddiff --git a/apps/server/src/mods/contentPack.ts b/apps/server/src/mods/contentPack.ts
iindex bfe6062..747a891 100644
---- a/apps/server/src/mods/contentPack.ts
++++ b/apps/server/src/mods/contentPack.ts
@@@ -121,7 +121,8 @@ const validateSchema = ajv.compile(CONTENT_PACK_SCHEMA)
/** Which capability each pack section needs. */
export function capabilitiesUsedBy(pack: ContentPack): string[] {
const used: string[] = []
-- if (pack.map || Object.values(pack.defs ?? {}).some((list) => (list ?? []).length > 0)) used.push(CAP_DEFINE)
++ if (pack.map || Object.values(pack.defs ?? {}).some((list) => (list ?? []).length > 0))
++ used.push(CAP_DEFINE)
if ((pack.announcements ?? []).length > 0) used.push(CAP_ANNOUNCE)
if ((pack.props ?? []).length > 0) used.push(CAP_PLACE_PROP)
return used
@@@ -195,7 +196,8 @@ export async function loadDefinitionPacks(
for (const mod of await mods.list()) {
if (mod.status !== 'enabled') continue
const pack = mod.pack as unknown as ContentPackV2
-- if (!pack.map && Object.values(pack.defs ?? {}).every((list) => (list ?? []).length === 0)) continue
++ if (!pack.map && Object.values(pack.defs ?? {}).every((list) => (list ?? []).length === 0))
++ continue
const granted = (await mods.grants(mod.id)).some(
(g) => g.capability === CAP_DEFINE && g.revokedAt === null,
)
ddiff --git a/apps/server/src/net/networkAuth.test.ts b/apps/server/src/net/networkAuth.test.ts
iindex da0e830..9525696 100644
---- a/apps/server/src/net/networkAuth.test.ts
++++ b/apps/server/src/net/networkAuth.test.ts
@@@ -12,7 +12,8 @@ import { canEditMap, rankOf, resolveNetworkUser } from './networkAuth.js'
*/
const b64 = (o: unknown): string => Buffer.from(JSON.stringify(o)).toString('base64url')
--const token = (claims: Record<string, unknown>): string => `${b64({ alg: 'RS256' })}.${b64(claims)}.sig`
++const token = (claims: Record<string, unknown>): string =>
++ `${b64({ alg: 'RS256' })}.${b64(claims)}.sig`
let server: Server
let url: string
@@@ -35,18 +36,38 @@ describe('rank from the staff map', () => {
expect(rankOf({ role: 'admin' })).toBe('admin')
expect(rankOf({ role: 'admin', is_owner: true })).toBe('owner')
expect(rankOf({ role: 'user', is_owner: true })).toBe(null)
-- expect(rankOf({ role: 'global_mod', staff_caps: ['staff.games.manage'], staff_map: '1.1.0' })).toBe('admin')
-- expect(rankOf({ role: 'admin', staff_caps: ['staff.moderation.chat'], staff_map: '1.1.0' })).toBe('moderator')
-- expect(canEditMap('owner') && canEditMap('admin') && !canEditMap('moderator') && !canEditMap(null)).toBe(true)
++ expect(
++ rankOf({ role: 'global_mod', staff_caps: ['staff.games.manage'], staff_map: '1.1.0' }),
++ ).toBe('admin')
++ expect(
++ rankOf({ role: 'admin', staff_caps: ['staff.moderation.chat'], staff_map: '1.1.0' }),
++ ).toBe('moderator')
++ expect(
++ canEditMap('owner') && canEditMap('admin') && !canEditMap('moderator') && !canEditMap(null),
++ ).toBe(true)
})
it('reads the accepted token and prefers the account role after a change', async () => {
-- account = { id: 7, username: 'goosely', role: 'admin', subject_id: 'usr_01J8Z3Q4R5S6T7V8W9X0Y1Z2A3' }
-- expect((await resolveNetworkUser(url, token({ role: 'admin', is_owner: true })))?.rank).toBe('owner')
++ account = {
++ id: 7,
++ username: 'goosely',
++ role: 'admin',
++ subject_id: 'usr_01J8Z3Q4R5S6T7V8W9X0Y1Z2A3',
++ }
++ expect((await resolveNetworkUser(url, token({ role: 'admin', is_owner: true })))?.rank).toBe(
++ 'owner',
++ )
// a username is not the owner claim
expect((await resolveNetworkUser(url, token({ role: 'admin' })))?.rank).toBe('admin')
account = { id: 8, username: 'demoted', role: 'user' }
-- expect((await resolveNetworkUser(url, token({ role: 'admin', staff_caps: ['staff.games.manage'], staff_map: '1.1.0' })))?.rank).toBe(null)
++ expect(
++ (
++ await resolveNetworkUser(
++ url,
++ token({ role: 'admin', staff_caps: ['staff.games.manage'], staff_map: '1.1.0' }),
++ )
++ )?.rank,
++ ).toBe(null)
account = { id: 9, username: 'promoted', role: 'admin' }
expect((await resolveNetworkUser(url, token({ role: 'user' })))?.rank).toBe('admin')
})
@@@ -57,11 +78,17 @@ describe('rank from the staff map', () => {
const walk = (dir: string): void => {
for (const name of readdirSync(dir)) {
const f = join(dir, name)
-- if (statSync(f).isDirectory()) { walk(f); continue }
++ if (statSync(f).isDirectory()) {
++ walk(f)
++ continue
++ }
if (!/\.ts$/.test(name) || name.endsWith('.test.ts')) continue
-- readFileSync(f, 'utf8').split('\n').forEach((line, i) => {
-- if (/OWNER_USERNAME|\brole\s*[!=]==?\s*['"](admin|global_mod|moderator)['"]/.test(line)) offenders.push(`${relative(root, f)}:${i + 1}`)
-- })
++ readFileSync(f, 'utf8')
++ .split('\n')
++ .forEach((line, i) => {
++ if (/OWNER_USERNAME|\brole\s*[!=]==?\s*['"](admin|global_mod|moderator)['"]/.test(line))
++ offenders.push(`${relative(root, f)}:${i + 1}`)
++ })
}
}
walk(root)
ddiff --git a/apps/server/src/net/sharedAssets.test.ts b/apps/server/src/net/sharedAssets.test.ts
iindex 6b91f89..3945fb2 100644
---- a/apps/server/src/net/sharedAssets.test.ts
++++ b/apps/server/src/net/sharedAssets.test.ts
@@@ -41,7 +41,12 @@ describe('/shared (D42)', () => {
})
it('serves only the browser files openvibe-shared lists', async () => {
-- for (const p of ['/shared/serve.js', '/shared/../package.json', '/shared/', '/other/navbar.js']) {
++ for (const p of [
++ '/shared/serve.js',
++ '/shared/../package.json',
++ '/shared/',
++ '/other/navbar.js',
++ ]) {
expect((await fetch(base + p)).status).toBe(404)
}
expect((await fetch(`${base}/shared/navbar.js`, { method: 'POST' })).status).toBe(404)
ddiff --git a/apps/server/src/observability/release.test.ts b/apps/server/src/observability/release.test.ts
iindex 3f600f4..78b661a 100644
---- a/apps/server/src/observability/release.test.ts
++++ b/apps/server/src/observability/release.test.ts
@@@ -3,7 +3,14 @@ import { tmpdir } from 'node:os'
import { join } from 'node:path'
import type { IncomingMessage, ServerResponse } from 'node:http'
import { describe, expect, it } from 'vitest'
--import { buildRelease, gitCommit, isDirectLoopback, prometheusText, releaseHandler, wantsPrometheus } from './release.js'
++import {
++ buildRelease,
++ gitCommit,
++ isDirectLoopback,
++ prometheusText,
++ releaseHandler,
++ wantsPrometheus,
++} from './release.js'
const SHA = 'a'.repeat(40)
function checkout(kind: 'loose' | 'packed' | 'detached'): string {
@@@ -13,7 +20,8 @@ function checkout(kind: 'loose' | 'packed' | 'detached'): string {
if (kind === 'detached') writeFileSync(join(root, '.git', 'HEAD'), `${SHA}\n`)
else writeFileSync(join(root, '.git', 'HEAD'), 'ref: refs/heads/main\n')
if (kind === 'loose') writeFileSync(join(root, '.git', 'refs', 'heads', 'main'), `${SHA}\n`)
-- if (kind === 'packed') writeFileSync(join(root, '.git', 'packed-refs'), `# pack-refs\n${SHA} refs/heads/main\n`)
++ if (kind === 'packed')
++ writeFileSync(join(root, '.git', 'packed-refs'), `# pack-refs\n${SHA} refs/heads/main\n`)
return root
}
@@@ -28,18 +36,32 @@ describe('release manifest (D43)', () => {
})
it('builds the shared manifest shape; RELEASE_SHA wins', () => {
-- const m = buildRelease(checkout('loose'), { env: {}, now: () => new Date('2026-09-24T00:00:00Z') })
++ const m = buildRelease(checkout('loose'), {
++ env: {},
++ now: () => new Date('2026-09-24T00:00:00Z'),
++ })
expect(m.service).toBe('games')
expect(m.release).toBe(SHA.slice(0, 12))
expect(m.components.server).toEqual({ kind: 'server', version: SHA.slice(0, 12) })
expect(m.booted_at).toBe('2026-09-24T00:00:00.000Z')
-- expect(buildRelease('/nowhere', { env: { RELEASE_SHA: 'b'.repeat(40) } }).release).toBe('b'.repeat(12))
++ expect(buildRelease('/nowhere', { env: { RELEASE_SHA: 'b'.repeat(40) } }).release).toBe(
++ 'b'.repeat(12),
++ )
})
it('answers GET /release.json only', () => {
-- const handle = releaseHandler(buildRelease('/nowhere', { env: { RELEASE_SHA: 'c'.repeat(40) } }))
++ const handle = releaseHandler(
++ buildRelease('/nowhere', { env: { RELEASE_SHA: 'c'.repeat(40) } }),
++ )
const out: { status?: number; body?: string | undefined } = {}
-- const res = { writeHead: (s: number) => { out.status = s }, end: (b?: string) => { out.body = b } } as unknown as ServerResponse
++ const res = {
++ writeHead: (s: number) => {
++ out.status = s
++ },
++ end: (b?: string) => {
++ out.body = b
++ },
++ } as unknown as ServerResponse
expect(handle({ url: '/api/x', method: 'GET' } as IncomingMessage, res)).toBe(false)
expect(handle({ url: '/release.json?x=1', method: 'GET' } as IncomingMessage, res)).toBe(true)
expect(out.status).toBe(200)
@@@ -47,7 +69,8 @@ describe('release manifest (D43)', () => {
})
it('metrics are for direct loopback callers only', () => {
-- const req = (addr: string, headers: Record<string, string> = {}) => ({ socket: { remoteAddress: addr }, headers }) as unknown as IncomingMessage
++ const req = (addr: string, headers: Record<string, string> = {}) =>
++ ({ socket: { remoteAddress: addr }, headers }) as unknown as IncomingMessage
expect(isDirectLoopback(req('127.0.0.1'))).toBe(true)
expect(isDirectLoopback(req('::1'))).toBe(true)
expect(isDirectLoopback(req('127.0.0.1', { 'x-forwarded-for': '1.2.3.4' }))).toBe(false)
@@@ -56,9 +79,19 @@ describe('release manifest (D43)', () => {
})
it('serves the metrics snapshot as Prometheus text when a scraper asks', () => {
-- const text = prometheusText({ tick: 42, tickDurationMs: 0.5, sessions: 3, name: 'x', nested: { a: 1 }, bad: Number.NaN })
-- expect(text).toBe('# TYPE games_tick gauge\ngames_tick 42\n# TYPE games_tick_duration_ms gauge\ngames_tick_duration_ms 0.5\n# TYPE games_sessions gauge\ngames_sessions 3\n')
-- const req = (accept: string, url = '/metrics') => ({ headers: { accept }, url }) as unknown as IncomingMessage
++ const text = prometheusText({
++ tick: 42,
++ tickDurationMs: 0.5,
++ sessions: 3,
++ name: 'x',
++ nested: { a: 1 },
++ bad: Number.NaN,
++ })
++ expect(text).toBe(
++ '# TYPE games_tick gauge\ngames_tick 42\n# TYPE games_tick_duration_ms gauge\ngames_tick_duration_ms 0.5\n# TYPE games_sessions gauge\ngames_sessions 3\n',
++ )
++ const req = (accept: string, url = '/metrics') =>
++ ({ headers: { accept }, url }) as unknown as IncomingMessage
expect(wantsPrometheus(req('text/plain;version=0.0.4;q=0.5,*/*;q=0.1'))).toBe(true)
expect(wantsPrometheus(req('application/openmetrics-text;version=1.0.0'))).toBe(true)
expect(wantsPrometheus(req('', '/metrics?format=prometheus'))).toBe(true)
ddiff --git a/apps/server/src/observability/release.ts b/apps/server/src/observability/release.ts
iindex c4bf1d4..ddd39cc 100644
---- a/apps/server/src/observability/release.ts
++++ b/apps/server/src/observability/release.ts
@@@ -61,7 +61,10 @@ export function buildRelease(
root: string,
{ env = process.env, now = () => new Date() }: { env?: NodeJS.ProcessEnv; now?: () => Date } = {},
): ReleaseManifest {
-- const sha = (env.RELEASE_SHA && /^[0-9a-f]{7,40}$/.test(env.RELEASE_SHA) ? env.RELEASE_SHA : gitCommit(root)) ?? 'unknown'
++ const sha =
++ (env.RELEASE_SHA && /^[0-9a-f]{7,40}$/.test(env.RELEASE_SHA)
++ ? env.RELEASE_SHA
++ : gitCommit(root)) ?? 'unknown'
const release = sha.slice(0, 12)
const req = createRequire(import.meta.url)
const packages: Record<string, string> = {}
@@@ -78,7 +81,10 @@ export function buildRelease(
packages,
min_client_release: env.MIN_CLIENT_RELEASE ?? null,
mixed_version_window_hours: 24,
-- components: { client: { kind: 'script', version: release }, server: { kind: 'server', version: release } },
++ components: {
++ client: { kind: 'script', version: release },
++ server: { kind: 'server', version: release },
++ },
schema_generation: null,
schema_compatible_from: null,
contract_ranges: {},
@@@ -89,8 +95,16 @@ export function buildRelease(
export function releaseHandler(manifest: ReleaseManifest) {
const body = JSON.stringify(manifest)
return (req: IncomingMessage, res: ServerResponse): boolean => {
-- if ((req.url ?? '').split('?')[0] !== '/release.json' || (req.method !== 'GET' && req.method !== 'HEAD')) return false
-- res.writeHead(200, { 'content-type': 'application/json', 'cache-control': 'no-cache', 'access-control-allow-origin': '*' })
++ if (
++ (req.url ?? '').split('?')[0] !== '/release.json' ||
++ (req.method !== 'GET' && req.method !== 'HEAD')
++ )
++ return false
++ res.writeHead(200, {
++ 'content-type': 'application/json',
++ 'cache-control': 'no-cache',
++ 'access-control-allow-origin': '*',
++ })
res.end(req.method === 'HEAD' ? undefined : body)
return true
}
@@@ -112,7 +126,10 @@ export function prometheusText(snapshot: Record<string, unknown>): string {
const lines: string[] = []
for (const [k, v] of Object.entries(snapshot)) {
if (typeof v !== 'number' || !Number.isFinite(v)) continue
-- const name = `games_${k.replace(/([a-z0-9])([A-Z])/g, '$1_$2').replace(/[^A-Za-z0-9_]/g, '_').toLowerCase()}`
++ const name = `games_${k
++ .replace(/([a-z0-9])([A-Z])/g, '$1_$2')
++ .replace(/[^A-Za-z0-9_]/g, '_')
++ .toLowerCase()}`
lines.push(`# TYPE ${name} gauge`, `${name} ${v}`)
}
return `${lines.join('\n')}\n`
ddiff --git a/packages/content/src/index.ts b/packages/content/src/index.ts
iindex 576c759..3954a67 100644
---- a/packages/content/src/index.ts
++++ b/packages/content/src/index.ts
@@@ -53,7 +53,18 @@ export function createContent(packs: ContentPackV2[] = []): ContentRegistry {
factions: FACTIONS,
markets: MARKETS,
jobs: JOBS,
-- world: { id: 'base', name: 'Base', groundHalfExtent: 80, spawnPoint: [0, 1.2, 4], spawnYaw: 0, flatTerrain: true, statics: [], resourceNodes: [], initialProps: [], zones: [] },
++ world: {
++ id: 'base',
++ name: 'Base',
++ groundHalfExtent: 80,
++ spawnPoint: [0, 1.2, 4],
++ spawnYaw: 0,
++ flatTerrain: true,
++ statics: [],
++ resourceNodes: [],
++ initialProps: [],
++ zones: [],
++ },
},
[SCRAPLANDIA_PACK, ...packs],
)
ddiff --git a/packages/content/src/pack.test.ts b/packages/content/src/pack.test.ts
iindex 332f5a3..6ade289 100644
---- a/packages/content/src/pack.test.ts
++++ b/packages/content/src/pack.test.ts
@@@ -188,7 +188,10 @@ describe('contentDigest', () => {
})
it('changes when the pack map changes', () => {
-- const changed = { ...base, world: { ...base.world, spawnPoint: [1, 1.2, 4] as [number, number, number] } }
++ const changed = {
++ ...base,
++ world: { ...base.world, spawnPoint: [1, 1.2, 4] as [number, number, number] },
++ }
expect(contentDigest(changed)).not.toBe(contentDigest(base))
})
})
@@@ -198,11 +201,24 @@ describe('Scraplandia default pack', () => {
// Recorded from main 545f6c1, packages/content/src/defs/scrapcity.ts.
// The released city is an editor-built blank slate: no seeded entity ids.
const expected = {
-- id: 'openvibeville_v2', name: 'Scrap City', flatTerrain: true,
-- groundHalfExtent: 80, spawnPoint: [0, 1.2, 4], spawnYaw: 0,
-- statics: [], resourceNodes: [], initialProps: [],
-- zones: [{ id: 'city', name: 'Scrap City', min: [-20.5, -1, -20.5],
-- max: [20.5, 8, 20.5], rules: { pvp: false, build: false, physgun: true } }],
++ id: 'openvibeville_v2',
++ name: 'Scrap City',
++ flatTerrain: true,
++ groundHalfExtent: 80,
++ spawnPoint: [0, 1.2, 4],
++ spawnYaw: 0,
++ statics: [],
++ resourceNodes: [],
++ initialProps: [],
++ zones: [
++ {
++ id: 'city',
++ name: 'Scrap City',
++ min: [-20.5, -1, -20.5],
++ max: [20.5, 8, 20.5],
++ rules: { pvp: false, build: false, physgun: true },
++ },
++ ],
}
expect(SCRAPLANDIA_PACK.map).toEqual(expected)
expect(createContent().world).toEqual(expected)
ddiff --git a/packages/gameplay/src/movement/determinism.test.ts b/packages/gameplay/src/movement/determinism.test.ts
iindex ba8cdb8..fde631e 100644
---- a/packages/gameplay/src/movement/determinism.test.ts
++++ b/packages/gameplay/src/movement/determinism.test.ts
@@@ -565,7 +565,8 @@ const corpusTerrain = (): BodyDesc => ({
const corpusIsland = (): BodyDesc[] => {
const out: BodyDesc[] = []
for (let x = -1; x <= 1; x++)
-- for (let z = -1; z <= 1; z++) out.push(corpusBox([0.8, 0.8, 0.8], vec3(x * 0.9, 0.4, 6 + z * 0.9)))
++ for (let z = -1; z <= 1; z++)
++ out.push(corpusBox([0.8, 0.8, 0.8], vec3(x * 0.9, 0.4, 6 + z * 0.9)))
return out
}
@@@ -591,7 +592,12 @@ interface CorpusScene {
}
const CORPUS: CorpusScene[] = [
-- { name: 'flat ground', bodies: [corpusFloor()], script: corpusTicks(90, corpusWalk), mockExpressible: true },
++ {
++ name: 'flat ground',
++ bodies: [corpusFloor()],
++ script: corpusTicks(90, corpusWalk),
++ mockExpressible: true,
++ },
{
name: 'stairs',
bodies: [corpusFloor(), ...corpusStairs()],
ddiff --git a/packages/physics/src/conformance.test.ts b/packages/physics/src/conformance.test.ts
iindex 3730f58..ac26972 100644
---- a/packages/physics/src/conformance.test.ts
++++ b/packages/physics/src/conformance.test.ts
@@@ -1356,30 +1356,27 @@ function conformance(a: Adapter): void {
close()
})
-- it.skipIf(maybe('step advances'))(
-- 'step advances exactly the dt it is given',
-- () => {
-- const probe = (dt: number, ticks: number) => {
-- const w = open()
-- const box = w.addBody(dynamicBoxDesc(vec3(0, 900, 0), 0.5, 10))
-- stepFor(w, ticks, dt)
-- const v = pos()
-- w.getLinearVelocity(box, v)
-- close()
-- return v.y
-- }
-- // Rapier honours the dt it is given: 30 steps of 1/30 s and 60 steps
-- // of 1/60 s are the same simulated second (the damping is applied per
-- // step, so they agree to within a few mm/s, not bit-exactly).
-- expect(probe(1 / 30, 30)).toBeCloseTo(probe(1 / 60, 60), 1)
-- // An extra simulated second is very nearly twice the velocity.
-- expect(probe(1 / 30, 60) / probe(1 / 30, 30)).toBeGreaterThan(1.9)
-- expect(probe(1 / 30, 60) / probe(1 / 30, 30)).toBeLessThan(2.1)
-- // One simulated second of this gravity: about -16.5 m/s.
-- expect(probe(1 / 30, 30)).toBeLessThan(-15)
-- expect(probe(1 / 30, 30)).toBeGreaterThan(-17)
-- },
-- )
++ it.skipIf(maybe('step advances'))('step advances exactly the dt it is given', () => {
++ const probe = (dt: number, ticks: number) => {
++ const w = open()
++ const box = w.addBody(dynamicBoxDesc(vec3(0, 900, 0), 0.5, 10))
++ stepFor(w, ticks, dt)
++ const v = pos()
++ w.getLinearVelocity(box, v)
++ close()
++ return v.y
++ }
++ // Rapier honours the dt it is given: 30 steps of 1/30 s and 60 steps
++ // of 1/60 s are the same simulated second (the damping is applied per
++ // step, so they agree to within a few mm/s, not bit-exactly).
++ expect(probe(1 / 30, 30)).toBeCloseTo(probe(1 / 60, 60), 1)
++ // An extra simulated second is very nearly twice the velocity.
++ expect(probe(1 / 30, 60) / probe(1 / 30, 30)).toBeGreaterThan(1.9)
++ expect(probe(1 / 30, 60) / probe(1 / 30, 30)).toBeLessThan(2.1)
++ // One simulated second of this gravity: about -16.5 m/s.
++ expect(probe(1 / 30, 30)).toBeLessThan(-15)
++ expect(probe(1 / 30, 30)).toBeGreaterThan(-17)
++ })
it('a resting dynamic body on the ground settles', () => {
const w = open()
ddiff --git a/packages/protocol/src/codec.test.ts b/packages/protocol/src/codec.test.ts
iindex 74a241e..018a930 100644
---- a/packages/protocol/src/codec.test.ts
++++ b/packages/protocol/src/codec.test.ts
@@@ -9,7 +9,14 @@ describe('protocol codec', () => {
const messages: ClientMessage[] = [
// hello carries no identity field: authentication happens at the
// WebSocket upgrade (ADR-0007 decision 8).
-- { t: 'hello', v: PROTOCOL_VERSION, contentDigest: 'v2-1234abcd', slot: 0, name: 'Tester', appearance: defaultAppearance() },
++ {
++ t: 'hello',
++ v: PROTOCOL_VERSION,
++ contentDigest: 'v2-1234abcd',
++ slot: 0,
++ name: 'Tester',
++ appearance: defaultAppearance(),
++ },
{ t: 'input', seq: 42, moveX: 1, moveZ: -0.5, yawQ: 12345, pitchQ: -4096, intents: 5 },
{ t: 'use', target: 'abc123' },
{ t: 'craft', recipe: 'craft_wooden_crate' },
@@@ -44,16 +51,29 @@ describe('protocol codec', () => {
})
it('decodes an older hello without a digest for a typed join rejection', () => {
-- const old = decodeClientMessage(JSON.stringify({
-- t: 'hello', v: PROTOCOL_VERSION - 1, slot: 0, name: 'Old',
-- appearance: defaultAppearance(),
-- }))
++ const old = decodeClientMessage(
++ JSON.stringify({
++ t: 'hello',
++ v: PROTOCOL_VERSION - 1,
++ slot: 0,
++ name: 'Old',
++ appearance: defaultAppearance(),
++ }),
++ )
expect(old).toMatchObject({ t: 'hello' })
expect(old).not.toHaveProperty('contentDigest')
-- expect(decodeClientMessage(JSON.stringify({
-- t: 'hello', v: PROTOCOL_VERSION, contentDigest: 'garbage',
-- slot: 0, name: 'Bad', appearance: defaultAppearance(),
-- }))).toBeNull()
++ expect(
++ decodeClientMessage(
++ JSON.stringify({
++ t: 'hello',
++ v: PROTOCOL_VERSION,
++ contentDigest: 'garbage',
++ slot: 0,
++ name: 'Bad',
++ appearance: defaultAppearance(),
++ }),
++ ),
++ ).toBeNull()
})
it('refuses unknown intent bits and out-of-range quantised angles', () => {
ddiff --git a/packages/protocol/src/messages/client.ts b/packages/protocol/src/messages/client.ts
iindex 1517260..85f27ed 100644
---- a/packages/protocol/src/messages/client.ts
++++ b/packages/protocol/src/messages/client.ts
@@@ -12,7 +12,10 @@ export const ClientHelloSchema = z.object({
t: z.literal('hello'),
v: z.number().int(),
/** Digest of the client's loaded content definitions. Omitted by old clients. */
-- contentDigest: z.string().regex(/^v[0-9]+-[0-9a-f]{8}$/).optional(),
++ contentDigest: z
++ .string()
++ .regex(/^v[0-9]+-[0-9a-f]{8}$/)
++ .optional(),
/** Character slot under this account (MMO-style, 3 max). */
slot: z.number().int().min(0).max(2).default(0),
name: z.string().min(1).max(24),
ddiff --git a/test/run.js b/test/run.js
iindex b43f319..d085215 100644
---- a/test/run.js
++++ b/test/run.js
@@@ -193,6 +193,8 @@ for (const f of chosen) {
}
for (const tail of tails) console.log(`\n${tail}`)
if (!bad && r.status !== 0) {
-- console.log(`\n── vitest exited ${r.status ?? r.signal} although every file passed ──\n${vitestOut.split('\n').slice(-30).join('\n')}`)
++ console.log(
++ `\n── vitest exited ${r.status ?? r.signal} although every file passed ──\n${vitestOut.split('\n').slice(-30).join('\n')}`,
++ )
}
process.exit(bad || r.status !== 0 ? 1 : 0)