From a4942879b2e2da614a258ad30a78d65f32d4e4b1 Mon Sep 17 00:00:00 2001 From: Thomas Jung Date: Tue, 3 Nov 2026 00:57:23 -0600 Subject: [PATCH 001/165] =?UTF-8?q?docs(CLAUDE.md):=20correct=20draft=5Fad?= =?UTF-8?q?min=5Fintegrity=20gotcha=20=E2=80=94=20flag=20is=20OFF,=20not?= =?UTF-8?q?=20ON=20(#2909/#2993)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Top Gotchas entry described draft_admin_integrity as ON with DB-level cascade delete. That's stale: #2909 disabled the flag for all profiles after it caused the 2026-11-02 PROD outage (flag on + no cascade constraints β†’ orphaned drafts β†’ un-editable missions), and #3003 removed the constraint migration. Rewrite to reflect reality: flag OFF, pre-10.2 delete hooks in use, real table names (ADMINSERVICE_*_DRAFTS), the orphan-sweep script, and that re-enabling the dad strategy is blocked on the unresolved HDI make-graph problem (#2993). πŸ€– Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- CLAUDE.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CLAUDE.md b/CLAUDE.md index 5524b4750..e996b5228 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -159,4 +159,4 @@ The load-bearing few. **Full detail for every relocated item β†’ [tutorials-ims- - **External channels subsystem** β€” `Channels` entity (`db/channels.cds`) is the source of truth; re-ingest via `npm run seed-channels`; directory at `/channels` (baked by `fetch-channels` in `build:all`); verb-lane fill via `npm run promote-channels`; community items never land in `START_HERE`. β†’ [channels.md](docs/developers/reference/channels.md). - **TechEd sessions subsystem (#2312)** β€” new `TechEd*` entities (`db/external/teched.cds`, NOT reused `CommunityEvents`), loaded via a **`using` side-effect** in `db/external-content.cds` (don't delete the import); RainFocus ingest (`POST https://events.rainfocus.com/api/sessions`, headers `rfapiprofileid`/`rfwidgetid` per venue from `RAINFOCUS_{TE26,TEV26}_PROFILE`/`_WIDGET` env β€” live-confirmed defaults baked in, IDs may rotate; speakers embedded in `participants[]`, tracks in `attributevalues[attribute=="Track"]`, offset-paginated); idempotent seed via `scripts/seed-teched.cjs`; public `/build/teched` feed; corrected fetcher + delta cron jobs shipped in #2332; `/teched/` page + island, KG projection, embeddings, and Devtoberfest cross-linking are later units. β†’ [teched.md](docs/developers/reference/teched.md). - **Signed provenance envelope (#2245)** β€” `PROVENANCE_ENVELOPE_ENABLED` (DB config, default OFF, DEV-first); two anonymous Express endpoints (`/content/tutorials/:slug/provenance` and `/.well-known/tutorial-provenance/jwks.json`); signing key = `PROVENANCE_SIGNING_KEY` credstore secret (Ed25519 PKCS8 PEM); rotate via `/admin-ui/#secrets`. Fail-open. β†’ gotchas.md "Signed provenance envelope". -- **Optimized draft deletion `draft_admin_integrity` ON (#2909, CAP 10.2)** β€” `cds.fiori.draft_admin_integrity: true` in package.json; draft discard cascade-deletes draft `CompletionPathItems`/`GroupPathItems` at the DB level, so rollup reconcile now fires via `after('DISCARD','Missions.drafts'/'Groups.drafts')` (the `.drafts` target is required), NOT the per-row child `after('DELETE')` hooks (those stay for direct programmatic deletes). Deleting an active entity right after `draftActivate` can 403 ("a draft exists") until the residual draft is discarded. Constraint migration deploys to HANA (`.hdbconstraint`, `NOT VALIDATED`) BEFORE the flag serves. β†’ gotchas.md "Optimized draft deletion". Re-enable runbook: docs/developers/operations/draft-integrity-reenable.md. +- **Optimized draft deletion `draft_admin_integrity` is OFF (#2909 / #2993, CAP 10.2)** β€” `cds.fiori.draft_admin_integrity: false` in package.json (all profiles). It was briefly enabled but shipped to PROD WITHOUT the required HANA `ON DELETE CASCADE` constraints, orphaning draft rows and making missions un-editable (`DRAFT_ALREADY_EXISTS` / `Not Found`, no discard affordance) β€” the 2026-11-02 PROD outage. Disabled for all profiles; draft deletion uses the pre-10.2 per-row `after('DELETE')` child hooks + `after('DISCARD')` root reconcile. The real draft shadow tables are `ADMINSERVICE_*_DRAFTS` (NOT `com_sap_developers_ims_*_drafts`). Orphan sweep: `scripts/cleanup-draft-orphans.cjs --commit` (deletes draft rows whose `DraftAdministrativeData_DraftUUID` has no admin-data parent; preserves live drafts). Re-enabling the `dad` strategy is blocked on an unresolved HDI make-graph problem for the cascade `.hdbconstraint` (inline FK β†’ error 8250014; standalone constraint β†’ 8212001 on delta deploy) β€” tracked in #2993; the flag must stay OFF until that deploys cleanly to a real HANA container. β†’ gotchas.md "Optimized draft deletion". Re-enable runbook: docs/developers/operations/draft-integrity-reenable.md. From 5ce27f5fd0f6c50dbb9069aa4658c23e4a784897 Mon Sep 17 00:00:00 2001 From: Thomas Jung Date: Tue, 3 Nov 2026 05:43:45 -0600 Subject: [PATCH 002/165] fix(script): prime CDS model before cds.entities() in fix-archcenter-urls (#757 regression) fix-archcenter-urls.cjs called cds.entities() after cds.connect.to('db') with no cds.load(), tripping the #757 guard and failing the unit suite. Under 'cds bind --exec' this throws 'cds.entities is not a function' at runtime. Prime via cds.linked(await cds.load('*')) before connect and resolve the namespace off the linked model, mirroring backfill-categories.cjs. --- scripts/fix-archcenter-urls.cjs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/scripts/fix-archcenter-urls.cjs b/scripts/fix-archcenter-urls.cjs index 69724415c..f3f056883 100644 --- a/scripts/fix-archcenter-urls.cjs +++ b/scripts/fix-archcenter-urls.cjs @@ -40,8 +40,13 @@ const FIXES = [ const COMMIT = process.argv.includes('--commit'); async function main() { + // Prime the CDS model before connect: under `cds bind --exec` the model is + // not auto-linked, so cds.entities() would throw "is not a function" (#757). + // cds.load('*') returns a bare CSN; wrap via cds.linked() so .entities() is + // iterable, and resolve the namespace off the linked model directly (#911). + const linked = cds.linked(await cds.load('*')); await cds.connect.to('db'); - const { HelpDocs, HelpDocConceptLinks } = cds.entities('com.sap.developers.ims.external'); + const { HelpDocs, HelpDocConceptLinks } = linked.entities('com.sap.developers.ims.external'); console.log(`\nfix-archcenter-urls β€” ${COMMIT ? 'COMMIT' : 'DRY-RUN'} mode`); console.log('='.repeat(72)); From 11580bba6cf927bd4bfc8257cb598250201488dc Mon Sep 17 00:00:00 2001 From: Thomas Jung Date: Tue, 3 Nov 2026 08:20:32 -0600 Subject: [PATCH 003/165] fix(concepts): observe HelpDocs in concept-input-digest; retire URL-fix nudge (#2998) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The concept re-render delta phase keyed its digest on *ConceptLinks tables (incl. HelpDocConceptLinks) but not on HelpDocs itself. The archcenter is read live from HelpDocs.url at publish time, so a bare HelpDocs.url/title change never bumped the digest -> concept pages phase-skipped and the corrected URL never re-baked into the served BLOB. - Add a HelpDocs probe (max(modifiedAt)+count(*)) to computeConceptInputDigest. - Retire the manual HelpDocConceptLinks 'digest nudge' workaround in scripts/fix-archcenter-urls.cjs β€” the HelpDocs.url UPDATE now bumps the digest directly via managed modifiedAt. - Add a unit test proving a HelpDocs-only probe change moves the digest. --- scripts/fix-archcenter-urls.cjs | 40 ++++++-------------------- srv/lib/concept-input-digest.js | 5 ++++ test/unit/concept-input-digest.test.js | 10 +++++++ 3 files changed, 23 insertions(+), 32 deletions(-) diff --git a/scripts/fix-archcenter-urls.cjs b/scripts/fix-archcenter-urls.cjs index f3f056883..4460442ef 100644 --- a/scripts/fix-archcenter-urls.cjs +++ b/scripts/fix-archcenter-urls.cjs @@ -46,7 +46,7 @@ async function main() { // iterable, and resolve the namespace off the linked model directly (#911). const linked = cds.linked(await cds.load('*')); await cds.connect.to('db'); - const { HelpDocs, HelpDocConceptLinks } = linked.entities('com.sap.developers.ims.external'); + const { HelpDocs } = linked.entities('com.sap.developers.ims.external'); console.log(`\nfix-archcenter-urls β€” ${COMMIT ? 'COMMIT' : 'DRY-RUN'} mode`); console.log('='.repeat(72)); @@ -54,8 +54,6 @@ async function main() { let planned = 0; let changed = 0; const plan = []; - const presentIds = []; // every target HelpDoc row that EXISTS (for the nudge), - // independent of whether its url needed changing. for (const { sourceId, url: newUrl } of FIXES) { // Metadata-only read (never SELECT description/LOB alongside) β€” LOB-locator safe. @@ -68,7 +66,6 @@ async function main() { console.log(` MISSING ${sourceId}\n (no row β€” skipping; nothing to fix)`); continue; } - presentIds.push(row.ID); if (row.url === newUrl) { console.log(` OK ${sourceId}\n already ${newUrl}`); continue; @@ -94,35 +91,14 @@ async function main() { } console.log(`\nApplied: ${changed} url row(s) updated (${plan.length === 0 ? 'all already correct' : ''}).`); - // Concept-publish digest nudge (#2948 follow-up): the concept re-render phase - // keys its delta on concept-input-digest.js, which probes max(modifiedAt) over - // HelpDocConceptLinks β€” but NOT over HelpDocs. So a bare HelpDocs.url change - // does NOT bump the digest, and a full publish would phase-skip and never - // re-bake the corrected href into the concept BLOB. Touching the linking rows - // bumps their managed modifiedAt (= $now on any UPDATE) so the next full - // publish re-renders the affected concept pages. We write each row's EXISTING - // modelVersion back unchanged β€” content-neutral (no column value altered), - // modifiedAt is the only field that moves. - // - // IMPORTANT: nudge ALL present target HelpDocs' links, not just the ones whose - // url changed this run. On a re-run where urls are already correct (idempotent - // path), the data is right but the served BLOB may still be stale β€” the nudge - // is exactly what forces the re-bake, so it must fire regardless of `changed`. - let touched = 0; - if (presentIds.length > 0) { - const linkRows = await SELECT.from(HelpDocConceptLinks) - .columns('ID', 'modelVersion') - .where({ helpDoc_ID: { in: presentIds } }); - for (const lr of linkRows) { - touched += await UPDATE(HelpDocConceptLinks) - .set({ modelVersion: lr.modelVersion }) // same value β†’ only modifiedAt moves - .where({ ID: lr.ID }); - } - } - console.log(`Digest nudge: touched ${touched} HelpDocConceptLinks row(s) (bumps modifiedAt for concept re-render; values unchanged).`); + // The UPDATE to HelpDocs.url bumps its managed modifiedAt (= $now), which the + // concept-publish digest now observes directly (#2998: HelpDocs probe added to + // concept-input-digest.js). So the next full publish re-renders the affected + // concept pages without any further action β€” the old HelpDocConceptLinks + // "digest nudge" workaround is no longer needed and has been removed. - if (plan.length === 0 && touched === 0) { - console.log('\nNothing to apply and no links to nudge.\n'); + if (plan.length === 0) { + console.log('\nNothing to apply β€” all target urls already correct.\n'); return; } diff --git a/srv/lib/concept-input-digest.js b/srv/lib/concept-input-digest.js index b7c2bb9e1..921433160 100644 --- a/srv/lib/concept-input-digest.js +++ b/srv/lib/concept-input-digest.js @@ -35,6 +35,11 @@ export async function computeConceptInputDigest(db, namespace) { await probe('ApiDocConceptLinks', ext.ApiDocConceptLinks, 'modifiedAt'); await probe('SampleConceptLinks', ext.SampleConceptLinks, 'modifiedAt'); await probe('HelpDocConceptLinks', ext.HelpDocConceptLinks, 'modifiedAt'); + // #2998: the archcenter is read live from HelpDocs.url at publish time + // (concept-detail.ejs:36). A bare HelpDocs.url/title change touches no *ConceptLinks + // row, so without this probe the concept re-render phase-skips and the corrected URL + // never re-bakes. Over-triggers slightly (any HelpDocs change), which is acceptable. + await probe('HelpDocs', ext.HelpDocs, 'modifiedAt'); await probe('CommunityEventConceptLinks', ext.CommunityEventConceptLinks, 'extractedAt'); // no managed await probe('CommunityEvents', ext.CommunityEvents, 'modifiedAt'); return digestFromProbes(probes); diff --git a/test/unit/concept-input-digest.test.js b/test/unit/concept-input-digest.test.js index aa63d4754..2dbd88553 100644 --- a/test/unit/concept-input-digest.test.js +++ b/test/unit/concept-input-digest.test.js @@ -14,4 +14,14 @@ describe('digestFromProbes', () => { expect(digestFromProbes(bumped)).not.toBe(digestFromProbes(base)); }); it('is order-independent', () => expect(digestFromProbes([base[1], base[0]])).toBe(digestFromProbes(base))); + + // #2998: a change confined to the HelpDocs table (e.g. HelpDocs.url repaired, + // no *ConceptLinks row touched) must move the digest so the concept re-render + // phase does not skip. This guards the HelpDocs probe added to + // computeConceptInputDigest. + it('changes when only the HelpDocs probe moves', () => { + const withHelpDocs = [...base, { t: 'HelpDocs', max: '2026-10-01T00:00:00Z', n: 100 }]; + const bumped = [...base, { t: 'HelpDocs', max: '2026-10-02T00:00:00Z', n: 100 }]; + expect(digestFromProbes(bumped)).not.toBe(digestFromProbes(withHelpDocs)); + }); }); From 18c83496d5872e02dbad26e8725c76038873608b Mon Sep 17 00:00:00 2001 From: Thomas Jung Date: Tue, 3 Nov 2026 08:31:50 -0600 Subject: [PATCH 004/165] fix(app-space): average branching-path time instead of summing all tutorials (#3007) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Track 'average time to complete' summed every tutorial across all segments, double-counting branching (fork / parallel-any) paths β€” a two-lane fork reported the combined time of both branches. Extract trackTotalTime into app-space-time.ts: linear segments sum as-is, fork segments average their per-lane totals, parallel-any clusters average their per-tutorial times (sum of optional paths / number of paths). Legacy flat items[] payloads still sum. --- hugo-apps/src/app-space/AppSpace.vue | 8 +- .../__tests__/app-space-time.test.ts | 97 +++++++++++++++++++ hugo-apps/src/app-space/app-space-time.ts | 82 ++++++++++++++++ 3 files changed, 183 insertions(+), 4 deletions(-) create mode 100644 hugo-apps/src/app-space/__tests__/app-space-time.test.ts create mode 100644 hugo-apps/src/app-space/app-space-time.ts diff --git a/hugo-apps/src/app-space/AppSpace.vue b/hugo-apps/src/app-space/AppSpace.vue index 1582a9dd6..0537f01ed 100644 --- a/hugo-apps/src/app-space/AppSpace.vue +++ b/hugo-apps/src/app-space/AppSpace.vue @@ -5,6 +5,7 @@ import { useConfetti } from '../composables/useConfetti' import { checkpointBarcodeUrl as _checkpointBarcodeUrl, shouldShowCheckpointBarcode as _shouldShowCheckpointBarcode } from './checkpoint-barcode' import { isItemUnlocked as _isItemUnlocked, isTutorialOpenable as _isTutorialOpenable } from './app-space-gating' import type { GatingItem } from './app-space-gating' +import { trackTotalTime } from './app-space-time' // ── Configuration ───────────────────────────────────────────────── const eventId = ref(null) @@ -393,10 +394,9 @@ function completedCount(track: AppSpaceTrack): number { return tutorialItems(track).filter(i => i.status === 'COMPLETED').length } -function trackTotalTime(track: AppSpaceTrack): number { - if (hasSegments(track)) return segmentTutorials(track).reduce((sum, i) => sum + i.timeToComplete, 0) - return tutorialItems(track).reduce((sum, i) => sum + i.timeToComplete, 0) -} +// #3007: trackTotalTime now lives in ./app-space-time (imported above). Branching +// (fork / parallel-any) segments are AVERAGED across their optional paths rather +// than summed, so a track's headline time reflects the one path a learner walks. // #2887 authored completion rule: a track is complete when its reward is // satisfiable. A segment carrying a synthetic lane prize (CHECKPOINT+prizeId) diff --git a/hugo-apps/src/app-space/__tests__/app-space-time.test.ts b/hugo-apps/src/app-space/__tests__/app-space-time.test.ts new file mode 100644 index 000000000..a339512bf --- /dev/null +++ b/hugo-apps/src/app-space/__tests__/app-space-time.test.ts @@ -0,0 +1,97 @@ +import { describe, it, expect } from 'vitest' +import { trackTotalTime } from '../app-space-time' +import type { TimeTrack } from '../app-space-time' + +// #3007: a track's "average time to complete" must not sum every tutorial when +// the track branches. Mandatory (linear) segments sum as-is; each branch segment +// contributes the AVERAGE of its alternatives' totals β€” the sum of all optional +// path times divided by the number of optional paths. + +function tut(m: number) { + return { type: 'TUTORIAL' as const, timeToComplete: m } +} + +describe('trackTotalTime (#3007)', () => { + it('sums a single linear segment (20 + 40 = 60)', () => { + const track: TimeTrack = { + segments: [{ order: 1, kind: 'linear', items: [tut(20), tut(40)] }], + } + expect(trackTotalTime(track)).toBe(60) + }) + + it('averages the lanes of a fork segment (lane A 30, lane B 50 => 40)', () => { + const track: TimeTrack = { + segments: [ + { order: 1, kind: 'fork', lanes: [ + { label: 'A', items: [tut(30)] }, + { label: 'B', items: [tut(50)] }, + ] }, + ], + } + expect(trackTotalTime(track)).toBe(40) + }) + + it('averages a fork over per-lane TOTALS, not per-tutorial (lane A 10+20=30, lane B 50 => 40)', () => { + const track: TimeTrack = { + segments: [ + { order: 1, kind: 'fork', lanes: [ + { label: 'A', items: [tut(10), tut(20)] }, + { label: 'B', items: [tut(50)] }, + ] }, + ], + } + expect(trackTotalTime(track)).toBe(40) + }) + + it('adds mandatory linear time to averaged fork time (60 + avg(30,50)=40 => 100)', () => { + const track: TimeTrack = { + segments: [ + { order: 1, kind: 'linear', items: [tut(20), tut(40)] }, + { order: 2, kind: 'fork', lanes: [ + { label: 'A', items: [tut(10), tut(20)] }, + { label: 'B', items: [tut(50)] }, + ] }, + ], + } + expect(trackTotalTime(track)).toBe(100) + }) + + it('averages a parallel-any cluster β€” pick any one (20, 40, 60 => 40)', () => { + const track: TimeTrack = { + segments: [{ order: 1, kind: 'parallel-any', items: [tut(20), tut(40), tut(60)] }], + } + expect(trackTotalTime(track)).toBe(40) + }) + + it('ignores non-tutorial items (synthetic prize checkpoints have timeToComplete 0)', () => { + const track: TimeTrack = { + segments: [ + { order: 1, kind: 'fork', lanes: [ + { label: 'A', items: [tut(40), { type: 'CHECKPOINT', timeToComplete: 0, prizeId: 9 }] }, + { label: 'B', items: [tut(60)] }, + ] }, + ], + } + expect(trackTotalTime(track)).toBe(50) + }) + + it('falls back to flat items[] when a track has no segments (legacy payload)', () => { + const track: TimeTrack = { + items: [tut(15), tut(45), { type: 'CHECKPOINT', timeToComplete: 0 }], + } + expect(trackTotalTime(track)).toBe(60) + }) + + it('returns 0 for an empty track', () => { + expect(trackTotalTime({ segments: [] })).toBe(0) + expect(trackTotalTime({ items: [] })).toBe(0) + expect(trackTotalTime({})).toBe(0) + }) + + it('treats an empty fork lane list as contributing 0 (no divide-by-zero)', () => { + const track: TimeTrack = { + segments: [{ order: 1, kind: 'fork', lanes: [] }], + } + expect(trackTotalTime(track)).toBe(0) + }) +}) diff --git a/hugo-apps/src/app-space/app-space-time.ts b/hugo-apps/src/app-space/app-space-time.ts new file mode 100644 index 000000000..eef859770 --- /dev/null +++ b/hugo-apps/src/app-space/app-space-time.ts @@ -0,0 +1,82 @@ +/** + * Pure time-aggregation helper for the App Space track overview. + * + * Extracted to a sibling module (same pattern as app-space-gating.ts / + * checkpoint-barcode.ts) so AppSpace.vue and tests can share it without mounting + * the full component. + * + * #3007 "Average time to complete": a track's headline time must NOT sum every + * tutorial when the track branches. A branch is an OPTIONAL choice β€” the learner + * walks one path, not all of them β€” so summing every alternative overstates the + * time (the TechEd "Joule Agent Lab" fork reported the combined time of both + * branches). Instead: + * + * - linear (mandatory run of items) β†’ SUM every tutorial + * - parallel-any (pick ANY ONE of N tutorials) β†’ AVERAGE the N tutorial times + * - fork (pick ONE of N lanes/groups) β†’ AVERAGE the N per-lane totals + * + * Track total = sum of linear times + the per-branch averages. "Average" here is + * exactly the issue's definition: sum of the optional paths' times divided by the + * number of optional paths. Only TUTORIAL items carry time (synthetic prize + * CHECKPOINTs are timeToComplete 0), so non-tutorials wash out of the math. + */ + +export interface TimeItem { + type: 'TUTORIAL' | 'CHECKPOINT' | 'PRIZE' + timeToComplete: number + [key: string]: unknown +} + +export interface TimeForkLane { + label: string + items: TimeItem[] +} + +export type TimeSegment = + | { order: number; kind: 'linear'; items: TimeItem[] } + | { order: number; kind: 'parallel-any'; items: TimeItem[] } + | { order: number; kind: 'fork'; lanes: TimeForkLane[] } + +export interface TimeTrack { + items?: TimeItem[] + segments?: TimeSegment[] +} + +/** Sum the time of only the TUTORIAL items in a list. */ +function sumTutorialTime(items: TimeItem[]): number { + return items.reduce((sum, i) => (i.type === 'TUTORIAL' ? sum + (i.timeToComplete || 0) : sum), 0) +} + +/** Mean of a list of numbers; 0 for an empty list (no divide-by-zero). */ +function mean(values: number[]): number { + if (values.length === 0) return 0 + return values.reduce((a, b) => a + b, 0) / values.length +} + +/** Time contributed by one segment, per the #3007 branching rules. */ +function segmentTime(seg: TimeSegment): number { + if (seg.kind === 'fork') { + // Each lane is one optional path; its cost is the sum of its tutorials. + return mean(seg.lanes.map(lane => sumTutorialTime(lane.items))) + } + if (seg.kind === 'parallel-any') { + // Each tutorial is one optional path; the learner completes exactly one. + const times = seg.items.filter(i => i.type === 'TUTORIAL').map(i => i.timeToComplete || 0) + return mean(times) + } + // linear: mandatory β€” every tutorial counts. + return sumTutorialTime(seg.items) +} + +/** + * Total "average time to complete" for a track, in the same unit as + * `item.timeToComplete` (minutes). Prefers the segment structure (#2887) when + * present so branching paths are averaged; falls back to summing the legacy flat + * `items[]` for older payloads that predate segments. + */ +export function trackTotalTime(track: TimeTrack): number { + if (Array.isArray(track.segments) && track.segments.length > 0) { + return track.segments.reduce((sum, seg) => sum + segmentTime(seg), 0) + } + return sumTutorialTime(track.items ?? []) +} From 8a61bd8512588b1038db9f395be5957e3e7102c4 Mon Sep 17 00:00:00 2001 From: Thomas Jung Date: Tue, 3 Nov 2026 08:38:28 -0600 Subject: [PATCH 005/165] refactor(app-space): drop unused index signature on TimeItem; add combined-branches test Code-review follow-up: removing [key: string]: unknown from TimeItem makes AppSpaceTrack cleanly assignable to TimeTrack (fixes a tsc TS2345 at the trackTotalTime call site). Add a test covering multiple branch segments in one track (linear + fork + parallel-any). --- .../app-space/__tests__/app-space-time.test.ts | 16 +++++++++++++++- hugo-apps/src/app-space/app-space-time.ts | 1 - 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/hugo-apps/src/app-space/__tests__/app-space-time.test.ts b/hugo-apps/src/app-space/__tests__/app-space-time.test.ts index a339512bf..06f751f98 100644 --- a/hugo-apps/src/app-space/__tests__/app-space-time.test.ts +++ b/hugo-apps/src/app-space/__tests__/app-space-time.test.ts @@ -63,11 +63,25 @@ describe('trackTotalTime (#3007)', () => { expect(trackTotalTime(track)).toBe(40) }) + it('sums contributions across multiple branch segments (linear 60 + fork avg 40 + cluster avg 30 => 130)', () => { + const track: TimeTrack = { + segments: [ + { order: 1, kind: 'linear', items: [tut(60)] }, + { order: 2, kind: 'fork', lanes: [ + { label: 'A', items: [tut(30)] }, + { label: 'B', items: [tut(50)] }, + ] }, // avg 40 + { order: 3, kind: 'parallel-any', items: [tut(20), tut(40)] }, // avg 30 + ], + } + expect(trackTotalTime(track)).toBe(130) + }) + it('ignores non-tutorial items (synthetic prize checkpoints have timeToComplete 0)', () => { const track: TimeTrack = { segments: [ { order: 1, kind: 'fork', lanes: [ - { label: 'A', items: [tut(40), { type: 'CHECKPOINT', timeToComplete: 0, prizeId: 9 }] }, + { label: 'A', items: [tut(40), { type: 'CHECKPOINT', timeToComplete: 0 }] }, { label: 'B', items: [tut(60)] }, ] }, ], diff --git a/hugo-apps/src/app-space/app-space-time.ts b/hugo-apps/src/app-space/app-space-time.ts index eef859770..5b1a92e83 100644 --- a/hugo-apps/src/app-space/app-space-time.ts +++ b/hugo-apps/src/app-space/app-space-time.ts @@ -24,7 +24,6 @@ export interface TimeItem { type: 'TUTORIAL' | 'CHECKPOINT' | 'PRIZE' timeToComplete: number - [key: string]: unknown } export interface TimeForkLane { From 77205ae8880727812efb4220ada16fb167b6461b Mon Sep 17 00:00:00 2001 From: Thomas Jung Date: Tue, 3 Nov 2026 09:25:58 -0600 Subject: [PATCH 006/165] feat(community): make RSS transport an admin-toggleable DB feature flag (#3011) community.sap.com's Cloudflare bot management now 403-challenges the Khoros LiQL API path (/api/2.0/search) that the default 'khoros' transport relies on, breaking community-blogs-fetch in PROD (all 3 sources errored, fetched=0) and degrading homepage RSS silently. The raw /rss/ feeds remain open, and the curl transport (TLS-fingerprint bypass) still reaches them. Previously the only switch was the RSS_TRANSPORT env var, which reverts on any redeploy that doesn't re-set it. Move the khoros-vs-curl choice into the feature-flags registry as COMMUNITY_RSS_USE_CURL (kind:'db', ImsConfig key flag.community.rss.useCurl, default true -> curl) so operators can flip it from the Admin UI Feature Flag viewer and it survives deploys. - New shared srv/lib/rss-transport-mode.js rssMode(): env RSS_TRANSPORT wins when set (operator override + test seam; 'fetch' stays test-only), else the DB flag. Replaces the two drifted copies in the fetchers. - Covers both community-blogs-fetcher and homepage-rss-fetcher. - Default curl because the khoros path is currently dead. Verified in PROD: after cf set-env RSS_TRANSPORT curl + restart, the next fetch ran fetched=60 inserted=10 updated=47 errored=0. Refs #3011 --- packages/core/feature-flags/registry.js | 7 ++++ srv/lib/community-blogs-fetcher.js | 11 +----- srv/lib/homepage-rss-fetcher.js | 7 +--- srv/lib/rss-transport-mode.js | 33 +++++++++++++++++ test/unit/rss-transport-mode.test.js | 48 +++++++++++++++++++++++++ 5 files changed, 90 insertions(+), 16 deletions(-) create mode 100644 srv/lib/rss-transport-mode.js create mode 100644 test/unit/rss-transport-mode.test.js diff --git a/packages/core/feature-flags/registry.js b/packages/core/feature-flags/registry.js index cd0406fef..1463ee774 100644 --- a/packages/core/feature-flags/registry.js +++ b/packages/core/feature-flags/registry.js @@ -351,6 +351,13 @@ export const FEATURE_FLAGS = [ description: 'Scheduled AI classifier that drains PENDING CommunityBlogPosts rows via SAP Generative AI Hub. Kill switch β€” set false to skip all classification runs. DB-driven config (ImsConfig key flag.community.blogsClassifier); no env var. Default ON.', howToChange: featureFlagUpsert('COMMUNITY_BLOGS_CLASSIFIER_ENABLED', 'flag.community.blogsClassifier'), }, + { + key: 'COMMUNITY_RSS_USE_CURL', label: 'Community RSS transport: curl vs Khoros', category: 'Content', + kind: 'db', imsConfigKey: 'flag.community.rss.useCurl', + valueType: 'boolean', default: true, issue: '#3011', status: 'ga', + description: 'Selects the transport the SAP Community RSS fetchers (community-blogs-fetch + homepage RSS) use against community.sap.com. ON (default) β†’ curl on the raw /rss/ feeds (borrows a TLS fingerprint Cloudflare passes). OFF β†’ the Khoros LiQL JSON API (/api/2.0/search). Default is ON because Cloudflare now 403-challenges the Khoros API path (#3011). The RSS_TRANSPORT env var, when set to curl/khoros/fetch, overrides this flag (fetch is test-only). DB-driven config (ImsConfig key flag.community.rss.useCurl).', + howToChange: featureFlagUpsert('COMMUNITY_RSS_USE_CURL', 'flag.community.rss.useCurl'), + }, { key: 'HOMEPAGE_NEWS_RELEVANCE_ENABLED', label: 'Homepage news relevance scoring', category: 'Content', kind: 'db', imsConfigKey: 'flag.homepage.newsRelevance', diff --git a/srv/lib/community-blogs-fetcher.js b/srv/lib/community-blogs-fetcher.js index aeb3e93c6..ec0d6e993 100644 --- a/srv/lib/community-blogs-fetcher.js +++ b/srv/lib/community-blogs-fetcher.js @@ -22,20 +22,11 @@ import { parseRss, RSS_FETCH_HEADERS } from './rss-parse.js'; import { curlFetch } from './curl-transport.js'; import { buildKhorosUrl, khorosFetch } from './khoros-transport.js'; import { backfillManagedApiQuery } from './community-blog-source-defaults.js'; +import { rssMode } from './rss-transport-mode.js'; import * as metrics from './metrics.js'; const log = cds.log('community-blogs-fetcher'); -// (#1144) Tri-state transport. Default 'khoros' hits the unauthenticated -// Khoros LiQL JSON API (dodges the Cloudflare egress-IP 403 that curl also -// hit β€” see memory cloudflare-ja3-blocks-node-fetch-not-ua). 'curl' reverts -// to the #1145 curl transport (kill switch); 'fetch' uses native fetch on -// the raw RSS feed (local/tests). Toggle: cf set-env RSS_TRANSPORT curl. -function rssMode() { - const m = process.env.RSS_TRANSPORT; - return m === 'curl' || m === 'fetch' ? m : 'khoros'; -} - const TIMEOUT_MS = 5000; const MAX_REDIRECTS = 3; const SNIPPET_MAX_CHARS = 500; diff --git a/srv/lib/homepage-rss-fetcher.js b/srv/lib/homepage-rss-fetcher.js index 025d02e9e..c6dc968e8 100644 --- a/srv/lib/homepage-rss-fetcher.js +++ b/srv/lib/homepage-rss-fetcher.js @@ -15,15 +15,10 @@ import { safeFetch } from './safe-fetch.js'; import { parseRss, RSS_FETCH_HEADERS } from './rss-parse.js'; import { curlFetch } from './curl-transport.js'; import { buildKhorosUrl, khorosFetch } from './khoros-transport.js'; +import { rssMode } from './rss-transport-mode.js'; const log = cds.log('homepage-rss-fetcher'); -// (#1144) Tri-state transport β€” see community-blogs-fetcher.js. Default khoros. -function rssMode() { - const m = process.env.RSS_TRANSPORT; - return m === 'curl' || m === 'fetch' ? m : 'khoros'; -} - // Derive a Khoros LiQL predicate from a community.sap.com RSS feed URL. // board feeds carry ?board.id=; returns null if not derivable. function apiQueryFromFeedUrl(url) { diff --git a/srv/lib/rss-transport-mode.js b/srv/lib/rss-transport-mode.js new file mode 100644 index 000000000..80ede9d07 --- /dev/null +++ b/srv/lib/rss-transport-mode.js @@ -0,0 +1,33 @@ +// srv/lib/rss-transport-mode.js +// +// Single source of truth for which transport the SAP Community RSS fetchers +// use. Shared by community-blogs-fetcher.js and homepage-rss-fetcher.js β€” they +// previously each carried an identical copy of rssMode(), which drifted risk. +// +// Tri-state transport (see khoros-transport.js / curl-transport.js): +// 'khoros' β€” unauthenticated Khoros LiQL JSON API (community.sap.com/api/2.0/search) +// 'curl' β€” curl binary on the raw RSS feed (borrows a TLS fingerprint +// Cloudflare passes; Node undici's ClientHello is JA3/JA4-flagged) +// 'fetch' β€” native fetch on the raw RSS feed (local/tests only) +// +// Resolution order: +// 1. process.env.RSS_TRANSPORT, when set to a recognized value β€” an explicit +// operator override and the mechanism every unit test relies on. 'fetch' +// is ONLY reachable this way (test-only; never a DB-config outcome). +// 2. else the COMMUNITY_RSS_USE_CURL DB feature flag (ImsConfig key +// flag.community.rss.useCurl, default true β†’ 'curl'): the admin-toggleable +// switch between the two production transports. Default is 'curl' because +// Cloudflare now 403-challenges the Khoros API path (#), so 'curl' +// on the still-open /rss/ feeds is the working transport. +// +// isFlagEnabled() is synchronous and fail-safe (returns the declared registry +// default on a cold/errored cache), so rssMode() stays synchronous and never +// throws β€” matching the fetchers' fail-open contract. + +import { isFlagEnabled } from './feature-flags/db-flags.js'; + +export function rssMode() { + const m = process.env.RSS_TRANSPORT; + if (m === 'curl' || m === 'fetch' || m === 'khoros') return m; + return isFlagEnabled('COMMUNITY_RSS_USE_CURL') ? 'curl' : 'khoros'; +} diff --git a/test/unit/rss-transport-mode.test.js b/test/unit/rss-transport-mode.test.js new file mode 100644 index 000000000..4eb3853ff --- /dev/null +++ b/test/unit/rss-transport-mode.test.js @@ -0,0 +1,48 @@ +// test/unit/rss-transport-mode.test.js +// +// Unit coverage for the shared rssMode() resolver (#3010). Verifies the +// env-wins-then-DB-flag precedence that both community-blogs-fetcher and +// homepage-rss-fetcher rely on. + +import { describe, it, expect, afterEach, vi } from 'vitest'; + +// Mock the DB flag reader so the test controls the flag value without a DB. +const isFlagEnabled = vi.fn(); +vi.mock('../../srv/lib/feature-flags/db-flags.js', () => ({ + isFlagEnabled: (...a) => isFlagEnabled(...a), +})); + +const { rssMode } = await import('../../srv/lib/rss-transport-mode.js'); + +afterEach(() => { + delete process.env.RSS_TRANSPORT; + isFlagEnabled.mockReset(); +}); + +describe('rssMode()', () => { + it('env RSS_TRANSPORT wins when set to a recognized value', () => { + for (const v of ['curl', 'khoros', 'fetch']) { + process.env.RSS_TRANSPORT = v; + isFlagEnabled.mockReturnValue(true); + expect(rssMode()).toBe(v); + // Env override must short-circuit before consulting the flag. + expect(isFlagEnabled).not.toHaveBeenCalled(); + } + }); + + it('falls back to the DB flag when env is unset', () => { + isFlagEnabled.mockReturnValue(true); + expect(rssMode()).toBe('curl'); + expect(isFlagEnabled).toHaveBeenCalledWith('COMMUNITY_RSS_USE_CURL'); + + isFlagEnabled.mockReturnValue(false); + expect(rssMode()).toBe('khoros'); + }); + + it('ignores an unrecognized env value and consults the flag', () => { + process.env.RSS_TRANSPORT = 'bogus'; + isFlagEnabled.mockReturnValue(false); + expect(rssMode()).toBe('khoros'); + expect(isFlagEnabled).toHaveBeenCalledWith('COMMUNITY_RSS_USE_CURL'); + }); +}); From d7f258b1a1d4b2c4e64351dac1b138b60a6d7cdc Mon Sep 17 00:00:00 2001 From: Thomas Jung Date: Tue, 3 Nov 2026 09:59:25 -0600 Subject: [PATCH 007/165] docs: design for Kasimir Arcade attract-mode App-Space game (#3009) --- ...6-11-03-kasimir-arcade-app-space-design.md | 100 ++++++++++++++++++ 1 file changed, 100 insertions(+) create mode 100644 docs/superpowers/specs/2026-11-03-kasimir-arcade-app-space-design.md diff --git a/docs/superpowers/specs/2026-11-03-kasimir-arcade-app-space-design.md b/docs/superpowers/specs/2026-11-03-kasimir-arcade-app-space-design.md new file mode 100644 index 000000000..a95b957cb --- /dev/null +++ b/docs/superpowers/specs/2026-11-03-kasimir-arcade-app-space-design.md @@ -0,0 +1,100 @@ +# Design: Kasimir Arcade β€” a 2.5D attract-mode game view for App-Space + +- **Issue:** [sap-tutorials/tutorials-ims#3009](https://github.com/sap-tutorials/tutorials-ims/issues/3009) +- **Date:** 2026-11-03 +- **Status:** Approved direction (brainstorm + spike validated); ready for planning +- **Author:** Thomas Jung (brainstormed with Claude) + +## Intent & the why + +App-Space is the event-floor landing page developers use at SAP events (ASUG Tech Connect, TechEd, etc.): log in β†’ pick a track β†’ complete tutorials β†’ earn a prize. Today it's a static grid of track cards. At a busy booth it does nothing to pull people in from across the room. + +We want a **retro-arcade entry layer** that is eye-catching through **color and motion**: when the kiosk is idle, **Kasimir the Cat** auto-runs through a Sonic-style 2.5D platform level whose zones *are* the event's tracks. The game is the hook that gets someone to walk over; the existing grid is still where the real work happens. Movement is the point β€” a self-playing demo loop is what catches the eye. + +Crucially this is **one URL**. The kiosk loads the normal App-Space page. After a period of inactivity it auto-switches into the game ("attract mode"); any input hands control to the visitor for a few seconds of real play; reaching a track's prize chest (or a prompt) returns them to the real grid, opened on that track, for the actual login-and-launch. Walk away β†’ attract mode resumes. A manual "β–Ά Play / Demo" link lets staff trigger it on demand. + +## What it is + +A new Vue 3 island (`hugo-apps/src/app-space-game/`) rendered as an **overlay on the existing App-Space page** β€” same page, same URL, no route change. It renders a **Three.js** 2.5D side-scroller: a true 3D world with lighting, shadows, parallax depth and a side-scroll camera, with **Kasimir as a "Paper-Mario" billboard** (a flat sprite standing upright in the 3D scene, lit, casting a shadow). It reads the **same data the grid already uses** and reflects the visitor's real progress when logged in. + +### The level reads the track + +Validated in the spike β€” the level is not decoration, it encodes the track structure: + +- **One zone per track** (the ASUG event has 11), each with its own brand-accent color, sky, parallax pillars, and a name banner. Zones chain leftβ†’right into one continuous level. +- **One coin per tutorial**, with the **tutorial title floating above it**. Collecting coins is Kasimir passing the tutorials. +- **Checkpoint** in a track β†’ a lit **archway gate** Kasimir runs through. +- **Prize** β†’ a glowing **treasure chest** at the zone end, labeled with the reward. The chest is the **track selector**: reaching it is how you pick that track. +- **State is visible**: completed tutorials render as green/collected coins and the zone shows **βœ“ CLEARED** (logged-in only); a **locked/gated** track is dimmed, barred, and labeled with its unlock condition (e.g. "πŸ”’ finish ABAP"). + +### The idle β†’ game β†’ handoff loop + +1. Kiosk shows the normal App-Space grid at the usual URL. +2. After **N seconds** of no mouse/touch/key (default **60s**, configurable), the grid dims and the Three.js game fades in β€” Kasimir auto-running the attract loop, looping forever, never stuck. +3. A visitor touches/clicks/keys anything β†’ they control Kasimir (run + jump) for a short platformer run. +4. Reaching a zone's **prize chest**, or tapping a **"Pick this track β†’"** prompt, fades the game out and **returns the real grid** scrolled/opened to that track. +5. Idle again β†’ after the timeout, attract mode resumes. +6. A visible **"β–Ά Demo"** link in the page header triggers the game immediately. + +## How it gets made (decided) + +- **Platform:** New Vue 3 island in `hugo-apps/`, built into `hugo/static/js/`, mounted onto the existing App-Space page. Follows the current island pattern (`createApp(X).mount('#el')`). Reuses the app's existing dark/Horizon theming. +- **Engine:** **Three.js** (2.5D). Chosen for the boldest visual ceiling β€” real 3D depth, lighting, shadows. Spike proved it runs at 60fps in-browser via the standard module build. +- **Kasimir art:** **Paper-Mario billboard** using the **real existing Kasimir art** already in the repo β€” `hugo/static/images/devtoberfest/selfie/thumbnails/Kasimir_Smiles.png` (grey SAP-collar tuxedo cat, transparent PNG, 320Γ—447) as the primary/run pose, with the other selfie poses (`Kasimir_Hat`, `Kasimir_Josh`, `Kasimir_Types`) and `app/display-app-drawings/public/kasimir-tuxedo.png` available for idle/jump/celebrate states. **No commissioned 3D model** β€” lowest art risk, still a clearly-3D scene. (The repo's `kasimir.svg` is a placeholder emoji; do not use it.) +- **Data:** Reuses the existing App-Space payload with **zero backend changes** β€” `GET /api/getAppSpaceProgress(eventLegacyId=N)` (CAP function returning `paths[]` β†’ `segments`/`items` with per-item `unlocked` flags and `authenticated` state), fallback `/app-space-data.json`. The game maps: pathβ†’zone, TUTORIAL itemβ†’titled coin, CHECKPOINTβ†’gate, PRIZEβ†’chest, `unlocked:false`β†’locked zone, completed statusβ†’cleared coin/zone. +- **Reflects real progress:** When `authenticated` is true, completed tutorials render as collected/green and finished tracks as CLEARED. Logged-out: everything shows as available (purely cosmetic), game still fully playable. +- **Single URL / attract mode:** Inactivity timer on the existing page swaps grid↔game; manual trigger link; any input exits attract and gives control; handoff returns to the grid focused on the chosen track. +- **Feature-flagged & progressively rolled out**, DEV-first, consistent with how this repo ships risky UI. Flag OFF = today's grid, untouched. + +## Goals + +- Eye-catching color + motion that reads across a crowded event floor. +- Self-playing attract loop that never gets stuck and conveys which tracks exist. +- The level genuinely represents the track (tutorials, checkpoints, prizes, locked/cleared state), not just themed scenery. +- One URL on the kiosk; graceful, obvious handoff to the real grid for login/launch. +- Reflects the logged-in visitor's real progress. +- Zero backend changes; reuse the existing App-Space data contract. +- Fail-safe: flag OFF, or any game error, falls back to today's grid. + +## Non-goals + +- Not a replacement for the grid. The grid remains the canonical login/pick/launch surface; the game is an entry layer. +- No new game-progress persistence, leaderboard, or scoring stored server-side (score is ephemeral, in-session). +- No commissioned 2D sprite-sheet or 3D model in v1; reuse existing art. +- Not a full game with lives/levels/enemies β€” it's a short, friendly, unloseable run. +- No change to tutorial content, the publish pipeline, or the CAP services. + +## Anti-goals (these mean failure even if it "works") + +- **A kiosk that gets stuck** on the game and can't get back to the grid β€” a visitor must always reach the real flow in seconds. Any input, and a persistent exit affordance, must escape. +- **Shipping stale/broken on a kiosk GPU** β€” if WebGL is unavailable or the scene errors, it must silently fall back to the grid, never show a black screen or a crash. +- **Breaking the current App-Space page** for the (common) case where the game is off or unsupported. +- **Misrepresenting progress** β€” showing a locked track as open, or an incomplete track as CLEARED, would mislead a real attendee. +- Performance so heavy it overheats/janks an all-day booth machine. + +## Constraints + +- Must live behind a feature flag, default OFF, DEV-first rollout (repo convention for risky UI; see CLAUDE.md). +- Must not require any change to CAP services, the HANA content model, or the publish pipeline. +- PROD event cutover is end of July 2026; this is additive and flag-gated, so it can ship and bake on DEV without pressure on that date. +- Reuse existing Kasimir art; no external art dependency that blocks the build. +- Three.js is a new frontend dependency β€” must be bundled through the existing `hugo-apps` Vite build, code-split so it does not bloat non-App-Space pages. +- Kiosk browsers vary; WebGL2 preferred with graceful degradation / fallback-to-grid when absent. +- Admin-UI/approuter bundle-staleness rules do **not** apply (this is a Hugo island, not an approuter admin app), but the island-manifest/hashing build steps (`build:all`) do β€” the merged JS/CSS must ship hashed correctly. + +## Left to the builder / plan + +- Exact mapping code from `getAppSpaceProgress` payload (segments: linear / parallel-any / fork) into the zone/coin/gate/chest scene graph, including fork lanes. +- Inactivity-timer mechanism and the grid↔game mount/unmount choreography on the shared page (overlay vs. swap, transition). +- The handoff target: how "reaching track X's chest" scrolls/opens the real grid to track X (reuse `syncTrackFromUrl` / selectedTrack). +- Feature-flag names and gating (follow existing App-Space / KG flag conventions), plus the WebGL-support / error fallback path. +- Three.js bundling + code-splitting strategy in the Vite `hugo-apps` build; asset-loading of the Kasimir PNGs. +- Attract-AI pathing so Kasimir never stalls and the loop reads well; which poses map to run/jump/idle/celebrate. +- Accessibility & input: keyboard + touch + the manual Demo link; respect `prefers-reduced-motion`; ensure the grid remains the accessible canonical surface. +- Test strategy (unit for the dataβ†’scene mapping; the engine itself is spike-grade and largely manual/visual). +- Where the Demo trigger link lives in the header and its label/affordance. + +## Prior art in-repo (reference, not reuse) + +- `hugo-apps/src/devtoberfest/CatGame.vue` and `hugo-apps/src/ktt/components/KasimirStage.vue` β€” earlier Kasimir games/animations. **Reference only**; this is a fresh, more ambitious build per the brainstorm. +- Spike (throwaway, validated the look): `.superpowers/brainstorm/843-1793720188/content/kasimir-spike-v2b.html`. **Keeping any spike code is a new request** β€” the real island is built fresh through the plan. From bfe3655fbf646d3b8c1c093b5a8216af7986b332 Mon Sep 17 00:00:00 2001 From: Thomas Jung Date: Tue, 3 Nov 2026 10:19:25 -0600 Subject: [PATCH 008/165] docs: fold builder-check decisions into Kasimir Arcade design (#3009) --- ...6-11-03-kasimir-arcade-app-space-design.md | 28 ++++++++++++++++--- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/docs/superpowers/specs/2026-11-03-kasimir-arcade-app-space-design.md b/docs/superpowers/specs/2026-11-03-kasimir-arcade-app-space-design.md index a95b957cb..ae3aab46b 100644 --- a/docs/superpowers/specs/2026-11-03-kasimir-arcade-app-space-design.md +++ b/docs/superpowers/specs/2026-11-03-kasimir-arcade-app-space-design.md @@ -46,6 +46,26 @@ Validated in the spike β€” the level is not decoration, it encodes the track str - **Single URL / attract mode:** Inactivity timer on the existing page swaps grid↔game; manual trigger link; any input exits attract and gives control; handoff returns to the grid focused on the chosen track. - **Feature-flagged & progressively rolled out**, DEV-first, consistent with how this repo ships risky UI. Flag OFF = today's grid, untouched. +## Resolved decisions (builder-check round) + +A fresh builder review of this doc surfaced five gaps. The three below change what gets built and were decided by Tom; the two after are the author's (Claude's) calls. + +**Decided by Tom:** + +1. **Per-zone color = client-side palette.** The `getAppSpaceProgress` payload carries **no** per-track color/theme/accent field (verified against the CDS return type and the segment builders), and we hold the zero-backend-changes rule. So the game derives an 11-entry palette **client-side** from track index/title. Consequence: "the level reads the track" remains literally true for **tutorials, checkpoints, prizes, and cleared/locked state** (all data-driven); the zone *color* is attractive-but-arbitrary, not brand-matched. This is acceptable and is **not** a progress-misrepresentation anti-goal (color is decoration; the structural/state encoding is faithful). A future backend color field could upgrade this without reworking the game. + +2. **Every zone always ends in a selectable goal.** Prizes are not always literal `type:'PRIZE'` items β€” the real payload often models a prize as a **synthetic `type:'CHECKPOINT'` item carrying `prizeId`** (per the #2887/#2889 lane-end pattern), and some tracks have no prize at all. Mapping rule: literal PRIZE β†’ chest; prize-checkpoint (CHECKPOINT + `prizeId`) β†’ chest; **neither β†’ a generic "track goal" flag** that still acts as the selector. Guarantee: **no zone is ever unselectable** (directly serves the kiosk-never-stuck anti-goal). + +3. **Fork / parallel-any tracks flatten to linear for v1.** A side-scroller is linear; real tracks contain `fork` (labeled lanes) and `parallel-any` (any-order) segments. v1 **flattens** every segment to one leftβ†’right line of coins (all tutorials, in segment order). Consequence: branching structure is not visually shown, but **every tutorial still appears** and the attract AI stays simple and never stalls. Faithful lane rendering is explicitly a possible v2, not v1. + +**Author's calls (sensible-builder decisions):** + +4. **Handoff + auth.** Reaching a zone's goal always opens the real grid on that track's detail. If the visitor is **anonymous**, the handoff lands them on the track detail and surfaces the normal sign-in affordance (the grid's existing `startLogin` path) β€” it does **not** dead-end on a locked prize. Attract-driven navigation must not pollute browser history (replace, don't push, when the transition is automatic). + +5. **No change to `AppSpace.vue`.** The game ships as a **new sibling island** (`app-space-game`) mounted alongside the existing App-Space island on the same Hugo page. Attract-mode state, the overlay/dim, and the "β–Ά Demo" trigger live in the **new** island; the existing grid island is left untouched. (If a tiny hook into the grid proves unavoidable during planning, that's a plan-level call, but the intent is zero edits to `AppSpace.vue`.) + +**Build fact confirmed by the review:** the `hugo-apps` Vite config already uses per-island entries that are content-hashed via the island-manifest step, so adding an `app-space-game` entry is a known low-risk pattern. **Three.js must be a dynamic import** so it loads only for this island and never bloats other pages. + ## Goals - Eye-catching color + motion that reads across a crowded event floor. @@ -84,11 +104,11 @@ Validated in the spike β€” the level is not decoration, it encodes the track str ## Left to the builder / plan -- Exact mapping code from `getAppSpaceProgress` payload (segments: linear / parallel-any / fork) into the zone/coin/gate/chest scene graph, including fork lanes. -- Inactivity-timer mechanism and the grid↔game mount/unmount choreography on the shared page (overlay vs. swap, transition). -- The handoff target: how "reaching track X's chest" scrolls/opens the real grid to track X (reuse `syncTrackFromUrl` / selectedTrack). +- Exact mapping code from `getAppSpaceProgress` payload into the zone/coin/gate/chest scene graph, applying the resolved rules above (flatten fork/parallel-any to linear; always-a-selector goal; client-side palette). +- Inactivity-timer mechanism and the grid↔game mount/unmount choreography on the shared page (overlay vs. swap, transition), kept within the new sibling island. +- The handoff target: how "reaching track X's goal" scrolls/opens the real grid to track X (reuse `syncTrackFromUrl` / selectedTrack), with replace-not-push for automatic transitions. - Feature-flag names and gating (follow existing App-Space / KG flag conventions), plus the WebGL-support / error fallback path. -- Three.js bundling + code-splitting strategy in the Vite `hugo-apps` build; asset-loading of the Kasimir PNGs. +- Three.js code-splitting (dynamic import) in the Vite `hugo-apps` build; asset-loading of the Kasimir PNGs. - Attract-AI pathing so Kasimir never stalls and the loop reads well; which poses map to run/jump/idle/celebrate. - Accessibility & input: keyboard + touch + the manual Demo link; respect `prefers-reduced-motion`; ensure the grid remains the accessible canonical surface. - Test strategy (unit for the dataβ†’scene mapping; the engine itself is spike-grade and largely manual/visual). From f477191317660f6b4adcdc7b6425a458460b29eb Mon Sep 17 00:00:00 2001 From: Thomas Jung Date: Tue, 3 Nov 2026 10:23:12 -0600 Subject: [PATCH 009/165] docs: implementation plan for Kasimir Arcade App-Space game (#3009) --- .../2026-11-03-kasimir-arcade-app-space.md | 181 ++++++++++++++++++ 1 file changed, 181 insertions(+) create mode 100644 docs/superpowers/plans/2026-11-03-kasimir-arcade-app-space.md diff --git a/docs/superpowers/plans/2026-11-03-kasimir-arcade-app-space.md b/docs/superpowers/plans/2026-11-03-kasimir-arcade-app-space.md new file mode 100644 index 000000000..61338a782 --- /dev/null +++ b/docs/superpowers/plans/2026-11-03-kasimir-arcade-app-space.md @@ -0,0 +1,181 @@ +# Kasimir Arcade β€” App-Space Attract-Mode Game Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Add a feature-flagged Three.js 2.5D attract-mode game ("Kasimir Arcade") as an entry layer over the existing App-Space page β€” same URL β€” that reads the event's real track data, auto-plays when idle, and hands off to the real grid when a visitor picks a track. + +**Architecture:** A new, self-contained Vue 3 island (`app-space-game`) mounts as a sibling to the existing App-Space island on the same Hugo page. A pure data-mapping module turns the existing `getAppSpaceProgress` payload into a renderer-agnostic "level model" (zones/coins/gates/goals). A Three.js renderer (dynamically imported so it never loads on other pages) draws the level and runs the attract/play loop. The existing `AppSpace.vue` grid island is **not** modified. Everything is gated by a runtime DB flag and falls back to the untouched grid when the flag is OFF, WebGL is unavailable, or the game errors. + +**Tech Stack:** Vue 3 + TypeScript, Three.js (new dep, dynamic import), Vite (`hugo-apps` island build), Hugo layout mount, CAP `ImsConfig`-backed feature flag, Vitest. + +**Spec:** `docs/superpowers/specs/2026-11-03-kasimir-arcade-app-space-design.md` + +## Global Constraints + +- **Zero backend changes to data:** reuse `GET /api/getAppSpaceProgress(eventLegacyId=N)` as-is; fallback `/app-space-data.json`. No change to CAP services, HANA model, or publish pipeline. (The feature *flag* reuses the existing generic `ImsConfig` flag mechanism β€” no new persistence.) +- **Do not modify `hugo-apps/src/app-space/AppSpace.vue`** or its `main.ts`. The game ships as a new sibling island. +- **Three.js must be a dynamic `import()`** inside the island, so it is code-split and only fetched for this island β€” never bloats other pages. +- **Feature flag default OFF, DEV-first.** Flag OFF β‡’ game never mounts/renders; the page is exactly today's grid. +- **Fail-safe:** no WebGL, or any game error β‡’ silently fall back to the grid; never a black screen or crash; never break the existing page. +- **Native HTTP only:** use built-in `fetch` (Node 20+/browser); no axios/node-fetch. +- **Kasimir art is existing repo assets** (served from `hugo/static/...`): primary `images/devtoberfest/selfie/thumbnails/Kasimir_Smiles.png`. Do **not** use `kasimir.svg` (placeholder emoji). +- **Item "done" rule:** an item is completed iff `item.status === 'COMPLETED'` (mirrors the grid). +- **Fork/parallel-any segments flatten to linear** for v1 (all tutorials in segment order). +- **Every zone always ends in a selectable goal** (literal PRIZE β†’ chest; CHECKPOINT with `prizeId` β†’ chest; neither β†’ generic "track goal" that still selects). No zone is ever unselectable. +- **Per-zone color is derived client-side** from zone index (payload has no color field). Color is decoration; structural/state encoding is the faithful part. +- **Automatic navigation uses `history.replaceState` semantics** (replace, not push) so attract-mode transitions don't pollute browser history. + +## Review Focus + +- **Empty / single-zone / 11-zone events** β€” level model must build a valid, playable, looping level for 0 paths (render nothing, stay on grid), 1 path, and the full 11. Covered in Task 2 tests. +- **A path whose only "prize" is a synthetic CHECKPOINT-with-`prizeId`, or no prize at all** β€” must still yield exactly one goal object per zone. Covered in Task 2 tests. +- **Anonymous vs authenticated payload** (`authenticated` flag absent on old payloads) β€” logged-out must render all coins as available (never crash on missing status), logged-in reflects `COMPLETED`. Covered in Task 2 tests. +- **WebGL unavailable / `getContext` returns null on a kiosk GPU** β€” must resolve to "unsupported" and leave the grid visible, not throw. Covered in Task 4 tests. +- **Flag OFF or probe fails (503/network)** β€” island must not render the game and must leave the grid intact. Covered in Task 3 tests. + +--- + +## File Structure + +- `hugo-apps/src/app-space-game/main.ts` β€” island entry; reads mount el, probes flag + WebGL, decides mount-or-bail. +- `hugo-apps/src/app-space-game/AppSpaceGame.vue` β€” Vue shell: owns attract/idle state machine, overlay/dim, "β–Ά Demo" trigger, handoff navigation. Dynamically imports the renderer. +- `hugo-apps/src/app-space-game/level-model.ts` β€” pure functions: payload β†’ `LevelModel` (zones/coins/gates/goals). No Three.js, no DOM. The testable core. +- `hugo-apps/src/app-space-game/feature.ts` β€” flag probe + WebGL-support check (best-effort, never throws). +- `hugo-apps/src/app-space-game/renderer.ts` β€” Three.js scene builder + attract/play loop over a `LevelModel`. Imported dynamically. +- `hugo-apps/src/app-space-game/types.ts` β€” shared types (`LevelModel`, `Zone`, `Coin`, `Goal`, payload shapes reused from app-space). +- `hugo-apps/src/app-space-game/__tests__/level-model.test.ts`, `feature.test.ts`, `AppSpaceGame.test.ts`. +- Modify: `hugo-apps/vite.config.ts` β€” add `'app-space-game'` island entry. +- Modify: `hugo/layouts/app-space/list.html` β€” add sibling mount div + island script tag. + +--- + +## Task 1: Island scaffold + build wiring (mounts, does nothing visible) + +**Files:** +- Create: `hugo-apps/src/app-space-game/main.ts`, `hugo-apps/src/app-space-game/AppSpaceGame.vue`, `hugo-apps/src/app-space-game/types.ts` +- Modify: `hugo-apps/vite.config.ts:302` (add entry near `arcade`), `hugo/layouts/app-space/list.html` +- Test: `hugo-apps/src/app-space-game/__tests__/mount.test.ts` + +**Interfaces:** +- Produces: island entry `app-space-game`; `AppSpaceGame.vue` default export (Vue component) mounting on `#app-space-game`; `types.ts` exporting `AppSpaceItem`, `AppSpaceTrack`, `AppSpaceData` (re-declared to match `hugo-apps/src/app-space/AppSpace.vue` interfaces, lines 53–118) plus `LevelModel`/`Zone`/`Coin`/`Goal` (defined in Task 2). + +- [ ] **Step 1: Write the failing test** β€” `mount.test.ts`: mounting `AppSpaceGame` with no flag/WebGL stubs renders nothing game-related (no ``, grid untouched); asserts component mounts without throwing and exposes a `data-app-space-game` root. +- [ ] **Step 2: Run test, verify it fails** β€” `cd hugo-apps && npx vitest run src/app-space-game/__tests__/mount.test.ts` β€” Expected: FAIL (module not found). +- [ ] **Step 3: Implement** the three files minimally: `types.ts` with the payload interfaces copied to match app-space; `AppSpaceGame.vue` rendering only a hidden root `
`; `main.ts` = `const el=document.getElementById('app-space-game'); if (el) createApp(AppSpaceGame).mount(el)`. Add the Vite entry `'app-space-game': resolve(__dirname, 'src/app-space-game/main.ts')`. Add to the Hugo layout: `
` + ``. +- [ ] **Step 4: Run test, verify it passes.** +- [ ] **Step 5: Commit** β€” `feat(app-space-game): island scaffold + build wiring (#3009)`. + +--- + +## Task 2: Level model β€” payload β†’ LevelModel (the testable core) + +**Files:** +- Create: `hugo-apps/src/app-space-game/level-model.ts` +- Modify: `hugo-apps/src/app-space-game/types.ts` (add model types) +- Test: `hugo-apps/src/app-space-game/__tests__/level-model.test.ts` + +**Interfaces:** +- Consumes: `AppSpaceData`/`AppSpaceTrack`/`AppSpaceItem` from `types.ts`. +- Produces: `buildLevelModel(data: AppSpaceData): LevelModel` where + `LevelModel = { zones: Zone[] }`; + `Zone = { id: string; title: string; colorIndex: number; cleared: boolean; locked: boolean; coins: Coin[]; gates: Gate[]; goal: Goal }`; + `Coin = { title: string; done: boolean; available: boolean }`; + `Gate = { afterCoinIndex: number }`; + `Goal = { kind: 'prize' | 'checkpoint' | 'track'; label: string; prizeId: number | null; trackId: string }`. + Also `zoneColor(colorIndex: number): string` returning a hex from an 11-entry palette (index mod palette length). + +- [ ] **Step 1: Write the failing tests** β€” `level-model.test.ts`, each with spec values: + - empty `paths` β‡’ `zones.length === 0`. + - one linear path, 3 TUTORIAL items β‡’ 1 zone, 3 coins in order, titles preserved. + - fork + parallel-any segments β‡’ coins flattened to one ordered list (count = total tutorials across lanes/clusters). + - path with a literal `type:'PRIZE'` item β‡’ `goal.kind==='prize'`. + - path whose only prize is `type:'CHECKPOINT'` + `prizeId` β‡’ `goal.kind==='checkpoint'`, `prizeId` preserved. + - path with neither β‡’ `goal.kind==='track'`, `goal.trackId` set. + - `authenticated` + some `status:'COMPLETED'` β‡’ those coins `done:true`; zone with all tutorials COMPLETED β‡’ `cleared:true`. + - anonymous payload (no `authenticated`, items lack `status`) β‡’ all coins `available:true, done:false`; no throw. + - `unlocked:false` items β‡’ zone `locked:true`, coins `available:false`. + - CHECKPOINT item mid-list β‡’ a `Gate` with correct `afterCoinIndex`. + - `zoneColor(0..11)` returns distinct hex strings, wraps at palette length. +- [ ] **Step 2: Run tests, verify they fail** β€” `npx vitest run src/app-space-game/__tests__/level-model.test.ts` β€” Expected: FAIL (`buildLevelModel` not defined). +- [ ] **Step 3: Implement `buildLevelModel` and `zoneColor`** in `level-model.ts`. Flatten `segments` (linear/parallel-any/fork lanes) in order to a coin list; TUTORIALβ†’coin, CHECKPOINTβ†’gate unless it carries `prizeId` (then it's the goal); choose goal by the resolved-decision rule; derive `locked`/`cleared`/`done` from `unlocked` and `status==='COMPLETED'`. Pure functions, no DOM/Three. +- [ ] **Step 4: Run tests, verify they pass.** +- [ ] **Step 5: Commit** β€” `feat(app-space-game): level model from app-space payload (#3009)`. + +--- + +## Task 3: Feature gate β€” flag probe + data load + mount decision + +**Files:** +- Create: `hugo-apps/src/app-space-game/feature.ts` +- Modify: `hugo-apps/src/app-space-game/main.ts` (wire the gate), `AppSpaceGame.vue` (fetch + build model on mount) +- Test: `hugo-apps/src/app-space-game/__tests__/feature.test.ts` + +**Interfaces:** +- Produces: + `isGameEnabled(): Promise` β€” best-effort flag probe (reuses the generic `ImsConfig` flag path; returns false on 503/network/absent, never throws). + `isWebGLAvailable(): boolean` β€” creates a throwaway canvas, returns `!!getContext('webgl2'|'webgl')`, false on any error. + `loadAppSpaceData(): Promise` β€” `fetch('/api/getAppSpaceProgress(...)')` with the same eventId resolution the grid uses; fallback `/app-space-data.json`; null on failure. + +- [ ] **Step 1: Write the failing tests** β€” `feature.test.ts`: `isGameEnabled` resolves false on a mocked 503 and on network reject; true on ok. `isWebGLAvailable` returns false when `getContext` is stubbed to null. `loadAppSpaceData` returns parsed data on ok, falls back to the json file on primary failure, null when both fail. (Mock `fetch`/`getContext`.) +- [ ] **Step 2: Run tests, verify they fail.** +- [ ] **Step 3: Implement `feature.ts`** per signatures (model the flag probe on `hugo-apps/src/ktt/lib/server.ts` `isKttEnabled`). Wire `main.ts`: mount the component always, but the component only proceeds to render the game when `isGameEnabled() && isWebGLAvailable() && data` β€” otherwise it stays hidden (grid intact). +- [ ] **Step 4: Run tests, verify they pass.** +- [ ] **Step 5: Commit** β€” `feat(app-space-game): feature gate (flag + webgl + data load) (#3009)`. + +--- + +## Task 4: Three.js renderer + attract/play loop (dynamic import) + +**Files:** +- Create: `hugo-apps/src/app-space-game/renderer.ts` +- Modify: `AppSpaceGame.vue` (dynamic-import the renderer when gate passes), `hugo-apps/package.json` (add `three`) +- Test: `hugo-apps/src/app-space-game/__tests__/renderer.guard.test.ts` + +**Interfaces:** +- Consumes: `LevelModel` (Task 2). +- Produces: `createGame(canvas: HTMLCanvasElement, model: LevelModel, opts: { onPickTrack: (trackId: string) => void }): { start(): void; stop(): void; setPlayerControl(on: boolean): void }`. The renderer builds zones (colored via `zoneColor`), titled coins, gates, goal chests/flags, the Kasimir billboard (texture from the existing PNG path), runs the auto-run attract loop, and calls `onPickTrack` when Kasimir reaches a zone goal under player control. + +- [ ] **Step 1: Write the failing test** β€” `renderer.guard.test.ts` (pure logic only; no real WebGL in Vitest): import `renderer.ts`, assert `createGame` is a function and that calling `stop()` before `start()` does not throw. (Three.js is heavy; keep engine testing manual/visual per spec. This test only guards the module contract.) +- [ ] **Step 2: Run test, verify it fails.** +- [ ] **Step 3: Implement `renderer.ts`** β€” port the validated spike (`.superpowers/brainstorm/843-1793720188/content/kasimir-spike-v2b.html`) into a typed module over a `LevelModel`: lighting, shadows, parallax, per-zone color, titled coins, checkpoint gates, goal object, Kasimir billboard + shadow, camera follow, attract auto-run that loops, `setPlayerControl` to hand over, goal-reached β‡’ `onPickTrack`. Add `three` to `hugo-apps/package.json` deps. In `AppSpaceGame.vue`, `await import('./renderer')` only after the gate passes. +- [ ] **Step 4: Run test, verify it passes**, and run `npx vitest run src/app-space-game` (all island tests green). +- [ ] **Step 5: Commit** β€” `feat(app-space-game): three.js renderer + attract loop (#3009)`. + +--- + +## Task 5: Attract state machine, overlay/dim, Demo trigger, handoff + +**Files:** +- Modify: `hugo-apps/src/app-space-game/AppSpaceGame.vue` +- Test: `hugo-apps/src/app-space-game/__tests__/AppSpaceGame.test.ts` + +**Interfaces:** +- Consumes: `createGame` (Task 4), `isGameEnabled/isWebGLAvailable/loadAppSpaceData` (Task 3), `buildLevelModel` (Task 2). +- Produces: the finished island behavior (no new exported API). + +- [ ] **Step 1: Write the failing tests** β€” `AppSpaceGame.test.ts` (mock `createGame`): (a) with gate stubbed to pass, after the idle timeout (fake timers, default 60000ms) the overlay becomes visible and `game.start()` is called; (b) a simulated input event while attract is showing calls `setPlayerControl(true)` and keeps the overlay; (c) the "β–Ά Demo" trigger is present and clicking it starts the game immediately; (d) `onPickTrack(id)` navigates to the track via `history.replaceState` (assert `location`/`replaceState` spy, not `pushState`) and hides the overlay; (e) with gate stubbed to fail, no overlay, no timer, `createGame` never called. +- [ ] **Step 2: Run tests, verify they fail.** +- [ ] **Step 3: Implement** the idle-timer state machine (reset on mouse/touch/key; respect `prefers-reduced-motion` by not auto-starting), the dim/overlay, the Demo trigger button, and the handoff: `onPickTrack` sets the track in the URL (`?...&track=` style the grid already understands via `syncTrackFromUrl`) using `replaceState` for auto-nav, then hides the overlay so the untouched grid shows selected. Mount into the sibling div from Task 1. +- [ ] **Step 4: Run tests, verify they pass.** +- [ ] **Step 5: Commit** β€” `feat(app-space-game): attract loop, demo trigger, track handoff (#3009)`. + +--- + +## Task 6: Build verification + docs + +**Files:** +- Modify: `docs/developers/architecture/frontend-apps.md` (one-line island entry), CLAUDE.md subsystem one-liner if warranted. +- Test: production island build. + +- [ ] **Step 1:** Run `npm run build:all` (via `scripts/quiet-run.sh`) and verify the `app-space-game` entry appears content-hashed in `hugo/static/js/.vite/manifest.json` and that `three` is code-split into its own chunk (not in other islands' bundles). +- [ ] **Step 2:** Grep the built output to confirm no other island entry gained a `three` import. +- [ ] **Step 3:** Add the one-line island doc entry. +- [ ] **Step 4: Commit** β€” `docs,build(app-space-game): verify code-split build + document island (#3009)`. + +--- + +## Self-review notes + +- **Spec coverage:** attract/idleβ†’gameβ†’handoff (T5), one-URL + Demo trigger (T5), zone-per-track + coins/gates/goal + cleared/locked (T2+T4), reflects-progress (T2), client palette (T2), flatten forks (T2), always-a-selector (T2), zero-backend + reuse payload (T3, Global Constraints), new sibling island / no AppSpace.vue edit (T1, Global Constraints), Three.js dynamic import/code-split (T4+T6), WebGL + flag fallback (T3), fail-safe (T3/T5). All mapped. +- **Type consistency:** `LevelModel`/`Zone`/`Coin`/`Gate`/`Goal` defined once in `types.ts`/Task 2 and consumed unchanged by Tasks 4–5; `createGame`'s `onPickTrack(trackId)` matches the handoff in Task 5; `status==='COMPLETED'` and `unlocked:false` match the grid's own rules. +- **Proportion:** plan is close to the spec's length; code blocks are test names/assertions and signatures, not bodies (the renderer body is the ported spike, left to the implementer). From b01aa9a7ebdd59364171704f2e279b08b3fdcc68 Mon Sep 17 00:00:00 2001 From: Thomas Jung Date: Tue, 3 Nov 2026 10:36:32 -0600 Subject: [PATCH 010/165] feat(app-space-game): island scaffold + build wiring (#3009) --- hugo-apps/src/app-space-game/AppSpaceGame.vue | 11 +++ .../app-space-game/__tests__/mount.test.ts | 27 ++++++ hugo-apps/src/app-space-game/main.ts | 5 ++ hugo-apps/src/app-space-game/types.ts | 85 +++++++++++++++++++ hugo-apps/vite.config.ts | 1 + hugo/layouts/app-space/list.html | 2 + 6 files changed, 131 insertions(+) create mode 100644 hugo-apps/src/app-space-game/AppSpaceGame.vue create mode 100644 hugo-apps/src/app-space-game/__tests__/mount.test.ts create mode 100644 hugo-apps/src/app-space-game/main.ts create mode 100644 hugo-apps/src/app-space-game/types.ts diff --git a/hugo-apps/src/app-space-game/AppSpaceGame.vue b/hugo-apps/src/app-space-game/AppSpaceGame.vue new file mode 100644 index 000000000..798366c73 --- /dev/null +++ b/hugo-apps/src/app-space-game/AppSpaceGame.vue @@ -0,0 +1,11 @@ + + + + + diff --git a/hugo-apps/src/app-space-game/__tests__/mount.test.ts b/hugo-apps/src/app-space-game/__tests__/mount.test.ts new file mode 100644 index 000000000..dd3fe55b8 --- /dev/null +++ b/hugo-apps/src/app-space-game/__tests__/mount.test.ts @@ -0,0 +1,27 @@ +// @vitest-environment happy-dom +import { describe, it, expect, vi, beforeEach } from 'vitest' +import { mount } from '@vue/test-utils' + +describe('AppSpaceGame mount scaffold', () => { + beforeEach(() => { + vi.restoreAllMocks() + }) + + it('mounts without throwing and renders data-app-space-game root', async () => { + const AppSpaceGame = (await import('../AppSpaceGame.vue')).default + + const wrapper = mount(AppSpaceGame) + + // Component mounts successfully + expect(wrapper.exists()).toBe(true) + + // Root element has the data attribute + expect(wrapper.element.getAttribute('data-app-space-game')).toBe('') + + // No canvas or game-related content is rendered + expect(wrapper.find('canvas').exists()).toBe(false) + + // The component is hidden/minimal + expect(wrapper.text()).toBe('') + }) +}) diff --git a/hugo-apps/src/app-space-game/main.ts b/hugo-apps/src/app-space-game/main.ts new file mode 100644 index 000000000..b7ecd66b6 --- /dev/null +++ b/hugo-apps/src/app-space-game/main.ts @@ -0,0 +1,5 @@ +import { createApp } from 'vue' +import AppSpaceGame from './AppSpaceGame.vue' + +const el = document.getElementById('app-space-game') +if (el) createApp(AppSpaceGame).mount(el) diff --git a/hugo-apps/src/app-space-game/types.ts b/hugo-apps/src/app-space-game/types.ts new file mode 100644 index 000000000..8dcec132c --- /dev/null +++ b/hugo-apps/src/app-space-game/types.ts @@ -0,0 +1,85 @@ +// Payload interfaces copied from hugo-apps/src/app-space/AppSpace.vue (lines 53–118) +// to avoid cross-island import dependency. + +export interface AppSpaceItem { + imsId: number | null + title: string + type: 'TUTORIAL' | 'CHECKPOINT' | 'PRIZE' + status: string + progress: number + experience: string + timeToComplete: number + url: string + description: string + recordId?: number + unlocked?: boolean + temporarilyUnavailable?: boolean + unavailableReason?: string + prizeId?: number | null +} + +export interface ForkLane { + label: string + items: AppSpaceItem[] +} + +export interface LinearSegment { + order: number + kind: 'linear' + items: AppSpaceItem[] +} + +export interface ClusterSegment { + order: number + kind: 'parallel-any' + items: AppSpaceItem[] +} + +export interface ForkSegment { + order: number + kind: 'fork' + lanes: ForkLane[] +} + +export type AppSpaceSegment = LinearSegment | ClusterSegment | ForkSegment + +export interface AppSpaceTrack { + id: string + title: string + description: string + items: AppSpaceItem[] + segments?: AppSpaceSegment[] +} + +export interface AppSpaceData { + eventId: number + eventName: string + eventDescription?: string + hasLogo?: boolean + authenticated?: boolean + type: string + paths: AppSpaceTrack[] +} + +// Task 2+ interfaces (declared here for future use, not implemented in Task 1) +export interface Zone { + id: string + name: string +} + +export interface Coin { + id: string + value: number +} + +export interface Goal { + id: string + description: string +} + +export interface LevelModel { + id: string + zones: Zone[] + coins: Coin[] + goals: Goal[] +} diff --git a/hugo-apps/vite.config.ts b/hugo-apps/vite.config.ts index 82f6c9e92..776c10a69 100644 --- a/hugo-apps/vite.config.ts +++ b/hugo-apps/vite.config.ts @@ -297,6 +297,7 @@ export default defineConfig({ input: { navigator: resolve(__dirname, 'src/navigator/main.ts'), 'app-space': resolve(__dirname, 'src/app-space/main.ts'), + 'app-space-game': resolve(__dirname, 'src/app-space-game/main.ts'), devtoberfest: resolve(__dirname, 'src/devtoberfest/main.ts'), gameboard: resolve(__dirname, 'src/gameboard/main.ts'), arcade: resolve(__dirname, 'src/arcade/main.ts'), diff --git a/hugo/layouts/app-space/list.html b/hugo/layouts/app-space/list.html index 8b469ea1b..4786ef41d 100644 --- a/hugo/layouts/app-space/list.html +++ b/hugo/layouts/app-space/list.html @@ -1,4 +1,6 @@ {{ define "main" }}
+
+ {{ end }} From 36af9b5010b96c20d8693e66110f13b9a8dd4422 Mon Sep 17 00:00:00 2001 From: Thomas Jung Date: Tue, 3 Nov 2026 10:41:53 -0600 Subject: [PATCH 011/165] feat(app-space-game): level model from app-space payload (#3009) --- .../__tests__/level-model.test.ts | 535 ++++++++++++++++++ hugo-apps/src/app-space-game/level-model.ts | 191 +++++++ hugo-apps/src/app-space-game/types.ts | 32 +- 3 files changed, 747 insertions(+), 11 deletions(-) create mode 100644 hugo-apps/src/app-space-game/__tests__/level-model.test.ts create mode 100644 hugo-apps/src/app-space-game/level-model.ts diff --git a/hugo-apps/src/app-space-game/__tests__/level-model.test.ts b/hugo-apps/src/app-space-game/__tests__/level-model.test.ts new file mode 100644 index 000000000..87e840331 --- /dev/null +++ b/hugo-apps/src/app-space-game/__tests__/level-model.test.ts @@ -0,0 +1,535 @@ +import { describe, it, expect } from 'vitest' +import { buildLevelModel, zoneColor } from '../level-model' +import type { AppSpaceData } from '../types' + +describe('buildLevelModel', () => { + it('empty paths β‡’ zones.length === 0', () => { + const data: AppSpaceData = { + eventId: 1, + eventName: 'Test Event', + type: 'test', + paths: [], + } + const result = buildLevelModel(data) + expect(result.zones).toHaveLength(0) + }) + + it('one linear path, 3 TUTORIAL items β‡’ 1 zone, 3 coins in order, titles preserved', () => { + const data: AppSpaceData = { + eventId: 1, + eventName: 'Test Event', + type: 'test', + paths: [ + { + id: 'track1', + title: 'Track One', + description: '', + items: [], + segments: [ + { + order: 0, + kind: 'linear', + items: [ + { + imsId: 1, + title: 'Tutorial A', + type: 'TUTORIAL', + status: 'NEW', + progress: 0, + experience: '100', + timeToComplete: 30, + url: '', + description: '', + }, + { + imsId: 2, + title: 'Tutorial B', + type: 'TUTORIAL', + status: 'NEW', + progress: 0, + experience: '100', + timeToComplete: 30, + url: '', + description: '', + }, + { + imsId: 3, + title: 'Tutorial C', + type: 'TUTORIAL', + status: 'NEW', + progress: 0, + experience: '100', + timeToComplete: 30, + url: '', + description: '', + }, + ], + }, + ], + }, + ], + } + const result = buildLevelModel(data) + expect(result.zones).toHaveLength(1) + expect(result.zones[0].coins).toHaveLength(3) + expect(result.zones[0].coins[0].title).toBe('Tutorial A') + expect(result.zones[0].coins[1].title).toBe('Tutorial B') + expect(result.zones[0].coins[2].title).toBe('Tutorial C') + }) + + it('fork + parallel-any segments β‡’ coins flattened to one ordered list', () => { + const data: AppSpaceData = { + eventId: 1, + eventName: 'Test Event', + type: 'test', + paths: [ + { + id: 'track2', + title: 'Track Two', + description: '', + items: [], + segments: [ + { + order: 0, + kind: 'linear', + items: [ + { + imsId: 1, + title: 'Linear 1', + type: 'TUTORIAL', + status: 'NEW', + progress: 0, + experience: '100', + timeToComplete: 30, + url: '', + description: '', + }, + ], + }, + { + order: 1, + kind: 'parallel-any', + items: [ + { + imsId: 2, + title: 'Cluster 1', + type: 'TUTORIAL', + status: 'NEW', + progress: 0, + experience: '100', + timeToComplete: 30, + url: '', + description: '', + }, + { + imsId: 3, + title: 'Cluster 2', + type: 'TUTORIAL', + status: 'NEW', + progress: 0, + experience: '100', + timeToComplete: 30, + url: '', + description: '', + }, + ], + }, + { + order: 2, + kind: 'fork', + lanes: [ + { + label: 'Lane A', + items: [ + { + imsId: 4, + title: 'Fork Lane A 1', + type: 'TUTORIAL', + status: 'NEW', + progress: 0, + experience: '100', + timeToComplete: 30, + url: '', + description: '', + }, + ], + }, + { + label: 'Lane B', + items: [ + { + imsId: 5, + title: 'Fork Lane B 1', + type: 'TUTORIAL', + status: 'NEW', + progress: 0, + experience: '100', + timeToComplete: 30, + url: '', + description: '', + }, + ], + }, + ], + }, + ], + }, + ], + } + const result = buildLevelModel(data) + expect(result.zones).toHaveLength(1) + expect(result.zones[0].coins).toHaveLength(5) + expect(result.zones[0].coins[0].title).toBe('Linear 1') + expect(result.zones[0].coins[1].title).toBe('Cluster 1') + expect(result.zones[0].coins[2].title).toBe('Cluster 2') + expect(result.zones[0].coins[3].title).toBe('Fork Lane A 1') + expect(result.zones[0].coins[4].title).toBe('Fork Lane B 1') + }) + + it('path with a literal type:PRIZE item β‡’ goal.kind==="prize"', () => { + const data: AppSpaceData = { + eventId: 1, + eventName: 'Test Event', + type: 'test', + paths: [ + { + id: 'track3', + title: 'Track Three', + description: '', + items: [], + segments: [ + { + order: 0, + kind: 'linear', + items: [ + { + imsId: 1, + title: 'Tutorial', + type: 'TUTORIAL', + status: 'NEW', + progress: 0, + experience: '100', + timeToComplete: 30, + url: '', + description: '', + }, + { + imsId: null, + title: 'The Prize', + type: 'PRIZE', + status: 'NEW', + progress: 0, + experience: '0', + timeToComplete: 0, + url: '', + description: '', + prizeId: 42, + }, + ], + }, + ], + }, + ], + } + const result = buildLevelModel(data) + expect(result.zones[0].goal.kind).toBe('prize') + expect(result.zones[0].goal.label).toBe('The Prize') + expect(result.zones[0].goal.prizeId).toBe(42) + }) + + it('path whose only prize is type:CHECKPOINT + prizeId β‡’ goal.kind==="checkpoint", prizeId preserved', () => { + const data: AppSpaceData = { + eventId: 1, + eventName: 'Test Event', + type: 'test', + paths: [ + { + id: 'track4', + title: 'Track Four', + description: '', + items: [], + segments: [ + { + order: 0, + kind: 'linear', + items: [ + { + imsId: 1, + title: 'Tutorial', + type: 'TUTORIAL', + status: 'NEW', + progress: 0, + experience: '100', + timeToComplete: 30, + url: '', + description: '', + }, + { + imsId: 2, + title: 'Checkpoint Prize', + type: 'CHECKPOINT', + status: 'NEW', + progress: 0, + experience: '50', + timeToComplete: 0, + url: '', + description: '', + prizeId: 99, + }, + ], + }, + ], + }, + ], + } + const result = buildLevelModel(data) + expect(result.zones[0].goal.kind).toBe('checkpoint') + expect(result.zones[0].goal.label).toBe('Checkpoint Prize') + expect(result.zones[0].goal.prizeId).toBe(99) + }) + + it('path with neither PRIZE nor prizeId CHECKPOINT β‡’ goal.kind==="track", trackId set', () => { + const data: AppSpaceData = { + eventId: 1, + eventName: 'Test Event', + type: 'test', + paths: [ + { + id: 'track5', + title: 'Track Five', + description: '', + items: [], + segments: [ + { + order: 0, + kind: 'linear', + items: [ + { + imsId: 1, + title: 'Tutorial', + type: 'TUTORIAL', + status: 'NEW', + progress: 0, + experience: '100', + timeToComplete: 30, + url: '', + description: '', + }, + ], + }, + ], + }, + ], + } + const result = buildLevelModel(data) + expect(result.zones[0].goal.kind).toBe('track') + expect(result.zones[0].goal.trackId).toBe('track5') + }) + + it('authenticated + some status:COMPLETED β‡’ those coins done:true; zone with all tutorials COMPLETED β‡’ cleared:true', () => { + const data: AppSpaceData = { + eventId: 1, + eventName: 'Test Event', + authenticated: true, + type: 'test', + paths: [ + { + id: 'track6', + title: 'Track Six', + description: '', + items: [], + segments: [ + { + order: 0, + kind: 'linear', + items: [ + { + imsId: 1, + title: 'Tutorial 1', + type: 'TUTORIAL', + status: 'COMPLETED', + progress: 100, + experience: '100', + timeToComplete: 30, + url: '', + description: '', + }, + { + imsId: 2, + title: 'Tutorial 2', + type: 'TUTORIAL', + status: 'COMPLETED', + progress: 100, + experience: '100', + timeToComplete: 30, + url: '', + description: '', + }, + ], + }, + ], + }, + ], + } + const result = buildLevelModel(data) + expect(result.zones[0].coins[0].done).toBe(true) + expect(result.zones[0].coins[1].done).toBe(true) + expect(result.zones[0].cleared).toBe(true) + }) + + it('anonymous payload (no authenticated, items lack status) β‡’ all coins available:true, done:false; no throw', () => { + const data: AppSpaceData = { + eventId: 1, + eventName: 'Test Event', + type: 'test', + paths: [ + { + id: 'track7', + title: 'Track Seven', + description: '', + items: [], + segments: [ + { + order: 0, + kind: 'linear', + items: [ + { + imsId: 1, + title: 'Tutorial', + type: 'TUTORIAL', + // no status property + progress: 0, + experience: '100', + timeToComplete: 30, + url: '', + description: '', + }, + ], + }, + ], + }, + ], + } + const result = buildLevelModel(data) + expect(result.zones[0].coins[0].done).toBe(false) + expect(result.zones[0].coins[0].available).toBe(true) + }) + + it('unlocked:false items β‡’ zone locked:true, coins available:false', () => { + const data: AppSpaceData = { + eventId: 1, + eventName: 'Test Event', + type: 'test', + paths: [ + { + id: 'track8', + title: 'Track Eight', + description: '', + items: [], + segments: [ + { + order: 0, + kind: 'linear', + items: [ + { + imsId: 1, + title: 'Locked Tutorial', + type: 'TUTORIAL', + status: 'NEW', + progress: 0, + experience: '100', + timeToComplete: 30, + url: '', + description: '', + unlocked: false, + }, + ], + }, + ], + }, + ], + } + const result = buildLevelModel(data) + expect(result.zones[0].locked).toBe(true) + expect(result.zones[0].coins[0].available).toBe(false) + }) + + it('CHECKPOINT item mid-list β‡’ a Gate with correct afterCoinIndex', () => { + const data: AppSpaceData = { + eventId: 1, + eventName: 'Test Event', + type: 'test', + paths: [ + { + id: 'track9', + title: 'Track Nine', + description: '', + items: [], + segments: [ + { + order: 0, + kind: 'linear', + items: [ + { + imsId: 1, + title: 'Tutorial 1', + type: 'TUTORIAL', + status: 'NEW', + progress: 0, + experience: '100', + timeToComplete: 30, + url: '', + description: '', + }, + { + imsId: 2, + title: 'Checkpoint', + type: 'CHECKPOINT', + status: 'NEW', + progress: 0, + experience: '50', + timeToComplete: 0, + url: '', + description: '', + // no prizeId, so becomes a gate + }, + { + imsId: 3, + title: 'Tutorial 2', + type: 'TUTORIAL', + status: 'NEW', + progress: 0, + experience: '100', + timeToComplete: 30, + url: '', + description: '', + }, + ], + }, + ], + }, + ], + } + const result = buildLevelModel(data) + expect(result.zones[0].coins).toHaveLength(2) // only tutorials + expect(result.zones[0].gates).toHaveLength(1) + expect(result.zones[0].gates[0].afterCoinIndex).toBe(0) + }) + + it('zoneColor(0..11) returns distinct hex strings, wraps at palette length', () => { + const colors = Array.from({ length: 12 }, (_, i) => zoneColor(i)) + // Check that the first 11 are distinct + const uniqueColors = new Set(colors.slice(0, 11)) + expect(uniqueColors.size).toBe(11) + + // Check wraparound: index 11 should equal index 0 (mod palette length) + expect(zoneColor(11)).toBe(zoneColor(11 % 11)) + // And verify palette length is 11 + expect(zoneColor(0)).toBe(zoneColor(11)) + + // Verify all are valid hex colors + colors.forEach(color => { + expect(color).toMatch(/^#[0-9a-fA-F]{6}$/) + }) + }) +}) diff --git a/hugo-apps/src/app-space-game/level-model.ts b/hugo-apps/src/app-space-game/level-model.ts new file mode 100644 index 000000000..4782ad7bf --- /dev/null +++ b/hugo-apps/src/app-space-game/level-model.ts @@ -0,0 +1,191 @@ +import type { AppSpaceData, AppSpaceSegment, AppSpaceItem } from './types' +import type { LevelModel, Zone, Coin, Gate, Goal } from './types' + +// Palette of 11 distinct hex colors (SAP Horizon-inspired, on-brand-ish) +const ZONE_COLOR_PALETTE = [ + '#0B5394', // SAP Blue + '#1F70C1', // Bright Blue + '#0F5133', // Dark Green + '#107C10', // Accent Green + '#6E3932', // Warm Brown + '#A4373A', // Red Accent + '#6B3FA0', // Purple + '#004B87', // Navy + '#C85E1E', // Orange + '#5E4C3C', // Taupe + '#2D3B45', // Slate +] + +export function zoneColor(colorIndex: number): string { + const index = colorIndex % ZONE_COLOR_PALETTE.length + return ZONE_COLOR_PALETTE[index] +} + +function flattenSegments(segments: AppSpaceSegment[]): { + items: AppSpaceItem[] + checkpointIndices: Map +} { + const items: AppSpaceItem[] = [] + const checkpointIndices = new Map() + + segments.forEach(segment => { + if (segment.kind === 'linear' || segment.kind === 'parallel-any') { + segment.items.forEach(item => { + if (item.type === 'TUTORIAL') { + items.push(item) + } else if (item.type === 'CHECKPOINT') { + checkpointIndices.set(items.length - 1, item) + } else if (item.type === 'PRIZE') { + items.push(item) + } + }) + } else if (segment.kind === 'fork') { + // Process lanes in order, then items within each lane + segment.lanes.forEach(lane => { + lane.items.forEach(item => { + if (item.type === 'TUTORIAL') { + items.push(item) + } else if (item.type === 'CHECKPOINT') { + checkpointIndices.set(items.length - 1, item) + } else if (item.type === 'PRIZE') { + items.push(item) + } + }) + }) + } + }) + + return { items, checkpointIndices } +} + +function buildCoinsAndGates( + items: AppSpaceItem[], + checkpointIndices: Map, + isAuthenticated: boolean, + isLocked: boolean +): { coins: Coin[]; gates: Gate[] } { + const coins: Coin[] = [] + const gates: Gate[] = [] + + items.forEach((item, idx) => { + if (item.type === 'TUTORIAL') { + const coin: Coin = { + title: item.title, + done: isAuthenticated && item.status === 'COMPLETED', + available: !isLocked, + } + coins.push(coin) + } else if (item.type === 'PRIZE') { + // PRIZE items are represented as coins + const coin: Coin = { + title: item.title, + done: isAuthenticated && item.status === 'COMPLETED', + available: !isLocked, + } + coins.push(coin) + } + }) + + // Process checkpoints: if they have prizeId, they become goals (handled elsewhere) + // Otherwise they become gates + checkpointIndices.forEach((checkpointItem, coinIndex) => { + if (!checkpointItem.prizeId) { + gates.push({ + afterCoinIndex: coinIndex, + }) + } + }) + + return { coins, gates } +} + +function findGoal( + items: AppSpaceItem[], + checkpointIndices: Map, + trackId: string +): Goal { + // First, look for a literal PRIZE item + const prizeItem = items.find(item => item.type === 'PRIZE') + if (prizeItem) { + return { + kind: 'prize', + label: prizeItem.title, + prizeId: prizeItem.prizeId ?? null, + trackId, + } + } + + // Next, look for a CHECKPOINT with prizeId + let checkpointWithPrize: AppSpaceItem | undefined + checkpointIndices.forEach(checkpointItem => { + if (checkpointItem.prizeId) { + checkpointWithPrize = checkpointItem + } + }) + + if (checkpointWithPrize) { + return { + kind: 'checkpoint', + label: checkpointWithPrize.title, + prizeId: checkpointWithPrize.prizeId ?? null, + trackId, + } + } + + // Default: track goal + return { + kind: 'track', + label: 'Complete Track', + prizeId: null, + trackId, + } +} + +export function buildLevelModel(data: AppSpaceData): LevelModel { + const zones: Zone[] = [] + const isAuthenticated = data.authenticated ?? false + + data.paths.forEach((track, trackIndex) => { + const segments = track.segments ?? [] + + if (segments.length === 0) { + return // Skip tracks with no segments + } + + const { items, checkpointIndices } = flattenSegments(segments) + + // Determine if zone is locked (any item is unlocked:false) + const isLocked = items.some(item => item.unlocked === false) + + // Filter out PRIZE items from coin list, keep only TUTORIAL + const tutorialItems = items.filter(item => item.type === 'TUTORIAL') + + // Build coins and gates + const { coins, gates } = buildCoinsAndGates(items, checkpointIndices, isAuthenticated, isLocked) + + // Determine if zone is cleared: at least 1 tutorial AND all tutorials COMPLETED + const hasAtLeastOneCompletion = + isAuthenticated && tutorialItems.length > 0 && tutorialItems.some(item => item.status === 'COMPLETED') + const allTutorialsCompleted = + isAuthenticated && tutorialItems.length > 0 && tutorialItems.every(item => item.status === 'COMPLETED') + const cleared = allTutorialsCompleted + + // Build goal + const goal = findGoal(items, checkpointIndices, track.id) + + const zone: Zone = { + id: track.id, + title: track.title, + colorIndex: trackIndex, + cleared, + locked: isLocked, + coins, + gates, + goal, + } + + zones.push(zone) + }) + + return { zones } +} diff --git a/hugo-apps/src/app-space-game/types.ts b/hugo-apps/src/app-space-game/types.ts index 8dcec132c..4aa4f0f24 100644 --- a/hugo-apps/src/app-space-game/types.ts +++ b/hugo-apps/src/app-space-game/types.ts @@ -61,25 +61,35 @@ export interface AppSpaceData { paths: AppSpaceTrack[] } -// Task 2+ interfaces (declared here for future use, not implemented in Task 1) -export interface Zone { - id: string - name: string +// Task 2+ interfaces: Level model for app-space gameplay +export interface Coin { + title: string + done: boolean + available: boolean } -export interface Coin { - id: string - value: number +export interface Gate { + afterCoinIndex: number } export interface Goal { + kind: 'prize' | 'checkpoint' | 'track' + label: string + prizeId: number | null + trackId: string +} + +export interface Zone { id: string - description: string + title: string + colorIndex: number + cleared: boolean + locked: boolean + coins: Coin[] + gates: Gate[] + goal: Goal } export interface LevelModel { - id: string zones: Zone[] - coins: Coin[] - goals: Goal[] } From d495d75b6fb8907195aad7325b963f8e3ae10a53 Mon Sep 17 00:00:00 2001 From: Thomas Jung Date: Tue, 3 Nov 2026 10:48:33 -0600 Subject: [PATCH 012/165] feat(app-space-game): feature gate (flag + webgl + data load) (#3009) --- hugo-apps/src/app-space-game/AppSpaceGame.vue | 28 ++- .../app-space-game/__tests__/feature.test.ts | 191 ++++++++++++++++++ hugo-apps/src/app-space-game/feature.ts | 91 +++++++++ 3 files changed, 308 insertions(+), 2 deletions(-) create mode 100644 hugo-apps/src/app-space-game/__tests__/feature.test.ts create mode 100644 hugo-apps/src/app-space-game/feature.ts diff --git a/hugo-apps/src/app-space-game/AppSpaceGame.vue b/hugo-apps/src/app-space-game/AppSpaceGame.vue index 798366c73..d1732fb61 100644 --- a/hugo-apps/src/app-space-game/AppSpaceGame.vue +++ b/hugo-apps/src/app-space-game/AppSpaceGame.vue @@ -3,9 +3,33 @@ diff --git a/hugo-apps/src/app-space-game/__tests__/feature.test.ts b/hugo-apps/src/app-space-game/__tests__/feature.test.ts new file mode 100644 index 000000000..793ba1cfc --- /dev/null +++ b/hugo-apps/src/app-space-game/__tests__/feature.test.ts @@ -0,0 +1,191 @@ +// @vitest-environment happy-dom +import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest' + +// ── isGameEnabled ────────────────────────────────────────────────────────── +describe('isGameEnabled', () => { + beforeEach(() => { + vi.resetAllMocks() + }) + + afterEach(() => { + vi.restoreAllMocks() + }) + + it('returns false when the flag endpoint responds 503', async () => { + vi.stubGlobal('fetch', vi.fn().mockResolvedValueOnce({ ok: false, status: 503 })) + const { isGameEnabled } = await import('../feature') + expect(await isGameEnabled()).toBe(false) + }) + + it('returns false when the network request rejects', async () => { + vi.stubGlobal('fetch', vi.fn().mockRejectedValueOnce(new Error('Network error'))) + const { isGameEnabled } = await import('../feature') + expect(await isGameEnabled()).toBe(false) + }) + + it('returns true when the flag endpoint responds ok', async () => { + vi.stubGlobal('fetch', vi.fn().mockResolvedValueOnce({ ok: true, status: 200 })) + const { isGameEnabled } = await import('../feature') + expect(await isGameEnabled()).toBe(true) + }) +}) + +// ── isWebGLAvailable ────────────────────────────────────────────────────── +describe('isWebGLAvailable', () => { + let originalCreateElement: typeof document.createElement + + beforeEach(() => { + originalCreateElement = document.createElement.bind(document) + }) + + afterEach(() => { + vi.restoreAllMocks() + // Restore original createElement + vi.spyOn(document, 'createElement').mockRestore() + }) + + it('returns false when getContext returns null for both webgl2 and webgl', async () => { + vi.spyOn(document, 'createElement').mockReturnValue({ + getContext: vi.fn().mockReturnValue(null), + } as unknown as HTMLCanvasElement) + const { isWebGLAvailable } = await import('../feature') + expect(isWebGLAvailable()).toBe(false) + }) + + it('returns true when getContext returns a non-null context for webgl2', async () => { + const mockCtx = { drawingBufferWidth: 0 } + vi.spyOn(document, 'createElement').mockReturnValue({ + getContext: vi.fn().mockImplementation((type: string) => + type === 'webgl2' ? mockCtx : null, + ), + } as unknown as HTMLCanvasElement) + const { isWebGLAvailable } = await import('../feature') + expect(isWebGLAvailable()).toBe(true) + }) + + it('returns true when getContext returns a non-null context for webgl (fallback)', async () => { + const mockCtx = { drawingBufferWidth: 0 } + vi.spyOn(document, 'createElement').mockReturnValue({ + getContext: vi.fn().mockImplementation((type: string) => + type === 'webgl' ? mockCtx : null, + ), + } as unknown as HTMLCanvasElement) + const { isWebGLAvailable } = await import('../feature') + expect(isWebGLAvailable()).toBe(true) + }) + + it('returns false when getContext throws', async () => { + vi.spyOn(document, 'createElement').mockReturnValue({ + getContext: vi.fn().mockImplementation(() => { + throw new Error('WebGL not supported') + }), + } as unknown as HTMLCanvasElement) + const { isWebGLAvailable } = await import('../feature') + expect(isWebGLAvailable()).toBe(false) + }) +}) + +// ── loadAppSpaceData ────────────────────────────────────────────────────── +describe('loadAppSpaceData', () => { + const mockData = { + eventId: 42, + eventName: 'Test Event', + type: 'DEVTOBERFEST', + paths: [], + } + + const mockFallbackData = { + eventId: 0, + eventName: 'Fallback Event', + type: 'DEFAULT', + paths: [], + } + + beforeEach(() => { + vi.resetAllMocks() + // Reset location to default (no eventid param) + Object.defineProperty(window, 'location', { + value: { search: '' }, + writable: true, + configurable: true, + }) + }) + + afterEach(() => { + vi.restoreAllMocks() + }) + + it('returns parsed data on a successful primary fetch', async () => { + vi.stubGlobal('fetch', vi.fn().mockResolvedValueOnce({ + ok: true, + json: vi.fn().mockResolvedValueOnce(mockData), + })) + const { loadAppSpaceData } = await import('../feature') + const result = await loadAppSpaceData() + expect(result).toEqual(mockData) + }) + + it('falls back to /app-space-data.json when the primary fetch fails (non-ok response)', async () => { + vi.stubGlobal('fetch', vi.fn() + .mockResolvedValueOnce({ ok: false, status: 404 }) + .mockResolvedValueOnce({ + ok: true, + json: vi.fn().mockResolvedValueOnce(mockFallbackData), + }), + ) + const { loadAppSpaceData } = await import('../feature') + const result = await loadAppSpaceData() + expect(result).toEqual(mockFallbackData) + }) + + it('falls back to /app-space-data.json when the primary fetch throws', async () => { + vi.stubGlobal('fetch', vi.fn() + .mockRejectedValueOnce(new Error('Network fail')) + .mockResolvedValueOnce({ + ok: true, + json: vi.fn().mockResolvedValueOnce(mockFallbackData), + }), + ) + const { loadAppSpaceData } = await import('../feature') + const result = await loadAppSpaceData() + expect(result).toEqual(mockFallbackData) + }) + + it('returns null when both primary and fallback fetches fail', async () => { + vi.stubGlobal('fetch', vi.fn() + .mockRejectedValueOnce(new Error('Primary fail')) + .mockRejectedValueOnce(new Error('Fallback fail')), + ) + const { loadAppSpaceData } = await import('../feature') + const result = await loadAppSpaceData() + expect(result).toBeNull() + }) + + it('returns null when both primary and fallback respond non-ok', async () => { + vi.stubGlobal('fetch', vi.fn() + .mockResolvedValueOnce({ ok: false, status: 503 }) + .mockResolvedValueOnce({ ok: false, status: 404 }), + ) + const { loadAppSpaceData } = await import('../feature') + const result = await loadAppSpaceData() + expect(result).toBeNull() + }) + + it('uses eventid from URL search params (case-insensitive) in primary fetch URL', async () => { + Object.defineProperty(window, 'location', { + value: { search: '?eventId=99' }, + writable: true, + configurable: true, + }) + const fetchMock = vi.fn().mockResolvedValueOnce({ + ok: true, + json: vi.fn().mockResolvedValueOnce({ ...mockData, eventId: 99 }), + }) + vi.stubGlobal('fetch', fetchMock) + const { loadAppSpaceData } = await import('../feature') + await loadAppSpaceData() + expect(fetchMock).toHaveBeenCalledWith( + expect.stringContaining('eventLegacyId=99'), + ) + }) +}) diff --git a/hugo-apps/src/app-space-game/feature.ts b/hugo-apps/src/app-space-game/feature.ts new file mode 100644 index 000000000..bc1e39806 --- /dev/null +++ b/hugo-apps/src/app-space-game/feature.ts @@ -0,0 +1,91 @@ +/** + * Feature gate for the Kasimir Arcade App-Space game island. + * All checks are best-effort β€” no function here throws into the UI. + * + * Pattern modelled on hugo-apps/src/ktt/lib/server.ts (isKttEnabled). + * Uses native fetch (Node/browser built-in β€” no axios or other HTTP libs). + */ + +import type { AppSpaceData } from './types' + +/** + * Probe whether the App-Space game feature flag is enabled server-side. + * + * The flag is a runtime DB value (no redeploy required), so the client must + * ask the server. We probe the getAppSpaceProgress endpoint with a dummy id: + * the service rejects with 503 when the APP_SPACE_GAME_ENABLED flag is OFF. + * Returns false on 503/network error, true on ok β€” best-effort, never throws. + * + * // TODO(#3009): Replace this probe with a dedicated lightweight flag endpoint + * // (e.g. /api/isAppSpaceGameEnabled) once one is added to the CAP service. + * // Until then, reachability of getAppSpaceProgress(eventLegacyId=0) serves + * // as the enable signal. + */ +export async function isGameEnabled(): Promise { + try { + const r = await fetch('/api/getAppSpaceProgress(eventLegacyId=0)') + return r.ok + } catch { + return false + } +} + +/** + * Synchronously detect WebGL support by creating a throwaway canvas. + * Returns true if either webgl2 or webgl contexts are available. + * Wraps ALL operations in try/catch so any throw β†’ false. + */ +export function isWebGLAvailable(): boolean { + try { + const canvas = document.createElement('canvas') + return !!(canvas.getContext('webgl2') || canvas.getContext('webgl')) + } catch { + return false + } +} + +/** + * Load App-Space progress data for the current page's event. + * + * Resolves the eventId from the URL search params case-insensitively + * (matching the pattern in hugo-apps/src/app-space/AppSpace.vue lines 194–198). + * Falls back to `/app-space-data.json` if the primary endpoint fails. + * Returns null if both fetches fail. Never throws. + */ +export async function loadAppSpaceData(): Promise { + // Resolve eventId from URL params case-insensitively (same as AppSpace.vue) + let eventId: number | null = null + try { + const params = new URLSearchParams(window.location.search) + for (const [k, v] of params) { + if (k.toLowerCase() === 'eventid') { + eventId = Number(v) + break + } + } + } catch { + // URL parsing failed β€” proceed with no eventId + } + + const param = eventId ? `eventLegacyId=${eventId}` : `eventLegacyId=0` + + // Primary: fetch from CAP + try { + const res = await fetch(`/api/getAppSpaceProgress(${param})`) + if (res.ok) { + return (await res.json()) as AppSpaceData + } + } catch { + // Primary failed β€” fall through to fallback + } + + // Fallback: static JSON file + try { + const res = await fetch('/app-space-data.json') + if (res.ok) return (await res.json()) as AppSpaceData + } catch { + // Fallback also failed + } + + return null +} From 8b1eeed22d9ff21b769176c86b26524015772dfe Mon Sep 17 00:00:00 2001 From: Thomas Jung Date: Tue, 3 Nov 2026 11:00:10 -0600 Subject: [PATCH 013/165] feat(app-space-game): three.js renderer + attract loop (#3009) --- hugo-apps/package-lock.json | 7 + hugo-apps/package.json | 1 + hugo-apps/src/app-space-game/AppSpaceGame.vue | 43 +- .../app-space-game/__tests__/mount.test.ts | 6 +- .../__tests__/renderer.guard.test.ts | 72 +++ hugo-apps/src/app-space-game/renderer.ts | 469 ++++++++++++++++++ 6 files changed, 589 insertions(+), 9 deletions(-) create mode 100644 hugo-apps/src/app-space-game/__tests__/renderer.guard.test.ts create mode 100644 hugo-apps/src/app-space-game/renderer.ts diff --git a/hugo-apps/package-lock.json b/hugo-apps/package-lock.json index 958940471..dfb796710 100644 --- a/hugo-apps/package-lock.json +++ b/hugo-apps/package-lock.json @@ -17,6 +17,7 @@ "onnxruntime-web": "^1.20.0", "sigma": "3.0.3", "socket.io-client": "^4.8.0", + "three": "^0.161.0", "vue": "^3.5.0", "vue-virtual-scroller": "3.0.4" }, @@ -1580,6 +1581,12 @@ "node": ">=0.10.0" } }, + "node_modules/three": { + "version": "0.161.0", + "resolved": "https://registry.npmjs.org/three/-/three-0.161.0.tgz", + "integrity": "sha512-LC28VFtjbOyEu5b93K0bNRLw1rQlMJ85lilKsYj6dgTu+7i17W+JCCEbvrpmNHF1F3NAUqDSWq50UD7w9H2xQw==", + "license": "MIT" + }, "node_modules/tinyglobby": { "version": "0.2.16", "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.16.tgz", diff --git a/hugo-apps/package.json b/hugo-apps/package.json index de7b9ff81..6515f2559 100644 --- a/hugo-apps/package.json +++ b/hugo-apps/package.json @@ -9,6 +9,7 @@ }, "dependencies": { "@imgly/background-removal": "^1.7.0", + "three": "^0.161.0", "@mediapipe/tasks-vision": "^0.10.35", "canvas-confetti": "1.9.4", "cropperjs": "1.6.2", diff --git a/hugo-apps/src/app-space-game/AppSpaceGame.vue b/hugo-apps/src/app-space-game/AppSpaceGame.vue index d1732fb61..9e9af37c7 100644 --- a/hugo-apps/src/app-space-game/AppSpaceGame.vue +++ b/hugo-apps/src/app-space-game/AppSpaceGame.vue @@ -1,17 +1,29 @@ diff --git a/hugo-apps/src/app-space-game/__tests__/mount.test.ts b/hugo-apps/src/app-space-game/__tests__/mount.test.ts index dd3fe55b8..d832e9e08 100644 --- a/hugo-apps/src/app-space-game/__tests__/mount.test.ts +++ b/hugo-apps/src/app-space-game/__tests__/mount.test.ts @@ -18,10 +18,10 @@ describe('AppSpaceGame mount scaffold', () => { // Root element has the data attribute expect(wrapper.element.getAttribute('data-app-space-game')).toBe('') - // No canvas or game-related content is rendered - expect(wrapper.find('canvas').exists()).toBe(false) + // Task 4: canvas is present in the template (renderer mounts into it when gate passes) + expect(wrapper.find('canvas').exists()).toBe(true) - // The component is hidden/minimal + // No text content - the game canvas has no visible text nodes expect(wrapper.text()).toBe('') }) }) diff --git a/hugo-apps/src/app-space-game/__tests__/renderer.guard.test.ts b/hugo-apps/src/app-space-game/__tests__/renderer.guard.test.ts new file mode 100644 index 000000000..0c7e22a33 --- /dev/null +++ b/hugo-apps/src/app-space-game/__tests__/renderer.guard.test.ts @@ -0,0 +1,72 @@ +// @vitest-environment happy-dom +// Module-contract guard for renderer.ts β€” NO real WebGL. +// Three.js WebGLRenderer is mocked so this test runs in jsdom/node without a GPU. +// Only asserts the exported API shape and that stop() before start() is safe. +import { describe, it, expect, vi, beforeEach } from 'vitest' + +// Mock `three` before importing renderer so WebGLRenderer never hits the real WebGL path. +vi.mock('three', async () => { + const actual = await vi.importActual('three') + const MockWebGLRenderer = vi.fn().mockImplementation(() => ({ + setPixelRatio: vi.fn(), + setSize: vi.fn(), + render: vi.fn(), + shadowMap: { enabled: false, type: null }, + domElement: document.createElement('canvas'), + dispose: vi.fn(), + })) + return { ...actual, WebGLRenderer: MockWebGLRenderer } +}) + +describe('renderer module contract', () => { + beforeEach(() => { + vi.clearAllMocks() + }) + + it('createGame is a function', async () => { + const { createGame } = await import('../renderer') + expect(typeof createGame).toBe('function') + }) + + it('createGame returns an object with start/stop/setPlayerControl methods', async () => { + const { createGame } = await import('../renderer') + const canvas = document.createElement('canvas') + const model = { zones: [] } + const game = createGame(canvas, model, { onPickTrack: () => {} }) + expect(typeof game.start).toBe('function') + expect(typeof game.stop).toBe('function') + expect(typeof game.setPlayerControl).toBe('function') + }) + + it('stop() before start() does not throw', async () => { + const { createGame } = await import('../renderer') + const canvas = document.createElement('canvas') + const model = { zones: [] } + const game = createGame(canvas, model, { onPickTrack: () => {} }) + expect(() => game.stop()).not.toThrow() + }) + + it('setPlayerControl() is callable without start()', async () => { + const { createGame } = await import('../renderer') + const canvas = document.createElement('canvas') + const model = { zones: [] } + const game = createGame(canvas, model, { onPickTrack: () => {} }) + expect(() => game.setPlayerControl(true)).not.toThrow() + expect(() => game.setPlayerControl(false)).not.toThrow() + }) + + it('createGame is safe when WebGLRenderer throws (defensive gate)', async () => { + const three = await import('three') + vi.mocked(three.WebGLRenderer).mockImplementationOnce(() => { + throw new Error('WebGL not supported') + }) + const { createGame } = await import('../renderer') + const canvas = document.createElement('canvas') + const model = { zones: [] } + // Should not throw - returns a safe no-op object + expect(() => { + const game = createGame(canvas, model, { onPickTrack: () => {} }) + game.stop() + }).not.toThrow() + }) +}) diff --git a/hugo-apps/src/app-space-game/renderer.ts b/hugo-apps/src/app-space-game/renderer.ts new file mode 100644 index 000000000..6a90f73c2 --- /dev/null +++ b/hugo-apps/src/app-space-game/renderer.ts @@ -0,0 +1,469 @@ +// Three.js renderer for the app-space-game island. +// Builds a 2.5D attract-loop scene from a LevelModel and runs the game loop. +// Spike reference: .superpowers/sdd/2026-11-03-kasimir-arcade-app-space/spike-reference.html + +import * as THREE from 'three' +import type { LevelModel, Zone, Coin, Gate, Goal } from './types' +import { zoneColor } from './level-model' + +const ZONE_W = 26 +const GY = 0 +const KASIMIR_TEX = '/images/devtoberfest/selfie/thumbnails/Kasimir_Smiles.png' + +// --- helpers --- + +/** Build a canvas-texture sprite for floating text labels. */ +function makeLabel(text: string, color: string = '#ffffff'): THREE.Sprite { + const cv = document.createElement('canvas') + cv.width = 512 + cv.height = 96 + const g = cv.getContext('2d')! + g.font = 'bold 40px system-ui' + g.textAlign = 'center' + g.textBaseline = 'middle' + g.lineWidth = 8 + g.strokeStyle = 'rgba(0,0,0,.75)' + g.strokeText(text, 256, 48) + g.fillStyle = color + g.fillText(text, 256, 48) + const tex = new THREE.CanvasTexture(cv) + const mat = new THREE.SpriteMaterial({ map: tex, transparent: true }) + const sprite = new THREE.Sprite(mat) + sprite.scale.set(6, 1.1, 1) + return sprite +} + +/** Parse a CSS hex color string to a THREE.Color-compatible integer. */ +function hexToInt(hex: string): number { + return parseInt(hex.replace('#', ''), 16) +} + +interface CoinMesh extends THREE.Mesh { + userData: { alive: boolean; title: string } +} + +interface ZoneMeta { + name: string + locked: boolean + cleared: boolean + goalX: number + start: number + coinCount: number +} + +// --- scene builder --- + +function buildScene( + scene: THREE.Scene, + model: LevelModel +): { markers: CoinMesh[]; zoneMeta: ZoneMeta[]; totalLen: number } { + const markers: CoinMesh[] = [] + const zoneMeta: ZoneMeta[] = [] + let runningX = 5 + + model.zones.forEach((zone: Zone) => { + const accentInt = hexToInt(zoneColor(zone.colorIndex)) + const isLocked = zone.locked + const isCleared = zone.cleared + const x0 = runningX - 3 + + // Ground slab + const ground = new THREE.Mesh( + new THREE.BoxGeometry(ZONE_W, 1, 10), + new THREE.MeshStandardMaterial({ + color: isLocked ? 0x161a28 : 0x10203a, + roughness: 0.9, + }) + ) + ground.position.set(x0 + ZONE_W / 2, GY - 0.5, 0) + ground.receiveShadow = true + scene.add(ground) + + // Accent seam at ground level + const seam = new THREE.Mesh( + new THREE.BoxGeometry(ZONE_W, 0.08, 10.05), + new THREE.MeshBasicMaterial({ color: isLocked ? 0x333848 : accentInt }) + ) + seam.position.set(x0 + ZONE_W / 2, GY + 0.02, 0) + scene.add(seam) + + // Sky backdrop wall + // Derive a darker sky tint from the accent color + const accentColor = new THREE.Color(accentInt) + const skyColor = new THREE.Color( + accentColor.r * 0.12, + accentColor.g * 0.12, + accentColor.b * 0.18 + ) + const wall = new THREE.Mesh( + new THREE.PlaneGeometry(ZONE_W, 24), + new THREE.MeshBasicMaterial({ color: skyColor }) + ) + wall.position.set(x0 + ZONE_W / 2, 9, -10) + scene.add(wall) + + // Parallax pillars + for (let p = 0; p < 3; p++) { + const pil = new THREE.Mesh( + new THREE.BoxGeometry(1.2, 11, 1.2), + new THREE.MeshStandardMaterial({ + color: accentInt, + roughness: 0.5, + emissive: accentInt, + emissiveIntensity: isLocked ? 0.03 : 0.18, + transparent: isLocked, + opacity: isLocked ? 0.4 : 1, + }) + ) + pil.position.set(x0 + 5 + p * 8, 5.5, -6) + pil.castShadow = !isLocked + scene.add(pil) + } + + // Zone name banner + const banner = makeLabel(zone.title.toUpperCase(), '#ffffff') + banner.scale.set(10, 1.8, 1) + banner.position.set(x0 + ZONE_W / 2, 11, -5) + scene.add(banner) + + const zStart = runningX + + // Coins - one per zone.coins entry + zone.coins.forEach((coin: Coin, k: number) => { + const isAvailable = coin.available + const isDone = coin.done + const coinMesh = new THREE.Mesh( + new THREE.TorusGeometry(0.5, 0.18, 10, 24), + new THREE.MeshStandardMaterial({ + color: isDone ? 0x49b35a : isAvailable ? 0xf0b42a : 0x555555, + metalness: 0.9, + roughness: 0.25, + emissive: isDone ? 0x1f5e2a : isAvailable ? 0x7a5200 : 0x111111, + emissiveIntensity: 0.5, + transparent: !isAvailable, + opacity: !isAvailable ? 0.45 : 1, + }) + ) as CoinMesh + coinMesh.position.set(runningX, 2.1 + Math.sin(k) * 0.6, 0) + coinMesh.castShadow = !isLocked + coinMesh.userData = { alive: !isDone && isAvailable, title: coin.title } + scene.add(coinMesh) + + const coinColor = isDone ? '#9fe7a8' : isAvailable ? '#ffe9a3' : '#888888' + const tag = makeLabel(coin.title, coinColor) + tag.position.set(runningX, 3.5, 0) + scene.add(tag) + + if (!isLocked && !isDone) markers.push(coinMesh) + runningX += 4.5 + }) + + // Checkpoint gates - one per zone.gates entry + zone.gates.forEach((gate: Gate) => { + // Position gate after the coin at gate.afterCoinIndex + // Each coin is 4.5 units wide; offset from zStart + const gateX = zStart + (gate.afterCoinIndex + 1) * 4.5 + 0.5 + const gateMat = new THREE.MeshStandardMaterial({ + color: 0xffb02e, + emissive: 0xffb02e, + emissiveIntensity: 0.4, + metalness: 0.4, + }) + const lPost = new THREE.Mesh(new THREE.BoxGeometry(0.4, 5, 0.4), gateMat) + lPost.position.set(gateX, 2.5, 0) + lPost.castShadow = true + scene.add(lPost) + const rPost = lPost.clone() + rPost.position.set(gateX + 2.4, 2.5, 0) + scene.add(rPost) + const top = new THREE.Mesh(new THREE.BoxGeometry(3.2, 0.5, 0.5), gateMat) + top.position.set(gateX + 1.2, 4.8, 0) + scene.add(top) + const ct = makeLabel('CHECKPOINT', '#ffd27a') + ct.position.set(gateX + 1.2, 5.7, 0) + scene.add(ct) + runningX = Math.max(runningX, gateX + 5) + }) + + // Goal object at zone end + const goalX = runningX + 1.5 + const goal: Goal = zone.goal + const isTrack = goal.kind === 'track' + + if (isTrack) { + // Flag pole for track-select goal + const pole = new THREE.Mesh( + new THREE.BoxGeometry(0.2, 5, 0.2), + new THREE.MeshStandardMaterial({ + color: isLocked ? 0x444444 : 0xc0c0c0, + metalness: 0.7, + }) + ) + pole.position.set(goalX, 2.5, 0) + pole.castShadow = true + scene.add(pole) + const flag = new THREE.Mesh( + new THREE.PlaneGeometry(2, 1.2), + new THREE.MeshBasicMaterial({ + color: isLocked ? 0x444444 : accentInt, + side: THREE.DoubleSide, + }) + ) + flag.position.set(goalX + 1, 4.5, 0) + scene.add(flag) + const glow = new THREE.PointLight( + isLocked ? 0x333333 : accentInt, + isLocked ? 0 : 2.5, + 10 + ) + glow.position.set(goalX, 4, 1) + scene.add(glow) + const pt = makeLabel( + (isLocked ? 'LOCKED ' : '') + goal.label, + isLocked ? '#ffb02e' : '#ffe9a3' + ) + pt.position.set(goalX, 5.2, 0) + scene.add(pt) + } else { + // Chest for prize / checkpoint goal + const chestMat = new THREE.MeshStandardMaterial({ + color: isLocked ? 0x444444 : 0xc98b3a, + metalness: 0.5, + roughness: 0.4, + emissive: isLocked ? 0 : 0x3a2608, + emissiveIntensity: 0.3, + }) + const chest = new THREE.Mesh(new THREE.BoxGeometry(2.4, 1.8, 2), chestMat) + chest.position.set(goalX, 1.4, 0) + chest.castShadow = true + scene.add(chest) + const lid = new THREE.Mesh(new THREE.BoxGeometry(2.5, 0.7, 2.1), chestMat) + lid.position.set(goalX, 2.5, 0) + lid.castShadow = true + scene.add(lid) + const glow = new THREE.PointLight( + isLocked ? 0x333333 : 0xffcf6b, + isLocked ? 0 : 3, + 10 + ) + glow.position.set(goalX, 3, 1) + scene.add(glow) + const prizeLabel = (isLocked ? '[locked] ' : '') + goal.label + const pt = makeLabel(prizeLabel, isLocked ? '#ffb02e' : '#ffe9a3') + pt.position.set(goalX, 4, 0) + scene.add(pt) + if (isLocked) { + const lk = makeLabel('LOCKED - complete prerequisites', '#ffb02e') + lk.scale.set(7, 1.3, 1) + lk.position.set(goalX, 4.9, 0) + scene.add(lk) + } + } + + runningX = goalX + 6 + + zoneMeta.push({ + name: zone.title, + locked: isLocked, + cleared: isCleared, + goalX, + start: zStart, + coinCount: zone.coins.length, + }) + }) + + return { markers, zoneMeta, totalLen: runningX } +} + +// --- public API --- + +export interface GameHandle { + start(): void + stop(): void + setPlayerControl(on: boolean): void +} + +export function createGame( + canvas: HTMLCanvasElement, + model: LevelModel, + opts: { onPickTrack: (trackId: string) => void } +): GameHandle { + // Defensive: if WebGL fails, return safe no-ops + let renderer: THREE.WebGLRenderer + try { + renderer = new THREE.WebGLRenderer({ canvas, antialias: true }) + } catch (_e) { + return { + start() {}, + stop() {}, + setPlayerControl(_on: boolean) {}, + } + } + + renderer.setPixelRatio(Math.min(devicePixelRatio, 2)) + renderer.shadowMap.enabled = true + renderer.shadowMap.type = THREE.PCFSoftShadowMap + + const scene = new THREE.Scene() + scene.fog = new THREE.Fog(0x06080f, 20, 52) + + const camera = new THREE.PerspectiveCamera(42, canvas.clientWidth / canvas.clientHeight || 1, 0.1, 200) + + // Lighting + scene.add(new THREE.AmbientLight(0x5566aa, 1.1)) + const key = new THREE.DirectionalLight(0xffffff, 1.5) + key.position.set(-6, 14, 9) + key.castShadow = true + key.shadow.mapSize.set(2048, 2048) + key.shadow.camera.near = 1 + key.shadow.camera.far = 70 + key.shadow.camera.left = -40 + key.shadow.camera.right = 40 + key.shadow.camera.top = 24 + key.shadow.camera.bottom = -12 + scene.add(key) + + const kLight = new THREE.PointLight(0x9ab8ff, 2, 16) + scene.add(kLight) + + // Build scene from model + const { markers, zoneMeta, totalLen } = buildScene(scene, model) + + // Kasimir billboard + const tex = new THREE.TextureLoader().load(KASIMIR_TEX) + tex.colorSpace = THREE.SRGBColorSpace + const kMat = new THREE.MeshBasicMaterial({ map: tex, transparent: true, alphaTest: 0.3 }) + const kasimir = new THREE.Mesh(new THREE.PlaneGeometry(2.6, 3.6), kMat) + kasimir.position.set(5, 1.8, 0.3) + scene.add(kasimir) + + // Kasimir shadow blob + const blob = new THREE.Mesh( + new THREE.CircleGeometry(1.1, 24), + new THREE.MeshBasicMaterial({ color: 0, transparent: true, opacity: 0.4 }) + ) + blob.rotation.x = -Math.PI / 2 + blob.position.set(5, 0.03, 0) + scene.add(blob) + + // Game state + let kasX = 5 + let kasY = 1.8 + let vy = 0 + let onGround = true + let playerControl = false + let rafId = 0 + let started = false + + // Zone hit tracking for onPickTrack + const goalHit = new Set() + + const SPEED = 6 + const GRAV = -24 + const JUMP = 10 + + const clock = new THREE.Clock() + + function resizeToCanvas() { + const w = canvas.clientWidth || canvas.width || 800 + const h = canvas.clientHeight || canvas.height || 600 + camera.aspect = w / h + camera.updateProjectionMatrix() + renderer.setSize(w, h, false) + } + resizeToCanvas() + + function tick() { + const dt = Math.min(clock.getDelta(), 0.05) + const t = clock.elapsedTime + + // Attract auto-run: advance Kasimir forward, loop at end + if (!playerControl) { + kasX += SPEED * dt + if (kasX > totalLen - 4) { + kasX = 5 + kasY = 1.8 + vy = 0 + onGround = true + goalHit.clear() + // Reset coin visibility for attract loop + markers.forEach(m => { + m.userData.alive = true + m.visible = true + }) + } + // Auto-jump + if (onGround && Math.sin(t * 2.1) > 0.8) { + vy = JUMP + onGround = false + } + } + + // Physics + vy += GRAV * dt + let newY = kasY + vy * dt + if (newY <= 1.8) { + newY = 1.8 + vy = 0 + onGround = true + } + kasY = newY + + // Update Kasimir position + kasimir.position.set(kasX, kasY, 0.3) + kasimir.rotation.z = Math.sin(t * 11) * 0.04 + blob.position.set(kasX, 0.03, 0) + blob.scale.setScalar(1 - (kasY - 1.8) * 0.12) + kLight.position.set(kasX, kasY + 1, 3) + + // Camera follow + camera.position.x += (kasX - camera.position.x) * Math.min(1, dt * 4) + camera.position.y = 3.4 + Math.sin(t * 1.2) * 0.15 + camera.position.z = 13 + camera.lookAt(camera.position.x, 2.4, 0) + + // Coin collection + markers.forEach(coin => { + if (!coin.userData.alive) return + coin.rotation.y += dt * 3 + if (Math.abs(coin.position.x - kasX) < 1.2 && Math.abs(coin.position.y - kasY) < 1.6) { + coin.userData.alive = false + coin.visible = false + } + }) + + // Goal reach check (player-control mode only, for onPickTrack) + if (playerControl) { + zoneMeta.forEach((zm, zi) => { + const zoneId = model.zones[zi]?.id + if (zoneId && !goalHit.has(zoneId) && !zm.locked && Math.abs(kasX - zm.goalX) < 1.3) { + goalHit.add(zoneId) + opts.onPickTrack(zoneId) + } + }) + } + + renderer.render(scene, camera) + rafId = requestAnimationFrame(tick) + } + + return { + start() { + if (started) return + started = true + resizeToCanvas() + clock.start() + rafId = requestAnimationFrame(tick) + }, + stop() { + if (rafId) { + cancelAnimationFrame(rafId) + rafId = 0 + } + started = false + }, + setPlayerControl(on: boolean) { + playerControl = on + }, + } +} From f376db0b7c967a5a59a188a493964685e7fb3fec Mon Sep 17 00:00:00 2001 From: Thomas Jung Date: Tue, 3 Nov 2026 11:04:18 -0600 Subject: [PATCH 014/165] fix(app-space-game): onPickTrack passes goal.trackId not zone.id (#3009) --- hugo-apps/src/app-space-game/renderer.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/hugo-apps/src/app-space-game/renderer.ts b/hugo-apps/src/app-space-game/renderer.ts index 6a90f73c2..7a1586815 100644 --- a/hugo-apps/src/app-space-game/renderer.ts +++ b/hugo-apps/src/app-space-game/renderer.ts @@ -435,10 +435,14 @@ export function createGame( // Goal reach check (player-control mode only, for onPickTrack) if (playerControl) { zoneMeta.forEach((zm, zi) => { - const zoneId = model.zones[zi]?.id + const zone = model.zones[zi] + const zoneId = zone?.id if (zoneId && !goalHit.has(zoneId) && !zm.locked && Math.abs(kasX - zm.goalX) < 1.3) { goalHit.add(zoneId) - opts.onPickTrack(zoneId) + // Prefer goal.trackId (set for all goal kinds per buildLevelModel); + // fall back to zone.id so every goal-reach always fires a usable id. + const trackId = zone.goal.trackId ?? zoneId + opts.onPickTrack(trackId) } }) } From 9f450476682263332955bab34aaaee851cb2b70a Mon Sep 17 00:00:00 2001 From: Thomas Jung Date: Tue, 3 Nov 2026 11:42:09 -0600 Subject: [PATCH 015/165] feat(app-space-game): attract loop, demo trigger, track handoff (#3009) --- hugo-apps/src/app-space-game/AppSpaceGame.vue | 182 ++++++++++- .../__tests__/AppSpaceGame.test.ts | 284 ++++++++++++++++++ 2 files changed, 452 insertions(+), 14 deletions(-) create mode 100644 hugo-apps/src/app-space-game/__tests__/AppSpaceGame.test.ts diff --git a/hugo-apps/src/app-space-game/AppSpaceGame.vue b/hugo-apps/src/app-space-game/AppSpaceGame.vue index 9e9af37c7..a2835e5de 100644 --- a/hugo-apps/src/app-space-game/AppSpaceGame.vue +++ b/hugo-apps/src/app-space-game/AppSpaceGame.vue @@ -1,6 +1,30 @@ @@ -11,34 +35,123 @@ import { isGameEnabled, isWebGLAvailable, loadAppSpaceData } from './feature' import { buildLevelModel } from './level-model' import type { GameHandle } from './renderer' +// Default idle timeout before attract mode fires, in ms +const IDLE_TIMEOUT_MS = 60_000 + // Gate state - stays false until all conditions are satisfied -const ready = ref(false) -// Level model built from data - available when ready is true +const gateReady = ref(false) +// Level model built from data - available when gateReady is true const levelModel = ref(null) // Canvas ref for Three.js const canvasRef = ref(null) +// Whether the attract overlay is currently shown +const attractVisible = ref(false) +// Whether the user has taken control from attract mode +let inPlayerControl = false // Game handle for cleanup let game: GameHandle | null = null +// Idle timer handle +let idleTimer: ReturnType | null = null + +/** Returns true when the user prefers reduced motion. */ +function prefersReducedMotion(): boolean { + try { + return window.matchMedia('(prefers-reduced-motion: reduce)').matches + } catch { + return false + } +} + +/** Reset the idle countdown. When reduced motion is on, the timer never fires. */ +function resetIdleTimer(): void { + if (idleTimer !== null) { + clearTimeout(idleTimer) + idleTimer = null + } + if (!gateReady.value || prefersReducedMotion()) return + idleTimer = setTimeout(() => { + startAttract() + }, IDLE_TIMEOUT_MS) +} +/** Start attract mode: show overlay + call game.start(). */ +function startAttract(): void { + if (!game) return + attractVisible.value = true + inPlayerControl = false + game.start() +} + +/** Build the track URL preserving existing search params. */ +function buildTrackUrl(trackId: string): string { + const params = new URLSearchParams(window.location.search) + params.set('track', trackId) + const qs = params.toString() + return window.location.pathname + (qs ? '?' + qs : '') +} + +/** Called by the renderer when Kasimir reaches a goal. */ function onPickTrack(trackId: string): void { - // Task 5 will wire real navigation here - console.debug('[app-space-game] onPickTrack', trackId) + // 1. Update the URL without adding a history entry + const newUrl = buildTrackUrl(trackId) + window.history.replaceState({ track: trackId }, '', newUrl) + // 2. Tell the grid island (AppSpace.vue popstate listener) to sync + window.dispatchEvent(new PopStateEvent('popstate', { state: { track: trackId } })) + // 3. Hide overlay and stop renderer + attractVisible.value = false + inPlayerControl = false + game?.stop() } +/** Handler called on any page-level input event. */ +function onUserInput(): void { + if (attractVisible.value && !inPlayerControl) { + // Hand control to the player but keep overlay up + inPlayerControl = true + game?.setPlayerControl(true) + return + } + // Not in attract mode - just reset idle timer + if (!attractVisible.value) { + resetIdleTimer() + } +} + +/** Events that reset the idle timer (or hand control in attract). */ +const INPUT_EVENTS: string[] = ['mousemove', 'touchstart', 'keydown'] + onMounted(async () => { - // All three conditions must pass; any failure leaves ready false (grid intact) - const [enabled, webgl] = await Promise.all([ - isGameEnabled(), - Promise.resolve(isWebGLAvailable()), - ]) + // All three conditions must pass; any failure leaves gateReady false (grid intact) + let enabled: boolean, webgl: boolean + try { + const results = await Promise.all([ + isGameEnabled(), + Promise.resolve(isWebGLAvailable()), + ]) + enabled = results[0] + webgl = results[1] + } catch { + return + } if (!enabled || !webgl) return - const data = await loadAppSpaceData() + let data + try { + data = await loadAppSpaceData() + } catch { + return + } if (!data) return // Build the level model - levelModel.value = buildLevelModel(data) - ready.value = true + let model + try { + model = buildLevelModel(data) + } catch { + return + } + levelModel.value = model + gateReady.value = true // Dynamic import - Three.js is code-split, only loaded after gate passes const { createGame } = await import('./renderer') @@ -47,10 +160,24 @@ onMounted(async () => { if (!canvas) return game = createGame(canvas, levelModel.value, { onPickTrack }) - game.start() + + // Arm input listeners for idle timer and player-control handoff + for (const ev of INPUT_EVENTS) { + window.addEventListener(ev, onUserInput, { passive: true }) + } + + // Start the idle countdown + resetIdleTimer() }) onUnmounted(() => { + if (idleTimer !== null) { + clearTimeout(idleTimer) + idleTimer = null + } + for (const ev of INPUT_EVENTS) { + window.removeEventListener(ev, onUserInput) + } game?.stop() game = null }) @@ -58,9 +185,36 @@ onUnmounted(() => { diff --git a/hugo-apps/src/app-space-game/__tests__/AppSpaceGame.test.ts b/hugo-apps/src/app-space-game/__tests__/AppSpaceGame.test.ts new file mode 100644 index 000000000..57c48a0dc --- /dev/null +++ b/hugo-apps/src/app-space-game/__tests__/AppSpaceGame.test.ts @@ -0,0 +1,284 @@ +// @vitest-environment happy-dom +import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest' +import { mount, flushPromises } from '@vue/test-utils' + +// -- Shared mock data -------------------------------------------------------- + +const MOCK_DATA = { + eventId: 1, + eventName: 'Test', + type: 'DEVTOBERFEST', + paths: [ + { + id: 'track-alpha', + title: 'Alpha Track', + description: 'desc', + items: [], + segments: [ + { + order: 0, + kind: 'linear' as const, + items: [ + { + imsId: 1, + title: 'Tutorial 1', + type: 'TUTORIAL' as const, + status: 'OPEN', + progress: 0, + experience: '', + timeToComplete: 5, + url: '/tutorials/test', + description: 'desc', + }, + ], + }, + ], + }, + ], +} + +// -- Game handle factory ----------------------------------------------------- + +function makeGameHandle() { + return { + start: vi.fn(), + stop: vi.fn(), + setPlayerControl: vi.fn(), + } +} + +// -- Module mocks (hoisted so vi.mock applies before imports) ---------------- + +vi.mock('../feature', () => ({ + isGameEnabled: vi.fn(), + isWebGLAvailable: vi.fn(), + loadAppSpaceData: vi.fn(), +})) + +vi.mock('../renderer', () => ({ + createGame: vi.fn(), +})) + +// -- Helpers ----------------------------------------------------------------- + +async function mountWithGatePass() { + const { isGameEnabled, isWebGLAvailable, loadAppSpaceData } = await import('../feature') + const { createGame } = await import('../renderer') + const gameHandle = makeGameHandle() + + vi.mocked(isGameEnabled).mockResolvedValue(true) + vi.mocked(isWebGLAvailable).mockReturnValue(true) + vi.mocked(loadAppSpaceData).mockResolvedValue(MOCK_DATA) + vi.mocked(createGame).mockReturnValue(gameHandle) + + const AppSpaceGame = (await import('../AppSpaceGame.vue')).default + const wrapper = mount(AppSpaceGame) + // Flush all promises to complete the async onMounted gate check + await flushPromises() + + return { wrapper, gameHandle, createGame: vi.mocked(createGame) } +} + +async function mountWithGateFail() { + const { isGameEnabled, isWebGLAvailable, loadAppSpaceData } = await import('../feature') + const { createGame } = await import('../renderer') + + vi.mocked(isGameEnabled).mockResolvedValue(false) + vi.mocked(isWebGLAvailable).mockReturnValue(false) + vi.mocked(loadAppSpaceData).mockResolvedValue(null) + vi.mocked(createGame).mockReset() + + const AppSpaceGame = (await import('../AppSpaceGame.vue')).default + const wrapper = mount(AppSpaceGame) + await flushPromises() + + return { wrapper, createGame: vi.mocked(createGame) } +} + +// -- Test suites ------------------------------------------------------------- + +describe('AppSpaceGame attract state machine', () => { + beforeEach(() => { + // Fake only macrotask timers (setTimeout/clearTimeout) so the idle SM is + // controllable. Exclude setImmediate so that flushPromises() (which + // internally uses setImmediate) continues to drain microtasks normally. + vi.useFakeTimers({ + toFake: ['setTimeout', 'clearTimeout', 'setInterval', 'clearInterval'], + }) + vi.resetAllMocks() + // Default matchMedia: no reduced motion preference + Object.defineProperty(window, 'matchMedia', { + writable: true, + configurable: true, + value: vi.fn().mockImplementation((query: string) => ({ + matches: false, + media: query, + onchange: null, + addListener: vi.fn(), + removeListener: vi.fn(), + addEventListener: vi.fn(), + removeEventListener: vi.fn(), + dispatchEvent: vi.fn(), + })), + }) + }) + + afterEach(() => { + vi.useRealTimers() + vi.restoreAllMocks() + }) + + // (a) Idle timer fires after 60000ms -> overlay visible + game.start() called + it('(a) shows overlay and calls game.start() after idle timeout', async () => { + const { wrapper, gameHandle } = await mountWithGatePass() + + // Verify gate passed (Demo button present) + expect(wrapper.find('[data-demo-trigger]').exists()).toBe(true) + + // Before timeout: overlay should not be visible + expect(wrapper.find('[data-game-overlay]').exists()).toBe(false) + + // Advance timers past the idle timeout + await vi.advanceTimersByTimeAsync(60000) + + // Overlay should now be visible + expect(wrapper.find('[data-game-overlay]').exists()).toBe(true) + // game.start() should have been called + expect(gameHandle.start).toHaveBeenCalled() + }) + + // (b) Input event while attract is showing -> setPlayerControl(true), overlay still up + it('(b) input event while attract is showing hands control but keeps overlay', async () => { + const { wrapper, gameHandle } = await mountWithGatePass() + + // Trigger idle timeout to show attract mode + await vi.advanceTimersByTimeAsync(60000) + + expect(wrapper.find('[data-game-overlay]').exists()).toBe(true) + + // Simulate a mousemove event while attract is showing + window.dispatchEvent(new MouseEvent('mousemove')) + await flushPromises() + + // setPlayerControl(true) should have been called + expect(gameHandle.setPlayerControl).toHaveBeenCalledWith(true) + // Overlay stays up (still visible) + expect(wrapper.find('[data-game-overlay]').exists()).toBe(true) + }) + + // (c) "Demo" button is present; clicking it starts the game immediately + it('(c) Demo button is present and clicking it starts the game immediately', async () => { + const { wrapper, gameHandle } = await mountWithGatePass() + + const demoBtn = wrapper.find('[data-demo-trigger]') + expect(demoBtn.exists()).toBe(true) + + // Click Demo button - should start attract mode without waiting for timer + await demoBtn.trigger('click') + await flushPromises() + + expect(wrapper.find('[data-game-overlay]').exists()).toBe(true) + expect(gameHandle.start).toHaveBeenCalled() + }) + + // (d) onPickTrack navigates via replaceState (NOT pushState) + dispatches popstate + hides overlay + it('(d) onPickTrack uses replaceState, dispatches popstate, and hides overlay', async () => { + const { wrapper, createGame } = await mountWithGatePass() + + // Spy on history methods + const replaceStateSpy = vi.spyOn(window.history, 'replaceState') + const pushStateSpy = vi.spyOn(window.history, 'pushState') + const dispatchEventSpy = vi.spyOn(window, 'dispatchEvent') + + // Trigger attract to get overlay visible first + await vi.advanceTimersByTimeAsync(60000) + expect(wrapper.find('[data-game-overlay]').exists()).toBe(true) + + // Extract onPickTrack from the createGame call args + expect(createGame).toHaveBeenCalled() + const callArgs = createGame.mock.calls[0] + const opts = callArgs[2] as { onPickTrack: (id: string) => void } + const onPickTrack = opts.onPickTrack + + // Invoke onPickTrack with a track id + onPickTrack('track-alpha') + await flushPromises() + + // replaceState should have been called (NOT pushState) + expect(replaceStateSpy).toHaveBeenCalled() + expect(pushStateSpy).not.toHaveBeenCalled() + + // The URL passed to replaceState should contain track=track-alpha + const replaceCall = replaceStateSpy.mock.calls[0] + const urlArg = replaceCall[2] as string + expect(urlArg).toContain('track=track-alpha') + + // popstate event should have been dispatched + const popstateDispatched = dispatchEventSpy.mock.calls.some( + ([evt]) => evt instanceof PopStateEvent && evt.type === 'popstate' + ) + expect(popstateDispatched).toBe(true) + + // Overlay should be hidden after handoff + expect(wrapper.find('[data-game-overlay]').exists()).toBe(false) + }) + + // (e) Gate fails -> no overlay, no Demo button, no timer, createGame never called + it('(e) gate failure: no overlay, no Demo button, createGame never called', async () => { + const { wrapper, createGame } = await mountWithGateFail() + + // No overlay + expect(wrapper.find('[data-game-overlay]').exists()).toBe(false) + // No demo button + expect(wrapper.find('[data-demo-trigger]').exists()).toBe(false) + // createGame never called + expect(createGame).not.toHaveBeenCalled() + + // Advance timers - nothing should happen + await vi.advanceTimersByTimeAsync(120000) + await flushPromises() + + // Still no overlay + expect(wrapper.find('[data-game-overlay]').exists()).toBe(false) + expect(createGame).not.toHaveBeenCalled() + }) + + // prefers-reduced-motion: idle auto-start disabled, Demo button still works + it('prefers-reduced-motion: idle timer does NOT auto-start, Demo button still works', async () => { + // Override matchMedia to report reduced motion preference + Object.defineProperty(window, 'matchMedia', { + writable: true, + configurable: true, + value: vi.fn().mockImplementation((query: string) => ({ + matches: query === '(prefers-reduced-motion: reduce)', + media: query, + onchange: null, + addListener: vi.fn(), + removeListener: vi.fn(), + addEventListener: vi.fn(), + removeEventListener: vi.fn(), + dispatchEvent: vi.fn(), + })), + }) + + const { wrapper, gameHandle } = await mountWithGatePass() + + // Gate should pass - Demo button present + expect(wrapper.find('[data-demo-trigger]').exists()).toBe(true) + + // Advance past idle timeout + await vi.advanceTimersByTimeAsync(60000) + + // Overlay must NOT have appeared (reduced motion blocks idle auto-start) + expect(wrapper.find('[data-game-overlay]').exists()).toBe(false) + expect(gameHandle.start).not.toHaveBeenCalled() + + // Demo button should still work + const demoBtn = wrapper.find('[data-demo-trigger]') + await demoBtn.trigger('click') + await flushPromises() + + expect(wrapper.find('[data-game-overlay]').exists()).toBe(true) + expect(gameHandle.start).toHaveBeenCalled() + }) +}) From c882b80fef19ed6b32794954b0fd29d63fb7362f Mon Sep 17 00:00:00 2001 From: Thomas Jung Date: Tue, 3 Nov 2026 11:49:28 -0600 Subject: [PATCH 016/165] docs,build(app-space-game): verify code-split build + document island (#3009) --- docs/developers/architecture/frontend-apps.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/developers/architecture/frontend-apps.md b/docs/developers/architecture/frontend-apps.md index 532fc0dc0..17b6c1826 100644 --- a/docs/developers/architecture/frontend-apps.md +++ b/docs/developers/architecture/frontend-apps.md @@ -57,6 +57,7 @@ Compiled by `build:apps` (a single Vite project) into `hugo/static/js/.js` | --- | --- | --- | | `navigator` | `/` (homepage) | Tutorial navigator with filters and search | | `app-space` | `/app-space` | Event-themed Vue SPA (Joule/Sapphire theme overlays); progress via `/api/getEventProgress`, QR via `/api/qrcode`, live updates via Socket.IO | +| `app-space-game` | `/app-space` (sibling overlay) | Kasimir Arcade (#3009): feature-flagged Three.js 2.5D attract-mode game layered over `app-space`. Idle -> auto-plays; reads the same `getAppSpaceProgress` payload (tracks=zones, tutorials=coins); hands off to the grid via `?track=` + popstate. Three.js is dynamically imported (code-split into the `renderer` chunk); flag/WebGL/data gate falls back to the grid. Does not modify `app-space`. | | `event-display` | `/event-display` | Launcher for the standalone display dashboard | | `nav-dropdown` | All pages (header) | Mission/group dropdown in the shellbar | | `scanner-vue` | `/scanner-vue/` | Vue 3 mobile-optimized scanner using native `BarcodeDetector`, falls back to manual JSON input | From 0fbe75c4e9045449510574b0b0aaa4760e235ec4 Mon Sep 17 00:00:00 2001 From: Thomas Jung Date: Tue, 3 Nov 2026 11:56:47 -0600 Subject: [PATCH 017/165] chore(app-space-game): ASCII-normalize comments, drop dead code, add json-reject fallback test (#3009) --- .../app-space-game/__tests__/feature.test.ts | 22 ++++++++++++++++--- .../__tests__/level-model.test.ts | 20 ++++++++--------- .../app-space-game/__tests__/mount.test.ts | 6 +---- .../__tests__/renderer.guard.test.ts | 2 +- hugo-apps/src/app-space-game/feature.ts | 14 ++++++------ hugo-apps/src/app-space-game/level-model.ts | 2 -- hugo-apps/src/app-space-game/types.ts | 2 +- 7 files changed, 39 insertions(+), 29 deletions(-) diff --git a/hugo-apps/src/app-space-game/__tests__/feature.test.ts b/hugo-apps/src/app-space-game/__tests__/feature.test.ts index 793ba1cfc..91170c983 100644 --- a/hugo-apps/src/app-space-game/__tests__/feature.test.ts +++ b/hugo-apps/src/app-space-game/__tests__/feature.test.ts @@ -1,7 +1,7 @@ // @vitest-environment happy-dom import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest' -// ── isGameEnabled ────────────────────────────────────────────────────────── +// --- isGameEnabled --- describe('isGameEnabled', () => { beforeEach(() => { vi.resetAllMocks() @@ -30,7 +30,7 @@ describe('isGameEnabled', () => { }) }) -// ── isWebGLAvailable ────────────────────────────────────────────────────── +// --- isWebGLAvailable --- describe('isWebGLAvailable', () => { let originalCreateElement: typeof document.createElement @@ -85,7 +85,7 @@ describe('isWebGLAvailable', () => { }) }) -// ── loadAppSpaceData ────────────────────────────────────────────────────── +// --- loadAppSpaceData --- describe('loadAppSpaceData', () => { const mockData = { eventId: 42, @@ -125,6 +125,22 @@ describe('loadAppSpaceData', () => { expect(result).toEqual(mockData) }) + it('falls back to /app-space-data.json when primary response.ok=true but .json() rejects', async () => { + vi.stubGlobal('fetch', vi.fn() + .mockResolvedValueOnce({ + ok: true, + json: vi.fn().mockRejectedValueOnce(new Error('bad json')), + }) + .mockResolvedValueOnce({ + ok: true, + json: vi.fn().mockResolvedValueOnce(mockFallbackData), + }), + ) + const { loadAppSpaceData } = await import('../feature') + const result = await loadAppSpaceData() + expect(result).toEqual(mockFallbackData) + }) + it('falls back to /app-space-data.json when the primary fetch fails (non-ok response)', async () => { vi.stubGlobal('fetch', vi.fn() .mockResolvedValueOnce({ ok: false, status: 404 }) diff --git a/hugo-apps/src/app-space-game/__tests__/level-model.test.ts b/hugo-apps/src/app-space-game/__tests__/level-model.test.ts index 87e840331..867ef391d 100644 --- a/hugo-apps/src/app-space-game/__tests__/level-model.test.ts +++ b/hugo-apps/src/app-space-game/__tests__/level-model.test.ts @@ -3,7 +3,7 @@ import { buildLevelModel, zoneColor } from '../level-model' import type { AppSpaceData } from '../types' describe('buildLevelModel', () => { - it('empty paths β‡’ zones.length === 0', () => { + it('empty paths -> zones.length === 0', () => { const data: AppSpaceData = { eventId: 1, eventName: 'Test Event', @@ -14,7 +14,7 @@ describe('buildLevelModel', () => { expect(result.zones).toHaveLength(0) }) - it('one linear path, 3 TUTORIAL items β‡’ 1 zone, 3 coins in order, titles preserved', () => { + it('one linear path, 3 TUTORIAL items -> 1 zone, 3 coins in order, titles preserved', () => { const data: AppSpaceData = { eventId: 1, eventName: 'Test Event', @@ -77,7 +77,7 @@ describe('buildLevelModel', () => { expect(result.zones[0].coins[2].title).toBe('Tutorial C') }) - it('fork + parallel-any segments β‡’ coins flattened to one ordered list', () => { + it('fork + parallel-any segments -> coins flattened to one ordered list', () => { const data: AppSpaceData = { eventId: 1, eventName: 'Test Event', @@ -186,7 +186,7 @@ describe('buildLevelModel', () => { expect(result.zones[0].coins[4].title).toBe('Fork Lane B 1') }) - it('path with a literal type:PRIZE item β‡’ goal.kind==="prize"', () => { + it('path with a literal type:PRIZE item -> goal.kind==="prize"', () => { const data: AppSpaceData = { eventId: 1, eventName: 'Test Event', @@ -237,7 +237,7 @@ describe('buildLevelModel', () => { expect(result.zones[0].goal.prizeId).toBe(42) }) - it('path whose only prize is type:CHECKPOINT + prizeId β‡’ goal.kind==="checkpoint", prizeId preserved', () => { + it('path whose only prize is type:CHECKPOINT + prizeId -> goal.kind==="checkpoint", prizeId preserved', () => { const data: AppSpaceData = { eventId: 1, eventName: 'Test Event', @@ -288,7 +288,7 @@ describe('buildLevelModel', () => { expect(result.zones[0].goal.prizeId).toBe(99) }) - it('path with neither PRIZE nor prizeId CHECKPOINT β‡’ goal.kind==="track", trackId set', () => { + it('path with neither PRIZE nor prizeId CHECKPOINT -> goal.kind==="track", trackId set', () => { const data: AppSpaceData = { eventId: 1, eventName: 'Test Event', @@ -326,7 +326,7 @@ describe('buildLevelModel', () => { expect(result.zones[0].goal.trackId).toBe('track5') }) - it('authenticated + some status:COMPLETED β‡’ those coins done:true; zone with all tutorials COMPLETED β‡’ cleared:true', () => { + it('authenticated + some status:COMPLETED -> those coins done:true; zone with all tutorials COMPLETED -> cleared:true', () => { const data: AppSpaceData = { eventId: 1, eventName: 'Test Event', @@ -377,7 +377,7 @@ describe('buildLevelModel', () => { expect(result.zones[0].cleared).toBe(true) }) - it('anonymous payload (no authenticated, items lack status) β‡’ all coins available:true, done:false; no throw', () => { + it('anonymous payload (no authenticated, items lack status) -> all coins available:true, done:false; no throw', () => { const data: AppSpaceData = { eventId: 1, eventName: 'Test Event', @@ -415,7 +415,7 @@ describe('buildLevelModel', () => { expect(result.zones[0].coins[0].available).toBe(true) }) - it('unlocked:false items β‡’ zone locked:true, coins available:false', () => { + it('unlocked:false items -> zone locked:true, coins available:false', () => { const data: AppSpaceData = { eventId: 1, eventName: 'Test Event', @@ -454,7 +454,7 @@ describe('buildLevelModel', () => { expect(result.zones[0].coins[0].available).toBe(false) }) - it('CHECKPOINT item mid-list β‡’ a Gate with correct afterCoinIndex', () => { + it('CHECKPOINT item mid-list -> a Gate with correct afterCoinIndex', () => { const data: AppSpaceData = { eventId: 1, eventName: 'Test Event', diff --git a/hugo-apps/src/app-space-game/__tests__/mount.test.ts b/hugo-apps/src/app-space-game/__tests__/mount.test.ts index d832e9e08..b4a8b0481 100644 --- a/hugo-apps/src/app-space-game/__tests__/mount.test.ts +++ b/hugo-apps/src/app-space-game/__tests__/mount.test.ts @@ -1,12 +1,8 @@ // @vitest-environment happy-dom -import { describe, it, expect, vi, beforeEach } from 'vitest' +import { describe, it, expect } from 'vitest' import { mount } from '@vue/test-utils' describe('AppSpaceGame mount scaffold', () => { - beforeEach(() => { - vi.restoreAllMocks() - }) - it('mounts without throwing and renders data-app-space-game root', async () => { const AppSpaceGame = (await import('../AppSpaceGame.vue')).default diff --git a/hugo-apps/src/app-space-game/__tests__/renderer.guard.test.ts b/hugo-apps/src/app-space-game/__tests__/renderer.guard.test.ts index 0c7e22a33..889d4f5e5 100644 --- a/hugo-apps/src/app-space-game/__tests__/renderer.guard.test.ts +++ b/hugo-apps/src/app-space-game/__tests__/renderer.guard.test.ts @@ -1,5 +1,5 @@ // @vitest-environment happy-dom -// Module-contract guard for renderer.ts β€” NO real WebGL. +// Module-contract guard for renderer.ts - NO real WebGL. // Three.js WebGLRenderer is mocked so this test runs in jsdom/node without a GPU. // Only asserts the exported API shape and that stop() before start() is safe. import { describe, it, expect, vi, beforeEach } from 'vitest' diff --git a/hugo-apps/src/app-space-game/feature.ts b/hugo-apps/src/app-space-game/feature.ts index bc1e39806..9b68bbb12 100644 --- a/hugo-apps/src/app-space-game/feature.ts +++ b/hugo-apps/src/app-space-game/feature.ts @@ -1,9 +1,9 @@ /** * Feature gate for the Kasimir Arcade App-Space game island. - * All checks are best-effort β€” no function here throws into the UI. + * All checks are best-effort - no function here throws into the UI. * * Pattern modelled on hugo-apps/src/ktt/lib/server.ts (isKttEnabled). - * Uses native fetch (Node/browser built-in β€” no axios or other HTTP libs). + * Uses native fetch (Node/browser built-in - no axios or other HTTP libs). */ import type { AppSpaceData } from './types' @@ -14,7 +14,7 @@ import type { AppSpaceData } from './types' * The flag is a runtime DB value (no redeploy required), so the client must * ask the server. We probe the getAppSpaceProgress endpoint with a dummy id: * the service rejects with 503 when the APP_SPACE_GAME_ENABLED flag is OFF. - * Returns false on 503/network error, true on ok β€” best-effort, never throws. + * Returns false on 503/network error, true on ok - best-effort, never throws. * * // TODO(#3009): Replace this probe with a dedicated lightweight flag endpoint * // (e.g. /api/isAppSpaceGameEnabled) once one is added to the CAP service. @@ -33,7 +33,7 @@ export async function isGameEnabled(): Promise { /** * Synchronously detect WebGL support by creating a throwaway canvas. * Returns true if either webgl2 or webgl contexts are available. - * Wraps ALL operations in try/catch so any throw β†’ false. + * Wraps ALL operations in try/catch so any throw -> false. */ export function isWebGLAvailable(): boolean { try { @@ -48,7 +48,7 @@ export function isWebGLAvailable(): boolean { * Load App-Space progress data for the current page's event. * * Resolves the eventId from the URL search params case-insensitively - * (matching the pattern in hugo-apps/src/app-space/AppSpace.vue lines 194–198). + * (matching the pattern in hugo-apps/src/app-space/AppSpace.vue lines 194-198). * Falls back to `/app-space-data.json` if the primary endpoint fails. * Returns null if both fetches fail. Never throws. */ @@ -64,7 +64,7 @@ export async function loadAppSpaceData(): Promise { } } } catch { - // URL parsing failed β€” proceed with no eventId + // URL parsing failed - proceed with no eventId } const param = eventId ? `eventLegacyId=${eventId}` : `eventLegacyId=0` @@ -76,7 +76,7 @@ export async function loadAppSpaceData(): Promise { return (await res.json()) as AppSpaceData } } catch { - // Primary failed β€” fall through to fallback + // Primary failed - fall through to fallback } // Fallback: static JSON file diff --git a/hugo-apps/src/app-space-game/level-model.ts b/hugo-apps/src/app-space-game/level-model.ts index 4782ad7bf..3f82a9501 100644 --- a/hugo-apps/src/app-space-game/level-model.ts +++ b/hugo-apps/src/app-space-game/level-model.ts @@ -164,8 +164,6 @@ export function buildLevelModel(data: AppSpaceData): LevelModel { const { coins, gates } = buildCoinsAndGates(items, checkpointIndices, isAuthenticated, isLocked) // Determine if zone is cleared: at least 1 tutorial AND all tutorials COMPLETED - const hasAtLeastOneCompletion = - isAuthenticated && tutorialItems.length > 0 && tutorialItems.some(item => item.status === 'COMPLETED') const allTutorialsCompleted = isAuthenticated && tutorialItems.length > 0 && tutorialItems.every(item => item.status === 'COMPLETED') const cleared = allTutorialsCompleted diff --git a/hugo-apps/src/app-space-game/types.ts b/hugo-apps/src/app-space-game/types.ts index 4aa4f0f24..175f278d2 100644 --- a/hugo-apps/src/app-space-game/types.ts +++ b/hugo-apps/src/app-space-game/types.ts @@ -1,4 +1,4 @@ -// Payload interfaces copied from hugo-apps/src/app-space/AppSpace.vue (lines 53–118) +// Payload interfaces copied from hugo-apps/src/app-space/AppSpace.vue (lines 53-118) // to avoid cross-island import dependency. export interface AppSpaceItem { From e10646a6c745ffd0c7b2ceb356534e5d148387ad Mon Sep 17 00:00:00 2001 From: Thomas Jung Date: Tue, 3 Nov 2026 12:37:28 -0600 Subject: [PATCH 018/165] fix(app-space-game): player movement + exit, real default-OFF flag, gate-test determinism (#3009) C1: Add player-driven movement in renderer.ts (ArrowRight/ArrowUp/Space keyboard; pointer-right-half=run, pointer-left-half=tap=jump). attach/detachPlayerInput called by setPlayerControl so listeners are always cleaned up in stop(). Add [data-game-close] button + Escape key handler in AppSpaceGame.vue so the player can always exit the overlay (closeGame: hide overlay, game.stop(), restart idle timer, no URL change). C2: Add appSpaceGameEnabled:Boolean to getAppSpaceProgress return type in CDS. Import isFlagEnabled from ./lib/feature-flags/db-flags.js in developer-service.js; read APP_SPACE_GAME_ENABLED flag (default OFF, ImsConfig-backed) inside a try/catch->false wrapper and return it on the payload. Remove the isGameEnabled() reachability probe from feature.ts; AppSpaceGame.vue gates on data.appSpaceGameEnabled===true instead. Eliminates extra heavy query. M2: Add dispose() to GameHandle interface and implementation; call game?.dispose?.() in onUnmounted to free WebGL contexts + geometries/materials. M3: Add TextureLoader onError callback for Kasimir texture (silent console.warn, no throw, scene already survives missing texture). Tests: remove 3 isGameEnabled probe tests (function removed); add tests for flag-OFF gate, close button, and Escape key. 37/37 pass on Node 22.23.1. --- hugo-apps/src/app-space-game/AppSpaceGame.vue | 98 ++++++++++++++--- .../__tests__/AppSpaceGame.test.ts | 77 ++++++++++++- .../app-space-game/__tests__/feature.test.ts | 31 +----- hugo-apps/src/app-space-game/feature.ts | 26 +---- hugo-apps/src/app-space-game/renderer.ts | 104 +++++++++++++++++- hugo-apps/src/app-space-game/types.ts | 1 + srv/developer-service.cds | 15 +-- srv/developer-service.js | 13 ++- 8 files changed, 280 insertions(+), 85 deletions(-) diff --git a/hugo-apps/src/app-space-game/AppSpaceGame.vue b/hugo-apps/src/app-space-game/AppSpaceGame.vue index a2835e5de..c2b8bf6b2 100644 --- a/hugo-apps/src/app-space-game/AppSpaceGame.vue +++ b/hugo-apps/src/app-space-game/AppSpaceGame.vue @@ -18,7 +18,22 @@ role="dialog" aria-label="Kasimir Arcade Demo" aria-modal="true" - /> + > + + + + +
+ Arrow keys or tap to move →   Reach the flag to pick a track +
+
import { ref, onMounted, onUnmounted } from 'vue' import type { LevelModel } from './types' -import { isGameEnabled, isWebGLAvailable, loadAppSpaceData } from './feature' +import { isWebGLAvailable, loadAppSpaceData } from './feature' import { buildLevelModel } from './level-model' import type { GameHandle } from './renderer' @@ -46,6 +61,8 @@ const levelModel = ref(null) const canvasRef = ref(null) // Whether the attract overlay is currently shown const attractVisible = ref(false) +// Whether the player has taken over (shows hint) +const showPlayerHint = ref(false) // Whether the user has taken control from attract mode let inPlayerControl = false // Game handle for cleanup @@ -78,10 +95,20 @@ function resetIdleTimer(): void { function startAttract(): void { if (!game) return attractVisible.value = true + showPlayerHint.value = false inPlayerControl = false game.start() } +/** Close the game overlay without picking a track - return to grid as-is. */ +function closeGame(): void { + attractVisible.value = false + showPlayerHint.value = false + inPlayerControl = false + game?.stop() + resetIdleTimer() +} + /** Build the track URL preserving existing search params. */ function buildTrackUrl(trackId: string): string { const params = new URLSearchParams(window.location.search) @@ -99,15 +126,23 @@ function onPickTrack(trackId: string): void { window.dispatchEvent(new PopStateEvent('popstate', { state: { track: trackId } })) // 3. Hide overlay and stop renderer attractVisible.value = false + showPlayerHint.value = false inPlayerControl = false game?.stop() } /** Handler called on any page-level input event. */ -function onUserInput(): void { +function onUserInput(e: Event): void { + // Escape key always closes the game overlay + if (e instanceof KeyboardEvent && e.key === 'Escape' && attractVisible.value) { + closeGame() + return + } + if (attractVisible.value && !inPlayerControl) { // Hand control to the player but keep overlay up inPlayerControl = true + showPlayerHint.value = true game?.setPlayerControl(true) return } @@ -121,20 +156,10 @@ function onUserInput(): void { const INPUT_EVENTS: string[] = ['mousemove', 'touchstart', 'keydown'] onMounted(async () => { - // All three conditions must pass; any failure leaves gateReady false (grid intact) - let enabled: boolean, webgl: boolean - try { - const results = await Promise.all([ - isGameEnabled(), - Promise.resolve(isWebGLAvailable()), - ]) - enabled = results[0] - webgl = results[1] - } catch { - return - } - if (!enabled || !webgl) return + // WebGL must be available + if (!isWebGLAvailable()) return + // Load data - the payload carries the feature flag let data try { data = await loadAppSpaceData() @@ -143,6 +168,9 @@ onMounted(async () => { } if (!data) return + // Gate on the server-side flag (default OFF - absent or false means no game) + if (data.appSpaceGameEnabled !== true) return + // Build the level model let model try { @@ -163,7 +191,7 @@ onMounted(async () => { // Arm input listeners for idle timer and player-control handoff for (const ev of INPUT_EVENTS) { - window.addEventListener(ev, onUserInput, { passive: true }) + window.addEventListener(ev, onUserInput, { passive: ev !== 'keydown' }) } // Start the idle countdown @@ -179,6 +207,7 @@ onUnmounted(() => { window.removeEventListener(ev, onUserInput) } game?.stop() + game?.dispose?.() game = null }) @@ -217,4 +246,39 @@ onUnmounted(() => { z-index: 9999; background: rgba(6, 8, 15, 0.92); } + +[data-game-close] { + position: absolute; + top: 16px; + right: 16px; + z-index: 10001; + padding: 6px 14px; + border: 1px solid rgba(255, 255, 255, 0.4); + border-radius: 4px; + background: rgba(6, 8, 15, 0.85); + color: #ffffff; + font-size: 13px; + font-family: system-ui, sans-serif; + cursor: pointer; +} + +[data-game-close]:hover { + background: rgba(60, 20, 20, 0.9); +} + +[data-player-hint] { + position: absolute; + bottom: 24px; + left: 50%; + transform: translateX(-50%); + z-index: 10001; + padding: 6px 16px; + border-radius: 4px; + background: rgba(6, 8, 15, 0.8); + color: rgba(255, 255, 255, 0.7); + font-size: 12px; + font-family: system-ui, sans-serif; + white-space: nowrap; + pointer-events: none; +} diff --git a/hugo-apps/src/app-space-game/__tests__/AppSpaceGame.test.ts b/hugo-apps/src/app-space-game/__tests__/AppSpaceGame.test.ts index 57c48a0dc..56c9372a0 100644 --- a/hugo-apps/src/app-space-game/__tests__/AppSpaceGame.test.ts +++ b/hugo-apps/src/app-space-game/__tests__/AppSpaceGame.test.ts @@ -8,6 +8,7 @@ const MOCK_DATA = { eventId: 1, eventName: 'Test', type: 'DEVTOBERFEST', + appSpaceGameEnabled: true, paths: [ { id: 'track-alpha', @@ -44,13 +45,13 @@ function makeGameHandle() { start: vi.fn(), stop: vi.fn(), setPlayerControl: vi.fn(), + dispose: vi.fn(), } } // -- Module mocks (hoisted so vi.mock applies before imports) ---------------- vi.mock('../feature', () => ({ - isGameEnabled: vi.fn(), isWebGLAvailable: vi.fn(), loadAppSpaceData: vi.fn(), })) @@ -62,11 +63,10 @@ vi.mock('../renderer', () => ({ // -- Helpers ----------------------------------------------------------------- async function mountWithGatePass() { - const { isGameEnabled, isWebGLAvailable, loadAppSpaceData } = await import('../feature') + const { isWebGLAvailable, loadAppSpaceData } = await import('../feature') const { createGame } = await import('../renderer') const gameHandle = makeGameHandle() - vi.mocked(isGameEnabled).mockResolvedValue(true) vi.mocked(isWebGLAvailable).mockReturnValue(true) vi.mocked(loadAppSpaceData).mockResolvedValue(MOCK_DATA) vi.mocked(createGame).mockReturnValue(gameHandle) @@ -80,10 +80,9 @@ async function mountWithGatePass() { } async function mountWithGateFail() { - const { isGameEnabled, isWebGLAvailable, loadAppSpaceData } = await import('../feature') + const { isWebGLAvailable, loadAppSpaceData } = await import('../feature') const { createGame } = await import('../renderer') - vi.mocked(isGameEnabled).mockResolvedValue(false) vi.mocked(isWebGLAvailable).mockReturnValue(false) vi.mocked(loadAppSpaceData).mockResolvedValue(null) vi.mocked(createGame).mockReset() @@ -95,6 +94,21 @@ async function mountWithGateFail() { return { wrapper, createGame: vi.mocked(createGame) } } +async function mountWithFlagOff() { + const { isWebGLAvailable, loadAppSpaceData } = await import('../feature') + const { createGame } = await import('../renderer') + + vi.mocked(isWebGLAvailable).mockReturnValue(true) + vi.mocked(loadAppSpaceData).mockResolvedValue({ ...MOCK_DATA, appSpaceGameEnabled: false }) + vi.mocked(createGame).mockReset() + + const AppSpaceGame = (await import('../AppSpaceGame.vue')).default + const wrapper = mount(AppSpaceGame) + await flushPromises() + + return { wrapper, createGame: vi.mocked(createGame) } +} + // -- Test suites ------------------------------------------------------------- describe('AppSpaceGame attract state machine', () => { @@ -243,6 +257,59 @@ describe('AppSpaceGame attract state machine', () => { expect(createGame).not.toHaveBeenCalled() }) + // (f) Server flag OFF (appSpaceGameEnabled: false) -> gate does not pass + it('(f) server flag OFF: no overlay, no Demo button, createGame never called', async () => { + const { wrapper, createGame } = await mountWithFlagOff() + + // No overlay + expect(wrapper.find('[data-game-overlay]').exists()).toBe(false) + // No demo button + expect(wrapper.find('[data-demo-trigger]').exists()).toBe(false) + // createGame never called + expect(createGame).not.toHaveBeenCalled() + + // Advance timers - nothing should happen + await vi.advanceTimersByTimeAsync(120000) + await flushPromises() + + expect(wrapper.find('[data-game-overlay]').exists()).toBe(false) + expect(createGame).not.toHaveBeenCalled() + }) + + // (g) Close button is visible when overlay is showing; clicking it hides overlay + it('(g) close button hides overlay without picking a track', async () => { + const { wrapper, gameHandle } = await mountWithGatePass() + + // Trigger attract mode + await vi.advanceTimersByTimeAsync(60000) + expect(wrapper.find('[data-game-overlay]').exists()).toBe(true) + + const closeBtn = wrapper.find('[data-game-close]') + expect(closeBtn.exists()).toBe(true) + + await closeBtn.trigger('click') + await flushPromises() + + expect(wrapper.find('[data-game-overlay]').exists()).toBe(false) + expect(gameHandle.stop).toHaveBeenCalled() + }) + + // (h) Escape key closes the overlay + it('(h) Escape key closes the game overlay', async () => { + const { wrapper, gameHandle } = await mountWithGatePass() + + // Trigger attract mode + await vi.advanceTimersByTimeAsync(60000) + expect(wrapper.find('[data-game-overlay]').exists()).toBe(true) + + // Dispatch Escape keydown on window + window.dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape' })) + await flushPromises() + + expect(wrapper.find('[data-game-overlay]').exists()).toBe(false) + expect(gameHandle.stop).toHaveBeenCalled() + }) + // prefers-reduced-motion: idle auto-start disabled, Demo button still works it('prefers-reduced-motion: idle timer does NOT auto-start, Demo button still works', async () => { // Override matchMedia to report reduced motion preference diff --git a/hugo-apps/src/app-space-game/__tests__/feature.test.ts b/hugo-apps/src/app-space-game/__tests__/feature.test.ts index 91170c983..c91fc8be7 100644 --- a/hugo-apps/src/app-space-game/__tests__/feature.test.ts +++ b/hugo-apps/src/app-space-game/__tests__/feature.test.ts @@ -1,34 +1,9 @@ // @vitest-environment happy-dom import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest' -// --- isGameEnabled --- -describe('isGameEnabled', () => { - beforeEach(() => { - vi.resetAllMocks() - }) - - afterEach(() => { - vi.restoreAllMocks() - }) - - it('returns false when the flag endpoint responds 503', async () => { - vi.stubGlobal('fetch', vi.fn().mockResolvedValueOnce({ ok: false, status: 503 })) - const { isGameEnabled } = await import('../feature') - expect(await isGameEnabled()).toBe(false) - }) - - it('returns false when the network request rejects', async () => { - vi.stubGlobal('fetch', vi.fn().mockRejectedValueOnce(new Error('Network error'))) - const { isGameEnabled } = await import('../feature') - expect(await isGameEnabled()).toBe(false) - }) - - it('returns true when the flag endpoint responds ok', async () => { - vi.stubGlobal('fetch', vi.fn().mockResolvedValueOnce({ ok: true, status: 200 })) - const { isGameEnabled } = await import('../feature') - expect(await isGameEnabled()).toBe(true) - }) -}) +// Note: isGameEnabled was removed in C2 fix (#3009). The game enable signal is +// now carried by data.appSpaceGameEnabled from loadAppSpaceData(), which reads +// the server-side ImsConfig flag APP_SPACE_GAME_ENABLED (default OFF). // --- isWebGLAvailable --- describe('isWebGLAvailable', () => { diff --git a/hugo-apps/src/app-space-game/feature.ts b/hugo-apps/src/app-space-game/feature.ts index 9b68bbb12..0a407c29e 100644 --- a/hugo-apps/src/app-space-game/feature.ts +++ b/hugo-apps/src/app-space-game/feature.ts @@ -8,28 +8,6 @@ import type { AppSpaceData } from './types' -/** - * Probe whether the App-Space game feature flag is enabled server-side. - * - * The flag is a runtime DB value (no redeploy required), so the client must - * ask the server. We probe the getAppSpaceProgress endpoint with a dummy id: - * the service rejects with 503 when the APP_SPACE_GAME_ENABLED flag is OFF. - * Returns false on 503/network error, true on ok - best-effort, never throws. - * - * // TODO(#3009): Replace this probe with a dedicated lightweight flag endpoint - * // (e.g. /api/isAppSpaceGameEnabled) once one is added to the CAP service. - * // Until then, reachability of getAppSpaceProgress(eventLegacyId=0) serves - * // as the enable signal. - */ -export async function isGameEnabled(): Promise { - try { - const r = await fetch('/api/getAppSpaceProgress(eventLegacyId=0)') - return r.ok - } catch { - return false - } -} - /** * Synchronously detect WebGL support by creating a throwaway canvas. * Returns true if either webgl2 or webgl contexts are available. @@ -51,6 +29,10 @@ export function isWebGLAvailable(): boolean { * (matching the pattern in hugo-apps/src/app-space/AppSpace.vue lines 194-198). * Falls back to `/app-space-data.json` if the primary endpoint fails. * Returns null if both fetches fail. Never throws. + * + * The returned payload now includes `appSpaceGameEnabled` (Boolean) from the + * server's ImsConfig-backed feature flag (APP_SPACE_GAME_ENABLED, default OFF). + * The caller gates the game on `data.appSpaceGameEnabled === true`. */ export async function loadAppSpaceData(): Promise { // Resolve eventId from URL params case-insensitively (same as AppSpace.vue) diff --git a/hugo-apps/src/app-space-game/renderer.ts b/hugo-apps/src/app-space-game/renderer.ts index 7a1586815..763767f71 100644 --- a/hugo-apps/src/app-space-game/renderer.ts +++ b/hugo-apps/src/app-space-game/renderer.ts @@ -281,6 +281,7 @@ export interface GameHandle { start(): void stop(): void setPlayerControl(on: boolean): void + dispose?(): void } export function createGame( @@ -297,6 +298,7 @@ export function createGame( start() {}, stop() {}, setPlayerControl(_on: boolean) {}, + dispose() {}, } } @@ -330,7 +332,15 @@ export function createGame( const { markers, zoneMeta, totalLen } = buildScene(scene, model) // Kasimir billboard - const tex = new THREE.TextureLoader().load(KASIMIR_TEX) + const tex = new THREE.TextureLoader().load( + KASIMIR_TEX, + undefined, + undefined, + (_err) => { + // Texture failed to load - scene already survives without it (transparent material) + try { console.warn('[app-space-game] Kasimir texture failed to load') } catch { /* silent */ } + } + ) tex.colorSpace = THREE.SRGBColorSpace const kMat = new THREE.MeshBasicMaterial({ map: tex, transparent: true, alphaTest: 0.3 }) const kasimir = new THREE.Mesh(new THREE.PlaneGeometry(2.6, 3.6), kMat) @@ -364,6 +374,33 @@ export function createGame( const clock = new THREE.Clock() + // --- Player input state --- + const keys = { right: false, jump: false } + + function onKeyDown(e: KeyboardEvent): void { + if (e.key === 'ArrowRight') keys.right = true + if (e.key === 'ArrowUp' || e.key === ' ') keys.jump = true + } + + function onKeyUp(e: KeyboardEvent): void { + if (e.key === 'ArrowRight') keys.right = false + if (e.key === 'ArrowUp' || e.key === ' ') keys.jump = false + } + + // Pointer/touch: tap anywhere on the canvas = jump; hold right half = run right + let pointerDown = false + let pointerRightHalf = false + + function onPointerDown(e: PointerEvent): void { + pointerDown = true + pointerRightHalf = e.clientX > canvas.getBoundingClientRect().left + canvas.clientWidth / 2 + } + + function onPointerUp(): void { + pointerDown = false + pointerRightHalf = false + } + function resizeToCanvas() { const w = canvas.clientWidth || canvas.width || 800 const h = canvas.clientHeight || canvas.height || 600 @@ -377,8 +414,8 @@ export function createGame( const dt = Math.min(clock.getDelta(), 0.05) const t = clock.elapsedTime - // Attract auto-run: advance Kasimir forward, loop at end if (!playerControl) { + // Attract auto-run: advance Kasimir forward, loop at end kasX += SPEED * dt if (kasX > totalLen - 4) { kasX = 5 @@ -397,9 +434,21 @@ export function createGame( vy = JUMP onGround = false } + } else { + // Player-driven movement: keyboard + pointer/touch + const moveRight = keys.right || (pointerDown && pointerRightHalf) + if (moveRight) { + kasX += SPEED * dt + } + const doJump = keys.jump || (pointerDown && !pointerRightHalf) + if (doJump && onGround) { + vy = JUMP + onGround = false + keys.jump = false // consume single-press so holding doesn't re-trigger + } } - // Physics + // Physics (shared between attract and player modes) vy += GRAV * dt let newY = kasY + vy * dt if (newY <= 1.8) { @@ -432,7 +481,7 @@ export function createGame( } }) - // Goal reach check (player-control mode only, for onPickTrack) + // Goal reach check - fires onPickTrack in player-control mode if (playerControl) { zoneMeta.forEach((zm, zi) => { const zone = model.zones[zi] @@ -451,6 +500,26 @@ export function createGame( rafId = requestAnimationFrame(tick) } + function attachPlayerInput(): void { + window.addEventListener('keydown', onKeyDown) + window.addEventListener('keyup', onKeyUp) + canvas.addEventListener('pointerdown', onPointerDown) + canvas.addEventListener('pointerup', onPointerUp) + canvas.addEventListener('pointerleave', onPointerUp) + } + + function detachPlayerInput(): void { + window.removeEventListener('keydown', onKeyDown) + window.removeEventListener('keyup', onKeyUp) + canvas.removeEventListener('pointerdown', onPointerDown) + canvas.removeEventListener('pointerup', onPointerUp) + canvas.removeEventListener('pointerleave', onPointerUp) + keys.right = false + keys.jump = false + pointerDown = false + pointerRightHalf = false + } + return { start() { if (started) return @@ -464,10 +533,37 @@ export function createGame( cancelAnimationFrame(rafId) rafId = 0 } + detachPlayerInput() + playerControl = false started = false }, setPlayerControl(on: boolean) { playerControl = on + if (on) { + attachPlayerInput() + } else { + detachPlayerInput() + } + }, + dispose() { + // Dispose renderer and GPU resources + try { + renderer.dispose() + scene.traverse(obj => { + if (obj instanceof THREE.Mesh) { + obj.geometry?.dispose() + if (Array.isArray(obj.material)) { + obj.material.forEach(m => m.dispose()) + } else { + (obj.material as THREE.Material)?.dispose() + } + } + }) + kMat.dispose() + tex.dispose() + } catch { + // Best-effort - never throw on dispose + } }, } } diff --git a/hugo-apps/src/app-space-game/types.ts b/hugo-apps/src/app-space-game/types.ts index 175f278d2..d780a3019 100644 --- a/hugo-apps/src/app-space-game/types.ts +++ b/hugo-apps/src/app-space-game/types.ts @@ -57,6 +57,7 @@ export interface AppSpaceData { eventDescription?: string hasLogo?: boolean authenticated?: boolean + appSpaceGameEnabled?: boolean type: string paths: AppSpaceTrack[] } diff --git a/srv/developer-service.cds b/srv/developer-service.cds index 3424d32ec..7cdacbbc7 100644 --- a/srv/developer-service.cds +++ b/srv/developer-service.cds @@ -232,13 +232,14 @@ service DeveloperService { // client whether to enable the logged-in-only realtime/confetti path. @(requires: 'any') function getAppSpaceProgress(eventLegacyId : Integer) returns { - eventId : Integer; - eventName : String; - eventDescription : String; - eventType : String; - hasLogo : Boolean; - authenticated : Boolean; - type : String; + eventId : Integer; + eventName : String; + eventDescription : String; + eventType : String; + hasLogo : Boolean; + authenticated : Boolean; + appSpaceGameEnabled : Boolean; + type : String; paths : many { id : Integer; title : String; diff --git a/srv/developer-service.js b/srv/developer-service.js index f444f0d30..677bd0550 100644 --- a/srv/developer-service.js +++ b/srv/developer-service.js @@ -16,6 +16,7 @@ import { rollUpParentsForCompletion } from './lib/completion-rollup.js'; import { renderMarkdown } from './lib/markdown.js'; import { buildTaskMap, effectiveLegacyId, expandGroupTutorials, buildPathItems, buildPathSegments, fetchGroupTitles, groupChildLegacyIds, buildRecordMap } from './lib/path-item-task-resolver.js'; import { annotateUnlocked } from './lib/app-space-segments.js'; +import { isFlagEnabled } from './lib/feature-flags/db-flags.js'; import { handleRequestAccountMerge, handleConfirmAccountMerge } from './lib/account-merge-request.js'; // Per-user rate limit for resetTutorialProgress β€” same window as the @@ -720,13 +721,21 @@ export default class DeveloperService extends cds.ApplicationService { // surface a SUPERSEDED row and render a done task as incomplete. const recordMap = buildRecordMap(userRecords); + let appSpaceGameEnabled = false; + try { + appSpaceGameEnabled = isFlagEnabled('APP_SPACE_GAME_ENABLED'); + } catch { + // flag check failed - leave as false (default OFF) + } + return { eventId: event.legacyId, eventName: event.name || '', - eventDescription: renderMarkdown(event.description), // #2296: admin-authored Markdown β†’ safe HTML for the app-space hero + eventDescription: renderMarkdown(event.description), // #2296: admin-authored Markdown -> safe HTML for the app-space hero eventType: event.eventType ?? 'OTHER', hasLogo: Boolean(event.hasLogo), - authenticated: Boolean(dbUser), // #2314/#2730: false only for anonymous callers β†’ client skips the logged-in-only realtime/confetti path. Truthy when user resolves via any tier (sapId, identity link, or pin). + authenticated: Boolean(dbUser), // #2314/#2730: false only for anonymous callers -> client skips the logged-in-only realtime/confetti path. Truthy when user resolves via any tier (sapId, identity link, or pin). + appSpaceGameEnabled, type: 'COMPLEX', paths: paths.map(p => { const pathItems = allItems.filter(i => i.path_ID === p.ID); From 252c9d87a2fd638c658301b615db52d3c6cc2eeb Mon Sep 17 00:00:00 2001 From: Thomas Jung Date: Tue, 3 Nov 2026 13:01:22 -0600 Subject: [PATCH 019/165] fix(app-space-game): register APP_SPACE_GAME_ENABLED feature flag (#3009) --- packages/core/feature-flags/registry.js | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/packages/core/feature-flags/registry.js b/packages/core/feature-flags/registry.js index 1463ee774..a8b4f06ff 100644 --- a/packages/core/feature-flags/registry.js +++ b/packages/core/feature-flags/registry.js @@ -438,9 +438,17 @@ export const FEATURE_FLAGS = [ key: 'KTT_ENABLED', label: 'KTT β€” Kasimir Teaches TLAs', category: 'Content', kind: 'db', imsConfigKey: 'flag.ktt.enabled', valueType: 'boolean', default: false, status: 'beta', - description: 'Enables the /explore/ktt/ acronym trainer and its /ktt CAP endpoints. Off β†’ completeLesson/syncProgress reject 503.', + description: 'Enables the /explore/ktt/ acronym trainer and its /ktt CAP endpoints. Off -> completeLesson/syncProgress reject 503.', howToChange: featureFlagUpsert('KTT_ENABLED', 'flag.ktt.enabled'), }, + // ---- App-Space Kasimir Arcade (#3009) ---- + { + key: 'APP_SPACE_GAME_ENABLED', label: 'App-Space Kasimir Arcade', category: 'Content', + kind: 'db', imsConfigKey: 'flag.appSpaceGame', + valueType: 'boolean', default: false, status: 'dev-only', + description: 'Enables the Kasimir Arcade attract-mode game overlay on the App-Space page (#3009). Off -> page shows the normal track grid. Surfaced on getAppSpaceProgress as appSpaceGameEnabled.', + howToChange: featureFlagUpsert('APP_SPACE_GAME_ENABLED', 'flag.appSpaceGame'), + }, // ---- Generative UI (research spike #2362) ---- { key: 'CHALLENGE_WIDGET_ENABLED', label: 'AI challenge widget (json-render POC)', category: 'Content', From 19847384765b8a44e5bcc62d67b7168de98966ba Mon Sep 17 00:00:00 2001 From: Thomas Jung Date: Tue, 3 Nov 2026 14:30:28 -0600 Subject: [PATCH 020/165] fix(docs): register draft-integrity-reenable runbook in sidebar The GitHub Pages build (predocs:build -> check-docs-sidebar.cjs) fails because docs/developers/operations/draft-integrity-reenable.md exists on disk but is not registered in the VitePress sidebar. Add the entry. --- docs/.vitepress/config.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/.vitepress/config.ts b/docs/.vitepress/config.ts index 97f06e4b1..15272cd37 100644 --- a/docs/.vitepress/config.ts +++ b/docs/.vitepress/config.ts @@ -179,6 +179,7 @@ export default defineConfig({ { text: 'Deploy checklist', link: '/developers/operations/deploy-checklist' }, { text: 'Deployment', link: '/developers/operations/deployment' }, { text: 'Devtoberfest selfie art', link: '/developers/operations/devtoberfest-selfie-assets' }, + { text: 'Draft integrity re-enable', link: '/developers/operations/draft-integrity-reenable' }, { text: 'Gameboard PROD data troubleshooting', link: '/developers/operations/gameboard-prod-data-troubleshooting' }, { text: 'GitHub App setup', link: '/developers/operations/github-app-setup' }, { text: 'GitHub dispatch PAT rotation', link: '/developers/operations/github-dispatch-pat-rotation' }, From caf4a256e9153879ae1758a54a5cc0a9a8606731 Mon Sep 17 00:00:00 2001 From: Thomas Jung Date: Tue, 3 Nov 2026 17:39:44 -0600 Subject: [PATCH 021/165] fix(app-space-game): render Kasimir arcade on-screen (canvas was ~1740px off-page) The App-Space game island mounts into an empty, zero-height
placed after the grid in the Hugo layout. The canvas used position:relative and the Demo trigger position:absolute, so both laid out in normal flow ~1740px down the page (below the fold) while the attract overlay (position:fixed, 92% opaque) covered the viewport. Net effect: the renderer drew correctly (1830 draw calls/s, valid level model, healthy WebGL2 context, all verified live) but onto a canvas nobody could see, and the manual Demo trigger was unreachable. Fixes: - Canvas to position:fixed, inset:0, 100vw/100vh so it anchors to the viewport over the overlay dim while attract mode is active (the actual "nothing renders" bug). - Demo trigger to position:fixed bottom-right (was absolute, landing at y~1748). - startAttract() awaits nextTick before game.start() so the fixed canvas is laid out at its real size before the initial resizeToCanvas() bakes the camera aspect (prevents a stretched first frame). - Add a window resize listener (armed in start(), removed in stop()/dispose()) so the full-viewport canvas keeps a correct aspect when the window resizes mid-attract. Verified live on DEV: applying the fixed canvas CSS made Kasimir, coins, pillars and the track flag render immediately; Demo button confirmed visible bottom-right. Note: the app-space-game unit suite was already red on origin/DEV (VTU "ref on hoisted vnodes" happy-dom issue, unrelated to this change). --- hugo-apps/src/app-space-game/AppSpaceGame.vue | 35 ++++++++++++++----- hugo-apps/src/app-space-game/renderer.ts | 8 +++++ 2 files changed, 35 insertions(+), 8 deletions(-) diff --git a/hugo-apps/src/app-space-game/AppSpaceGame.vue b/hugo-apps/src/app-space-game/AppSpaceGame.vue index c2b8bf6b2..4689dec18 100644 --- a/hugo-apps/src/app-space-game/AppSpaceGame.vue +++ b/hugo-apps/src/app-space-game/AppSpaceGame.vue @@ -35,16 +35,21 @@
- + +{{/* [tutorial-step-pacing] see u1-object-page.html for rationale. */}} +{{ $taskLists := resources.Get "js/task-lists.ts" | js.Build (dict "minify" true) }} + {{ if not site.Params.qa }}{{ end }} {{ end }} diff --git a/hugo/layouts/tutorials/u1-object-page.html b/hugo/layouts/tutorials/u1-object-page.html index b0ed9b8b4..71ec43c81 100644 --- a/hugo/layouts/tutorials/u1-object-page.html +++ b/hugo/layouts/tutorials/u1-object-page.html @@ -284,6 +284,11 @@

{{ .Title }}{{ if in .Params.ta {{ end }} + {{/* [tutorial-step-pacing] Populated client-side by tutorial.ts + (updateRemainingTime) β€” hidden by default, only shown once at + least half the steps declare a time. Not server-rendered because + it depends on live completed/skipped state. */}} + {{ if gt (int .Params.stepCount) 0 }}{{ end }}
@@ -514,6 +519,15 @@

Discussion

{{ $js := resources.Get "js/tutorial.ts" | js.Build (dict "minify" true) }} {{ end }} +{{/* [tutorial-step-pacing] Interactive checklists β€” progressive-enhances any + GFM task list (`- [ ]`) inside step content. Runs on every tutorial page + (cheap no-op scan when a tutorial has no task lists); gated the same as + tutorial.ts (not in previewMode, which has no progress-bar/#tutorial-data + slug to key localStorage off of). */}} +{{ if not site.Params.previewMode }} +{{ $taskLists := resources.Get "js/task-lists.ts" | js.Build (dict "minify" true) }} + +{{ end }} {{/* Issue #173: load the OS-picker runtime only when the page has OS-flavored option blocks (hasOsOptions=true) β€” matches the picker guard above. Without this, os-toggle.ts never mounts, the picker stays inert, and every OS panel @@ -594,13 +608,19 @@

Discussion

} // Mirror existing progress segments into the ring. + // [tutorial-step-pacing] Required-step denominator: stepCount minus any + // steps marked {optional} (data-optional="true" on .tutorial-step), and + // only non-optional steps count toward "completed" β€” mirrors the + // server-side completion calc in srv/developer-service.js. function updateRing() { const ring = document.getElementById('op-progress-ring'); const label = document.getElementById('op-progress-label'); const wrap = document.getElementById('op-progress'); if (!ring || !label || !wrap) return; - const total = parseInt(wrap.dataset.stepCount || '0', 10) || 0; - const completed = document.querySelectorAll('.tutorial-step.completed').length; + const rawTotal = parseInt(wrap.dataset.stepCount || '0', 10) || 0; + const optionalCount = document.querySelectorAll('.tutorial-step[data-optional="true"]').length; + const total = Math.max(0, rawTotal - optionalCount); + const completed = document.querySelectorAll('.tutorial-step.completed:not([data-optional="true"])').length; const pct = total ? Math.round((completed / total) * 100) : 0; ring.style.setProperty('--pct', String(pct)); label.textContent = `${completed}/${total}`; From d8c474fba9549aa6eac7e7dbeeaffef4fa36be77 Mon Sep 17 00:00:00 2001 From: Thomas Jung Date: Thu, 5 Nov 2026 15:07:12 -0500 Subject: [PATCH 042/165] feat(mcp): surface step time/optional metadata in get_tutorial_step MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - packages/core/tutorial-markdown-steps.js: runtime port of the heading- attribute stripper (title + timeMinutes + optional), fail-open on a malformed duration (never throws at MCP request time). - packages/mcp/tutorial-step-slicer.js: thread timeMinutes/optional through sliceStepMarkdown's cache + return shape. - packages/mcp/mcp-developer-tools.js: handleGetTutorialStep includes timeMinutes/optional on the markdown-format response (html format is unchanged β€” documented limitation, see PR description). - srv/developer-service-mcp.cds, srv-mcp/developer-service-mcp.cds, srv/search-service-mcp.cds: add timeMinutes/optional to the get_tutorial_step return type (all three call sites for this action). - srv/mcp/mcp-manifest.js: mention the new fields in the anonymous tool description. - Unit tests for the heading-attribute port in tutorial-markdown-steps.js. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- packages/core/tutorial-markdown-steps.js | 57 ++++++++++++++++++- packages/mcp/mcp-developer-tools.js | 9 ++- packages/mcp/tutorial-step-slicer.js | 12 +++- srv-mcp/developer-service-mcp.cds | 6 ++ srv/developer-service-mcp.cds | 6 ++ .../__tests__/tutorial-markdown-steps.test.js | 26 +++++++++ srv/mcp/mcp-manifest.js | 4 +- srv/search-service-mcp.cds | 3 + 8 files changed, 116 insertions(+), 7 deletions(-) diff --git a/packages/core/tutorial-markdown-steps.js b/packages/core/tutorial-markdown-steps.js index 0112a047b..553b84eb9 100644 --- a/packages/core/tutorial-markdown-steps.js +++ b/packages/core/tutorial-markdown-steps.js @@ -19,6 +19,7 @@ * - scripts/parsers/v2.ts (H3 split, [VALIDATE_n]/[DONE] stripping) * - scripts/parsers/fence-tracker.ts (CommonMark fence rules) * - scripts/parsers/html-comment-lines.ts (multi-line masking) + * - scripts/parsers/step-attrs.ts (tutorial-step-pacing heading attrs) * * Pure: no I/O, safe to unit-test directly. */ @@ -29,6 +30,43 @@ const VALIDATE_LINE = /^\s*\[VALIDATE_\d+\]\s*$/; const DONE_LINE = /^\s*\[DONE\]\s*$/; const H3 = /^### (.+)$/; +// [tutorial-step-pacing] Minimal runtime port of scripts/parsers/step-attrs.ts +// β€” title + timeSeconds + optional only (no lint-warning collection; this +// runs at MCP request time against already-published content, so a malformed +// attribute block fails OPEN: the brace is just left attached to the title, +// same as the build-time "unknown token" behaviour, rather than erroring the +// tool call). +const HEADING_ATTRS_RE = /^(.*?)\s*\{([^}]*)\}\s*$/; +const DURATION_RE = /^(?:(\d+)h)?(?:(\d+)m)?(?:(\d+)s)?$/; +const MAX_SECONDS = 8 * 3600; + +function parseStepHeadingAttrs(rawTitle) { + const match = rawTitle.match(HEADING_ATTRS_RE); + if (!match) return { title: rawTitle.trim() }; + const [, base, inner] = match; + const tokens = inner.trim().split(/\s+/).filter(Boolean); + if (tokens.length === 0) return { title: base.trim() }; + + let timeSeconds; + let optional = false; + let unknown = false; + for (const token of tokens) { + if (token === 'optional') { optional = true; continue; } + const timeMatch = token.match(/^time=(.+)$/); + if (timeMatch) { + const m = timeMatch[1].match(DURATION_RE); + if (!m || (!m[1] && !m[2] && !m[3])) { unknown = true; continue; } + const total = parseInt(m[1] || '0', 10) * 3600 + parseInt(m[2] || '0', 10) * 60 + parseInt(m[3] || '0', 10); + if (total <= 0 || total > MAX_SECONDS) { unknown = true; continue; } + timeSeconds = total; + continue; + } + unknown = true; + } + if (unknown) return { title: rawTitle.trim() }; + return { title: base.trim(), timeSeconds, optional: optional || undefined }; +} + const FENCE_OPEN = /^(\s{0,3})(`{3,}|~{3,})(.*)$/; const FENCE_CLOSE = /^(\s{0,3})(`{3,}|~{3,})\s*$/; @@ -131,12 +169,19 @@ function parseMarkdownSteps(body) { const raw = []; let currentTitle = ''; + let currentTimeSeconds; + let currentOptional; let currentLines = []; let inStep = false; const flush = () => { if (!inStep) return; - raw.push({ title: currentTitle, content: stripMarkers(currentLines).join('\n').trim() }); + raw.push({ + title: currentTitle, + content: stripMarkers(currentLines).join('\n').trim(), + timeSeconds: currentTimeSeconds, + optional: currentOptional, + }); }; for (let i = 0; i < lines.length; i++) { @@ -150,7 +195,10 @@ function parseMarkdownSteps(body) { const h3 = line.match(H3); if (h3) { flush(); - currentTitle = h3[1].trim(); + const attrs = parseStepHeadingAttrs(h3[1].trim()); + currentTitle = attrs.title; + currentTimeSeconds = attrs.timeSeconds; + currentOptional = attrs.optional; currentLines = []; inStep = true; continue; @@ -163,12 +211,15 @@ function parseMarkdownSteps(body) { const heading = `### ${s.title}`; const stripped = stripImageDirectiveComments(s.content).trim(); const markdown = stripped ? `${heading}\n\n${stripped}` : heading; - return { + const out = { number: idx + 1, title: s.title, markdown, text: stripMarkdownToText(markdown), }; + if (s.timeSeconds !== undefined) out.timeMinutes = Math.max(1, Math.round(s.timeSeconds / 60)); + if (s.optional) out.optional = true; + return out; }); } diff --git a/packages/mcp/mcp-developer-tools.js b/packages/mcp/mcp-developer-tools.js index 8d5553a17..a6740fa5c 100644 --- a/packages/mcp/mcp-developer-tools.js +++ b/packages/mcp/mcp-developer-tools.js @@ -121,7 +121,7 @@ export async function handleGetTutorialStep(req) { const slice = await sliceStepMarkdown(lcSlug, stepNumber); if (!slice) return req.reject(404, 'step not found'); - return { + const result = { slug: lcSlug, stepNumber, stepTitle: slice.stepTitle, @@ -130,6 +130,13 @@ export async function handleGetTutorialStep(req) { textLength: slice.text.length, totalSteps: slice.totalSteps, }; + // [tutorial-step-pacing] spec "Pacing data in machine consumers" β€” parsed + // from the step's `{time=… optional}` heading attribute block by + // packages/core/tutorial-markdown-steps.js. Omitted (not false/0) when + // the step doesn't use the syntax. + if (slice.timeMinutes !== undefined) result.timeMinutes = slice.timeMinutes; + if (slice.optional) result.optional = true; + return result; }); } diff --git a/packages/mcp/tutorial-step-slicer.js b/packages/mcp/tutorial-step-slicer.js index 3d96886bf..54665d32e 100644 --- a/packages/mcp/tutorial-step-slicer.js +++ b/packages/mcp/tutorial-step-slicer.js @@ -256,7 +256,12 @@ async function loadAndParseMarkdown(slug) { const steps = new Map(); for (const s of parsedSteps) { const md = absolutizeImagePaths(s.markdown, { slug, repo, branch }); - steps.set(s.number, { markdown: md, text: s.text, title: s.title }); + const entry = { markdown: md, text: s.text, title: s.title }; + // [tutorial-step-pacing] carried through to handleGetTutorialStep's + // response (spec "Pacing data in machine consumers"). + if (s.timeMinutes !== undefined) entry.timeMinutes = s.timeMinutes; + if (s.optional) entry.optional = true; + steps.set(s.number, entry); } const result = { steps, totalSteps: steps.size }; @@ -281,7 +286,10 @@ export async function sliceStepMarkdown(slug, stepNumber) { if (!parsed) return null; const step = parsed.steps.get(stepNumber); if (!step) return null; - return { markdown: step.markdown, text: step.text, stepTitle: step.title, totalSteps: parsed.totalSteps }; + const result = { markdown: step.markdown, text: step.text, stepTitle: step.title, totalSteps: parsed.totalSteps }; + if (step.timeMinutes !== undefined) result.timeMinutes = step.timeMinutes; + if (step.optional) result.optional = true; + return result; } export async function sliceAllSteps(slug) { diff --git a/srv-mcp/developer-service-mcp.cds b/srv-mcp/developer-service-mcp.cds index 91cdec32f..455eb6442 100644 --- a/srv-mcp/developer-service-mcp.cds +++ b/srv-mcp/developer-service-mcp.cds @@ -81,6 +81,12 @@ extend service DeveloperService { contentFormat : String; textLength : Integer; totalSteps : Integer; + // [tutorial-step-pacing] Present only when the step's heading declared + // `{time=… optional}` and format='markdown'. Absent (not 0/false) on + // plain steps and on format='html' (not yet threaded through the HTML + // slicer β€” see packages/mcp/tutorial-step-slicer.js). + timeMinutes : Integer; + optional : Boolean; }; /** Mark a step of a tutorial as completed for the signed-in user. diff --git a/srv/developer-service-mcp.cds b/srv/developer-service-mcp.cds index 8c23e1271..199045353 100644 --- a/srv/developer-service-mcp.cds +++ b/srv/developer-service-mcp.cds @@ -80,6 +80,12 @@ extend service DeveloperService { contentFormat : String; textLength : Integer; totalSteps : Integer; + // [tutorial-step-pacing] Present only when the step's heading declared + // `{time=… optional}` and format='markdown'. Absent (not 0/false) on + // plain steps and on format='html' (not yet threaded through the HTML + // slicer β€” see packages/mcp/tutorial-step-slicer.js). + timeMinutes : Integer; + optional : Boolean; }; /** Mark a step of a tutorial as completed for the signed-in user. diff --git a/srv/lib/__tests__/tutorial-markdown-steps.test.js b/srv/lib/__tests__/tutorial-markdown-steps.test.js index 9771e9ef5..6cc5b3625 100644 --- a/srv/lib/__tests__/tutorial-markdown-steps.test.js +++ b/srv/lib/__tests__/tutorial-markdown-steps.test.js @@ -113,3 +113,29 @@ describe('parseMarkdownSteps', () => { expect(parseMarkdownSteps(null)).toEqual([]); }); }); + +describe('parseMarkdownSteps β€” tutorial-step-pacing heading attributes', () => { + it('strips {time=…}/{optional} from the title and exposes timeMinutes/optional', () => { + const body = [ + '### Deploy the app {time=5m optional}', + 'body one', + '### Second step', + 'body two', + ].join('\n'); + const steps = parseMarkdownSteps(body); + expect(steps[0].title).toBe('Deploy the app'); + expect(steps[0].markdown.startsWith('### Deploy the app\n')).toBe(true); + expect(steps[0].timeMinutes).toBe(5); + expect(steps[0].optional).toBe(true); + expect(steps[1].timeMinutes).toBeUndefined(); + expect(steps[1].optional).toBeUndefined(); + }); + + it('fails open (title unchanged) on an invalid duration rather than throwing', () => { + const body = '### Deploy the app {time=5x}\nbody'; + expect(() => parseMarkdownSteps(body)).not.toThrow(); + const steps = parseMarkdownSteps(body); + expect(steps[0].title).toBe('Deploy the app {time=5x}'); + expect(steps[0].timeMinutes).toBeUndefined(); + }); +}); diff --git a/srv/mcp/mcp-manifest.js b/srv/mcp/mcp-manifest.js index 26e2a4e9a..797776439 100644 --- a/srv/mcp/mcp-manifest.js +++ b/srv/mcp/mcp-manifest.js @@ -43,7 +43,9 @@ function buildMcpManifest({ baseUrl } = {}) { description: "Return a single published tutorial step in the requested format " + "(markdown by default, or html) plus metadata (step title, text " + - "length, total steps). Public content.", + "length, total steps, and β€” when the step declares a {time=…}/" + + "{optional} heading attribute and format=markdown β€” timeMinutes/" + + "optional). Public content.", }, { name: 'search_events', diff --git a/srv/search-service-mcp.cds b/srv/search-service-mcp.cds index b257a8c65..52909261b 100644 --- a/srv/search-service-mcp.cds +++ b/srv/search-service-mcp.cds @@ -24,6 +24,9 @@ extend service SearchService { contentFormat : String; textLength : Integer; totalSteps : Integer; + // [tutorial-step-pacing] see srv/developer-service-mcp.cds. + timeMinutes : Integer; + optional : Boolean; }; /** Search the public SAP community events catalog β€” CodeJams, Devtoberfest, From 7fdc738d119a7d18338104a5be9831e9528bd1dd Mon Sep 17 00:00:00 2001 From: Thomas Jung Date: Thu, 5 Nov 2026 15:09:08 -0500 Subject: [PATCH 043/165] docs(tutorials): step pacing author guide + e2e spec + tasks checklist - docs/authors/writing-tutorials.md: new 3.3.1/3.3.2 sections documenting the {time=...}/{optional} heading attribute syntax and GFM checklists. - test/e2e/tutorial-step-pacing.spec.ts: optional-skip-completes-tutorial + checklist-persists-across-reload specs. Self-skips without a deployed SMOKE_BASE_URL/PLAYWRIGHT_BASE_URL AND until SLUG is populated with a published fixture tutorial using the new syntax (none exists yet). - openspec/changes/tutorial-step-pacing/tasks.md: check off all tasks. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- docs/authors/writing-tutorials.md | 44 +++++++++ .../changes/tutorial-step-pacing/tasks.md | 28 +++--- test/e2e/tutorial-step-pacing.spec.ts | 90 +++++++++++++++++++ 3 files changed, 148 insertions(+), 14 deletions(-) create mode 100644 test/e2e/tutorial-step-pacing.spec.ts diff --git a/docs/authors/writing-tutorials.md b/docs/authors/writing-tutorials.md index d58236c92..2df77b496 100644 --- a/docs/authors/writing-tutorials.md +++ b/docs/authors/writing-tutorials.md @@ -185,6 +185,50 @@ Rules of thumb: - Step content is plain Markdown: lists, code fences, images, links, bold/italic. - Inline HTML is escaped for safety. Stick to Markdown. +#### 3.3.1 Step time and optional steps β˜… NEW + +Add a trailing `{…}` attribute block to a step heading to declare a per-step time estimate +and/or mark the step optional: + +```markdown +### Deploy the app {time=5m} + +### Enable the trial entitlement {time=2m optional} +``` + +- `time=` β€” `5m`, `90s`, `1h`, or `1h30m` (max 8h). Renders a `⏱ N min` chip on the + step and contributes to the tutorial's total time (the page's frontmatter `time:` still wins + when both are set; the parser warns in the build log when the two disagree by more than 25%). + Readers also see a live "β‰ˆ N min left" estimate above the steps once at least half the steps + declare a time. +- `optional` β€” renders an "Optional" badge and a "Skip" button next to "Done". **Completing a + tutorial never requires an optional step** β€” mark a step optional when it only applies to some + readers (e.g. "if you don't already have a trial account"). At least one step must stay + required; a tutorial where every step is optional fails the build. +- The attribute block is stripped before the title is shown or slugged, so adding `{time=5m}` + to an existing heading is a safe, non-breaking edit β€” it does not change the step's anchor, + any `osOverrides` key, or existing readers' progress. +- Unrecognised content in the braces (e.g. `{status: ok}` that isn't step-pacing syntax) is left + in the title untouched and reported as a lint warning β€” it never silently disappears. + +#### 3.3.2 Interactive checklists β˜… NEW + +A standard GFM task list inside a step renders as live, checkable checkboxes instead of plain +bullets: + +```markdown +- [ ] Create a subaccount +- [ ] Enable the entitlement +- [x] Already have a BTP account (pre-checked β€” shown checked, doesn't count toward the counter) +``` + +Each unchecked item becomes a clickable checkbox; a small "x/y" counter above the list tracks +how many the reader has ticked, and the state persists in the reader's browser across reloads. +`[x]` items are rendered checked but excluded from that counter β€” use them for "already done" +context items, not for things you want the reader to actually tick off. Checking every +toggleable item in a step visually highlights that step's **Done** button, but never marks the +step done automatically β€” the reader still clicks Done. + ### 3.4 Images Place images alongside the Markdown file in a folder named after the slug: diff --git a/openspec/changes/tutorial-step-pacing/tasks.md b/openspec/changes/tutorial-step-pacing/tasks.md index 1b94c64f9..7fbb2e94d 100644 --- a/openspec/changes/tutorial-step-pacing/tasks.md +++ b/openspec/changes/tutorial-step-pacing/tasks.md @@ -1,25 +1,25 @@ ## 1. Parser -- [ ] 1.1 Extract and validate trailing heading attributes in `scripts/parsers/v2.ts` before title/slug computation -- [ ] 1.2 Add `timeSeconds` and `optional` to `TutorialStep` (`scripts/parsers/types.ts`) and step frontmatter rendering -- [ ] 1.3 Derive tutorial total time when frontmatter `time` is absent; lint for >25% mismatch and all-optional tutorials -- [ ] 1.4 Unit tests for slug stability, duration grammar, unknown-brace handling +- [x] 1.1 Extract and validate trailing heading attributes in `scripts/parsers/v2.ts` before title/slug computation +- [x] 1.2 Add `timeSeconds` and `optional` to `TutorialStep` (`scripts/parsers/types.ts`) and step frontmatter rendering +- [x] 1.3 Derive tutorial total time when frontmatter `time` is absent; lint for >25% mismatch and all-optional tutorials +- [x] 1.4 Unit tests for slug stability, duration grammar, unknown-brace handling ## 2. Backend completion -- [ ] 2.1 Publish `optionalSteps` with tutorial metadata (content publish + `TutorialMeta`) -- [ ] 2.2 Update tutorial-completion check in the progress service to ignore optional steps; tests incl. rollup -- [ ] 2.3 Deploy-order note: backend before content using `optional` +- [x] 2.1 Publish `optionalSteps` with tutorial metadata (content publish + `TutorialMeta`) +- [x] 2.2 Update tutorial-completion check in the progress service to ignore optional steps; tests incl. rollup +- [x] 2.3 Deploy-order note: backend before content using `optional` ## 3. Rendering and client -- [ ] 3.1 Time chip and Optional badge in step header (`tutorial-step.html` / tutorial layout) -- [ ] 3.2 Remaining-time indicator and Skip control in `hugo/assets/js/tutorial.ts` -- [ ] 3.3 `hugo/assets/js/task-lists.ts`: enable task-list checkboxes, stable IDs with text hash, localStorage persistence, counter, aria-live -- [ ] 3.4 Done-button emphasis when all items checked; CSS (light/dark) +- [x] 3.1 Time chip and Optional badge in step header (`tutorial-step.html` / tutorial layout) +- [x] 3.2 Remaining-time indicator and Skip control in `hugo/assets/js/tutorial.ts` +- [x] 3.3 `hugo/assets/js/task-lists.ts`: enable task-list checkboxes, stable IDs with text hash, localStorage persistence, counter, aria-live +- [x] 3.4 Done-button emphasis when all items checked; CSS (light/dark) ## 4. MCP, docs, tests -- [ ] 4.1 Include `timeMinutes`/`optional` in MCP step tools -- [ ] 4.2 Author docs: step heading attributes + checklists in `docs/authors/writing-tutorials.md` -- [ ] 4.3 e2e spec: optional skip completes tutorial, checklist persistence; corpus regression for unchanged titles/slugs +- [x] 4.1 Include `timeMinutes`/`optional` in MCP step tools +- [x] 4.2 Author docs: step heading attributes + checklists in `docs/authors/writing-tutorials.md` +- [x] 4.3 e2e spec: optional skip completes tutorial, checklist persistence; corpus regression for unchanged titles/slugs diff --git a/test/e2e/tutorial-step-pacing.spec.ts b/test/e2e/tutorial-step-pacing.spec.ts new file mode 100644 index 000000000..3bff6e4d2 --- /dev/null +++ b/test/e2e/tutorial-step-pacing.spec.ts @@ -0,0 +1,90 @@ +// e2e: tutorial-step-pacing β€” optional-step Skip completes the tutorial, and +// interactive checklist state persists across reload. +// +// Self-skips when SMOKE_BASE_URL / PLAYWRIGHT_BASE_URL is absent so `npm test` +// (unit suite) and credential-less local runs are unaffected, AND when SLUG +// below hasn't been populated yet (no tutorial using the `{time=…}`/ +// `{optional}` syntax has been published at the time this spec was written). +// +// SLUG: update this once a published tutorial exists with: +// - at least one step heading `{optional}` (completable without it) +// - at least one GFM task list (`- [ ] …`) inside a step +// e.g. `### Enable the trial entitlement {time=2m optional}` followed later by +// a checklist step. See docs/authors/writing-tutorials.md Β§3.3.1/3.3.2. + +import { describe, it, expect, beforeAll, afterAll } from 'vitest'; +import { hasBaseUrl } from './e2e.config.js'; +import { launchBrowser, newPage } from './_browser.js'; + +/** A tutorial that declares at least one `{optional}` step and one task list. */ +const SLUG = ''; // TODO: populate once a qualifying tutorial is deployed + +const TUTORIAL_PATH = `/tutorials/${SLUG}/`; + +describe.skipIf(!hasBaseUrl() || !SLUG)('e2e: tutorial-step-pacing (optional steps + checklists)', () => { + let browser: Awaited>; + + beforeAll(async () => { + browser = await launchBrowser(); + }); + + afterAll(async () => { + await browser?.close(); + }); + + it('skipping the optional step still completes the tutorial', async () => { + const { context, page } = await newPage(browser, { authenticated: true }); + try { + await page.addInitScript(() => localStorage.clear()); + await page.goto(TUTORIAL_PATH, { waitUntil: 'domcontentloaded' }); + + // Mark every non-optional step Done; click Skip on the optional one. + const stepEls = await page.$$('.tutorial-step'); + for (const stepEl of stepEls) { + const isOptional = await stepEl.getAttribute('data-optional'); + const header = await stepEl.$('.step-header'); + await header?.click(); // expand + if (isOptional === 'true') { + const skipBtn = await stepEl.$('[data-action="skip-step"]'); + await skipBtn?.click(); + } else { + const doneBtn = await stepEl.$('[data-action="mark-done"]'); + await doneBtn?.click(); + await page.waitForFunction( + (el) => el?.classList.contains('completed'), + stepEl, + { timeout: 10_000 }, + ); + } + } + + // The "completed" banner (tutorial.ts maybeShowCompletedBanner) should + // appear even though the optional step was never marked Done. + await page.waitForSelector('[data-banner="completed"]', { timeout: 10_000 }); + } finally { + await context.close(); + } + }); + + it('checklist state persists across reload', async () => { + const { context, page } = await newPage(browser, { authenticated: true }); + try { + await page.addInitScript(() => localStorage.clear()); + await page.goto(TUTORIAL_PATH, { waitUntil: 'domcontentloaded' }); + + const checkbox = await page.waitForSelector('.task-list-checkbox', { timeout: 10_000 }); + await checkbox.click(); + const counter = await page.waitForSelector('.task-list-counter', { timeout: 5_000 }); + const before = await counter.textContent(); + expect(before).toMatch(/^1\//); + + await page.reload({ waitUntil: 'domcontentloaded' }); + const checkboxAfter = await page.waitForSelector('.task-list-checkbox', { timeout: 10_000 }); + expect(await checkboxAfter.isChecked()).toBe(true); + const counterAfter = await page.waitForSelector('.task-list-counter', { timeout: 5_000 }); + expect(await counterAfter.textContent()).toBe(before); + } finally { + await context.close(); + } + }); +}); From 7727c56fffccd514acd06df946da4e045a89f220 Mon Sep 17 00:00:00 2001 From: Thomas Jung Date: Thu, 5 Nov 2026 15:31:51 -0500 Subject: [PATCH 044/165] =?UTF-8?q?feat(arcade):=20Kasimir=20space-game=20?= =?UTF-8?q?polish=20=E2=80=94=20game-feel,=20labels,=20textures=20(#3009)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Rework app-space-game rendering and layout: split label-layout into label-layer/labels/layout modules; add fx/scene/textures modules; refresh the Kasimir atlas. Tests updated and added (labels, layout, solvability). --- .gitignore | 3 + docs/developers/architecture/frontend-apps.md | 2 +- hugo-apps/src/app-space-game/AppSpaceGame.vue | 1253 ++++++++----- .../__tests__/AppSpaceGame.test.ts | 93 +- .../src/app-space-game/__tests__/anim.test.ts | 162 +- .../__tests__/autopilot.test.ts | 198 +- .../__tests__/frame-map.test.ts | 245 ++- .../__tests__/label-layout.test.ts | 83 - .../app-space-game/__tests__/labels.test.ts | 62 + .../app-space-game/__tests__/layout.test.ts | 63 + .../__tests__/obstacles.test.ts | 140 +- .../app-space-game/__tests__/physics.test.ts | 295 +-- .../__tests__/solvability.test.ts | 69 + hugo-apps/src/app-space-game/anim.ts | 151 +- hugo-apps/src/app-space-game/autopilot.ts | 232 +-- hugo-apps/src/app-space-game/constants.ts | 56 +- hugo-apps/src/app-space-game/fx.ts | 158 ++ .../src/app-space-game/kasimir-frames.json | 479 +++-- hugo-apps/src/app-space-game/label-layer.ts | 151 ++ hugo-apps/src/app-space-game/label-layout.ts | 130 -- hugo-apps/src/app-space-game/labels.ts | 71 + hugo-apps/src/app-space-game/layout.ts | 143 ++ hugo-apps/src/app-space-game/level-model.ts | 25 +- hugo-apps/src/app-space-game/obstacles.ts | 169 +- hugo-apps/src/app-space-game/physics.ts | 351 ++-- hugo-apps/src/app-space-game/renderer.ts | 1588 +++++++---------- hugo-apps/src/app-space-game/scene.ts | 520 ++++++ hugo-apps/src/app-space-game/sound.ts | 64 +- hugo-apps/src/app-space-game/textures.ts | 282 +++ hugo-apps/src/app-space-game/types.ts | 6 +- .../images/app-space-game/kasimir-atlas.png | Bin 4526162 -> 872686 bytes scripts/build-kasimir-atlas.mjs | 1024 +++++------ 32 files changed, 4795 insertions(+), 3473 deletions(-) delete mode 100644 hugo-apps/src/app-space-game/__tests__/label-layout.test.ts create mode 100644 hugo-apps/src/app-space-game/__tests__/labels.test.ts create mode 100644 hugo-apps/src/app-space-game/__tests__/layout.test.ts create mode 100644 hugo-apps/src/app-space-game/__tests__/solvability.test.ts create mode 100644 hugo-apps/src/app-space-game/fx.ts create mode 100644 hugo-apps/src/app-space-game/label-layer.ts delete mode 100644 hugo-apps/src/app-space-game/label-layout.ts create mode 100644 hugo-apps/src/app-space-game/labels.ts create mode 100644 hugo-apps/src/app-space-game/layout.ts create mode 100644 hugo-apps/src/app-space-game/scene.ts create mode 100644 hugo-apps/src/app-space-game/textures.ts diff --git a/.gitignore b/.gitignore index 3eab9d954..bbe5e43c2 100644 --- a/.gitignore +++ b/.gitignore @@ -55,6 +55,9 @@ hugo/static-qa/ !hugo/static/images/devtoberfest/arcade/**/*.png !hugo/static/images/devtoberfest/selfie/thumbnails/*.png !hugo/static/images/app-space-game/**/*.png +# Raw Kasimir sprite sheets are build inputs for scripts/build-kasimir-atlas.mjs β€” hugo/static is +# served publicly, so keep the multi-MB source art out of the repo and the deployed site. +hugo/static/images/app-space-game/sprite-src/ !hugo/static/images/devtoberfest/selfie/stickers/*.png !hugo/static/images/devtoberfest/selfie/backgrounds/*.png !app/display-app-drawings/public/*.png diff --git a/docs/developers/architecture/frontend-apps.md b/docs/developers/architecture/frontend-apps.md index 17b6c1826..6c322d1e7 100644 --- a/docs/developers/architecture/frontend-apps.md +++ b/docs/developers/architecture/frontend-apps.md @@ -57,7 +57,7 @@ Compiled by `build:apps` (a single Vite project) into `hugo/static/js/.js` | --- | --- | --- | | `navigator` | `/` (homepage) | Tutorial navigator with filters and search | | `app-space` | `/app-space` | Event-themed Vue SPA (Joule/Sapphire theme overlays); progress via `/api/getEventProgress`, QR via `/api/qrcode`, live updates via Socket.IO | -| `app-space-game` | `/app-space` (sibling overlay) | Kasimir Arcade (#3009): feature-flagged Three.js 2.5D attract-mode game layered over `app-space`. Idle -> auto-plays; reads the same `getAppSpaceProgress` payload (tracks=zones, tutorials=coins); hands off to the grid via `?track=` + popstate. Three.js is dynamically imported (code-split into the `renderer` chunk); flag/WebGL/data gate falls back to the grid. Does not modify `app-space`. | +| `app-space-game` | `/app-space` (sibling overlay) | Kasimir Arcade (#3009): feature-flagged Three.js 2.5D platformer layered over `app-space`. Idle -> auto-plays (autopilot jumps every hazard); a key press or tap (not mouse movement) hands control to the visitor. Reads the same `getAppSpaceProgress` payload (tracks=zones, tutorials=rings, prize/track=goal chest/flag); standing at a goal and pressing ↑/Enter (or tapping the prompt) hands off to the grid via `?track=` + popstate. Crates block, spikes/pits cost rings, every hazard is clearable (`__tests__/solvability.test.ts`). Tutorial titles are HTML overlay labels (`label-layer.ts`), not canvas sprites. Kasimir's sprite atlas is generated by `scripts/build-kasimir-atlas.mjs` from the raw sheets (feet-anchored, uniform scale). Three.js is dynamically imported (code-split into the `renderer` chunk); `?nogame=1`/flag/WebGL/data gate falls back to the grid. Does not modify `app-space`. | | `event-display` | `/event-display` | Launcher for the standalone display dashboard | | `nav-dropdown` | All pages (header) | Mission/group dropdown in the shellbar | | `scanner-vue` | `/scanner-vue/` | Vue 3 mobile-optimized scanner using native `BarcodeDetector`, falls back to manual JSON input | diff --git a/hugo-apps/src/app-space-game/AppSpaceGame.vue b/hugo-apps/src/app-space-game/AppSpaceGame.vue index 51d1ec41e..b6f32cba6 100644 --- a/hugo-apps/src/app-space-game/AppSpaceGame.vue +++ b/hugo-apps/src/app-space-game/AppSpaceGame.vue @@ -1,443 +1,810 @@ - - - - - + + + + + + + + diff --git a/hugo-apps/src/app-space-game/__tests__/AppSpaceGame.test.ts b/hugo-apps/src/app-space-game/__tests__/AppSpaceGame.test.ts index dacfc654b..02824c6f4 100644 --- a/hugo-apps/src/app-space-game/__tests__/AppSpaceGame.test.ts +++ b/hugo-apps/src/app-space-game/__tests__/AppSpaceGame.test.ts @@ -48,6 +48,9 @@ function makeGameHandle() { dispose: vi.fn(), onRingCount: vi.fn(), onEvent: vi.fn(), + onZone: vi.fn(), + onGoalPrompt: vi.fn(), + activateGoal: vi.fn(() => true), } } @@ -167,8 +170,8 @@ describe('AppSpaceGame attract state machine', () => { expect(gameHandle.start).toHaveBeenCalled() }) - // (b) Input event while attract is showing -> setPlayerControl(true), overlay still up - it('(b) input event while attract is showing hands control but keeps overlay', async () => { + // (b) A deliberate key press while attract is showing hands control; mouse movement alone does not + it('(b) key press while attract is showing hands control but keeps overlay; mousemove does not', async () => { const { wrapper, gameHandle } = await mountWithGatePass() // Trigger idle timeout to show attract mode @@ -176,14 +179,83 @@ describe('AppSpaceGame attract state machine', () => { expect(wrapper.find('[data-game-overlay]').exists()).toBe(true) - // Simulate a mousemove event while attract is showing + // A passer-by brushing the mouse must not stop the demo window.dispatchEvent(new MouseEvent('mousemove')) await flushPromises() + expect(gameHandle.setPlayerControl).not.toHaveBeenCalled() + expect(wrapper.find('.asg-attract-cta').exists()).toBe(true) + + window.dispatchEvent(new KeyboardEvent('keydown', { key: 'ArrowRight' })) + await flushPromises() - // setPlayerControl(true) should have been called expect(gameHandle.setPlayerControl).toHaveBeenCalledWith(true) - // Overlay stays up (still visible) expect(wrapper.find('[data-game-overlay]').exists()).toBe(true) + expect(wrapper.find('[data-player-hint]').exists()).toBe(true) + }) + + it('(b2) tap (pointerdown) also hands control to the player', async () => { + const { gameHandle } = await mountWithGatePass() + await vi.advanceTimersByTimeAsync(60000) + window.dispatchEvent(new Event('pointerdown')) + await flushPromises() + expect(gameHandle.setPlayerControl).toHaveBeenCalledWith(true) + }) + + it('(b3) a visitor who walks away hands control back to the autopilot after 30s', async () => { + const { wrapper, gameHandle } = await mountWithGatePass() + await vi.advanceTimersByTimeAsync(60000) + window.dispatchEvent(new KeyboardEvent('keydown', { key: 'ArrowRight' })) + await flushPromises() + expect(gameHandle.setPlayerControl).toHaveBeenLastCalledWith(true) + + await vi.advanceTimersByTimeAsync(20000) + window.dispatchEvent(new KeyboardEvent('keydown', { key: ' ' })) + await vi.advanceTimersByTimeAsync(20000) + expect(gameHandle.setPlayerControl).toHaveBeenLastCalledWith(true) + + await vi.advanceTimersByTimeAsync(11000) + await flushPromises() + expect(gameHandle.setPlayerControl).toHaveBeenLastCalledWith(false) + expect(wrapper.find('[data-game-overlay]').exists()).toBe(true) + expect(wrapper.find('.asg-attract-cta').exists()).toBe(true) + }) + + it('(k) goal prompt appears when the renderer reports a goal and activates it on click', async () => { + const { wrapper, gameHandle } = await mountWithGatePass() + await vi.advanceTimersByTimeAsync(60000) + const promptCb = gameHandle.onGoalPrompt.mock.calls[0]?.[0] as (g: unknown) => void + expect(typeof promptCb).toBe('function') + + promptCb({ trackId: 'track-alpha', title: 'Alpha Track', label: 'Complete Track', kind: 'track', locked: false }) + await flushPromises() + const btn = wrapper.find('button[data-goal-prompt]') + expect(btn.exists()).toBe(true) + expect(btn.text()).toContain('Alpha Track') + await btn.trigger('click') + expect(gameHandle.activateGoal).toHaveBeenCalled() + + promptCb({ trackId: 'track-beta', title: 'Beta Track', label: 'x', kind: 'track', locked: true }) + await flushPromises() + expect(wrapper.find('button[data-goal-prompt]').exists()).toBe(false) + expect(wrapper.find('[data-goal-prompt]').text()).toContain('locked') + }) + + it('(l) zone banner shows the current zone', async () => { + const { wrapper, gameHandle } = await mountWithGatePass() + await vi.advanceTimersByTimeAsync(60000) + const zoneCb = gameHandle.onZone.mock.calls[0]?.[0] as (z: unknown) => void + zoneCb({ index: 1, total: 4, title: 'Beta Track', color: '#123456', locked: false }) + await flushPromises() + const banner = wrapper.find('.asg-zone') + expect(banner.text()).toContain('Zone 2 / 4') + expect(banner.text()).toContain('Beta Track') + }) + + it('(m) renderer receives the label root and reduced-motion option', async () => { + const { createGame } = await mountWithGatePass() + const opts = createGame.mock.calls[0][2] as { labelRoot: HTMLElement | null; reducedMotion: boolean } + expect(opts.labelRoot).toBeInstanceOf(HTMLElement) + expect(opts.reducedMotion).toBe(false) }) // (c) "Demo" button is present; clicking it starts the game immediately @@ -201,6 +273,17 @@ describe('AppSpaceGame attract state machine', () => { expect(gameHandle.start).toHaveBeenCalled() }) + it('(c2) using the Demo button does not leave a stale idle timer that restarts the demo', async () => { + const { wrapper, gameHandle } = await mountWithGatePass() + window.dispatchEvent(new Event('pointerdown')) + await wrapper.find('[data-demo-trigger]').trigger('click') + await flushPromises() + expect(gameHandle.start).toHaveBeenCalledTimes(1) + await vi.advanceTimersByTimeAsync(61000) + await flushPromises() + expect(gameHandle.start).toHaveBeenCalledTimes(1) + }) + // (d) onPickTrack navigates via replaceState (NOT pushState) + dispatches popstate + hides overlay it('(d) onPickTrack uses replaceState, dispatches popstate, and hides overlay', async () => { const { wrapper, createGame } = await mountWithGatePass() diff --git a/hugo-apps/src/app-space-game/__tests__/anim.test.ts b/hugo-apps/src/app-space-game/__tests__/anim.test.ts index eafed71c3..70ecb5d4a 100644 --- a/hugo-apps/src/app-space-game/__tests__/anim.test.ts +++ b/hugo-apps/src/app-space-game/__tests__/anim.test.ts @@ -1,82 +1,80 @@ -import { describe, it, expect } from 'vitest' -import { selectClip, frameAt } from '../anim' -import type { AnimContext } from '../anim' - -// Minimal clip map for frameAt tests (loop + non-loop) -const testClips = { - idle: { frames: [0, 1, 2, 3, 4].map(() => ({})), fps: 8, loop: true }, - walk: { frames: [0, 1, 2].map(() => ({})), fps: 12, loop: true }, - run: { frames: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9].map(() => ({})), fps: 16, loop: true }, - jump: { frames: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11].map(() => ({})), fps: 12, loop: false }, - hit: { frames: [{}], fps: 10, loop: false }, - ko: { frames: [{}], fps: 8, loop: false }, - celebrate: { frames: [{}], fps: 10, loop: false }, - taunt: { frames: [{}], fps: 10, loop: false }, - chest: { frames: [{}], fps: 8, loop: false }, -} as Parameters[2] - -describe('selectClip', () => { - // Test 1: Airborne β†’ 'jump' - it('returns jump when airborne (onGround:false, vy non-zero, no event)', () => { - const ctx: AnimContext = { onGround: false, moveDir: 0, vy: -5, lastEvent: 'none', sinceEventMs: 0 } - expect(selectClip(ctx)).toBe('jump') - }) - - // Test 2: fell event overrides even if airborne - it('returns ko when lastEvent:fell and sinceEventMs < 800 (overrides locomotion)', () => { - const ctx: AnimContext = { onGround: false, moveDir: 0, vy: -5, lastEvent: 'fell', sinceEventMs: 200 } - expect(selectClip(ctx)).toBe('ko') - }) - - // Test 3: fell event expired β†’ falls through to locomotion - it('does NOT return ko when lastEvent:fell but sinceEventMs >= 800', () => { - const ctx: AnimContext = { onGround: false, moveDir: 0, vy: -5, lastEvent: 'fell', sinceEventMs: 2000 } - expect(selectClip(ctx)).not.toBe('ko') - }) - - // Test 4: hit event within window - it('returns hit when lastEvent:hit and sinceEventMs < 600', () => { - const ctx: AnimContext = { onGround: true, moveDir: 0, vy: 0, lastEvent: 'hit', sinceEventMs: 300 } - expect(selectClip(ctx)).toBe('hit') - }) - - // Test 5: pickup event β†’ celebrate - it('returns celebrate when lastEvent:pickup and sinceEventMs < 400', () => { - const ctx: AnimContext = { onGround: true, moveDir: 0, vy: 0, lastEvent: 'pickup', sinceEventMs: 200 } - expect(selectClip(ctx)).toBe('celebrate') - }) - - // Test 6: goal event β†’ celebrate - it('returns celebrate when lastEvent:goal and sinceEventMs < 800', () => { - const ctx: AnimContext = { onGround: true, moveDir: 0, vy: 0, lastEvent: 'goal', sinceEventMs: 600 } - expect(selectClip(ctx)).toBe('celebrate') - }) - - // Test 7: grounded + moving β†’ 'run' - it('returns run when grounded and moving (moveDir:1, vy:0)', () => { - const ctx: AnimContext = { onGround: true, moveDir: 1, vy: 0, lastEvent: 'none', sinceEventMs: 0 } - expect(selectClip(ctx)).toBe('run') - }) - - // Test 8: grounded + still β†’ 'idle' - it('returns idle when grounded and still (moveDir:0, vy:0)', () => { - const ctx: AnimContext = { onGround: true, moveDir: 0, vy: 0, lastEvent: 'none', sinceEventMs: 0 } - expect(selectClip(ctx)).toBe('idle') - }) -}) - -describe('frameAt', () => { - // Test 9: loop clip at elapsedMs=1000, fps=8, frames.length=5 - // Math.floor(1000 / (1000/8)) % 5 = Math.floor(8) % 5 = 8 % 5 = 3 - it('returns correct frame index for a loop clip (elapsedMs=1000, fps=8, 5 frames β†’ 3)', () => { - expect(frameAt('idle', 1000, testClips)).toBe(3) - }) - - // Test 10: non-loop clip past end clamps to frames.length - 1 - it('clamps to last frame index for a non-loop clip past the end', () => { - // jump clip has 12 frames, fps=12, non-loop - // at elapsedMs=10000, floor(10000/83.33) = 120 which is well past 11 (frames.length-1) - const lastIdx = testClips.jump.frames.length - 1 - expect(frameAt('jump', 10000, testClips)).toBe(lastIdx) - }) -}) +import { describe, it, expect } from 'vitest' +import { selectClip, frameAt, jumpFrame, playbackRate } from '../anim' +import type { AnimContext } from '../anim' +import { JUMP, SPEED } from '../constants' + +const ctx = (p: Partial = {}): AnimContext => ({ + onGround: true, vx: 0, vy: 0, lastEvent: 'none', sinceEventMs: 10_000, pose: null, ...p, +}) + +describe('selectClip', () => { + it('idle β†’ walk β†’ trot β†’ run as speed rises', () => { + expect(selectClip(ctx({ vx: 0 }))).toBe('idle') + expect(selectClip(ctx({ vx: SPEED * 0.3 }))).toBe('walk') + expect(selectClip(ctx({ vx: -SPEED * 0.6 }))).toBe('trot') + expect(selectClip(ctx({ vx: SPEED }))).toBe('run') + }) + + it('airborne β†’ jump', () => { + expect(selectClip(ctx({ onGround: false, vy: 3, vx: SPEED }))).toBe('jump') + }) + + it('holds the landing crouch briefly after touching down', () => { + expect(selectClip(ctx({ vx: SPEED, lastEvent: 'land', sinceEventMs: 40 }))).toBe('jump') + expect(selectClip(ctx({ vx: SPEED, lastEvent: 'land', sinceEventMs: 200 }))).toBe('run') + }) + + it('fell β†’ fall pose; hit β†’ dizzy on ground, flail in the air', () => { + expect(selectClip(ctx({ lastEvent: 'fell', onGround: false }))).toBe('fall') + expect(selectClip(ctx({ lastEvent: 'hit', sinceEventMs: 100, onGround: true }))).toBe('hit') + expect(selectClip(ctx({ lastEvent: 'hit', sinceEventMs: 100, onGround: false }))).toBe('fall') + expect(selectClip(ctx({ lastEvent: 'hit', sinceEventMs: 900, vx: SPEED }))).toBe('run') + }) + + it('a scripted pose overrides everything', () => { + expect(selectClip(ctx({ pose: 'reach', lastEvent: 'hit', sinceEventMs: 0 }))).toBe('reach') + expect(selectClip(ctx({ pose: 'celebrate', onGround: false }))).toBe('celebrate') + }) +}) + +describe('jumpFrame', () => { + it('follows the arc: rise β†’ apex β†’ descend, crouch on the ground', () => { + const f = (vy: number, extra: Partial = {}) => jumpFrame(ctx({ onGround: false, vy, ...extra }), 10) + expect(f(JUMP)).toBe(2) + expect(f(0)).toBe(5) + expect(f(-JUMP)).toBe(7) + expect(f(JUMP, { lastEvent: 'jump', sinceEventMs: 10 })).toBe(1) + expect(jumpFrame(ctx({ onGround: true }), 10)).toBe(8) + const seq = [JUMP, JUMP * 0.5, JUMP * 0.2, 0, -JUMP * 0.4, -JUMP].map(v => f(v)) + expect([...seq].sort((a, b) => a - b)).toEqual(seq) + }) + + it('never exceeds the clip length', () => { + expect(jumpFrame(ctx({ onGround: true }), 3)).toBe(2) + }) +}) + +describe('frameAt', () => { + const clips = { + idle: { frames: [1, 2, 3, 4], fps: 4, loop: true }, + taunt: { frames: [1, 2, 3], fps: 10, loop: false }, + } + it('loops looping clips', () => { + expect(frameAt('idle', 0, clips)).toBe(0) + expect(frameAt('idle', 1250, clips)).toBe(1) + }) + it('clamps one-shot clips on the last frame', () => { + expect(frameAt('taunt', 5000, clips)).toBe(2) + }) + it('returns 0 for unknown clips', () => { + expect(frameAt('run', 500, clips)).toBe(0) + }) +}) + +describe('playbackRate', () => { + it('scales locomotion with speed, clamped', () => { + expect(playbackRate('run', SPEED)).toBeCloseTo(1) + expect(playbackRate('run', 0.1)).toBe(0.6) + expect(playbackRate('idle', 0)).toBe(1) + }) +}) diff --git a/hugo-apps/src/app-space-game/__tests__/autopilot.test.ts b/hugo-apps/src/app-space-game/__tests__/autopilot.test.ts index 9bd546364..1ab34a78c 100644 --- a/hugo-apps/src/app-space-game/__tests__/autopilot.test.ts +++ b/hugo-apps/src/app-space-game/__tests__/autopilot.test.ts @@ -1,136 +1,62 @@ -import { describe, it, expect } from 'vitest' -import { autopilotInput, watchdog } from '../autopilot' -import type { ProgressTrack } from '../autopilot' - -// PhysicsState shape β€” matches physics.ts exactly (type-only; defined locally -// so this test runs even if physics.ts is not yet on disk). -interface PhysicsState { - x: number - y: number - vy: number - onGround: boolean - facing: 1 | -1 - lastSafeX: number -} - -interface WorldHazards { - obstacles: { x: number; w: number; h: number }[] - pits: { x: number; w: number }[] -} - -// Helper: default PhysicsState as specified in the brief -function mk(partial: Partial = {}): PhysicsState { - return { - x: 5, - y: 1.8, - vy: 0, - onGround: true, - facing: 1, - lastSafeX: 5, - ...partial, - } -} - -const noHazards: WorldHazards = { obstacles: [], pits: [] } - -describe('autopilotInput', () => { - // Test 1: Clear ground (empty hazards) β†’ {moveDir:1, jump:false} - it('clear ground: empty hazards β†’ moveDir:1, jump:false', () => { - const result = autopilotInput(mk(), noHazards) - expect(result).toEqual({ moveDir: 1, jump: false }) - }) - - // Test 2: moveDir is always 1 β€” even with a hazard dead ahead - it('moveDir is always 1 even with a hazard dead ahead', () => { - const hazards: WorldHazards = { - obstacles: [{ x: 5.5, w: 1, h: 2 }], - pits: [], - } - const result = autopilotInput(mk(), hazards) - expect(result.moveDir).toBe(1) - }) - - // Test 3: Pit edge just ahead within lookahead, onGround β†’ jump:true - it('pit edge within lookahead, onGround β†’ jump:true', () => { - // s.x=5, lookahead=3 β†’ checks x in [5, 8] - // Pit near edge at x=7, w=2 β†’ left edge = 6. Within lookahead. - const hazards: WorldHazards = { - obstacles: [], - pits: [{ x: 7, w: 2 }], - } - const result = autopilotInput(mk({ x: 5, onGround: true }), hazards) - expect(result.jump).toBe(true) - }) - - // Test 4: Hazard far away (beyond lookahead) β†’ jump:false - it('hazard far away (beyond lookahead) β†’ jump:false', () => { - // s.x=5, lookahead=3 β†’ hazard must be beyond x=8 - const hazards: WorldHazards = { - obstacles: [{ x: 15, w: 1, h: 2 }], - pits: [], - } - const result = autopilotInput(mk({ x: 5, onGround: true }), hazards) - expect(result.jump).toBe(false) - }) - - // Test 5: Not on ground (airborne) with hazard ahead β†’ jump:false - it('airborne with hazard ahead β†’ jump:false (no double-jump)', () => { - const hazards: WorldHazards = { - obstacles: [{ x: 7, w: 1, h: 2 }], - pits: [], - } - // onGround: false - const result = autopilotInput(mk({ x: 5, onGround: false }), hazards) - expect(result.jump).toBe(false) - }) -}) - -describe('watchdog', () => { - // Test 6: Watchdog fires: 4s stall with obstacle at xβ‰ˆ6 β†’ forced:true, next.x > 6+0.5, onGround true - it('fires after stall >3s with obstacle β†’ forced:true, next.x past obstacle far edge, onGround true', () => { - const s = mk({ x: 5 }) - // obstacle at x=6, w=1 β†’ far edge = 6 + 0.5 = 6.5 - const hazards: WorldHazards = { - obstacles: [{ x: 6, w: 1, h: 2 }], - pits: [], - } - const progress: ProgressTrack = { lastX: 5, lastAdvanceT: 0, nowT: 4 } - const { forced, next } = watchdog(progress, s, hazards) - expect(forced).toBe(true) - expect(next.x).toBeGreaterThan(6) // past the obstacle far edge - expect(next.onGround).toBe(true) - }) - - // Test 7: Watchdog quiet: nowT:2 (under 3s) β†’ forced:false, next === s - it('quiet when under 3s stall β†’ forced:false, next x unchanged', () => { - const s = mk({ x: 5 }) - const progress: ProgressTrack = { lastX: 5, lastAdvanceT: 0, nowT: 2 } - const { forced, next } = watchdog(progress, s, noHazards) - expect(forced).toBe(false) - expect(next.x).toBe(s.x) - }) - - // Test 8: Stalled >3s with no hazards β†’ forced:true, next.x === s.x + 8 - it('stalled >3s with no hazards β†’ forced:true, next.x === s.x + 8', () => { - const s = mk({ x: 5 }) - const progress: ProgressTrack = { lastX: 5, lastAdvanceT: 0, nowT: 4 } - const { forced, next } = watchdog(progress, s, noHazards) - expect(forced).toBe(true) - expect(next.x).toBe(5 + 8) - }) - - // Test 9: Lodged in obstacle, stalled >3s β†’ watchdog frees him (next.x beyond obstacle) - it('lodged in obstacle stalled >3s β†’ freed beyond obstacle far edge', () => { - // Kasimir at x=6 (inside obstacle centered at x=6, w=2 β†’ [5, 7]) - const s = mk({ x: 6 }) - const hazards: WorldHazards = { - obstacles: [{ x: 6, w: 2, h: 2 }], - pits: [], - } - const progress: ProgressTrack = { lastX: 6, lastAdvanceT: 0, nowT: 4 } - const { forced, next } = watchdog(progress, s, hazards) - expect(forced).toBe(true) - // far edge = 6 + 1 = 7 β†’ next.x must be > 7 - expect(next.x).toBeGreaterThan(7) - }) -}) +import { describe, it, expect } from 'vitest' +import { autopilotInput, watchdog } from '../autopilot' +import type { ProgressTrack } from '../autopilot' +import { initialState } from '../physics' +import type { WorldHazards } from '../layout' + +const world = (h: Partial = {}): WorldHazards => ({ + crates: [], spikes: [], pits: [], minX: 0, maxX: 200, ...h, +}) + +describe('autopilotInput', () => { + it('runs right without jumping on clear ground', () => { + expect(autopilotInput({ ...initialState(5), vx: 7 }, world())).toEqual({ moveDir: 1, jump: false }) + }) + + it('jumps when a pit edge is within launch distance', () => { + const r = autopilotInput({ ...initialState(5), vx: 7 }, world({ pits: [{ x0: 6.2, x1: 8.5 }] })) + expect(r).toEqual({ moveDir: 1, jump: true }) + }) + + it('does not jump while the hazard is still far away', () => { + const r = autopilotInput({ ...initialState(5), vx: 7 }, world({ crates: [{ x0: 15, x1: 16.3, top: 1.2 }] })) + expect(r.jump).toBe(false) + }) + + it('keeps holding jump while rising (full-height jump), releases while falling', () => { + const rising = { ...initialState(5), y: 1, vy: 5, onGround: false } + const falling = { ...rising, vy: -5 } + expect(autopilotInput(rising, world()).jump).toBe(true) + expect(autopilotInput(falling, world()).jump).toBe(false) + }) + + it('ignores hazards already passed', () => { + const r = autopilotInput({ ...initialState(10), vx: 7 }, world({ spikes: [{ x0: 7, x1: 8.5, top: 0.55 }] })) + expect(r.jump).toBe(false) + }) +}) + +describe('watchdog', () => { + const stalled: ProgressTrack = { lastX: 5, lastAdvanceT: 0, nowT: 4 } + + it('stays quiet while progress is recent', () => { + const s = initialState(5) + const r = watchdog({ lastX: 5, lastAdvanceT: 0, nowT: 2 }, s, world()) + expect(r.forced).toBe(false) + expect(r.next).toBe(s) + }) + + it('frees a wedge past the blocking hazard', () => { + const r = watchdog(stalled, initialState(5), world({ crates: [{ x0: 5.5, x1: 6.8, top: 1.4 }] })) + expect(r.forced).toBe(true) + expect(r.next.x).toBeGreaterThan(6.8) + expect(r.next.onGround).toBe(true) + expect(r.next.lastSafeX).toBe(r.next.x) + }) + + it('nudges forward when nothing is ahead', () => { + const r = watchdog(stalled, initialState(5), world()) + expect(r.forced).toBe(true) + expect(r.next.x).toBe(11) + }) +}) diff --git a/hugo-apps/src/app-space-game/__tests__/frame-map.test.ts b/hugo-apps/src/app-space-game/__tests__/frame-map.test.ts index 881540f99..3ed18a486 100644 --- a/hugo-apps/src/app-space-game/__tests__/frame-map.test.ts +++ b/hugo-apps/src/app-space-game/__tests__/frame-map.test.ts @@ -1,144 +1,101 @@ -import { describe, it, expect } from 'vitest' -import { readFileSync } from 'fs' -import { fileURLToPath } from 'url' -import path from 'path' - -const __dirname = path.dirname(fileURLToPath(import.meta.url)) -const FRAMES_PATH = path.resolve(__dirname, '../kasimir-frames.json') - -interface FrameRect { - x: number - y: number - w: number - h: number -} - -interface ClipDef { - frames: FrameRect[] - fps: number - loop: boolean -} - -interface FrameMap { - image: string - cell: { w: number; h: number } - clips: Record -} - -const REQUIRED_CLIPS = ['idle', 'walk', 'run', 'jump', 'hit', 'ko', 'celebrate', 'chest', 'taunt'] as const - -describe('kasimir-frames.json', () => { - let frameMap: FrameMap - let atlasW: number - let atlasH: number - - it('parses without error', () => { - const raw = readFileSync(FRAMES_PATH, 'utf8') - frameMap = JSON.parse(raw) as FrameMap - expect(frameMap).toBeTruthy() - expect(frameMap.image).toBeTruthy() - expect(frameMap.cell).toBeTruthy() - expect(frameMap.clips).toBeTruthy() - }) - - it('has required top-level fields', () => { - expect(typeof frameMap.image).toBe('string') - expect(frameMap.image.length).toBeGreaterThan(0) - expect(typeof frameMap.cell.w).toBe('number') - expect(typeof frameMap.cell.h).toBe('number') - expect(frameMap.cell.w).toBeGreaterThan(0) - expect(frameMap.cell.h).toBeGreaterThan(0) - }) - - it('contains all required clip keys', () => { - for (const key of REQUIRED_CLIPS) { - expect(frameMap.clips, `clip "${key}" must exist`).toHaveProperty(key) - } - }) - - it('each clip has at least 1 frame', () => { - for (const key of REQUIRED_CLIPS) { - const clip = frameMap.clips[key] - expect(clip.frames.length, `clip "${key}" must have β‰₯1 frame`).toBeGreaterThanOrEqual(1) - } - }) - - it('each clip has fps > 0', () => { - for (const key of REQUIRED_CLIPS) { - const clip = frameMap.clips[key] - expect(clip.fps, `clip "${key}" fps must be > 0`).toBeGreaterThan(0) - } - }) - - it('each clip has a boolean loop field', () => { - for (const key of REQUIRED_CLIPS) { - const clip = frameMap.clips[key] - expect(typeof clip.loop, `clip "${key}" loop must be boolean`).toBe('boolean') - } - }) - - it('atlas dimensions can be computed from clip frame positions', () => { - // Compute atlas bounds from frame positions + cell dimensions - let maxX = 0 - let maxY = 0 - for (const clip of Object.values(frameMap.clips)) { - for (const f of clip.frames) { - const right = f.x + f.w - const bottom = f.y + f.h - if (right > maxX) maxX = right - if (bottom > maxY) maxY = bottom - } - } - atlasW = maxX - atlasH = maxY - expect(atlasW).toBeGreaterThan(0) - expect(atlasH).toBeGreaterThan(0) - }) - - it('all frame boxes lie within atlas bounds', () => { - for (const [name, clip] of Object.entries(frameMap.clips)) { - for (let i = 0; i < clip.frames.length; i++) { - const f = clip.frames[i] - const msg = `clip "${name}" frame ${i}` - expect(f.x, `${msg}: x >= 0`).toBeGreaterThanOrEqual(0) - expect(f.y, `${msg}: y >= 0`).toBeGreaterThanOrEqual(0) - expect(f.w, `${msg}: w > 0`).toBeGreaterThan(0) - expect(f.h, `${msg}: h > 0`).toBeGreaterThan(0) - expect(f.x + f.w, `${msg}: right edge <= atlasW`).toBeLessThanOrEqual(atlasW) - expect(f.y + f.h, `${msg}: bottom edge <= atlasH`).toBeLessThanOrEqual(atlasH) - } - } - }) - - it('frame boxes are consistent with cell dimensions', () => { - const { w: cw, h: ch } = frameMap.cell - for (const [name, clip] of Object.entries(frameMap.clips)) { - for (let i = 0; i < clip.frames.length; i++) { - const f = clip.frames[i] - const msg = `clip "${name}" frame ${i}` - expect(f.w, `${msg}: w matches cell.w`).toBe(cw) - expect(f.h, `${msg}: h matches cell.h`).toBe(ch) - } - } - }) - - it('frame x coordinates align to cell grid', () => { - const { w: cw } = frameMap.cell - for (const [name, clip] of Object.entries(frameMap.clips)) { - for (let i = 0; i < clip.frames.length; i++) { - const f = clip.frames[i] - expect(f.x % cw, `clip "${name}" frame ${i}: x must be multiple of cell.w`).toBe(0) - } - } - }) - - it('frame y coordinates align to cell grid', () => { - const { h: ch } = frameMap.cell - for (const [name, clip] of Object.entries(frameMap.clips)) { - for (let i = 0; i < clip.frames.length; i++) { - const f = clip.frames[i] - expect(f.y % ch, `clip "${name}" frame ${i}: y must be multiple of cell.h`).toBe(0) - } - } - }) -}) +import { describe, it, expect, beforeAll } from 'vitest' +import { readFileSync } from 'fs' +import { fileURLToPath } from 'url' +import { createRequire } from 'module' +import path from 'path' + +const __dirname = path.dirname(fileURLToPath(import.meta.url)) +const ROOT = path.resolve(__dirname, '../../../..') +const FRAMES_PATH = path.resolve(__dirname, '../kasimir-frames.json') +const ATLAS_PATH = path.join(ROOT, 'hugo/static/images/app-space-game/kasimir-atlas.png') + +interface FrameMap { + image: string + atlas: { w: number; h: number; cols: number; rows: number } + cell: { w: number; h: number } + anchor: { x: number; y: number } + charH: number + clips: Record +} + +const fm = JSON.parse(readFileSync(FRAMES_PATH, 'utf8')) as FrameMap +const REQUIRED = ['idle', 'walk', 'trot', 'run', 'jump', 'hit', 'fall', 'ko', 'reach', 'chest', 'celebrate', 'taunt'] + +describe('kasimir-frames.json', () => { + it('has every clip the game uses, with frames, fps and loop', () => { + for (const k of REQUIRED) { + const c = fm.clips[k] + expect(c, k).toBeTruthy() + expect(c.frames.length, k).toBeGreaterThan(0) + expect(c.fps, k).toBeGreaterThan(0) + expect(typeof c.loop, k).toBe('boolean') + } + expect(fm.clips.jump.frames).toHaveLength(10) + expect(fm.clips.run.frames.length).toBeGreaterThanOrEqual(8) + }) + + it('uses a uniform grid inside the atlas, no larger than 2048px', () => { + expect(fm.atlas.w).toBe(fm.atlas.cols * fm.cell.w) + expect(fm.atlas.h).toBe(fm.atlas.rows * fm.cell.h) + expect(Math.max(fm.atlas.w, fm.atlas.h)).toBeLessThanOrEqual(2048) + for (const [name, c] of Object.entries(fm.clips)) { + for (const f of c.frames) { + expect(f.w, name).toBe(fm.cell.w) + expect(f.h, name).toBe(fm.cell.h) + expect(f.x % fm.cell.w, name).toBe(0) + expect(f.y % fm.cell.h, name).toBe(0) + expect(f.x + f.w).toBeLessThanOrEqual(fm.atlas.w) + expect(f.y + f.h).toBeLessThanOrEqual(fm.atlas.h) + } + } + }) + + it('anchors feet at the same point of every cell, horizontally centred (mirror-safe)', () => { + expect(fm.anchor.x).toBe(fm.cell.w / 2) + expect(fm.anchor.y).toBeGreaterThan(fm.cell.h * 0.9) + expect(fm.charH).toBeGreaterThan(100) + }) +}) + +describe('kasimir-atlas.png pixels', () => { + type Img = { data: Buffer; width: number } + let img: Img + + beforeAll(async () => { + const sharp = createRequire(import.meta.url)('sharp') + const { data, info } = await sharp(ATLAS_PATH).ensureAlpha().raw().toBuffer({ resolveWithObject: true }) + img = { data, width: info.width } + }) + + function bbox(f: { x: number; y: number; w: number; h: number }) { + let top = Infinity, bottom = -1 + for (let y = f.y; y < f.y + f.h; y++) { + for (let x = f.x; x < f.x + f.w; x++) { + if (img.data[(y * img.width + x) * 4 + 3] > 128) { + if (y < top) top = y + bottom = y + } + } + } + return { top: top - f.y, bottom: bottom - f.y } + } + + it('every frame stands on the shared baseline (no floating or sinking between clips)', () => { + for (const [name, c] of Object.entries(fm.clips)) { + c.frames.forEach((f, i) => { + const { bottom } = bbox(f) + expect(Math.abs(bottom + 1 - fm.anchor.y), `${name}[${i}]`).toBeLessThanOrEqual(3) + }) + } + }) + + it('standing poses from both source sheets render at the same character height', () => { + const standing = [...fm.clips.idle.frames, fm.clips.celebrate.frames[0], fm.clips.chest.frames[0]] + const heights = standing.map(f => { const b = bbox(f); return b.bottom - b.top }) + const min = Math.min(...heights), max = Math.max(...heights) + expect(max / min).toBeLessThan(1.3) + for (const h of fm.clips.idle.frames.map(f => { const b = bbox(f); return b.bottom - b.top })) { + expect(Math.abs(h - fm.charH) / fm.charH).toBeLessThan(0.05) + } + }) +}) diff --git a/hugo-apps/src/app-space-game/__tests__/label-layout.test.ts b/hugo-apps/src/app-space-game/__tests__/label-layout.test.ts deleted file mode 100644 index 5846f57ee..000000000 --- a/hugo-apps/src/app-space-game/__tests__/label-layout.test.ts +++ /dev/null @@ -1,83 +0,0 @@ -import { describe, it, expect } from 'vitest' -import { fitLabel, coinLabelY } from '../label-layout' - -describe('label-layout', () => { - describe('fitLabel', () => { - it('should handle short text in a single line', () => { - const result = fitLabel('Intro') - expect(result.lines.length).toBe(1) - expect(result.worldW).toBeLessThanOrEqual(4.2) - expect(result.canvasW).toBeGreaterThan(0) - expect(result.canvasH).toBeGreaterThan(0) - }) - - it('should wrap long title into at most 2 lines and keep worldW <= 4.2', () => { - const result = fitLabel('A Very Long Tutorial Title That Keeps Going And Going Forever') - expect(result.lines.length).toBeLessThanOrEqual(2) - expect(result.worldW).toBeLessThanOrEqual(4.2) - - // Check total text length constraint - const joinedText = result.lines.join('') - const totalLen = joinedText.length - expect(totalLen).toBeLessThanOrEqual(29) // 28 + ellipsis - }) - - it('should not throw on empty string and return valid non-zero result', () => { - expect(() => fitLabel('')).not.toThrow() - const result = fitLabel('') - expect(result.canvasW).toBeGreaterThan(0) - expect(result.canvasH).toBeGreaterThan(0) - expect(result.worldW).toBeGreaterThan(0) - }) - - it('should maintain worldW cap at 4.2 for any input', () => { - const inputs = ['Short', 'Medium length text here', 'A Very Long Tutorial Title That Keeps Going And Going Forever'] - inputs.forEach(input => { - const result = fitLabel(input) - expect(result.worldW).toBeLessThanOrEqual(4.2) - }) - }) - - it('should wrap lines to respect maxChars default (28)', () => { - const longText = 'This is a test of the emergency broadcast system' - const result = fitLabel(longText) - // Each line should ideally be around maxChars or less - result.lines.forEach(line => { - // Allow some tolerance for wrapping logic, but generally should be reasonable - expect(line.length).toBeLessThanOrEqual(35) // generous tolerance for wrapping - }) - }) - }) - - describe('coinLabelY', () => { - it('should return baseY + 0.4 for even coinIndex', () => { - const result = coinLabelY(2.0, 0) - expect(result).toBe(2.4) - }) - - it('should return baseY - 0.4 for odd coinIndex', () => { - const result = coinLabelY(2.0, 1) - expect(result).toBe(1.6) - }) - - it('should have 0.8 difference between consecutive coin labels', () => { - const base = 2.0 - const even = coinLabelY(base, 0) - const odd = coinLabelY(base, 1) - expect(even - odd).toBeCloseTo(0.8, 10) - }) - - it('should handle even/odd pairs correctly', () => { - const base = 3.5 - const result0 = coinLabelY(base, 0) - const result1 = coinLabelY(base, 1) - const result2 = coinLabelY(base, 2) - const result3 = coinLabelY(base, 3) - - expect(result0).toBe(base + 0.4) - expect(result1).toBe(base - 0.4) - expect(result2).toBe(base + 0.4) - expect(result3).toBe(base - 0.4) - }) - }) -}) diff --git a/hugo-apps/src/app-space-game/__tests__/labels.test.ts b/hugo-apps/src/app-space-game/__tests__/labels.test.ts new file mode 100644 index 000000000..e96aec35e --- /dev/null +++ b/hugo-apps/src/app-space-game/__tests__/labels.test.ts @@ -0,0 +1,62 @@ +import { describe, it, expect } from 'vitest' +import { labelModes, stackRects, tidyTitle } from '../labels' + +describe('labelModes', () => { + const xs = [0, 6, 12, 18, 24, 30, 90] + + it('shows full titles only for the nearest coins to Kasimir', () => { + const m = labelModes(xs, 13, 15, 20) + expect(m.filter(x => x === 'full')).toHaveLength(2) + expect(m[2]).toBe('full') + expect(m[1] === 'full' || m[3] === 'full').toBe(true) + }) + + it('shows badges for other on-screen coins and hides off-screen ones', () => { + const m = labelModes(xs, 13, 15, 20) + expect(m[5]).toBe('badge') + expect(m[6]).toBe('hidden') + }) + + it('shows no full titles when no coin is near Kasimir', () => { + const m = labelModes([0, 40], 20, 20, 30) + expect(m).toEqual(['badge', 'badge']) + }) +}) + +describe('stackRects', () => { + it('leaves non-overlapping rects untouched', () => { + expect(stackRects([{ x: 100, y: 200, w: 80, h: 30 }, { x: 300, y: 200, w: 80, h: 30 }])).toEqual([0, 0]) + }) + + it('lifts a colliding rect above its neighbour', () => { + const rects = [{ x: 100, y: 200, w: 160, h: 40 }, { x: 180, y: 200, w: 160, h: 40 }] + const off = stackRects(rects, 6) + expect(off[0]).toBe(0) + expect(off[1]).toBe(46) + }) + + it('produces a layout with no overlaps for a dense row', () => { + const rects = Array.from({ length: 6 }, (_, i) => ({ x: 100 + i * 50, y: 300, w: 200, h: 44 })) + const off = stackRects(rects, 4) + const boxes = rects.map((r, i) => ({ l: r.x - r.w / 2, r: r.x + r.w / 2, b: r.y - off[i], t: r.y - off[i] - r.h })) + for (let i = 0; i < boxes.length; i++) { + for (let j = i + 1; j < boxes.length; j++) { + const a = boxes[i], b = boxes[j] + const overlap = a.l < b.r && a.r > b.l && a.t < b.b && a.b > b.t + expect(overlap).toBe(false) + } + } + }) +}) + +describe('tidyTitle', () => { + it('keeps short titles and collapses whitespace', () => { + expect(tidyTitle(' Build an App ')).toBe('Build an App') + }) + it('cuts long titles at a word boundary with an ellipsis', () => { + const t = tidyTitle('Create a SAP Fiori elements application with SAP Business Application Studio and deploy it', 50) + expect(t.endsWith('…')).toBe(true) + expect(t.length).toBeLessThanOrEqual(51) + expect(t).not.toMatch(/\s…$/) + }) +}) diff --git a/hugo-apps/src/app-space-game/__tests__/layout.test.ts b/hugo-apps/src/app-space-game/__tests__/layout.test.ts new file mode 100644 index 000000000..7bc041a30 --- /dev/null +++ b/hugo-apps/src/app-space-game/__tests__/layout.test.ts @@ -0,0 +1,63 @@ +import { describe, it, expect } from 'vitest' +import { buildWorldLayout, goalAt, zoneIndexAt, COIN_Y, GOAL_REACH } from '../layout' +import { COIN_SPACING } from '../obstacles' +import type { LevelModel, Zone } from '../types' + +function zone(id: string, coins: number, extra: Partial = {}): Zone { + return { + id, title: id.toUpperCase(), colorIndex: 0, cleared: false, locked: false, + coins: Array.from({ length: coins }, (_, i) => ({ title: `T${i}`, done: false, available: true })), + gates: [], goal: { kind: 'track', label: 'Complete Track', prizeId: null, trackId: id }, + obstacles: [], pits: [], ...extra, + } +} + +describe('buildWorldLayout', () => { + const model: LevelModel = { + zones: [ + zone('a', 3, { gates: [{ afterCoinIndex: 0 }], obstacles: [{ x: 1.5, w: 1.3, h: 1, kind: 'crate' }] }), + zone('b', 2, { pits: [{ x: 0.5, w: 2 }], locked: true }), + ], + } + const L = buildWorldLayout(model) + + it('places coins at regular spacing on the walking line', () => { + const a = L.coins.filter(c => c.zoneIndex === 0) + expect(a).toHaveLength(3) + expect(a[1].x - a[0].x).toBe(COIN_SPACING) + expect(a.every(c => c.y === COIN_Y)).toBe(true) + }) + + it('zones are contiguous and the goal sits after the last coin', () => { + expect(L.zones[1].x0).toBe(L.zones[0].x1) + const lastA = L.coins.filter(c => c.zoneIndex === 0).at(-1)! + expect(L.zones[0].goal.x).toBeGreaterThan(lastA.x) + expect(L.zones[0].goal.x).toBeLessThan(L.zones[0].x1) + }) + + it('converts hazards to world edges between coins', () => { + const coins = L.coins.filter(c => c.zoneIndex === 0) + const crate = L.hazards.crates[0] + expect((crate.x0 + crate.x1) / 2).toBeCloseTo((coins[1].x + coins[2].x) / 2) + expect(crate.top).toBe(1) + expect(L.hazards.pits).toHaveLength(1) + }) + + it('carries locked state and track id onto goals', () => { + expect(L.zones[1].goal.locked).toBe(true) + expect(L.zones[1].goal.trackId).toBe('b') + }) + + it('zoneIndexAt / goalAt find the right zone and goal', () => { + expect(zoneIndexAt(L, 0)).toBe(0) + expect(zoneIndexAt(L, L.zones[1].x0 + 1)).toBe(1) + expect(goalAt(L, L.zones[0].goal.x + GOAL_REACH * 0.5)?.zoneId).toBe('a') + expect(goalAt(L, L.zones[0].goal.x - GOAL_REACH - 1)).toBeNull() + }) + + it('the level has walls just outside the first and last zone', () => { + expect(L.hazards.minX).toBe(0) + expect(L.hazards.maxX).toBe(L.length) + expect(L.length).toBeGreaterThan(L.zones[1].x1) + }) +}) diff --git a/hugo-apps/src/app-space-game/__tests__/obstacles.test.ts b/hugo-apps/src/app-space-game/__tests__/obstacles.test.ts index 775c72e05..35cfd3b81 100644 --- a/hugo-apps/src/app-space-game/__tests__/obstacles.test.ts +++ b/hugo-apps/src/app-space-game/__tests__/obstacles.test.ts @@ -1,78 +1,62 @@ -import { describe, it, expect } from 'vitest' -import { placeHazards, COIN_SPACING, MIN_LANDING } from '../obstacles' -import { JUMP_ARC } from '../constants' - -describe('placeHazards', () => { - it('1: determinism β€” same args produce deep-equal output', () => { - const a = placeHazards(9, 3) - const b = placeHazards(9, 3) - expect(a).toEqual(b) - }) - - it('2: both calls with same args are valid (completable-path check covers correctness)', () => { - const r0 = placeHazards(9, 0) - const r5 = placeHazards(9, 5) - // Both must be structurally valid (arrays of Obstacle/Pit) - expect(Array.isArray(r0.obstacles)).toBe(true) - expect(Array.isArray(r0.pits)).toBe(true) - expect(Array.isArray(r5.obstacles)).toBe(true) - expect(Array.isArray(r5.pits)).toBe(true) - }) - - it('3: every pit w <= JUMP_ARC.maxGap', () => { - const { pits } = placeHazards(12, 7) - for (const pit of pits) { - expect(pit.w).toBeLessThanOrEqual(JUMP_ARC.maxGap) - } - }) - - it('4: every obstacle h <= JUMP_ARC.maxHeight', () => { - const { obstacles } = placeHazards(12, 7) - for (const obs of obstacles) { - expect(obs.h).toBeLessThanOrEqual(JUMP_ARC.maxHeight) - } - }) - - it('5: completable-path β€” edge-to-edge gap between every adjacent pair >= MIN_LANDING', () => { - // Check across multiple seeds and coin counts to confirm the invariant holds - for (let seed = 0; seed <= 20; seed++) { - for (let coinCount = 3; coinCount <= 15; coinCount++) { - const { obstacles, pits } = placeHazards(coinCount, seed) - - // Build [leftEdge, rightEdge] in world units for every hazard - type Edge = { left: number; right: number } - const edges: Edge[] = [ - ...obstacles.map(o => ({ - left: o.x * COIN_SPACING - o.w / 2, - right: o.x * COIN_SPACING + o.w / 2, - })), - ...pits.map(p => ({ - left: p.x * COIN_SPACING - p.w / 2, - right: p.x * COIN_SPACING + p.w / 2, - })), - ].sort((a, b) => a.left - b.left) - - for (let i = 1; i < edges.length; i++) { - const edgeGap = edges[i].left - edges[i - 1].right - expect(edgeGap).toBeGreaterThanOrEqual(MIN_LANDING) - } - } - } - }) - - it('6: coinCount 0 -> empty arrays, no throw', () => { - expect(placeHazards(0, 1)).toEqual({ obstacles: [], pits: [] }) - }) - - it('6b: coinCount 1 -> empty arrays, no throw', () => { - expect(placeHazards(1, 1)).toEqual({ obstacles: [], pits: [] }) - }) - - it('7: hazard count <= floor(coinCount / 3)', () => { - for (const coinCount of [3, 6, 9, 12, 15]) { - const { obstacles, pits } = placeHazards(coinCount, coinCount) - const total = obstacles.length + pits.length - expect(total).toBeLessThanOrEqual(Math.floor(coinCount / 3)) - } - }) -}) +import { describe, it, expect } from 'vitest' +import { placeHazards, COIN_SPACING, MIN_LANDING } from '../obstacles' +import { JUMP_ARC, HAZARD_LIMITS } from '../constants' + +describe('placeHazards', () => { + it('is deterministic', () => { + expect(placeHazards(9, 3)).toEqual(placeHazards(9, 3)) + }) + + it('returns nothing for tiny zones', () => { + for (const n of [0, 1, 2]) expect(placeHazards(n, 1)).toEqual({ obstacles: [], pits: [] }) + }) + + it('keeps every hazard comfortably inside the jump arc (not frame-perfect)', () => { + for (let seed = 0; seed < 30; seed++) { + const { obstacles, pits } = placeHazards(14, seed) + for (const p of pits) expect(p.w).toBeLessThanOrEqual(JUMP_ARC.maxGap * 0.5 + 1e-9) + for (const o of obstacles) expect(o.h).toBeLessThanOrEqual(JUMP_ARC.maxHeight * 0.55 + 1e-9) + for (const o of obstacles) if (o.kind === 'spike') expect(o.h).toBe(HAZARD_LIMITS.spikeH) + } + }) + + it('never sits on a coin, in the first gap, or in a gate gap', () => { + for (let seed = 0; seed < 30; seed++) { + const { obstacles, pits } = placeHazards(12, seed, [4, 7]) + for (const h of [...obstacles, ...pits]) { + expect(h.x % 1).toBe(0.5) + expect(h.x).toBeGreaterThan(1) + expect([4.5, 7.5]).not.toContain(h.x) + } + } + }) + + it('leaves at least MIN_LANDING of ground between hazards', () => { + for (let seed = 0; seed <= 20; seed++) { + for (let n = 3; n <= 15; n++) { + const { obstacles, pits } = placeHazards(n, seed) + const edges = [...obstacles, ...pits] + .map(h => ({ l: h.x * COIN_SPACING - h.w / 2, r: h.x * COIN_SPACING + h.w / 2 })) + .sort((a, b) => a.l - b.l) + for (let i = 1; i < edges.length; i++) expect(edges[i].l - edges[i - 1].r).toBeGreaterThanOrEqual(MIN_LANDING) + } + } + }) + + it('uses a mix of pits, crates and spikes across zones', () => { + const kinds = new Set() + for (let seed = 0; seed < 20; seed++) { + const { obstacles, pits } = placeHazards(10, seed) + if (pits.length) kinds.add('pit') + for (const o of obstacles) kinds.add(o.kind) + } + expect(kinds).toEqual(new Set(['pit', 'crate', 'spike'])) + }) + + it('caps hazard count at ceil(coins / 2.5)', () => { + for (const n of [3, 6, 9, 12, 15]) { + const { obstacles, pits } = placeHazards(n, n) + expect(obstacles.length + pits.length).toBeLessThanOrEqual(Math.ceil(n / 2.5)) + } + }) +}) diff --git a/hugo-apps/src/app-space-game/__tests__/physics.test.ts b/hugo-apps/src/app-space-game/__tests__/physics.test.ts index 5fec34b77..4a2168bd9 100644 --- a/hugo-apps/src/app-space-game/__tests__/physics.test.ts +++ b/hugo-apps/src/app-space-game/__tests__/physics.test.ts @@ -1,142 +1,153 @@ -import { describe, it, expect } from 'vitest' -import { stepPhysics, respawn } from '../physics' -import type { PhysicsState, WorldHazards } from '../physics' - -// Helper: build a PhysicsState with sane defaults -function mk(partial: Partial = {}): PhysicsState { - return { - x: 5, - y: 1.8, - vy: 0, - onGround: true, - facing: 1, - lastSafeX: 5, - ...partial, - } -} - -const noHazards: WorldHazards = { obstacles: [], pits: [] } - -describe('stepPhysics', () => { - // Test 1: Gravity + land - it('gravity: airborne state drops and eventually lands at ground (y=1.8, onGround true)', () => { - let s = mk({ y: 5, vy: 0, onGround: false }) - const dt = 0.05 - // Run up to 200 steps (10 seconds) to let it fall - let landed = false - let prevY = s.y - for (let i = 0; i < 200; i++) { - const { next } = stepPhysics(s, { moveDir: 0, jump: false }, noHazards, dt) - if (i > 0) { - expect(next.y).toBeLessThanOrEqual(prevY + 0.001) // y should be decreasing or at ground - } - s = next - if (s.onGround) { - landed = true - break - } - prevY = s.y - } - expect(landed).toBe(true) - expect(s.y).toBeCloseTo(1.8, 4) - expect(s.onGround).toBe(true) - }) - - // Test 2: Jump - it('jump: grounded + jump:true -> next.vy > 0, next.onGround false', () => { - const s = mk() - const { next } = stepPhysics(s, { moveDir: 0, jump: true }, noHazards, 0.016) - expect(next.vy).toBeGreaterThan(0) - expect(next.onGround).toBe(false) - }) - - // Test 3: Jump consumed β€” second step while airborne does NOT add velocity - it('jump consumed: airborne + jump:true does NOT re-apply jump velocity', () => { - const s = mk() - const dt = 0.016 - // First step: jump - const { next: after1 } = stepPhysics(s, { moveDir: 0, jump: true }, noHazards, dt) - expect(after1.onGround).toBe(false) - const vyAfterJump = after1.vy - // Second step: still holding jump but airborne - const { next: after2 } = stepPhysics(after1, { moveDir: 0, jump: true }, noHazards, dt) - // vy should have DECREASED due to gravity (not increased by another JUMP) - expect(after2.vy).toBeLessThan(vyAfterJump) - }) - - // Test 4: Move right and left - it('move right: moveDir:1 -> next.x > s.x, facing 1', () => { - const s = mk() - const { next } = stepPhysics(s, { moveDir: 1, jump: false }, noHazards, 0.016) - expect(next.x).toBeGreaterThan(s.x) - expect(next.facing).toBe(1) - }) - - it('move left: moveDir:-1 -> next.x < s.x, facing -1', () => { - const s = mk() - const { next } = stepPhysics(s, { moveDir: -1, jump: false }, noHazards, 0.016) - expect(next.x).toBeLessThan(s.x) - expect(next.facing).toBe(-1) - }) - - // Test 5: Obstacle blocks (grounded, moving right into obstacle) - it('obstacle blocks: grounded moving right into obstacle -> x clamped to near edge, event hit', () => { - // Kasimir at x=5, obstacle at x=7 with w=1 -> left edge = 6.5 - // Moving right with large dt should be blocked at near edge - const obs = { x: 7, w: 1, h: 2 } - const hazards: WorldHazards = { obstacles: [obs], pits: [] } - const s = mk({ x: 5, y: 1.8, onGround: true }) - // Use a large dt so move would overshoot - const { next, event } = stepPhysics(s, { moveDir: 1, jump: false }, hazards, 0.5) - const nearEdge = obs.x - obs.w / 2 // 6.5 - expect(next.x).toBeLessThanOrEqual(nearEdge + 0.001) - expect(event).toBe('hit') - }) - - // Test 6: Obstacle cleared by jump (airborne above obstacle top) - it('obstacle cleared: airborne above obstacle top -> not blocked, event none', () => { - // Obstacle h=2, top = 1.8 + 2 = 3.8 - // Kasimir at y=5 (above top), moving right - const obs = { x: 7, w: 1, h: 2 } - const hazards: WorldHazards = { obstacles: [obs], pits: [] } - const s = mk({ x: 5, y: 5, vy: 0, onGround: false }) - const { next, event } = stepPhysics(s, { moveDir: 1, jump: false }, hazards, 0.5) - // Should pass through, x should have moved right - const nearEdge = obs.x - obs.w / 2 // 6.5 - expect(next.x).toBeGreaterThan(nearEdge) - expect(event).toBe('none') - }) - - // Test 7: Pit fall β€” grounded walking over pit - it('pit fall: grounded walking over pit -> event fell, y drops below 1.8', () => { - // Pit at x=8, w=2 -> [7, 9] - // Kasimir at x=8 (center of pit), onGround - const pit = { x: 8, w: 2 } - const hazards: WorldHazards = { obstacles: [], pits: [pit] } - const s = mk({ x: 8, y: 1.8, onGround: true }) - const { next, event } = stepPhysics(s, { moveDir: 0, jump: false }, hazards, 0.016) - expect(event).toBe('fell') - // After falling, y should drop below ground - expect(next.y).toBeLessThan(1.8) - }) - - // Test 8: Pit cleared β€” airborne over pit - it('pit cleared: airborne over pit -> no fell event, passes', () => { - const pit = { x: 8, w: 2 } - const hazards: WorldHazards = { obstacles: [], pits: [pit] } - // Airborne above pit - const s = mk({ x: 8, y: 4, vy: 2, onGround: false }) - const { event } = stepPhysics(s, { moveDir: 1, jump: false }, hazards, 0.016) - expect(event).not.toBe('fell') - }) - - // Test 9: respawn - it('respawn: lastSafeX:12, fallen (y:-3) -> x:12, y:1.8, onGround:true, vy:0', () => { - const s = mk({ x: 8, y: -3, vy: -10, onGround: false, lastSafeX: 12 }) - const result = respawn(s) - expect(result.x).toBe(12) - expect(result.y).toBe(1.8) - expect(result.onGround).toBe(true) - expect(result.vy).toBe(0) - }) -}) +import { describe, it, expect } from 'vitest' +import { stepPhysics, respawn, initialState, GROUND_Y } from '../physics' +import type { PhysicsState, PhysicsInput } from '../physics' +import type { WorldHazards } from '../layout' +import { FIXED_DT, JUMP_ARC, BODY_HW, INVULN_S } from '../constants' + +const world = (h: Partial = {}): WorldHazards => ({ + crates: [], spikes: [], pits: [], minX: 0, maxX: 200, ...h, +}) +const idle: PhysicsInput = { moveDir: 0, jump: false } + +function run(s: PhysicsState, input: PhysicsInput | ((s: PhysicsState, i: number) => PhysicsInput), h: WorldHazards, seconds: number) { + const events: string[] = [] + const n = Math.round(seconds / FIXED_DT) + for (let i = 0; i < n; i++) { + const inp = typeof input === 'function' ? input(s, i) : input + const r = stepPhysics(s, inp, h, FIXED_DT) + if (r.event !== 'none') events.push(r.event) + s = r.next + } + return { s, events } +} + +describe('stepPhysics', () => { + it('feet rest on the ground at y = 0', () => { + const { s } = run({ ...initialState(5), y: 4, onGround: false }, idle, world(), 2) + expect(s.y).toBe(GROUND_Y) + expect(s.onGround).toBe(true) + }) + + it('accelerates smoothly instead of snapping to full speed', () => { + const first = stepPhysics(initialState(5), { moveDir: 1, jump: false }, world(), FIXED_DT).next + expect(first.vx).toBeGreaterThan(0) + expect(first.vx).toBeLessThan(1) + const { s } = run(initialState(5), { moveDir: 1, jump: false }, world(), 1) + expect(s.vx).toBeCloseTo(7, 5) + }) + + it('moves left and faces left', () => { + const { s } = run(initialState(50), { moveDir: -1, jump: false }, world(), 0.5) + expect(s.x).toBeLessThan(50) + expect(s.facing).toBe(-1) + }) + + it('a full held jump reaches JUMP_ARC.maxHeight; a tap is lower (variable jump)', () => { + const peak = (hold: number) => { + let s = initialState(5) + let max = 0 + for (let i = 0; i < 200; i++) { + s = stepPhysics(s, { moveDir: 0, jump: i < hold }, world(), FIXED_DT).next + max = Math.max(max, s.y) + } + return max + } + const full = peak(200) + expect(full).toBeGreaterThan(JUMP_ARC.maxHeight * 0.95) + expect(full).toBeLessThanOrEqual(JUMP_ARC.maxHeight + 1e-6) + expect(peak(6)).toBeLessThan(JUMP_ARC.maxHeight * 0.6) + }) + + it('holding jump does not re-jump on landing (needs a fresh press)', () => { + const { events } = run(initialState(5), { moveDir: 0, jump: true }, world(), 2) + expect(events.filter(e => e === 'jump')).toHaveLength(1) + }) + + it('jump buffer: a press shortly before landing still jumps', () => { + let s: PhysicsState = { ...initialState(5), y: 0.3, vy: -6, onGround: false } + s = stepPhysics(s, { moveDir: 0, jump: true }, world(), FIXED_DT).next + const { events } = run(s, { moveDir: 0, jump: true }, world(), 0.1) + expect(events).toContain('jump') + }) + + it('coyote time: can still jump just after walking off a ledge', () => { + const pit = { x0: 10, x1: 14 } + let s: PhysicsState = { ...initialState(10.1), vx: 7 } + s = stepPhysics(s, { moveDir: 1, jump: false }, world({ pits: [pit] }), FIXED_DT).next + expect(s.onGround).toBe(false) + const r = stepPhysics(s, { moveDir: 1, jump: true }, world({ pits: [pit] }), FIXED_DT) + expect(r.event).toBe('jump') + }) + + it('crates block from the side with NO penalty event', () => { + const crate = { x0: 8, x1: 9.3, top: 1.2 } + const { s, events } = run(initialState(5), { moveDir: 1, jump: false }, world({ crates: [crate] }), 2) + expect(s.x).toBeCloseTo(crate.x0 - BODY_HW, 5) + expect(events).not.toContain('hit') + }) + + it('can land on top of a crate', () => { + const crate = { x0: 8, x1: 9.3, top: 1.2 } + const s0: PhysicsState = { ...initialState(8.6), y: 3, onGround: false } + const { s } = run(s0, idle, world({ crates: [crate] }), 1) + expect(s.y).toBeCloseTo(1.2, 5) + expect(s.onGround).toBe(true) + }) + + it('falls into a pit and reports fell', () => { + const pit = { x0: 10, x1: 13 } + const { s, events } = run(initialState(9), { moveDir: 1, jump: false }, world({ pits: [pit] }), 2) + expect(events).toContain('fell') + expect(s.x).toBeGreaterThan(pit.x0) + expect(s.x).toBeLessThan(pit.x1) + }) + + it('clears a max-width pit with a running jump', () => { + const pit = { x0: 12, x1: 12 + JUMP_ARC.maxGap * 0.5 } + const { s, events } = run( + { ...initialState(5), vx: 7 }, + (st) => ({ moveDir: 1, jump: st.x > pit.x0 - 1.4 && st.x < pit.x0 }), + world({ pits: [pit] }), + 2, + ) + expect(events).not.toContain('fell') + expect(s.x).toBeGreaterThan(pit.x1) + }) + + it('spikes cost a hit with knock-back and brief invulnerability', () => { + const spike = { x0: 8, x1: 9.5, top: 0.55 } + let s: PhysicsState = { ...initialState(6), vx: 7 } + let hit = false + for (let i = 0; i < 120 && !hit; i++) { + const r = stepPhysics(s, { moveDir: 1, jump: false }, world({ spikes: [spike] }), FIXED_DT) + s = r.next + hit = r.event === 'hit' + } + expect(hit).toBe(true) + expect(s.vx).toBeLessThan(0) + expect(s.vy).toBeGreaterThan(0) + expect(s.invuln).toBeCloseTo(INVULN_S, 5) + }) + + it('level walls stop Kasimir at both ends', () => { + const { s } = run(initialState(2), { moveDir: -1, jump: false }, world({ minX: 0, maxX: 30 }), 2) + expect(s.x).toBeCloseTo(BODY_HW, 5) + const r = run(initialState(28), { moveDir: 1, jump: false }, world({ minX: 0, maxX: 30 }), 2) + expect(r.s.x).toBeCloseTo(30 - BODY_HW, 5) + }) + + it('lastSafeX stays away from hazard edges', () => { + const pit = { x0: 10, x1: 13 } + const { s } = run(initialState(5), { moveDir: 1, jump: false }, world({ pits: [pit] }), 2) + expect(s.lastSafeX).toBeLessThanOrEqual(pit.x0 - 1.2) + }) +}) + +describe('respawn', () => { + it('returns to lastSafeX on the ground, stopped and briefly invulnerable', () => { + const s: PhysicsState = { ...initialState(8), y: -4, vy: -10, vx: 3, onGround: false, lastSafeX: 4 } + const r = respawn(s) + expect(r).toMatchObject({ x: 4, y: 0, vx: 0, vy: 0, onGround: true }) + expect(r.invuln).toBeGreaterThan(0) + }) +}) diff --git a/hugo-apps/src/app-space-game/__tests__/solvability.test.ts b/hugo-apps/src/app-space-game/__tests__/solvability.test.ts new file mode 100644 index 000000000..c883de7af --- /dev/null +++ b/hugo-apps/src/app-space-game/__tests__/solvability.test.ts @@ -0,0 +1,69 @@ +import { describe, it, expect } from 'vitest' +import { placeHazards } from '../obstacles' +import { buildWorldLayout } from '../layout' +import { initialState, stepPhysics } from '../physics' +import { autopilotInput } from '../autopilot' +import { FIXED_DT } from '../constants' +import type { LevelModel, Zone } from '../types' + +function zone(coinCount: number, seed: number, gateAfter: number[] = []): Zone { + const { obstacles, pits } = placeHazards(coinCount, seed, gateAfter) + return { + id: `z${seed}-${coinCount}`, + title: 'Zone', + colorIndex: seed, + cleared: false, + locked: false, + coins: Array.from({ length: coinCount }, (_, i) => ({ title: `T${i}`, done: false, available: true })), + gates: gateAfter.map(a => ({ afterCoinIndex: a })), + goal: { kind: 'track', label: 'Complete Track', prizeId: null, trackId: 't' }, + obstacles, + pits, + } +} + +function runAutopilot(model: LevelModel) { + const layout = buildWorldLayout(model) + let s = initialState(layout.spawnX) + const events: string[] = [] + let t = 0 + let lastAdvance = 0 + let bestX = s.x + let wedged = false + while (s.x < layout.length - 3 && t < 300) { + const { next, event } = stepPhysics(s, autopilotInput(s, layout.hazards), layout.hazards, FIXED_DT) + if (event === 'hit' || event === 'fell') events.push(`${event}@${s.x.toFixed(1)}`) + s = next + t += FIXED_DT + if (s.x > bestX + 0.5) { bestX = s.x; lastAdvance = t } + if (t - lastAdvance > 3) { wedged = true; break } + if (event === 'fell') break + } + return { events, wedged, finished: s.x >= layout.length - 3, layout } +} + +describe('level solvability (autopilot never needs the watchdog)', () => { + it('clears every seed 0..40 Γ— coin counts 3..15 without a hit, fall or wedge', () => { + const failures: string[] = [] + let hazardsSeen = 0 + for (let seed = 0; seed <= 40; seed++) { + for (let n = 3; n <= 15; n++) { + const r = runAutopilot({ zones: [zone(n, seed)] }) + hazardsSeen += r.layout.hazards.crates.length + r.layout.hazards.spikes.length + r.layout.hazards.pits.length + if (!r.finished || r.wedged || r.events.length) { + failures.push(`seed ${seed} n ${n}: finished=${r.finished} wedged=${r.wedged} ${r.events.join(',')}`) + } + } + } + expect(hazardsSeen).toBeGreaterThan(500) + expect(failures).toEqual([]) + }) + + it('clears a multi-zone level with gates', () => { + const model: LevelModel = { zones: [zone(8, 1, [3]), zone(12, 2, [5]), zone(5, 3)] } + const r = runAutopilot(model) + expect(r.events).toEqual([]) + expect(r.wedged).toBe(false) + expect(r.finished).toBe(true) + }) +}) diff --git a/hugo-apps/src/app-space-game/anim.ts b/hugo-apps/src/app-space-game/anim.ts index 9692de919..bcce1a8bb 100644 --- a/hugo-apps/src/app-space-game/anim.ts +++ b/hugo-apps/src/app-space-game/anim.ts @@ -1,77 +1,74 @@ -// Pure animation state machine for Kasimir sprite. -// No DOM, no Three.js, no canvas β€” only clip-selection and frame-index math. -// The renderer owns all UV updates. - -export type ClipName = 'idle' | 'walk' | 'run' | 'jump' | 'hit' | 'ko' | 'celebrate' | 'taunt' | 'chest' - -export interface AnimContext { - onGround: boolean - moveDir: -1 | 0 | 1 // from autopilot or player input - vy: number - lastEvent: 'none' | 'hit' | 'fell' | 'goal' | 'pickup' - sinceEventMs: number // ms since lastEvent fired -} - -/** - * Select the appropriate clip name from the current animation context. - * Priority is strict highest-first; event overrides (ko/hit/celebrate) beat - * locomotion/jump even when airborne. - */ -export function selectClip(ctx: AnimContext): ClipName { - const { onGround, moveDir, vy, lastEvent, sinceEventMs } = ctx - - // Priority 1: fell event β€” KO override - if (lastEvent === 'fell' && sinceEventMs < 800) { - return 'ko' - } - - // Priority 2: hit event - if (lastEvent === 'hit' && sinceEventMs < 600) { - return 'hit' - } - - // Priority 3: goal event β†’ celebrate - if (lastEvent === 'goal' && sinceEventMs < 800) { - return 'celebrate' - } - - // Priority 4: pickup event β†’ celebrate - if (lastEvent === 'pickup' && sinceEventMs < 400) { - return 'celebrate' - } - - // Priority 5: airborne - if (!onGround) { - return 'jump' - } - - // Priority 6: moving on ground (no walk/run distinction in v1 β€” always run; YAGNI) - if (moveDir !== 0 && vy === 0 && onGround) { - return 'run' - } - - // Default - return 'idle' -} - -/** - * Returns the 0-based frame index within a clip for the given elapsed time. - * - Loop clips wrap around. - * - Non-loop clips clamp to the last frame. - */ -export function frameAt( - clipName: ClipName, - elapsedMs: number, - clips: Record -): number { - const clip = clips[clipName] - const frameCount = clip.frames.length - const msPerFrame = 1000 / clip.fps - const rawIndex = Math.floor(elapsedMs / msPerFrame) - - if (clip.loop) { - return rawIndex % frameCount - } else { - return Math.min(rawIndex, frameCount - 1) - } -} +// Pure animation state machine for the Kasimir sprite. No DOM, no Three.js. + +import { JUMP, SPEED } from './constants' + +export type ClipName = + | 'idle' | 'walk' | 'trot' | 'run' | 'jump' + | 'hit' | 'fall' | 'ko' | 'reach' | 'chest' | 'celebrate' | 'taunt' + +export type AnimEvent = 'none' | 'hit' | 'fell' | 'land' | 'jump' | 'respawn' + +export interface AnimContext { + onGround: boolean + vx: number + vy: number + lastEvent: AnimEvent + sinceEventMs: number + /** Scripted pose (standing at a goal, celebrating) β€” overrides locomotion. */ + pose?: 'reach' | 'chest' | 'celebrate' | 'taunt' | null +} + +/** Strict priority: scripted pose > fall > hit > airborne > ground locomotion by speed. */ +export function selectClip(ctx: AnimContext): ClipName { + const { onGround, vx, lastEvent, sinceEventMs } = ctx + if (ctx.pose) return ctx.pose + if (lastEvent === 'fell') return 'fall' + if (lastEvent === 'respawn' && sinceEventMs < 700) return 'hit' + if (lastEvent === 'hit' && sinceEventMs < 700) return onGround ? 'hit' : 'fall' + if (!onGround) return 'jump' + if (lastEvent === 'land' && sinceEventMs < 90) return 'jump' + const speed = Math.abs(vx) + if (speed < 0.35) return 'idle' + if (speed < SPEED * 0.45) return 'walk' + if (speed < SPEED * 0.8) return 'trot' + return 'run' +} + +/** + * Jump frames are chosen by the arc, not by time, so the pose always matches the motion. + * Sheet order: 0 crouch Β· 1 push-off Β· 2–4 rise Β· 5 apex stretch Β· 6–7 descend Β· 8 land Β· 9 stand. + */ +export function jumpFrame(ctx: AnimContext, frameCount: number): number { + const last = frameCount - 1 + if (ctx.onGround) return Math.min(8, last) + if (ctx.lastEvent === 'jump' && ctx.sinceEventMs < 60) return Math.min(1, last) + const v = ctx.vy / JUMP + let f: number + if (v > 0.7) f = 2 + else if (v > 0.4) f = 3 + else if (v > 0.12) f = 4 + else if (v > -0.2) f = 5 + else if (v > -0.6) f = 6 + else f = 7 + return Math.min(f, last) +} + +/** Frame index within a time-driven clip. Loops wrap; one-shots hold the last frame. */ +export function frameAt( + clipName: ClipName, + elapsedMs: number, + clips: Partial>, +): number { + const clip = clips[clipName] + if (!clip || clip.frames.length === 0) return 0 + const raw = Math.floor((elapsedMs * clip.fps) / 1000) + return clip.loop ? raw % clip.frames.length : Math.min(raw, clip.frames.length - 1) +} + +/** Locomotion clips play faster the faster Kasimir moves, so feet don't skate. */ +export function playbackRate(clip: ClipName, vx: number): number { + const ref: Partial> = { walk: SPEED * 0.3, trot: SPEED * 0.62, run: SPEED } + const r = ref[clip] + if (!r) return 1 + return Math.min(1.4, Math.max(0.6, Math.abs(vx) / r)) +} diff --git a/hugo-apps/src/app-space-game/autopilot.ts b/hugo-apps/src/app-space-game/autopilot.ts index cfb597aa5..bc4230eaf 100644 --- a/hugo-apps/src/app-space-game/autopilot.ts +++ b/hugo-apps/src/app-space-game/autopilot.ts @@ -1,170 +1,62 @@ -import { SPEED, JUMP_ARC } from './constants' -import type { PhysicsState, WorldHazards } from './physics' - -// ───────────────────────────────────────────────────────────────────────────── -// Constants -// ───────────────────────────────────────────────────────────────────────────── - -/** Seconds of no x-advance before the watchdog teleports Kasimir forward. */ -const WEDGE_TIMEOUT = 3 - -/** - * Default lookahead distance (world units ahead of s.x to scan for hazards). - * 60 % of horizontal reach of a full jump arc β€” jump when the hazard is this - * close, giving time to arc over while occasionally mis-timing (adds life). - */ -const DEFAULT_LOOKAHEAD = SPEED * JUMP_ARC.airtime * 0.6 - -// ───────────────────────────────────────────────────────────────────────────── -// Types -// ───────────────────────────────────────────────────────────────────────────── - -export interface ProgressTrack { - lastX: number - lastAdvanceT: number - nowT: number -} - -// ───────────────────────────────────────────────────────────────────────────── -// Helpers -// ───────────────────────────────────────────────────────────────────────────── - -/** - * Return the x coordinate of the nearest hazard edge AHEAD of `px` - * (i.e. the left/near edge of obstacles, left edge of pits) that falls - * within [px, px + lookahead], or null if the runway is clear. - */ -function nearestHazardEdge( - px: number, - hazards: WorldHazards, - lookahead: number -): number | null { - let nearest: number | null = null - - for (const obs of hazards.obstacles) { - // Near (left) edge of obstacle - const edge = obs.x - obs.w / 2 - if (edge >= px && edge <= px + lookahead) { - if (nearest === null || edge < nearest) nearest = edge - } - } - - for (const pit of hazards.pits) { - // Near (left) edge of pit - const edge = pit.x - pit.w / 2 - if (edge >= px && edge <= px + lookahead) { - if (nearest === null || edge < nearest) nearest = edge - } - } - - return nearest -} - -/** - * Return the far (right) edge of the nearest hazard at or ahead of `px`, - * or null if no hazard is found. - */ -function nearestHazardFarEdge( - px: number, - hazards: WorldHazards -): number | null { - let nearestEdge: number | null = null - let farEdge: number | null = null - - for (const obs of hazards.obstacles) { - const edge = obs.x - obs.w / 2 - if (edge >= px) { - if (nearestEdge === null || edge < nearestEdge) { - nearestEdge = edge - farEdge = obs.x + obs.w / 2 - } - } - // Also handle the case where Kasimir is already inside the obstacle - const far = obs.x + obs.w / 2 - if (far > px && edge < px) { - if (nearestEdge === null || edge < nearestEdge) { - nearestEdge = edge - farEdge = far - } - } - } - - for (const pit of hazards.pits) { - const edge = pit.x - pit.w / 2 - if (edge >= px) { - if (nearestEdge === null || edge < nearestEdge) { - nearestEdge = edge - farEdge = pit.x + pit.w / 2 - } - } - // Inside a pit - const far = pit.x + pit.w / 2 - if (far > px && edge < px) { - if (nearestEdge === null || edge < nearestEdge) { - nearestEdge = edge - farEdge = far - } - } - } - - return farEdge -} - -// ───────────────────────────────────────────────────────────────────────────── -// Public API -// ───────────────────────────────────────────────────────────────────────────── - -/** - * Autopilot input decision for one game tick. - * - * - `moveDir` is ALWAYS `1` (moving right; stopping risks a wedge). - * - `jump: true` when a hazard near-edge is within `lookahead` world units - * AND the character is on the ground (no double-jumps). - * - * Pure and deterministic β€” never throws. - */ -export function autopilotInput( - s: PhysicsState, - hazards: WorldHazards, - lookahead?: number -): { moveDir: 1; jump: boolean } { - const la = lookahead !== undefined ? lookahead : DEFAULT_LOOKAHEAD - const hazardEdge = nearestHazardEdge(s.x, hazards, la) - const shouldJump = hazardEdge !== null && s.onGround - return { moveDir: 1, jump: shouldJump } -} - -/** - * Hard watchdog: if x hasn't advanced > 0.5 world units in WEDGE_TIMEOUT - * seconds, teleport Kasimir forward past the nearest blocking hazard. - * - * - `forced: true` β†’ state was reset; `next` is the teleported state. - * - `forced: false` β†’ no action; `next` is the original `s` unchanged. - * - * Pure and deterministic β€” never throws. - */ -export function watchdog( - progress: ProgressTrack, - s: PhysicsState, - hazards: WorldHazards -): { forced: boolean; next: PhysicsState } { - const stalled = progress.nowT - progress.lastAdvanceT > WEDGE_TIMEOUT - - if (!stalled) { - return { forced: false, next: s } - } - - // Find the far edge of the nearest hazard ahead (or overlapping) - const far = nearestHazardFarEdge(s.x, hazards) - const newX = far !== null ? far + 0.1 : s.x + 8 - - const next: PhysicsState = { - ...s, - x: newX, - y: 1.8, // GROUND_Y - vy: 0, - onGround: true, - } - - return { forced: true, next } -} +// Attract-mode driver: runs right and times jumps from the hazard geometry and the +// current speed so Kasimir clears every crate, spike and pit. Pure and deterministic. + +import { GRAV, JUMP, BODY_HW } from './constants' +import type { PhysicsState, PhysicsInput } from './physics' +import type { WorldHazards } from './layout' + +const WEDGE_TIMEOUT = 3 +const RISE_T = JUMP / Math.abs(GRAV) + +export interface ProgressTrack { + lastX: number + lastAdvanceT: number + nowT: number +} + +interface Ahead { x0: number; x1: number } + +/** Nearest hazard whose far edge is still ahead of Kasimir's centre. */ +function nextHazard(x: number, h: WorldHazards): Ahead | null { + let best: Ahead | null = null + const consider = (x0: number, x1: number) => { + if (x1 <= x) return + if (!best || x0 < best.x0) best = { x0, x1 } + } + for (const c of h.crates) consider(c.x0, c.x1) + for (const s of h.spikes) consider(s.x0, s.x1) + for (const p of h.pits) consider(p.x0, p.x1) + return best +} + +/** + * Jump so the apex lands over the hazard's middle. Holds the button while rising + * (full-height jump), releases on the way down so the next press registers. + */ +export function autopilotInput(s: PhysicsState, h: WorldHazards): PhysicsInput { + const holding = !s.onGround && s.vy > 0 + const hz = nextHazard(s.x, h) + if (!hz || !s.onGround) return { moveDir: 1, jump: holding } + const front = s.x + BODY_HW + const d = hz.x0 - front + const speed = Math.max(Math.abs(s.vx), 3) + const halfW = (hz.x1 - hz.x0) / 2 + const launchAt = Math.min(2.6, Math.max(0.25, speed * RISE_T - (halfW + BODY_HW))) + const launch = d <= launchAt + return { moveDir: 1, jump: launch || holding } +} + +/** Furthest-right x Kasimir has safely reached is tracked by the caller; this frees a wedge. */ +export function watchdog( + progress: ProgressTrack, + s: PhysicsState, + h: WorldHazards, +): { forced: boolean; next: PhysicsState } { + if (progress.nowT - progress.lastAdvanceT <= WEDGE_TIMEOUT) return { forced: false, next: s } + const hz = nextHazard(s.x, h) + const x = hz ? hz.x1 + 1.5 : s.x + 6 + return { + forced: true, + next: { ...s, x: Math.min(x, h.maxX - 1), y: 0, vx: 0, vy: 0, onGround: true, lastSafeX: Math.min(x, h.maxX - 1) }, + } +} diff --git a/hugo-apps/src/app-space-game/constants.ts b/hugo-apps/src/app-space-game/constants.ts index a20aa119d..33559f7b3 100644 --- a/hugo-apps/src/app-space-game/constants.ts +++ b/hugo-apps/src/app-space-game/constants.ts @@ -1,11 +1,45 @@ -export const SPEED = 6 // world units/sec horizontal -export const GRAV = -24 // world units/sec^2 -export const JUMP = 10 // initial jump velocity (world units/sec) -// Derived: a jump launched at JUMP under GRAV returns to launch height after -// t = 2*JUMP/|GRAV| seconds, during which horizontal travel = SPEED * t. -// maxHeight = JUMP^2 / (2*|GRAV|). -export const JUMP_ARC = { - airtime: (2 * JUMP) / Math.abs(GRAV), // seconds - maxGap: SPEED * ((2 * JUMP) / Math.abs(GRAV)), // world units clearable horizontally - maxHeight: (JUMP * JUMP) / (2 * Math.abs(GRAV)), -} +// World units: ground top is y = 0 and physics y is Kasimir's FEET. + +export const SPEED = 7 // max horizontal speed (units/s) +export const ACCEL = 38 // ground acceleration toward target speed +export const DECEL = 46 // ground deceleration when no input +export const TURN_ACCEL = 60 // ground acceleration when reversing direction +export const AIR_ACCEL = 26 // air control +export const GRAV = -34 // gravity (units/sΒ²) +export const JUMP = 13.5 // jump launch speed (units/s) +export const JUMP_CUT_GRAV = 2.4 // gravity multiplier while rising with jump released (variable jump) +export const FALL_GRAV = 1.25 // gravity multiplier while falling (snappier arcs) +export const MAX_FALL = -28 +export const COYOTE_S = 0.1 // can still jump this long after walking off a ledge +export const JUMP_BUFFER_S = 0.12 // a jump pressed this long before landing still fires +export const INVULN_S = 1.4 // invulnerability after a spike hit +export const FALL_LIMIT_Y = -5 // below this (inside a pit) Kasimir has fallen + +export const BODY_HW = 0.45 // collision half-width +export const BODY_H = 2.1 // collision height +export const KASIMIR_H = 2.5 // rendered character height (feet β†’ ear tips) + +export const FIXED_DT = 1 / 120 // physics step + +const RISE_T = JUMP / Math.abs(GRAV) +const MAX_H = (JUMP * JUMP) / (2 * Math.abs(GRAV)) +const FALL_T = Math.sqrt((2 * MAX_H) / (Math.abs(GRAV) * FALL_GRAV)) + +// Full-height jump with the button held, at full run speed. +export const JUMP_ARC = { + airtime: RISE_T + FALL_T, + maxHeight: MAX_H, + maxGap: SPEED * (RISE_T + FALL_T), +} + +// Hazards are tuned well inside the arc so ordinary, non-frame-perfect jumps clear them. +export const HAZARD_LIMITS = { + crateMaxH: MAX_H * 0.55, + crateMinH: 0.9, + crateW: 1.3, + pitMaxW: SPEED * (RISE_T + FALL_T) * 0.5, + pitMinW: 1.8, + spikeH: 0.55, + spikeMinW: 1.2, + spikeMaxW: 1.8, +} diff --git a/hugo-apps/src/app-space-game/fx.ts b/hugo-apps/src/app-space-game/fx.ts new file mode 100644 index 000000000..2d7b54101 --- /dev/null +++ b/hugo-apps/src/app-space-game/fx.ts @@ -0,0 +1,158 @@ +// Lightweight pooled particle effects: pickup sparkles, scattered rings, dust, confetti. + +import * as THREE from 'three' +import { sparkleTexture, softDotTexture } from './textures' + +interface Particle { + obj: THREE.Object3D + mat: THREE.Material & { opacity: number } + vx: number + vy: number + vz: number + life: number + max: number + gravity: number + spin: number + bounce: boolean + grow: number + active: boolean +} + +const CONFETTI = [0xff5a5f, 0xffc23a, 0x4cd97b, 0x3fa9ff, 0xb276ff, 0xffffff] + +export class Effects { + private pool: Particle[] = [] + private sparkleTex = sparkleTexture() + private dustTex = softDotTexture('rgba(230,220,200,0.8)', 'rgba(230,220,200,0)') + private ringGeo = new THREE.TorusGeometry(0.32, 0.1, 8, 20) + private confettiGeo = new THREE.PlaneGeometry(0.16, 0.26) + + constructor(private scene: THREE.Scene) {} + + private spawn(kind: 'sprite' | 'ring' | 'confetti', make: () => Particle): Particle { + const free = this.pool.find(p => !p.active && p.obj.userData.kind === kind) + if (free) { + free.active = true + free.obj.visible = true + return free + } + const p = make() + p.obj.userData.kind = kind + this.scene.add(p.obj) + this.pool.push(p) + return p + } + + private sprite(tex: THREE.Texture, additive: boolean): Particle { + const p = this.spawn('sprite', () => { + const mat = new THREE.SpriteMaterial({ map: tex, transparent: true, depthWrite: false }) + return this.blank(new THREE.Sprite(mat), mat) + }) + const m = p.mat as THREE.SpriteMaterial + const blending = additive ? THREE.AdditiveBlending : THREE.NormalBlending + if (m.map !== tex || m.blending !== blending) { + m.map = tex + m.blending = blending + m.needsUpdate = true + } + return p + } + + private blank(obj: THREE.Object3D, mat: THREE.Material & { opacity: number }): Particle { + return { obj, mat, vx: 0, vy: 0, vz: 0, life: 0, max: 1, gravity: 0, spin: 0, bounce: false, grow: 0, active: true } + } + + sparkle(x: number, y: number): void { + for (let i = 0; i < 10; i++) { + const a = (i / 10) * Math.PI * 2 + const p = this.sprite(this.sparkleTex, true) + p.obj.position.set(x, y, 0.4) + const s = 0.5 + Math.random() * 0.4 + p.obj.scale.set(s, s, 1) + Object.assign(p, { vx: Math.cos(a) * 4, vy: Math.sin(a) * 4 + 1, vz: 0, life: 0, max: 0.45, gravity: -6, spin: 0, bounce: false, grow: -1 }) + p.mat.opacity = 1 + } + } + + dust(x: number, y: number, dir: number): void { + for (let i = 0; i < 6; i++) { + const p = this.sprite(this.dustTex, false) + p.obj.position.set(x + (Math.random() - 0.5) * 0.6, y + 0.1, 0.5) + p.obj.scale.set(0.5, 0.5, 1) + Object.assign(p, { vx: (Math.random() - 0.5) * 3 - dir * 1.2, vy: 0.6 + Math.random(), vz: 0, life: 0, max: 0.5, gravity: 0, spin: 0, bounce: false, grow: 1.6 }) + p.mat.opacity = 0.7 + } + } + + scatterRings(x: number, y: number, count: number): void { + const n = Math.min(count, 16) + for (let i = 0; i < n; i++) { + const p = this.spawn('ring', () => { + const mat = new THREE.MeshStandardMaterial({ color: 0xffc23a, metalness: 0.9, roughness: 0.2, emissive: 0x8a5a00, emissiveIntensity: 0.6, transparent: true }) + return this.blank(new THREE.Mesh(this.ringGeo, mat), mat) + }) + const a = Math.PI * (0.15 + 0.7 * (i / Math.max(1, n - 1))) + p.obj.position.set(x, y + 1, 0.3) + Object.assign(p, { vx: Math.cos(a) * 6, vy: Math.sin(a) * 8, vz: (Math.random() - 0.5) * 2, life: 0, max: 1.3, gravity: -22, spin: 8, bounce: true, grow: 0 }) + p.obj.scale.setScalar(1) + p.mat.opacity = 1 + } + } + + confetti(x: number, y: number): void { + for (let i = 0; i < 60; i++) { + const p = this.spawn('confetti', () => { + const mat = new THREE.MeshBasicMaterial({ color: 0xffffff, side: THREE.DoubleSide, transparent: true }) + return this.blank(new THREE.Mesh(this.confettiGeo, mat), mat) + }) + ;(p.mat as THREE.MeshBasicMaterial).color.setHex(CONFETTI[i % CONFETTI.length]) + p.obj.position.set(x + (Math.random() - 0.5) * 1.5, y, 0.6) + p.obj.rotation.set(Math.random() * 3, Math.random() * 3, 0) + Object.assign(p, { + vx: (Math.random() - 0.5) * 9, vy: 7 + Math.random() * 7, vz: (Math.random() - 0.5) * 3, + life: 0, max: 2.2, gravity: -12, spin: 6 + Math.random() * 6, bounce: false, grow: 0, + }) + p.mat.opacity = 1 + } + } + + update(dt: number): void { + for (const p of this.pool) { + if (!p.active) continue + p.life += dt + if (p.life >= p.max) { + p.active = false + p.obj.visible = false + continue + } + p.vy += p.gravity * dt + p.obj.position.x += p.vx * dt + p.obj.position.y += p.vy * dt + p.obj.position.z += p.vz * dt + if (p.bounce && p.obj.position.y < 0.35 && p.vy < 0) { + p.obj.position.y = 0.35 + p.vy *= -0.55 + p.vx *= 0.8 + } + if (p.spin) { + p.obj.rotation.x += p.spin * dt + p.obj.rotation.y += p.spin * 0.7 * dt + } + if (p.grow) p.obj.scale.multiplyScalar(1 + p.grow * dt) + const k = p.life / p.max + p.mat.opacity = k < 0.6 ? p.mat.opacity : Math.max(0, (1 - k) / 0.4) + } + } + + dispose(): void { + for (const p of this.pool) { + this.scene.remove(p.obj) + p.mat.dispose() + } + this.pool = [] + this.sparkleTex.dispose() + this.dustTex.dispose() + this.ringGeo.dispose() + this.confettiGeo.dispose() + } +} diff --git a/hugo-apps/src/app-space-game/kasimir-frames.json b/hugo-apps/src/app-space-game/kasimir-frames.json index ab444996d..6e34bede3 100644 --- a/hugo-apps/src/app-space-game/kasimir-frames.json +++ b/hugo-apps/src/app-space-game/kasimir-frames.json @@ -1,309 +1,452 @@ { "image": "/images/app-space-game/kasimir-atlas.png", + "atlas": { + "w": 2044, + "h": 1944, + "cols": 7, + "rows": 9 + }, "cell": { - "w": 318, - "h": 607 + "w": 292, + "h": 216 + }, + "anchor": { + "x": 146, + "y": 212 }, + "charH": 172, "clips": { "idle": { "frames": [ { "x": 0, "y": 0, - "w": 318, - "h": 607 + "w": 292, + "h": 216 }, { - "x": 318, + "x": 292, "y": 0, - "w": 318, - "h": 607 + "w": 292, + "h": 216 }, { - "x": 636, + "x": 584, "y": 0, - "w": 318, - "h": 607 + "w": 292, + "h": 216 }, { - "x": 954, + "x": 876, "y": 0, - "w": 318, - "h": 607 + "w": 292, + "h": 216 }, { - "x": 1272, + "x": 1168, "y": 0, - "w": 318, - "h": 607 + "w": 292, + "h": 216 } ], - "fps": 8, + "fps": 6, "loop": true }, "walk": { "frames": [ { - "x": 1590, + "x": 1460, "y": 0, - "w": 318, - "h": 607 + "w": 292, + "h": 216 }, { - "x": 1908, + "x": 1752, "y": 0, - "w": 318, - "h": 607 + "w": 292, + "h": 216 }, { "x": 0, - "y": 607, - "w": 318, - "h": 607 + "y": 216, + "w": 292, + "h": 216 }, { - "x": 318, - "y": 607, - "w": 318, - "h": 607 + "x": 292, + "y": 216, + "w": 292, + "h": 216 }, { - "x": 636, - "y": 607, - "w": 318, - "h": 607 + "x": 584, + "y": 216, + "w": 292, + "h": 216 }, { - "x": 954, - "y": 607, - "w": 318, - "h": 607 + "x": 876, + "y": 216, + "w": 292, + "h": 216 }, { - "x": 1272, - "y": 607, - "w": 318, - "h": 607 + "x": 1168, + "y": 216, + "w": 292, + "h": 216 }, { - "x": 1590, - "y": 607, - "w": 318, - "h": 607 + "x": 1460, + "y": 216, + "w": 292, + "h": 216 }, { - "x": 1908, - "y": 607, - "w": 318, - "h": 607 + "x": 1752, + "y": 216, + "w": 292, + "h": 216 } ], "fps": 12, "loop": true }, - "run": { + "trot": { "frames": [ { "x": 0, - "y": 1214, - "w": 318, - "h": 607 + "y": 432, + "w": 292, + "h": 216 }, { - "x": 318, - "y": 1214, - "w": 318, - "h": 607 + "x": 292, + "y": 432, + "w": 292, + "h": 216 }, { - "x": 636, - "y": 1214, - "w": 318, - "h": 607 + "x": 584, + "y": 432, + "w": 292, + "h": 216 }, { - "x": 954, - "y": 1214, - "w": 318, - "h": 607 + "x": 876, + "y": 432, + "w": 292, + "h": 216 }, { - "x": 1272, - "y": 1214, - "w": 318, - "h": 607 + "x": 1168, + "y": 432, + "w": 292, + "h": 216 }, { - "x": 1590, - "y": 1214, - "w": 318, - "h": 607 + "x": 1460, + "y": 432, + "w": 292, + "h": 216 }, { - "x": 1908, - "y": 1214, - "w": 318, - "h": 607 + "x": 1752, + "y": 432, + "w": 292, + "h": 216 }, { "x": 0, - "y": 1821, - "w": 318, - "h": 607 - }, - { - "x": 318, - "y": 1821, - "w": 318, - "h": 607 + "y": 648, + "w": 292, + "h": 216 }, { - "x": 636, - "y": 1821, - "w": 318, - "h": 607 + "x": 292, + "y": 648, + "w": 292, + "h": 216 } ], - "fps": 16, + "fps": 14, "loop": true }, "jump": { "frames": [ { - "x": 954, - "y": 1821, - "w": 318, - "h": 607 + "x": 584, + "y": 648, + "w": 292, + "h": 216 + }, + { + "x": 876, + "y": 648, + "w": 292, + "h": 216 }, { - "x": 1272, - "y": 1821, - "w": 318, - "h": 607 + "x": 1168, + "y": 648, + "w": 292, + "h": 216 }, { - "x": 1590, - "y": 1821, - "w": 318, - "h": 607 + "x": 1460, + "y": 648, + "w": 292, + "h": 216 }, { - "x": 1908, - "y": 1821, - "w": 318, - "h": 607 + "x": 1752, + "y": 648, + "w": 292, + "h": 216 }, { "x": 0, - "y": 2428, - "w": 318, - "h": 607 + "y": 864, + "w": 292, + "h": 216 + }, + { + "x": 292, + "y": 864, + "w": 292, + "h": 216 + }, + { + "x": 584, + "y": 864, + "w": 292, + "h": 216 + }, + { + "x": 876, + "y": 864, + "w": 292, + "h": 216 + }, + { + "x": 1168, + "y": 864, + "w": 292, + "h": 216 + } + ], + "fps": 12, + "loop": false + }, + "run": { + "frames": [ + { + "x": 1460, + "y": 864, + "w": 292, + "h": 216 + }, + { + "x": 1752, + "y": 864, + "w": 292, + "h": 216 }, { - "x": 318, - "y": 2428, - "w": 318, - "h": 607 + "x": 0, + "y": 1080, + "w": 292, + "h": 216 }, { - "x": 636, - "y": 2428, - "w": 318, - "h": 607 + "x": 292, + "y": 1080, + "w": 292, + "h": 216 }, { - "x": 954, - "y": 2428, - "w": 318, - "h": 607 + "x": 584, + "y": 1080, + "w": 292, + "h": 216 }, { - "x": 1272, - "y": 2428, - "w": 318, - "h": 607 + "x": 876, + "y": 1080, + "w": 292, + "h": 216 }, { - "x": 1590, - "y": 2428, - "w": 318, - "h": 607 + "x": 1168, + "y": 1080, + "w": 292, + "h": 216 }, { - "x": 1908, - "y": 2428, - "w": 318, - "h": 607 + "x": 1460, + "y": 1080, + "w": 292, + "h": 216 + } + ], + "fps": 14, + "loop": true + }, + "taunt": { + "frames": [ + { + "x": 1752, + "y": 1080, + "w": 292, + "h": 216 }, { "x": 0, - "y": 3035, - "w": 318, - "h": 607 + "y": 1296, + "w": 292, + "h": 216 + }, + { + "x": 292, + "y": 1296, + "w": 292, + "h": 216 + }, + { + "x": 584, + "y": 1296, + "w": 292, + "h": 216 + }, + { + "x": 876, + "y": 1296, + "w": 292, + "h": 216 + }, + { + "x": 1168, + "y": 1296, + "w": 292, + "h": 216 } ], - "fps": 12, + "fps": 10, "loop": false }, "hit": { "frames": [ { - "x": 318, - "y": 3035, - "w": 318, - "h": 607 + "x": 1460, + "y": 1296, + "w": 292, + "h": 216 + }, + { + "x": 1752, + "y": 1296, + "w": 292, + "h": 216 } ], - "fps": 10, + "fps": 4, "loop": false }, - "celebrate": { + "fall": { "frames": [ { - "x": 636, - "y": 3035, - "w": 318, - "h": 607 + "x": 0, + "y": 1512, + "w": 292, + "h": 216 } ], - "fps": 10, + "fps": 1, "loop": false }, - "taunt": { + "reach": { "frames": [ { - "x": 954, - "y": 3035, - "w": 318, - "h": 607 + "x": 292, + "y": 1512, + "w": 292, + "h": 216 + }, + { + "x": 584, + "y": 1512, + "w": 292, + "h": 216 + }, + { + "x": 876, + "y": 1512, + "w": 292, + "h": 216 + }, + { + "x": 1168, + "y": 1512, + "w": 292, + "h": 216 + }, + { + "x": 1460, + "y": 1512, + "w": 292, + "h": 216 } ], - "fps": 10, - "loop": false + "fps": 8, + "loop": true }, "ko": { "frames": [ { - "x": 1272, - "y": 3035, - "w": 318, - "h": 607 + "x": 1752, + "y": 1512, + "w": 292, + "h": 216 } ], - "fps": 8, + "fps": 1, "loop": false }, "chest": { "frames": [ { - "x": 1590, - "y": 3035, - "w": 318, - "h": 607 + "x": 0, + "y": 1728, + "w": 292, + "h": 216 + }, + { + "x": 292, + "y": 1728, + "w": 292, + "h": 216 } ], - "fps": 8, - "loop": false + "fps": 4, + "loop": true + }, + "celebrate": { + "frames": [ + { + "x": 584, + "y": 1728, + "w": 292, + "h": 216 + }, + { + "x": 876, + "y": 1728, + "w": 292, + "h": 216 + } + ], + "fps": 5, + "loop": true } } } diff --git a/hugo-apps/src/app-space-game/label-layer.ts b/hugo-apps/src/app-space-game/label-layer.ts new file mode 100644 index 000000000..610b09d4e --- /dev/null +++ b/hugo-apps/src/app-space-game/label-layer.ts @@ -0,0 +1,151 @@ +// DOM overlay for readable labels. World anchors are projected to screen each frame; +// text is real HTML (crisp at any DPR, wraps with CSS) instead of canvas sprites. + +import * as THREE from 'three' +import type { WorldLayout } from './layout' +import { zoneColor } from './level-model' +import { labelModes, stackRects, tidyTitle } from './labels' +import type { LabelMode } from './labels' +import { KASIMIR_H } from './constants' + +const COIN_LABEL_Y = 3.4 // above Kasimir's head so titles never cover him +const GOAL_LABEL_Y = { track: 6.1, chest: 3.9 } +const TOP_SAFE_PX = 76 // keep clear of the HUD row (zone banner, rings, close) +const EDGE_PX = 8 + +interface Item { + el: HTMLDivElement + x: number + y: number + mode: LabelMode | 'goal' + w: number + h: number +} + +function el(cls: string, text?: string): HTMLDivElement { + const d = document.createElement('div') + d.className = cls + if (text !== undefined) d.textContent = text + return d +} + +export class LabelLayer { + private root: HTMLDivElement + private coins: Item[] = [] + private goals: Item[] = [] + private v = new THREE.Vector3() + + constructor(host: HTMLElement, private layout: WorldLayout) { + this.root = el('asg-labels') + this.root.setAttribute('aria-hidden', 'true') + host.appendChild(this.root) + + layout.coins.forEach(c => { + const item = el('asg-label is-hidden') + if (!c.available) item.classList.add('is-locked') + else if (c.done) item.classList.add('is-done') + const badge = el('asg-label__badge', c.done ? 'βœ“' : String(c.index + 1)) + const title = el('asg-label__title', tidyTitle(c.title)) + item.append(badge, title) + this.root.appendChild(item) + this.coins.push({ el: item, x: c.x, y: COIN_LABEL_Y, mode: 'hidden', w: 0, h: 0 }) + }) + + layout.zones.forEach((z, i) => { + const g = z.goal + const item = el('asg-goal is-hidden') + item.style.setProperty('--asg-accent', zoneColor(z.colorIndex)) + const icon = g.locked ? 'πŸ”’' : g.kind === 'track' ? '🏁' : '🎁' + const head = el('asg-goal__head', `${icon} ${i + 1}. ${tidyTitle(z.title, 60)}`) + const sub = el( + 'asg-goal__sub', + g.locked ? 'Locked β€” complete the prerequisites first' : g.kind === 'track' ? 'Open this track' : tidyTitle(g.label, 60), + ) + if (g.locked) item.classList.add('is-locked') + item.append(head, sub) + this.root.appendChild(item) + this.goals.push({ el: item, x: g.x, y: g.kind === 'track' ? GOAL_LABEL_Y.track : GOAL_LABEL_Y.chest, mode: 'hidden', w: 0, h: 0 }) + }) + } + + private toScreen(x: number, y: number, camera: THREE.Camera, w: number, h: number): { sx: number; sy: number; front: boolean } { + this.v.set(x, y, 0).project(camera) + return { sx: (this.v.x * 0.5 + 0.5) * w, sy: (-this.v.y * 0.5 + 0.5) * h, front: this.v.z < 1 } + } + + private setMode(it: Item, mode: Item['mode']): void { + if (it.mode === mode) return + it.mode = mode + it.el.classList.toggle('is-hidden', mode === 'hidden') + it.el.classList.toggle('is-full', mode === 'full' || mode === 'goal') + it.el.classList.toggle('is-badge', mode === 'badge') + it.w = it.el.offsetWidth + it.h = it.el.offsetHeight + } + + update(camera: THREE.PerspectiveCamera, camX: number, kasX: number, kasY: number, width: number, height: number): void { + const viewHalfW = Math.tan((camera.fov * Math.PI) / 360) * camera.position.z * camera.aspect + 2 + const modes = labelModes(this.coins.map(c => c.x), kasX, camX, viewHalfW) + const visible: Array<{ it: Item; sx: number; sy: number }> = [] + // Keep cards fully on screen and below the HUD row before resolving overlaps. + const push = (it: Item) => { + const p = this.toScreen(it.x, it.y, camera, width, height) + const halfW = it.w / 2 + visible.push({ + it, + sx: Math.min(width - halfW - EDGE_PX, Math.max(halfW + EDGE_PX, p.sx)), + sy: Math.max(it.h + TOP_SAFE_PX, p.sy), + }) + } + this.coins.forEach((it, i) => { + this.setMode(it, modes[i]) + if (modes[i] !== 'hidden') push(it) + }) + for (const it of this.goals) { + const show = Math.abs(it.x - camX) <= viewHalfW + this.setMode(it, show ? 'goal' : 'hidden') + if (show) push(it) + } + // Only full titles and goal cards are big enough to collide; badges stay on their coin. + // Place goal cards first, then coin titles nearest Kasimir. A coin title that would have + // to climb into the HUD row falls back to its badge instead of overlapping. + const prio = (v: { it: Item }) => (v.it.mode === 'goal' ? -1 : Math.abs(v.it.x - kasX)) + const rect = (v: { it: Item; sx: number; sy: number }) => ({ x: v.sx, y: v.sy, w: v.it.w || 160, h: v.it.h || 40 }) + let big = visible.filter(v => v.it.mode === 'full' || v.it.mode === 'goal').sort((a, b) => prio(a) - prio(b)) + let lift = stackRects(big.map(rect)) + const demoted = big.filter((v, i) => v.it.mode === 'full' && v.sy - lift[i] - v.it.h < TOP_SAFE_PX) + if (demoted.length) { + for (const v of demoted) this.setMode(v.it, 'badge') + big = big.filter(v => !demoted.includes(v)) + lift = stackRects(big.map(rect)) + } + const liftOf = new Map(big.map((v, i) => [v.it, lift[i]])) + // Cards that cover Kasimir (e.g. mid-jump) fade so he always stays visible. + const a = this.toScreen(kasX - 0.9, kasY, camera, width, height) + const b = this.toScreen(kasX + 0.9, kasY + KASIMIR_H, camera, width, height) + const kas = { l: a.sx, r: b.sx, t: b.sy, btm: a.sy } + for (const v of visible) { + const dy = liftOf.get(v.it) ?? 0 + const y = v.sy - dy + const covers = v.sx - v.it.w / 2 < kas.r && v.sx + v.it.w / 2 > kas.l && y - v.it.h < kas.btm && y > kas.t + v.it.el.classList.toggle('is-dim', covers) + v.it.el.style.transform = `translate3d(${v.sx.toFixed(1)}px, ${y.toFixed(1)}px, 0) translate(-50%, -100%)` + } + } + + /** Short-lived text that rises and fades at a world position (e.g. "+1"). */ + floater(x: number, y: number, text: string, kind: 'gain' | 'loss', camera: THREE.Camera, width: number, height: number): void { + const p = this.toScreen(x, y, camera, width, height) + const f = el(`asg-floater asg-floater--${kind}`, text) + f.style.left = `${p.sx}px` + f.style.top = `${p.sy}px` + this.root.appendChild(f) + setTimeout(() => f.remove(), 950) + } + + dispose(): void { + this.root.remove() + this.coins = [] + this.goals = [] + } +} diff --git a/hugo-apps/src/app-space-game/label-layout.ts b/hugo-apps/src/app-space-game/label-layout.ts deleted file mode 100644 index f65327559..000000000 --- a/hugo-apps/src/app-space-game/label-layout.ts +++ /dev/null @@ -1,130 +0,0 @@ -// Label layout and sizing logic for floating text sprites. -// Pure module: string/number math only, NO canvas/DOM access. -// The renderer owns the real THREE.CanvasTexture; it reads these sizes and draws the lines. - -const FONT_SIZE = 40 -const CHAR_WIDTH_EM = 0.55 // average character width at this font size, in em units -const PADDING_PX = 16 // padding on left/right of the text -const WORLD_W_CAP = 4.2 // just under the 4.5 coin pitch, prevents label overlap - -export interface FitLabelResult { - lines: string[] // 1 or 2 lines - canvasW: number // px, sized to the longest line at the fixed font + padding - canvasH: number // px, sized to the line count - worldW: number // three.js sprite world width, CAPPED so it never exceeds coin pitch + padding - worldH: number // world height, proportional to canvasH -} - -/** - * Wrap text into at most 2 lines on word boundaries. - * If still longer than maxChars total, ellipsize with … - * Size canvasW/canvasH from longest line length and line count at the fixed font. - * worldW is capped at WORLD_W_CAP so neighbors never overlap horizontally; - * worldH is scaled proportionally from canvas aspect. - * Empty string returns a valid non-zero result, never divides by zero. - */ -export function fitLabel(text: string, opts?: { maxChars?: number }): FitLabelResult { - const maxChars = opts?.maxChars ?? 28 - - // Handle empty string - if (!text || text.trim().length === 0) { - return { - lines: [''], - canvasW: PADDING_PX * 2 + 10, // minimal non-zero width - canvasH: 60, // one line height - worldW: 1, - worldH: 1, - } - } - - // Try single line first - if (text.length <= maxChars) { - const lineWidth = text.length * FONT_SIZE * CHAR_WIDTH_EM - const canvasW = Math.ceil(lineWidth + PADDING_PX * 2) - const canvasH = 60 // single line - const aspectRatio = canvasW / canvasH - const worldW = Math.min(aspectRatio * 1, WORLD_W_CAP) - - return { - lines: [text], - canvasW, - canvasH, - worldW, - worldH: 1, - } - } - - // Need to wrap into 2 lines - const words = text.split(/\s+/) - let line1 = '' - let line2 = '' - let currentLine = line1 - let isFirstLine = true - - for (const word of words) { - const testLine = currentLine ? currentLine + ' ' + word : word - - // If adding this word exceeds maxChars for a single line, try moving to line 2 - if (testLine.length > maxChars && currentLine) { - if (isFirstLine) { - line1 = currentLine - currentLine = word - isFirstLine = false - } else { - currentLine += ' ' + word - } - } else { - currentLine += (currentLine ? ' ' : '') + word - } - } - - if (isFirstLine) { - line1 = currentLine - } else { - line2 = currentLine - } - - // Check if we still exceed total maxChars; ellipsize if needed - const totalLen = line1.length + line2.length - if (totalLen > maxChars) { - // Ellipsize the second line - const availableInLine2 = maxChars - line1.length - 1 // -1 for ellipsis - if (availableInLine2 > 0) { - line2 = line2.substring(0, availableInLine2) + '…' - } else { - line1 = line1.substring(0, maxChars - 1) + '…' - line2 = '' - } - } - - const lines = line2 ? [line1, line2] : [line1] - - // Size canvas from longest line - const longestLine = lines.reduce((a, b) => a.length > b.length ? a : b, '') - const lineWidth = longestLine.length * FONT_SIZE * CHAR_WIDTH_EM - const canvasW = Math.ceil(lineWidth + PADDING_PX * 2) - const canvasH = lines.length * 30 + PADDING_PX // ~30px per line + padding - - // Scale world size, capped at WORLD_W_CAP - const aspectRatio = canvasW / canvasH - const worldW = Math.min(aspectRatio * 1, WORLD_W_CAP) - const worldH = (canvasH / canvasW) * worldW - - return { - lines, - canvasW, - canvasH, - worldW, - worldH, - } -} - -/** - * Return Y coordinate for coin labels, staggered by coin index. - * Even indices: baseY + 0.4 - * Odd indices: baseY - 0.4 - * This gives vertical separation of 0.8 between consecutive coins. - */ -export function coinLabelY(baseY: number, coinIndex: number): number { - return coinIndex % 2 === 0 ? baseY + 0.4 : baseY - 0.4 -} diff --git a/hugo-apps/src/app-space-game/labels.ts b/hugo-apps/src/app-space-game/labels.ts new file mode 100644 index 000000000..6d2577679 --- /dev/null +++ b/hugo-apps/src/app-space-game/labels.ts @@ -0,0 +1,71 @@ +// Pure label helpers: which coin titles are shown in full, and vertical stacking so +// visible labels never overlap. No DOM β€” the LabelLayer applies the results. + +export type LabelMode = 'full' | 'badge' | 'hidden' + +export const FULL_RADIUS = 8 // world units around Kasimir where titles are shown in full +export const MAX_FULL = 2 // at most this many full coin titles at once + +/** + * Coins within `viewHalfW` of the camera are visible. The `maxFull` coins nearest to + * Kasimir (and inside FULL_RADIUS) show their full title; the rest show a compact badge. + */ +export function labelModes( + coinXs: number[], + kasX: number, + camX: number, + viewHalfW: number, + opts: { fullRadius?: number; maxFull?: number } = {}, +): LabelMode[] { + const fullRadius = opts.fullRadius ?? FULL_RADIUS + const maxFull = opts.maxFull ?? MAX_FULL + const modes: LabelMode[] = coinXs.map(x => (Math.abs(x - camX) <= viewHalfW ? 'badge' : 'hidden')) + const near = coinXs + .map((x, i) => ({ i, d: Math.abs(x - kasX) })) + .filter(c => c.d <= fullRadius && modes[c.i] !== 'hidden') + .sort((a, b) => a.d - b.d) + .slice(0, maxFull) + for (const c of near) modes[c.i] = 'full' + return modes +} + +export interface LabelRect { x: number; y: number; w: number; h: number } // x = centre, y = bottom (screen px) + +/** + * Returns upward offsets (px) so no two rects overlap. Rects are placed in array order + * (callers pass highest priority first); each is lifted just above any earlier rect it + * would collide with. + */ +export function stackRects(rects: LabelRect[], gap = 6): number[] { + const offsets = new Array(rects.length).fill(0) + const placed: Array<{ l: number; r: number; top: number; bottom: number }> = [] + rects.forEach((r, i) => { + const l = r.x - r.w / 2 + const rr = r.x + r.w / 2 + let bottom = r.y + let moved = true + while (moved) { + moved = false + for (const p of placed) { + const overlapX = l < p.r + gap && rr > p.l - gap + const overlapY = bottom > p.top - gap && bottom - r.h < p.bottom + gap + if (overlapX && overlapY) { + bottom = p.top - gap + moved = true + } + } + } + offsets[i] = r.y - bottom + placed.push({ l, r: rr, top: bottom - r.h, bottom }) + }) + return offsets +} + +/** Trim very long titles at a word boundary; CSS line-clamp handles the rest. */ +export function tidyTitle(title: string, max = 90): string { + const t = (title ?? '').replace(/\s+/g, ' ').trim() + if (t.length <= max) return t + const cut = t.slice(0, max) + const sp = cut.lastIndexOf(' ') + return (sp > max * 0.6 ? cut.slice(0, sp) : cut).replace(/[\s,;:–-]+$/, '') + '…' +} diff --git a/hugo-apps/src/app-space-game/layout.ts b/hugo-apps/src/app-space-game/layout.ts new file mode 100644 index 000000000..c5a5c2f94 --- /dev/null +++ b/hugo-apps/src/app-space-game/layout.ts @@ -0,0 +1,143 @@ +// Pure world layout: converts the LevelModel into world-space positions shared by +// physics, autopilot, labels and the renderer. No Three.js, no DOM. + +import type { LevelModel, Goal } from './types' +import { COIN_SPACING } from './obstacles' + +export const ZONE_LEAD = 6 // ground before a zone's first coin (zone sign lives here) +export const GOAL_TAIL = 6 // ground after a zone's goal before the next zone starts +export const COIN_Y = 1.3 // coin centre height β€” collected by walking past +export const LEVEL_PAD = 4 // solid ground before the first and after the last zone +export const GOAL_REACH = 1.8 // |kasimir.x βˆ’ goal.x| within which the goal can be opened + +export interface Box { x0: number; x1: number; top: number } +export interface Span { x0: number; x1: number } + +export interface WorldHazards { + crates: Box[] + spikes: Box[] + pits: Span[] + minX: number + maxX: number +} + +export interface WorldCoin { + x: number + y: number + title: string + done: boolean + available: boolean + zoneIndex: number + index: number // 0-based within the zone +} + +export interface WorldGoal extends Goal { + x: number + zoneId: string + zoneTitle: string + zoneIndex: number + locked: boolean +} + +export interface WorldZone { + id: string + title: string + colorIndex: number + locked: boolean + cleared: boolean + x0: number + x1: number + coinCount: number + goal: WorldGoal +} + +export interface WorldLayout { + zones: WorldZone[] + coins: WorldCoin[] + gates: { x: number; zoneIndex: number }[] + hazards: WorldHazards + spawnX: number + length: number +} + +export function buildWorldLayout(model: LevelModel): WorldLayout { + const zones: WorldZone[] = [] + const coins: WorldCoin[] = [] + const gates: WorldLayout['gates'] = [] + const crates: Box[] = [] + const spikes: Box[] = [] + const pits: Span[] = [] + let cursor = LEVEL_PAD + + model.zones.forEach((zone, zi) => { + const x0 = cursor + const firstCoin = x0 + ZONE_LEAD + zone.coins.forEach((coin, k) => { + coins.push({ + x: firstCoin + k * COIN_SPACING, + y: COIN_Y, + title: coin.title, + done: coin.done, + available: coin.available, + zoneIndex: zi, + index: k, + }) + }) + for (const g of zone.gates) { + gates.push({ x: firstCoin + (g.afterCoinIndex + 0.5) * COIN_SPACING, zoneIndex: zi }) + } + for (const o of zone.obstacles) { + const cx = firstCoin + o.x * COIN_SPACING + const box = { x0: cx - o.w / 2, x1: cx + o.w / 2, top: o.h } + if (o.kind === 'spike') spikes.push(box) + else crates.push(box) + } + for (const p of zone.pits) { + const cx = firstCoin + p.x * COIN_SPACING + pits.push({ x0: cx - p.w / 2, x1: cx + p.w / 2 }) + } + const lastCoinX = zone.coins.length ? firstCoin + (zone.coins.length - 1) * COIN_SPACING : x0 + 2 + const goalX = lastCoinX + COIN_SPACING + const x1 = goalX + GOAL_TAIL + zones.push({ + id: zone.id, + title: zone.title, + colorIndex: zone.colorIndex, + locked: zone.locked, + cleared: zone.cleared, + x0, + x1, + coinCount: zone.coins.length, + goal: { ...zone.goal, x: goalX, zoneId: zone.id, zoneTitle: zone.title, zoneIndex: zi, locked: zone.locked }, + }) + cursor = x1 + }) + + const length = cursor + LEVEL_PAD + return { + zones, + coins, + gates, + hazards: { crates, spikes, pits, minX: 0, maxX: length }, + spawnX: 2, + length, + } +} + +/** Index of the zone containing world x (clamped to first/last). */ +export function zoneIndexAt(layout: WorldLayout, x: number): number { + const zs = layout.zones + if (!zs.length) return -1 + for (let i = 0; i < zs.length; i++) { + if (x < zs[i].x1) return i + } + return zs.length - 1 +} + +/** The goal Kasimir is standing at, if any. */ +export function goalAt(layout: WorldLayout, x: number): WorldGoal | null { + for (const z of layout.zones) { + if (Math.abs(x - z.goal.x) <= GOAL_REACH) return z.goal + } + return null +} diff --git a/hugo-apps/src/app-space-game/level-model.ts b/hugo-apps/src/app-space-game/level-model.ts index 771806f4b..4edaeb4ab 100644 --- a/hugo-apps/src/app-space-game/level-model.ts +++ b/hugo-apps/src/app-space-game/level-model.ts @@ -67,32 +67,27 @@ function buildCoinsAndGates( ): { coins: Coin[]; gates: Gate[] } { const coins: Coin[] = [] const gates: Gate[] = [] + // Coins are tutorials only; a PRIZE is the zone's goal (the chest), not a ring to collect. + // Track each item's position in the coin list so checkpoint gates stay aligned. + const coinIndexOfItem: number[] = [] - items.forEach((item, idx) => { + items.forEach(item => { if (item.type === 'TUTORIAL') { - const coin: Coin = { + coins.push({ title: item.title, done: isAuthenticated && item.status === 'COMPLETED', available: !isLocked, - } - coins.push(coin) - } else if (item.type === 'PRIZE') { - // PRIZE items are represented as coins - const coin: Coin = { - title: item.title, - done: isAuthenticated && item.status === 'COMPLETED', - available: !isLocked, - } - coins.push(coin) + }) } + coinIndexOfItem.push(coins.length - 1) }) // Process checkpoints: if they have prizeId, they become goals (handled elsewhere) // Otherwise they become gates - checkpointIndices.forEach((checkpointItem, coinIndex) => { + checkpointIndices.forEach((checkpointItem, itemIndex) => { if (!checkpointItem.prizeId) { gates.push({ - afterCoinIndex: coinIndex, + afterCoinIndex: itemIndex >= 0 ? coinIndexOfItem[itemIndex] : -1, }) } }) @@ -172,7 +167,7 @@ export function buildLevelModel(data: AppSpaceData): LevelModel { // Build goal const goal = findGoal(items, checkpointIndices, track.id) - const { obstacles, pits } = placeHazards(coins.length, trackIndex) + const { obstacles, pits } = placeHazards(coins.length, trackIndex, gates.map(g => g.afterCoinIndex)) const zone: Zone = { id: track.id, diff --git a/hugo-apps/src/app-space-game/obstacles.ts b/hugo-apps/src/app-space-game/obstacles.ts index 6f17fd735..aee6d18f8 100644 --- a/hugo-apps/src/app-space-game/obstacles.ts +++ b/hugo-apps/src/app-space-game/obstacles.ts @@ -1,90 +1,79 @@ -import type { Obstacle, Pit } from './types' -import { JUMP_ARC } from './constants' - -export const COIN_SPACING = 4.5 // 1 coin-index unit = 4.5 world units -export const MIN_LANDING = 1.5 // world units β€” minimum clear landing strip between hazards - -/** Mulberry32 seeded PRNG. Returns a function that produces values in [0, 1). */ -function mulberry32(seed: number): () => number { - let s = seed >>> 0 - return function () { - s |= 0 - s = (s + 0x6d2b79f5) | 0 - let t = Math.imul(s ^ (s >>> 15), 1 | s) - t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t - return ((t ^ (t >>> 14)) >>> 0) / 4294967296 - } -} - -/** - * Place obstacles and pits deterministically for a given zone. - * - coinCount 0 or 1 β†’ empty arrays - * - At most floor(coinCount / 3) hazards total - * - Every pit w <= JUMP_ARC.maxGap (w is in world units) - * - Every obstacle h <= JUMP_ARC.maxHeight, fixed w = 0.6 (world units) - * - Edge-to-edge gap between every adjacent pair of hazards >= MIN_LANDING - * - x values are in coin-index units (between integer indices, e.g. 1.5, 3.5) - */ -export function placeHazards(coinCount: number, zoneSeed: number): { obstacles: Obstacle[]; pits: Pit[] } { - if (coinCount <= 1) { - return { obstacles: [], pits: [] } - } - - const maxHazards = Math.floor(coinCount / 3) - if (maxHazards === 0) { - return { obstacles: [], pits: [] } - } - - const rand = mulberry32(zoneSeed) - - const obstacles: Obstacle[] = [] - const pits: Pit[] = [] - - // Candidate x positions: between each pair of coin slots (i + 0.5 for i = 0..coinCount-2) - const candidates: number[] = [] - for (let i = 0; i < coinCount - 1; i++) { - candidates.push(i + 0.5) - } - // Fisher-Yates shuffle using the seeded PRNG - for (let i = candidates.length - 1; i > 0; i--) { - const j = Math.floor(rand() * (i + 1)) - ;[candidates[i], candidates[j]] = [candidates[j], candidates[i]] - } - - // Track placed hazard [leftEdge, rightEdge] in world units for edge-to-edge spacing - const placedEdges: Array<{ left: number; right: number }> = [] - - for (const cx of candidates) { - if (obstacles.length + pits.length >= maxHazards) break - - const worldX = cx * COIN_SPACING - - // Randomly choose pit or obstacle (50/50) β€” decide width BEFORE spacing check - const isPit = rand() < 0.5 - const candidateW = isPit - ? Math.min((0.3 + rand() * 0.7) * JUMP_ARC.maxGap, JUMP_ARC.maxGap) - : 0.6 - - const candLeft = worldX - candidateW / 2 - const candRight = worldX + candidateW / 2 - - // Edge-to-edge check: reject unless gap to every already-placed hazard >= MIN_LANDING - const tooClose = placedEdges.some(e => { - const gapRight = candLeft - e.right // gap when candidate is to the right - const gapLeft = e.left - candRight // gap when candidate is to the left - const edgeGap = Math.max(gapRight, gapLeft) - return edgeGap < MIN_LANDING - }) - if (tooClose) continue - - if (isPit) { - pits.push({ x: cx, w: candidateW }) - } else { - const h = Math.min((0.5 + rand() * 0.5) * JUMP_ARC.maxHeight, JUMP_ARC.maxHeight) - obstacles.push({ x: cx, w: 0.6, h }) - } - placedEdges.push({ left: candLeft, right: candRight }) - } - - return { obstacles, pits } -} +import type { Obstacle, Pit } from './types' +import { HAZARD_LIMITS } from './constants' + +export const COIN_SPACING = 6 // 1 coin-index unit = 6 world units +export const MIN_LANDING = 2.5 // world units β€” minimum clear ground between hazards + +/** Mulberry32 seeded PRNG. Returns a function that produces values in [0, 1). */ +function mulberry32(seed: number): () => number { + let s = seed >>> 0 + return function () { + s |= 0 + s = (s + 0x6d2b79f5) | 0 + let t = Math.imul(s ^ (s >>> 15), 1 | s) + t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t + return ((t ^ (t >>> 14)) >>> 0) / 4294967296 + } +} + +/** + * Place hazards deterministically for one zone. + * - Hazards sit midway between coins (x = i + 0.5, coin-index units) so they never overlap a coin. + * - The first gap of every zone is kept clear (breathing room after the zone banner), as are + * gaps occupied by checkpoint gates (`blockedGaps` = coin indices a gate follows). + * - At most ceil(coinCount / 2.5) hazards; every hazard is comfortably clearable (see HAZARD_LIMITS). + * - Mix: pits, crates and spikes, roughly 40/35/25. + */ +export function placeHazards( + coinCount: number, + zoneSeed: number, + blockedGaps: number[] = [], +): { obstacles: Obstacle[]; pits: Pit[] } { + if (coinCount <= 2) return { obstacles: [], pits: [] } + + const rand = mulberry32(zoneSeed * 7919 + 17) + const maxHazards = Math.ceil(coinCount / 2.5) + const blocked = new Set(blockedGaps) + + const candidates: number[] = [] + for (let i = 1; i < coinCount - 1; i++) { + if (!blocked.has(i)) candidates.push(i + 0.5) + } + for (let i = candidates.length - 1; i > 0; i--) { + const j = Math.floor(rand() * (i + 1)) + ;[candidates[i], candidates[j]] = [candidates[j], candidates[i]] + } + + const obstacles: Obstacle[] = [] + const pits: Pit[] = [] + const placed: Array<{ left: number; right: number }> = [] + const L = HAZARD_LIMITS + + for (const cx of candidates) { + if (obstacles.length + pits.length >= maxHazards) break + const r = rand() + const kind: 'pit' | 'crate' | 'spike' = r < 0.4 ? 'pit' : r < 0.75 ? 'crate' : 'spike' + const w = + kind === 'pit' ? L.pitMinW + rand() * (L.pitMaxW - L.pitMinW) + : kind === 'crate' ? L.crateW + : L.spikeMinW + rand() * (L.spikeMaxW - L.spikeMinW) + const worldX = cx * COIN_SPACING + const left = worldX - w / 2 + const right = worldX + w / 2 + if (placed.some(e => Math.max(left - e.right, e.left - right) < MIN_LANDING)) continue + + if (kind === 'pit') { + pits.push({ x: cx, w }) + } else if (kind === 'crate') { + const h = L.crateMinH + rand() * (L.crateMaxH - L.crateMinH) + obstacles.push({ x: cx, w, h, kind: 'crate' }) + } else { + obstacles.push({ x: cx, w, h: L.spikeH, kind: 'spike' }) + } + placed.push({ left, right }) + } + + obstacles.sort((a, b) => a.x - b.x) + pits.sort((a, b) => a.x - b.x) + return { obstacles, pits } +} diff --git a/hugo-apps/src/app-space-game/physics.ts b/hugo-apps/src/app-space-game/physics.ts index eb07ab511..ad116d690 100644 --- a/hugo-apps/src/app-space-game/physics.ts +++ b/hugo-apps/src/app-space-game/physics.ts @@ -1,155 +1,196 @@ -import { SPEED, GRAV, JUMP } from './constants' - -export const GROUND_Y = 1.8 - -export interface PhysicsState { - x: number // world units - y: number // world units; ground = 1.8 (matches renderer kasY baseline) - vy: number - onGround: boolean - facing: 1 | -1 // 1 = right, -1 = left - lastSafeX: number // last x while grounded and NOT over a pit -} - -// Hazards here are in WORLD units (renderer converts from coin-index before calling). -export interface WorldHazards { - obstacles: { x: number; w: number; h: number }[] - pits: { x: number; w: number }[] -} - -/** Returns true if Kasimir's x is inside a pit's horizontal span */ -function overPit(x: number, pits: WorldHazards['pits']): boolean { - for (const p of pits) { - if (x >= p.x - p.w / 2 && x <= p.x + p.w / 2) return true - } - return false -} - -/** Returns true if Kasimir (point x, feet y) is inside an obstacle's body */ -function insideObstacle( - x: number, - y: number, - obs: WorldHazards['obstacles'][number], -): boolean { - const left = obs.x - obs.w / 2 - const right = obs.x + obs.w / 2 - const top = GROUND_Y + obs.h - return x >= left && x <= right && y < top -} - -export function stepPhysics( - s: PhysicsState, - input: { moveDir: -1 | 0 | 1; jump: boolean }, - hazards: WorldHazards, - dt: number, -): { next: PhysicsState; event: 'none' | 'hit' | 'fell' } { - let { x, y, vy, onGround, facing, lastSafeX } = s - let event: 'none' | 'hit' | 'fell' = 'none' - - // --- Jump --- - if (input.jump && onGround) { - vy = JUMP - onGround = false - } - - // --- Gravity --- - vy += GRAV * dt - y += vy * dt - - // --- Horizontal movement (tentative) --- - const dx = SPEED * input.moveDir * dt - let newX = x + dx - - if (input.moveDir !== 0) { - facing = input.moveDir > 0 ? 1 : -1 - } - - // --- Obstacle collision (AABB, x-axis) --- - // Check each obstacle; block if we would enter it while below its top. - // Use the original y (pre-gravity) to determine if Kasimir was above the obstacle. - // Kasimir is treated as a point-ish body; x is clamped to the obstacle's near edge. - let hitObstacle = false - for (const obs of hazards.obstacles) { - const obsLeft = obs.x - obs.w / 2 - const obsRight = obs.x + obs.w / 2 - const obsTop = GROUND_Y + obs.h - - // Vertical check: if Kasimir started above the obstacle top, he may pass - if (s.y >= obsTop) continue - - // Horizontal overlap: moving right crosses into the obstacle - if (input.moveDir > 0 && x <= obsLeft && newX > obsLeft) { - newX = obsLeft - hitObstacle = true - } - // Moving left crosses into the obstacle - else if (input.moveDir < 0 && x >= obsRight && newX < obsRight) { - newX = obsRight - hitObstacle = true - } - // Already inside (or standing still inside): push to nearest edge - else if (newX > obsLeft && newX < obsRight) { - const distLeft = Math.abs(newX - obsLeft) - const distRight = Math.abs(newX - obsRight) - newX = distLeft <= distRight ? obsLeft : obsRight - hitObstacle = true - } - } - - if (hitObstacle && onGround) { - event = 'hit' - } - - x = newX - - // --- Ground clamping / pit detection --- - const currentlyOverPit = overPit(x, hazards.pits) - - if (y <= GROUND_Y) { - if (currentlyOverPit) { - // Let Kasimir fall into the pit β€” do not clamp y - // Set event to 'fell' on first frame of falling into pit - if (!hitObstacle) { - event = 'fell' - } - } else { - // Solid ground β€” clamp - y = GROUND_Y - vy = 0 - onGround = true - } - } - - // --- lastSafeX update --- - // Update while grounded and NOT over a pit and NOT inside any obstacle - if (onGround && !currentlyOverPit && !hitObstacle) { - let inObs = false - for (const obs of hazards.obstacles) { - if (insideObstacle(x, y, obs)) { inObs = true; break } - } - if (!inObs) { - lastSafeX = x - } - } - - const next: PhysicsState = { - x, - y, - vy, - onGround, - facing: facing as 1 | -1, - lastSafeX, - } - - return { next, event } -} - -export function respawn(s: PhysicsState): PhysicsState { - return { - ...s, - x: s.lastSafeX, - y: GROUND_Y, - vy: 0, - onGround: true, - } -} +// Pure platformer physics for Kasimir. y is the FEET position; ground top is y = 0. +// Crates are solid (block from the side, standable on top); spikes cost rings; +// pits are gaps in the ground you fall through. + +import { + SPEED, ACCEL, DECEL, TURN_ACCEL, AIR_ACCEL, GRAV, JUMP, JUMP_CUT_GRAV, FALL_GRAV, MAX_FALL, + COYOTE_S, JUMP_BUFFER_S, INVULN_S, FALL_LIMIT_Y, BODY_HW, +} from './constants' +import type { WorldHazards } from './layout' + +export type { WorldHazards } from './layout' + +export const GROUND_Y = 0 +const EPS = 0.04 +const PIT_FORGIVE = 0.15 // feet may hang this far over a pit edge without falling +const SAFE_MARGIN = 1.2 // respawn points keep this much ground from any hazard + +export interface PhysicsState { + x: number + y: number + vx: number + vy: number + onGround: boolean + facing: 1 | -1 + coyote: number + jumpBuffer: number + jumpHeld: boolean + invuln: number + lastSafeX: number +} + +export interface PhysicsInput { + moveDir: -1 | 0 | 1 + jump: boolean // button currently held +} + +export type PhysicsEvent = 'none' | 'jump' | 'land' | 'hit' | 'fell' + +export function initialState(x: number): PhysicsState { + return { + x, y: GROUND_Y, vx: 0, vy: 0, onGround: true, facing: 1, + coyote: 0, jumpBuffer: 0, jumpHeld: false, invuln: 0, lastSafeX: x, + } +} + +function approach(v: number, target: number, step: number): number { + return v < target ? Math.min(v + step, target) : Math.max(v - step, target) +} + +export function overPit(x: number, pits: WorldHazards['pits']): boolean { + for (const p of pits) { + if (x > p.x0 + PIT_FORGIVE && x < p.x1 - PIT_FORGIVE) return true + } + return false +} + +function isSafe(x: number, h: WorldHazards): boolean { + const near = (x0: number, x1: number) => x > x0 - SAFE_MARGIN && x < x1 + SAFE_MARGIN + for (const p of h.pits) if (near(p.x0, p.x1)) return false + for (const c of h.crates) if (near(c.x0, c.x1)) return false + for (const s of h.spikes) if (near(s.x0, s.x1)) return false + return true +} + +export function stepPhysics( + s: PhysicsState, + input: PhysicsInput, + h: WorldHazards, + dt: number, +): { next: PhysicsState; event: PhysicsEvent } { + let { vx, vy, onGround, facing, coyote, jumpBuffer, invuln, lastSafeX } = s + const { x, y } = s + let event: PhysicsEvent = 'none' + const pressed = input.jump && !s.jumpHeld + + jumpBuffer = pressed ? JUMP_BUFFER_S : Math.max(0, jumpBuffer - dt) + coyote = onGround ? COYOTE_S : Math.max(0, coyote - dt) + invuln = Math.max(0, invuln - dt) + + // --- Horizontal velocity --- + const target = input.moveDir * SPEED + let accel: number + if (!onGround) accel = AIR_ACCEL + else if (input.moveDir === 0) accel = DECEL + else if (vx !== 0 && Math.sign(vx) !== input.moveDir) accel = TURN_ACCEL + else accel = ACCEL + vx = approach(vx, target, accel * dt) + if (input.moveDir !== 0) facing = input.moveDir + + // --- Jump (buffered + coyote) --- + if (jumpBuffer > 0 && (onGround || coyote > 0)) { + vy = JUMP + onGround = false + coyote = 0 + jumpBuffer = 0 + event = 'jump' + } + + // --- Gravity (variable jump height: releasing early cuts the rise) --- + let g = GRAV + if (vy > 0 && !input.jump) g *= JUMP_CUT_GRAV + else if (vy < 0) g *= FALL_GRAV + vy = Math.max(MAX_FALL, vy + g * dt) + + // --- X move + crate side collision --- + let nx = x + vx * dt + for (const c of h.crates) { + if (y >= c.top - EPS) continue // above the crate + if (nx + BODY_HW > c.x0 && nx - BODY_HW < c.x1) { + nx = x <= (c.x0 + c.x1) / 2 ? c.x0 - BODY_HW : c.x1 + BODY_HW + vx = 0 + } + } + // Inside a pit: its walls hold Kasimir in the hole. + if (y < GROUND_Y - EPS) { + for (const p of h.pits) { + if (x >= p.x0 && x <= p.x1) { + const lo = Math.min(p.x0 + BODY_HW, (p.x0 + p.x1) / 2) + const hi = Math.max(p.x1 - BODY_HW, (p.x0 + p.x1) / 2) + if (nx < lo) { nx = lo; vx = 0 } + if (nx > hi) { nx = hi; vx = 0 } + break + } + } + } + // Level walls + if (nx < h.minX + BODY_HW) { nx = h.minX + BODY_HW; vx = 0 } + if (nx > h.maxX - BODY_HW) { nx = h.maxX - BODY_HW; vx = 0 } + + // --- Y move + landing --- + let ny = y + vy * dt + const wasOnGround = s.onGround + onGround = false + let onCrate = false + if (vy <= 0) { + let top = -Infinity + for (const c of h.crates) { + if (nx + BODY_HW > c.x0 + EPS && nx - BODY_HW < c.x1 - EPS && y >= c.top - EPS && ny <= c.top) { + if (c.top > top) { top = c.top; onCrate = true } + } + } + if (y >= GROUND_Y - EPS && ny <= GROUND_Y && !overPit(nx, h.pits) && GROUND_Y > top) { + top = GROUND_Y + onCrate = false + } + if (top > -Infinity) { + ny = top + vy = 0 + onGround = true + if (!wasOnGround && event === 'none') event = 'land' + } + } + + // --- Spikes --- + if (invuln === 0) { + for (const sp of h.spikes) { + if (nx + BODY_HW * 0.7 > sp.x0 && nx - BODY_HW * 0.7 < sp.x1 && ny < sp.top) { + const away = Math.sign(nx - (sp.x0 + sp.x1) / 2) || -facing + vx = away * 5 + vy = 9 + ny = Math.max(ny, sp.top) + onGround = false + invuln = INVULN_S + event = 'hit' + break + } + } + } + + if (ny < FALL_LIMIT_Y) event = 'fell' + + if (onGround && !onCrate && isSafe(nx, h)) lastSafeX = nx + + return { + next: { + x: nx, y: ny, vx, vy, onGround, facing, coyote, jumpBuffer, + jumpHeld: input.jump, invuln, lastSafeX, + }, + event, + } +} + +/** State after a fall: back on the last safe ground, briefly invulnerable. */ +export function respawn(s: PhysicsState): PhysicsState { + return { + ...s, + x: s.lastSafeX, + y: GROUND_Y, + vx: 0, + vy: 0, + onGround: true, + coyote: 0, + jumpBuffer: 0, + invuln: INVULN_S, + } +} diff --git a/hugo-apps/src/app-space-game/renderer.ts b/hugo-apps/src/app-space-game/renderer.ts index 9612576ce..5ad3e7751 100644 --- a/hugo-apps/src/app-space-game/renderer.ts +++ b/hugo-apps/src/app-space-game/renderer.ts @@ -1,918 +1,672 @@ -// Three.js renderer for the app-space-game island. -// Builds a 2.5D attract-loop scene from a LevelModel and runs the game loop. -// Spike reference: .superpowers/sdd/2026-11-03-kasimir-arcade-app-space/spike-reference.html - -import * as THREE from 'three' -import type { LevelModel, Zone, Coin, Gate, Goal } from './types' -import { zoneColor } from './level-model' -import { fitLabel, coinLabelY } from './label-layout' -import { SPEED, GRAV, JUMP } from './constants' -import { selectClip, frameAt } from './anim' -import type { ClipName, AnimContext } from './anim' -import { GROUND_Y, stepPhysics, respawn } from './physics' -import type { PhysicsState, WorldHazards } from './physics' -import { autopilotInput, watchdog } from './autopilot' -import type { ProgressTrack } from './autopilot' -import { reset as ringsReset, collect as ringsCollect, onHit as ringsOnHit } from './rings' -import type { RingState } from './rings' -import { COIN_SPACING } from './obstacles' - -const ZONE_W = 26 -const GY = 0 -const KASIMIR_TEX = '/images/devtoberfest/selfie/thumbnails/Kasimir_Smiles.png' -const KASIMIR_ATLAS = '/images/app-space-game/kasimir-atlas.png' - -// --- helpers --- - -/** Build a canvas-texture sprite for floating text labels. */ -function makeLabel(text: string, color: string = '#ffffff'): THREE.Sprite { - const layout = fitLabel(text) - const cv = document.createElement('canvas') - cv.width = layout.canvasW - cv.height = layout.canvasH - const g = cv.getContext('2d')! - g.font = 'bold 40px system-ui' - g.textAlign = 'center' - g.textBaseline = 'middle' - g.lineWidth = 8 - g.strokeStyle = 'rgba(0,0,0,.75)' - g.fillStyle = color - - // Draw each wrapped line - const lineHeight = 30 - const verticalOffset = (layout.canvasH - layout.lines.length * lineHeight) / 2 + lineHeight / 2 - layout.lines.forEach((line, index) => { - const y = verticalOffset + index * lineHeight - g.strokeText(line, layout.canvasW / 2, y) - g.fillText(line, layout.canvasW / 2, y) - }) - - const tex = new THREE.CanvasTexture(cv) - const mat = new THREE.SpriteMaterial({ map: tex, transparent: true }) - const sprite = new THREE.Sprite(mat) - sprite.scale.set(layout.worldW, layout.worldH, 1) - return sprite -} - -/** Parse a CSS hex color string to a THREE.Color-compatible integer. */ -function hexToInt(hex: string): number { - return parseInt(hex.replace('#', ''), 16) -} - -interface CoinMesh extends THREE.Mesh { - userData: { alive: boolean; title: string } -} - -interface ZoneMeta { - name: string - locked: boolean - cleared: boolean - goalX: number - start: number - coinCount: number - hazards: WorldHazards -} - -// --- scene builder --- - -function buildScene( - scene: THREE.Scene, - model: LevelModel -): { markers: CoinMesh[]; zoneMeta: ZoneMeta[]; totalLen: number; obstacleMeshes: THREE.Mesh[] } { - const markers: CoinMesh[] = [] - const zoneMeta: ZoneMeta[] = [] - const obstacleMeshes: THREE.Mesh[] = [] - let runningX = 5 - - model.zones.forEach((zone: Zone) => { - const accentInt = hexToInt(zoneColor(zone.colorIndex)) - const isLocked = zone.locked - const isCleared = zone.cleared - const x0 = runningX - 3 - - // Ground slab - const ground = new THREE.Mesh( - new THREE.BoxGeometry(ZONE_W, 1, 10), - new THREE.MeshStandardMaterial({ - color: isLocked ? 0x161a28 : 0x10203a, - roughness: 0.9, - }) - ) - ground.position.set(x0 + ZONE_W / 2, GY - 0.5, 0) - ground.receiveShadow = true - scene.add(ground) - - // Accent seam at ground level - const seam = new THREE.Mesh( - new THREE.BoxGeometry(ZONE_W, 0.08, 10.05), - new THREE.MeshBasicMaterial({ color: isLocked ? 0x333848 : accentInt }) - ) - seam.position.set(x0 + ZONE_W / 2, GY + 0.02, 0) - scene.add(seam) - - // Sky backdrop wall - // Derive a darker sky tint from the accent color - const accentColor = new THREE.Color(accentInt) - const skyColor = new THREE.Color( - accentColor.r * 0.12, - accentColor.g * 0.12, - accentColor.b * 0.18 - ) - const wall = new THREE.Mesh( - new THREE.PlaneGeometry(ZONE_W, 24), - new THREE.MeshBasicMaterial({ color: skyColor }) - ) - wall.position.set(x0 + ZONE_W / 2, 9, -10) - scene.add(wall) - - // --- Parallax back-layers (A) --- - // Three depth layers of dark silhouette boxes behind the play plane. - // Static meshes β€” perspective parallax comes for free as the camera pans. - // No new lights. castShadow=false on all back-layer geometry. - const backLayerDefs = [ - { z: -22, tint: 0x0b1430, h: 16, spacing: 10, offset: 0 }, // far dunes - { z: -14, tint: 0x121e40, h: 11, spacing: 8, offset: 3 }, // mid ridges - { z: -8, tint: 0x192850, h: 7, spacing: 6, offset: 1.5 }, // near dressing (behind pillars at -6) - ] - for (const layer of backLayerDefs) { - // Stagger widths by segment index within zone so silhouettes vary - const segCount = Math.ceil(ZONE_W / layer.spacing) + 2 - for (let s = 0; s < segCount; s++) { - const segW = 5 + ((s + zone.colorIndex) % 3) * 2 - const segX = x0 + s * layer.spacing + layer.offset - layer.spacing - const bg = new THREE.Mesh( - new THREE.BoxGeometry(segW, layer.h, 1), - new THREE.MeshStandardMaterial({ - color: layer.tint, - roughness: 1, - emissive: layer.tint, - emissiveIntensity: 0.06, - }) - ) - bg.position.set(segX, layer.h / 2 - 1, layer.z) - bg.castShadow = false - scene.add(bg) - } - } - - // Parallax pillars (foreground accent columns, z=-6) - for (let p = 0; p < 3; p++) { - const pil = new THREE.Mesh( - new THREE.BoxGeometry(1.2, 11, 1.2), - new THREE.MeshStandardMaterial({ - color: accentInt, - roughness: 0.5, - emissive: accentInt, - emissiveIntensity: isLocked ? 0.03 : 0.18, - transparent: isLocked, - opacity: isLocked ? 0.4 : 1, - }) - ) - pil.position.set(x0 + 5 + p * 8, 5.5, -6) - pil.castShadow = !isLocked - scene.add(pil) - } - - // Zone name banner - const banner = makeLabel(zone.title.toUpperCase(), '#ffffff') - banner.scale.set(10, 1.8, 1) - banner.position.set(x0 + ZONE_W / 2, 11, -5) - scene.add(banner) - - const zStart = runningX - - // Coins - one per zone.coins entry - zone.coins.forEach((coin: Coin, k: number) => { - const isAvailable = coin.available - const isDone = coin.done - const coinMesh = new THREE.Mesh( - new THREE.TorusGeometry(0.5, 0.18, 10, 24), - new THREE.MeshStandardMaterial({ - color: isDone ? 0x49b35a : isAvailable ? 0xf0b42a : 0x555555, - metalness: 0.9, - roughness: 0.25, - emissive: isDone ? 0x1f5e2a : isAvailable ? 0x7a5200 : 0x111111, - emissiveIntensity: 0.5, - transparent: !isAvailable, - opacity: !isAvailable ? 0.45 : 1, - }) - ) as CoinMesh - coinMesh.position.set(runningX, 2.1 + Math.sin(k) * 0.6, 0) - coinMesh.castShadow = !isLocked - coinMesh.userData = { alive: !isDone && isAvailable, title: coin.title } - scene.add(coinMesh) - - const coinColor = isDone ? '#9fe7a8' : isAvailable ? '#ffe9a3' : '#888888' - const tag = makeLabel(coin.title, coinColor) - const tagY = coinLabelY(3.5, k) - tag.position.set(runningX, tagY, 0) - scene.add(tag) - - if (!isLocked && !isDone) markers.push(coinMesh) - runningX += 4.5 - }) - - // Checkpoint gates - one per zone.gates entry - zone.gates.forEach((gate: Gate) => { - // Position gate after the coin at gate.afterCoinIndex - // Each coin is 4.5 units wide; offset from zStart - const gateX = zStart + (gate.afterCoinIndex + 1) * 4.5 + 0.5 - const gateMat = new THREE.MeshStandardMaterial({ - color: 0xffb02e, - emissive: 0xffb02e, - emissiveIntensity: 0.4, - metalness: 0.4, - }) - const lPost = new THREE.Mesh(new THREE.BoxGeometry(0.4, 5, 0.4), gateMat) - lPost.position.set(gateX, 2.5, 0) - lPost.castShadow = true - scene.add(lPost) - const rPost = lPost.clone() - rPost.position.set(gateX + 2.4, 2.5, 0) - scene.add(rPost) - const top = new THREE.Mesh(new THREE.BoxGeometry(3.2, 0.5, 0.5), gateMat) - top.position.set(gateX + 1.2, 4.8, 0) - scene.add(top) - const ct = makeLabel('CHECKPOINT', '#ffd27a') - ct.position.set(gateX + 1.2, 5.7, 0) - scene.add(ct) - runningX = Math.max(runningX, gateX + 5) - }) - - // Goal object at zone end - const goalX = runningX + 1.5 - const goal: Goal = zone.goal - const isTrack = goal.kind === 'track' - - if (isTrack) { - // Taller poled pennant for track-select goal (B: goal glow-up) - // Pole: CylinderGeometry, ~7 units tall β€” center at y=3.5 so base sits at GY=0 - const pole = new THREE.Mesh( - new THREE.CylinderGeometry(0.1, 0.12, 7, 8), - new THREE.MeshStandardMaterial({ - color: isLocked ? 0x444444 : 0x9fb0cc, - metalness: 0.6, - roughness: 0.3, - }) - ) - pole.position.set(goalX, 3.5, 0) - pole.castShadow = true - scene.add(pole) - // Pennant: larger emissive plane offset to fly off the pole top - const pennant = new THREE.Mesh( - new THREE.PlaneGeometry(2.6, 1.5), - new THREE.MeshStandardMaterial({ - color: isLocked ? 0x444444 : accentInt, - emissive: isLocked ? 0x111111 : accentInt, - emissiveIntensity: isLocked ? 0.1 : 0.55, - side: THREE.DoubleSide, - transparent: isLocked, - opacity: isLocked ? 0.45 : 1, - }) - ) - // Position pennant so its left edge aligns with the pole top (y=7) - pennant.position.set(goalX + 1.3, 6.25, 0) - scene.add(pennant) - // Reuse/boost the existing goal glow PointLight (accent color; 0 intensity when locked) - const glow = new THREE.PointLight( - isLocked ? 0x333333 : accentInt, - isLocked ? 0 : 2.5, - 12 - ) - glow.position.set(goalX, 5.5, 1.5) - scene.add(glow) - const pt = makeLabel( - (isLocked ? 'LOCKED ' : '') + goal.label, - isLocked ? '#ffb02e' : '#ffe9a3' - ) - pt.position.set(goalX, 7.6, 0) - scene.add(pt) - } else { - // Open treasure chest for prize / checkpoint goal (B: goal glow-up) - // Warmer emissive material; locked state keeps dim/closed - const chestMat = new THREE.MeshStandardMaterial({ - color: isLocked ? 0x444444 : 0xffb648, - metalness: isLocked ? 0.3 : 0.5, - roughness: 0.4, - emissive: isLocked ? 0 : 0xffa02a, - emissiveIntensity: isLocked ? 0 : 0.5, - }) - const chest = new THREE.Mesh(new THREE.BoxGeometry(2.4, 1.8, 2), chestMat) - chest.position.set(goalX, 1.4, 0) - chest.castShadow = true - scene.add(chest) - // Lid: open when unlocked (rotate back on x-axis, hinge at back edge of chest top) - // Chest top is at y = 1.4 + 0.9 = 2.3; back face at z = -1.0 - // We translate lid to hinge position, then rotate so it swings open. - const lid = new THREE.Mesh(new THREE.BoxGeometry(2.5, 0.7, 2.1), chestMat) - if (isLocked) { - // Closed lid sitting flat on top of the chest - lid.position.set(goalX, 2.5, 0) - } else { - // Open lid: hinge at the back top edge; rotate β‰ˆ-0.9 rad (β‰ˆ52Β°) open - // Offset position to the hinge point (back of chest, lid half-depth back) - lid.position.set(goalX, 2.35, -0.75) - lid.rotation.x = -0.9 - } - lid.castShadow = !isLocked - scene.add(lid) - // Reuse the goal glow PointLight; boost intensity for open chest - const glow = new THREE.PointLight( - isLocked ? 0x333333 : 0xffcf6b, - isLocked ? 0 : 3.2, - 12 - ) - glow.position.set(goalX, 3, 1.5) - scene.add(glow) - const prizeLabel = (isLocked ? '[locked] ' : '') + goal.label - const pt = makeLabel(prizeLabel, isLocked ? '#ffb02e' : '#ffe9a3') - pt.position.set(goalX, 4, 0) - scene.add(pt) - if (isLocked) { - const lk = makeLabel('LOCKED - complete prerequisites', '#ffb02e') - lk.scale.set(7, 1.3, 1) - lk.position.set(goalX, 4.9, 0) - scene.add(lk) - } - } - - // Build WorldHazards for this zone (x: coin-index β†’ world units; w/h already world units) - const worldHazards: WorldHazards = { - obstacles: zone.obstacles.map(o => ({ - x: zStart + o.x * COIN_SPACING, - w: o.w, - h: o.h, - })), - pits: zone.pits.map(p => ({ - x: zStart + p.x * COIN_SPACING, - w: p.w, - })), - } - - // Render obstacle meshes (boxes sitting on the ground) - for (const obs of worldHazards.obstacles) { - const obsMesh = new THREE.Mesh( - new THREE.BoxGeometry(obs.w, obs.h, 1.5), - new THREE.MeshStandardMaterial({ - color: 0xff4444, - roughness: 0.6, - emissive: 0x330000, - emissiveIntensity: 0.4, - }) - ) - obsMesh.position.set(obs.x, GROUND_Y + obs.h / 2, 0.1) - obsMesh.castShadow = true - scene.add(obsMesh) - obstacleMeshes.push(obsMesh) - } - - // Render pit markers (visible hazard indicator at pit edges β€” no ground segment) - for (const pit of worldHazards.pits) { - // Pit marker: a darker strip below the ground to signal the gap - const pitMark = new THREE.Mesh( - new THREE.BoxGeometry(pit.w, 0.3, 1.5), - new THREE.MeshBasicMaterial({ color: 0x110011 }) - ) - pitMark.position.set(pit.x, GROUND_Y - 0.6, 0.1) - scene.add(pitMark) - obstacleMeshes.push(pitMark) - } - - runningX = goalX + 6 - - zoneMeta.push({ - name: zone.title, - locked: isLocked, - cleared: isCleared, - goalX, - start: zStart, - coinCount: zone.coins.length, - hazards: worldHazards, - }) - }) - - return { markers, zoneMeta, totalLen: runningX, obstacleMeshes } -} - -/** Merge all zone WorldHazards into a single flat WorldHazards for physics queries. */ -function mergeHazards(zoneMeta: ZoneMeta[]): WorldHazards { - const obstacles: WorldHazards['obstacles'] = [] - const pits: WorldHazards['pits'] = [] - for (const zm of zoneMeta) { - obstacles.push(...zm.hazards.obstacles) - pits.push(...zm.hazards.pits) +// Three.js renderer + game loop for the Kasimir Arcade island. +// Fixed-timestep physics with render interpolation, a damped look-ahead camera, +// an atlas-animated Kasimir, DOM labels, particle feedback and goal interaction. + +import * as THREE from 'three' +import type { LevelModel, Goal } from './types' +import { zoneColor } from './level-model' +import frameMapJson from './kasimir-frames.json' +import { BODY_HW, BODY_H, FIXED_DT, KASIMIR_H } from './constants' +import { buildWorldLayout, goalAt, zoneIndexAt, LEVEL_PAD } from './layout' +import type { WorldGoal } from './layout' +import { initialState, stepPhysics, respawn, overPit } from './physics' +import type { PhysicsState, PhysicsInput } from './physics' +import { autopilotInput, watchdog } from './autopilot' +import type { ProgressTrack } from './autopilot' +import { selectClip, frameAt, jumpFrame, playbackRate } from './anim' +import type { ClipName, AnimContext, AnimEvent } from './anim' +import { reset as ringsReset, collect as ringsCollect, onHit as ringsOnHit } from './rings' +import type { RingState } from './rings' +import { buildScene } from './scene' +import { Effects } from './fx' +import { LabelLayer } from './label-layer' +import { softDotTexture } from './textures' + +const KASIMIR_FALLBACK_TEX = '/images/devtoberfest/selfie/thumbnails/Kasimir_Smiles.png' +const CAM_BASE_Y = 4.2 +const CAM_LOOK_DROP = 1.0 +const CAM_DIST = 15 +const RESPAWN_S = 0.9 +const ATTRACT_PAUSE_S = 1.8 +const CELEBRATE_S = 1.4 +const WRAP_S = 0.8 + +export type GameEvent = 'hit' | 'pickup' | 'goal' | 'jump' | 'fall' + +export interface ZoneInfo { + index: number + total: number + title: string + color: string + locked: boolean +} + +export interface GoalPrompt { + trackId: string + title: string + label: string + kind: Goal['kind'] + locked: boolean +} + +export interface GameHandle { + start(): void + stop(): void + setPlayerControl(on: boolean): void + onRingCount(cb: (n: number) => void): void + onEvent(cb: (e: GameEvent) => void): void + onZone?(cb: (z: ZoneInfo | null) => void): void + onGoalPrompt?(cb: (g: GoalPrompt | null) => void): void + /** Open the goal Kasimir is standing at (player mode). Returns true if it opened. */ + activateGoal?(): boolean + dispose?(): void +} + +export interface GameOptions { + onPickTrack: (trackId: string) => void + /** Element that hosts the HTML label overlay (defaults to the canvas' parent). */ + labelRoot?: HTMLElement | null + reducedMotion?: boolean +} + +interface FrameMap { + image: string + atlas: { w: number; h: number; cols: number; rows: number } + cell: { w: number; h: number } + anchor: { x: number; y: number } + charH: number + clips: Record +} + +type Mode = 'play' | 'respawn' | 'pause' | 'celebrate' | 'wrap' + +function noopHandle(): GameHandle { + return { + start() {}, stop() {}, setPlayerControl() {}, onRingCount() {}, onEvent() {}, + onZone() {}, onGoalPrompt() {}, activateGoal: () => false, dispose() {}, + } +} + +/** Critically damped spring toward target (Unity SmoothDamp). */ +function smoothDamp(cur: number, target: number, vel: { v: number }, smooth: number, dt: number): number { + const omega = 2 / smooth + const x = omega * dt + const exp = 1 / (1 + x + 0.48 * x * x + 0.235 * x * x * x) + const change = cur - target + const temp = (vel.v + omega * change) * dt + vel.v = (vel.v - omega * temp) * exp + return target + (change + temp) * exp +} + +export function createGame(canvas: HTMLCanvasElement, model: LevelModel, opts: GameOptions): GameHandle { + let renderer: THREE.WebGLRenderer + try { + renderer = new THREE.WebGLRenderer({ canvas, antialias: true, powerPreference: 'high-performance' }) + } catch { + return noopHandle() + } + + const reducedMotion = opts.reducedMotion ?? false + renderer.setPixelRatio(Math.min(window.devicePixelRatio || 1, 2)) + if (renderer.shadowMap) { + renderer.shadowMap.enabled = true + renderer.shadowMap.type = THREE.PCFSoftShadowMap + } + ;(renderer as { toneMapping?: THREE.ToneMapping }).toneMapping = THREE.ACESFilmicToneMapping + ;(renderer as { toneMappingExposure?: number }).toneMappingExposure = 1.05 + + const layout = buildWorldLayout(model) + const hazards = layout.hazards + + const scene = new THREE.Scene() + scene.fog = new THREE.Fog(0x1a1640, 26, 95) + const camera = new THREE.PerspectiveCamera(40, 16 / 9, 0.1, 400) + + // Lighting: sky/ground fill, warm key with shadows that follows the camera, cool rim. + scene.add(new THREE.HemisphereLight(0xa9bcff, 0x2a1d10, 0.9)) + scene.add(new THREE.AmbientLight(0x404a80, 0.35)) + const key = new THREE.DirectionalLight(0xfff0dd, 1.7) + key.castShadow = true + key.shadow.mapSize.set(1024, 1024) + key.shadow.camera.left = -18 + key.shadow.camera.right = 18 + key.shadow.camera.top = 12 + key.shadow.camera.bottom = -6 + key.shadow.camera.near = 1 + key.shadow.camera.far = 60 + key.shadow.bias = -0.0008 + scene.add(key, key.target) + const rim = new THREE.DirectionalLight(0x7fa8ff, 0.6) + rim.position.set(6, 8, -12) + scene.add(rim) + + const world = buildScene(scene, layout) + const fx = new Effects(scene) + const labelHost = opts.labelRoot ?? canvas.parentElement + const labels = labelHost ? new LabelLayer(labelHost, layout) : null + + // ---------- Kasimir ---------- + const fm = frameMapJson as unknown as FrameMap + const wpp = KASIMIR_H / fm.charH + const kGeo = new THREE.PlaneGeometry(fm.cell.w * wpp, fm.cell.h * wpp) + // PlaneGeometry is centred; anchor.y is measured from the cell top β€” move the feet to the origin. + kGeo.translate(0, (fm.anchor.y - fm.cell.h / 2) * wpp, 0) + const kMat = new THREE.MeshBasicMaterial({ transparent: true, alphaTest: 0.02, depthWrite: false }) + const kMesh = new THREE.Mesh(kGeo, kMat) + kMesh.renderOrder = 5 + const kas = new THREE.Group() + kas.add(kMesh) + scene.add(kas) + + let atlas: THREE.Texture | null = null + let lastFrameKey = '' + new THREE.TextureLoader().load( + fm.image, + tex => { + tex.colorSpace = THREE.SRGBColorSpace + tex.repeat.set(1 / fm.atlas.cols, 1 / fm.atlas.rows) + tex.anisotropy = 4 + atlas = tex + kMat.map = tex + kMat.needsUpdate = true + lastFrameKey = '' + }, + undefined, + () => { + new THREE.TextureLoader().load(KASIMIR_FALLBACK_TEX, tex => { + tex.colorSpace = THREE.SRGBColorSpace + kMat.map = tex + kMat.needsUpdate = true + }) + }, + ) + + function showFrame(clip: ClipName, index: number, facing: 1 | -1): void { + if (!atlas) return + const def = fm.clips[clip] ?? fm.clips.idle + const f = def.frames[Math.max(0, Math.min(index, def.frames.length - 1))] + const k = `${f.x},${f.y},${facing}` + if (k === lastFrameKey) return + lastFrameKey = k + const cols = fm.atlas.cols, rows = fm.atlas.rows + const col = Math.round(f.x / fm.cell.w), row = Math.round(f.y / fm.cell.h) + atlas.repeat.set(facing === 1 ? 1 / cols : -1 / cols, 1 / rows) + atlas.offset.set(facing === 1 ? col / cols : (col + 1) / cols, 1 - (row + 1) / rows) + } + + const shadowTex = softDotTexture('rgba(0,0,0,0.55)', 'rgba(0,0,0,0)') + const blob = new THREE.Mesh(new THREE.PlaneGeometry(1.8, 0.8), new THREE.MeshBasicMaterial({ map: shadowTex, transparent: true, depthWrite: false })) + blob.rotation.x = -Math.PI / 2 + scene.add(blob) + + // ---------- State ---------- + let phys: PhysicsState = initialState(layout.spawnX) + let prevX = phys.x + let prevY = phys.y + let rings: RingState = ringsReset() + let mode: Mode = 'play' + let modeT = 0 + let pose: AnimContext['pose'] = null + let pendingTrack: string | null = null + let animEvent: AnimEvent = 'none' + let sinceEventMs = 0 + let clip: ClipName = 'idle' + let clipMs = 0 + let squash = 0 + let shake = 0 + let fade = 1 + let progress: ProgressTrack = { lastX: phys.x, lastAdvanceT: 0, nowT: 0 } + let simT = 0 + let acc = 0 + const visited = new Set() + let playerControl = false + let rafId = 0 + let started = false + let camX = phys.x + let camY = CAM_BASE_Y + const camVX = { v: 0 } + const camVY = { v: 0 } + let look = 2.4 + let currentZone = -1 + let currentGoal: WorldGoal | null = null + + let ringCb: ((n: number) => void) | null = null + let eventCb: ((e: GameEvent) => void) | null = null + let zoneCb: ((z: ZoneInfo | null) => void) | null = null + let goalCb: ((g: GoalPrompt | null) => void) | null = null + const safe = (f: (() => T) | undefined | null) => { try { f?.() } catch { /* never throw into the page */ } } + const emit = (e: GameEvent) => safe(() => eventCb?.(e)) + const emitRings = () => safe(() => ringCb?.(rings.count)) + + function width(): number { return canvas.clientWidth || canvas.width || 800 } + function height(): number { return canvas.clientHeight || canvas.height || 600 } + + function setAnimEvent(e: AnimEvent): void { + animEvent = e + sinceEventMs = 0 + } + + function teleport(next: PhysicsState): void { + phys = next + prevX = next.x + prevY = next.y + } + + function resetRun(): void { + teleport(initialState(layout.spawnX)) + rings = ringsReset() + mode = 'play' + modeT = 0 + pose = null + pendingTrack = null + setAnimEvent('none') + visited.clear() + for (const c of world.coins) { + c.alive = true + c.mesh.visible = true + } + for (const g of world.gates) g.lit = false + progress = { lastX: phys.x, lastAdvanceT: simT, nowT: simT } + camX = phys.x + look + camVX.v = 0 + fade = 0 + emitRings() + } + + // ---------- Input ---------- + const keys = { left: false, right: false, jump: false } + const pointers = new Map() + let swipeJumpUntil = 0 + + function gameKey(k: string): boolean { + return ['ArrowLeft', 'ArrowRight', 'ArrowUp', 'ArrowDown', ' ', 'Spacebar', 'Enter', 'a', 'A', 'd', 'D', 'w', 'W'].includes(k) + } + + function onKeyDown(e: KeyboardEvent): void { + if (!playerControl) return + if (gameKey(e.key)) e.preventDefault() + switch (e.key) { + case 'ArrowRight': case 'd': case 'D': keys.right = true; break + case 'ArrowLeft': case 'a': case 'A': keys.left = true; break + case ' ': case 'Spacebar': keys.jump = true; break + case 'ArrowUp': case 'w': case 'W': + if (!activateGoal()) keys.jump = true + break + case 'Enter': activateGoal(); break + } + } + + function onKeyUp(e: KeyboardEvent): void { + switch (e.key) { + case 'ArrowRight': case 'd': case 'D': keys.right = false; break + case 'ArrowLeft': case 'a': case 'A': keys.left = false; break + case ' ': case 'Spacebar': case 'ArrowUp': case 'w': case 'W': keys.jump = false; break + } + } + + function onPointerDown(e: PointerEvent): void { + const rect = canvas.getBoundingClientRect() + const rel = (e.clientX - rect.left) / (rect.width || 1) + const zone = rel < 1 / 3 ? 'left' : rel > 2 / 3 ? 'right' : 'jump' + pointers.set(e.pointerId, { zone, y0: e.clientY, t0: performance.now() }) + } + function onPointerMove(e: PointerEvent): void { + const p = pointers.get(e.pointerId) + if (p && p.y0 - e.clientY > 40 && performance.now() - p.t0 < 400) { + swipeJumpUntil = simT + 0.3 + p.y0 = -Infinity + } + } + function onPointerUp(e: PointerEvent): void { + pointers.delete(e.pointerId) + } + + function playerInput(): PhysicsInput { + let left = keys.left, right = keys.right, jump = keys.jump || simT < swipeJumpUntil + for (const p of pointers.values()) { + if (p.zone === 'left') left = true + else if (p.zone === 'right') right = true + else jump = true + } + const moveDir: -1 | 0 | 1 = right && !left ? 1 : left && !right ? -1 : 0 + return { moveDir, jump } + } + + // ---------- Goals ---------- + function promptFor(g: WorldGoal | null): GoalPrompt | null { + if (!g) return null + return { trackId: g.trackId ?? g.zoneId, title: g.zoneTitle, label: g.label, kind: g.kind, locked: g.locked } + } + + function activateGoal(): boolean { + if (!playerControl || mode !== 'play' || !phys.onGround) return false + const g = goalAt(layout, phys.x) + if (!g) return false + if (g.locked) { + shake = reducedMotion ? 0 : 0.15 + labels?.floater(g.x, 4.5, 'Locked', 'loss', camera, width(), height()) + return true + } + mode = 'celebrate' + modeT = 0 + pose = 'celebrate' + pendingTrack = g.trackId ?? g.zoneId + teleport({ ...phys, vx: 0 }) + fx.confetti(g.x, 4) + emit('goal') + return true + } + + // ---------- Simulation step ---------- + function collectCoins(): void { + for (let i = 0; i < world.coins.length; i++) { + const c = world.coins[i] + if (!c.alive || !c.collectable) continue + const cx = c.mesh.position.x + const cy = c.baseY + if (Math.abs(cx - phys.x) < BODY_HW + 0.42 && cy > phys.y - 0.4 && cy < phys.y + BODY_H + 0.4) { + c.alive = false + c.mesh.visible = false + rings = ringsCollect(rings) + fx.sparkle(cx, cy) + labels?.floater(cx, cy + 1.2, '+1', 'gain', camera, width(), height()) + emitRings() + emit('pickup') + } + } + } + + function loseRings(): void { + const { next, scattered } = ringsOnHit(rings) + rings = next + if (scattered > 0) { + fx.scatterRings(phys.x, phys.y, scattered) + labels?.floater(phys.x, phys.y + BODY_H + 0.8, `βˆ’${scattered}`, 'loss', camera, width(), height()) + } + } + + function step(dt: number): void { + simT += dt + modeT += dt + sinceEventMs += dt * 1000 + prevX = phys.x + prevY = phys.y + + if (mode === 'respawn') { + if (phys.y > -9) { + const r = stepPhysics(phys, { moveDir: 0, jump: false }, hazards, dt) + phys = { ...r.next, y: Math.max(r.next.y, -9) } + } + if (modeT >= RESPAWN_S) { + teleport(respawn(phys)) + mode = 'play' + modeT = 0 + setAnimEvent('respawn') + progress = { lastX: phys.x, lastAdvanceT: simT, nowT: simT } + } + return + } + if (mode === 'pause') { + phys = { ...phys, vx: 0 } + if (modeT >= ATTRACT_PAUSE_S) { mode = 'play'; pose = null } + return + } + if (mode === 'celebrate') { + if (modeT >= CELEBRATE_S && pendingTrack) { + const id = pendingTrack + pendingTrack = null + safe(() => opts.onPickTrack(id)) + } + return + } + if (mode === 'wrap') { + fade = Math.max(0, 1 - modeT / WRAP_S) + if (modeT >= WRAP_S) resetRun() + return + } + + // --- play --- + let input: PhysicsInput + if (playerControl) { + input = playerInput() + } else { + input = autopilotInput(phys, hazards) + // Attract mode pauses at each goal to show it off. + const zi = zoneIndexAt(layout, phys.x) + const g = zi >= 0 ? layout.zones[zi].goal : null + if (g && !visited.has(zi) && phys.onGround && phys.x >= g.x - 0.2) { + visited.add(zi) + mode = 'pause' + modeT = 0 + pose = g.locked ? null : g.kind === 'track' ? 'reach' : 'celebrate' + teleport({ ...phys, vx: 0 }) + if (!g.locked) fx.sparkle(g.x, 3) + return + } + } + + const { next, event } = stepPhysics(phys, input, hazards, dt) + phys = next + + if (event === 'jump') { + setAnimEvent('jump') + squash = -0.8 + fx.dust(phys.x, 0, phys.facing) + emit('jump') + } else if (event === 'land') { + setAnimEvent('land') + squash = 1 + fx.dust(phys.x, phys.y, 0) + } else if (event === 'hit') { + setAnimEvent('hit') + loseRings() + shake = reducedMotion ? 0 : 0.35 + emit('hit') + emitRings() + } else if (event === 'fell') { + setAnimEvent('fell') + loseRings() + mode = 'respawn' + modeT = 0 + emit('fall') + emitRings() + return + } + + collectCoins() + + if (!playerControl) { + if (phys.x - progress.lastX > 0.5) progress = { lastX: phys.x, lastAdvanceT: simT, nowT: simT } + progress = { ...progress, nowT: simT } + const wd = watchdog(progress, phys, hazards) + if (wd.forced) { + teleport(wd.next) + setAnimEvent('respawn') + progress = { lastX: phys.x, lastAdvanceT: simT, nowT: simT } + } + if (phys.x >= layout.length - LEVEL_PAD - 0.5) { + mode = 'wrap' + modeT = 0 + } + } + } + + // ---------- Frame ---------- + const clock = new THREE.Clock(false) + + function resize(): void { + const w = width(), h = height() + camera.aspect = w / h || 1 + // Pull back on narrow or short screens so the path ahead and the labels still fit. + const narrow = Math.max(1, 1.78 / camera.aspect) + const short = Math.max(1, 1 + (600 - h) / 1200) + camera.position.z = CAM_DIST * Math.min(1.7, narrow * short) + camera.updateProjectionMatrix() + renderer.setSize(w, h, false) + } + + function frame(): void { + const dt = Math.min(clock.getDelta(), 0.1) + acc += dt + let steps = 0 + while (acc >= FIXED_DT && steps < 24) { + step(FIXED_DT) + acc -= FIXED_DT + steps++ + if (!started) return + } + const alpha = acc / FIXED_DT + const rx = prevX + (phys.x - prevX) * alpha + const ry = prevY + (phys.y - prevY) * alpha + + // Animation + const ctx: AnimContext = { + onGround: phys.onGround, vx: phys.vx, vy: phys.vy, + lastEvent: animEvent, sinceEventMs, pose, + } + const nextClip = selectClip(ctx) + if (nextClip !== clip) { clip = nextClip; clipMs = 0 } else { clipMs += dt * 1000 * playbackRate(clip, phys.vx) } + const def = fm.clips[clip] + const idx = clip === 'jump' ? jumpFrame(ctx, def?.frames.length ?? 1) : frameAt(clip, clipMs, fm.clips as never) + showFrame(clip, idx, phys.facing) + + squash *= Math.exp(-dt * 10) + kas.scale.set(1 + 0.08 * squash, 1 - 0.12 * squash, 1) + kas.position.set(rx, ry, 0.35) + const blinking = phys.invuln > 0 && mode === 'play' + kMat.opacity = (blinking ? (Math.floor(simT * 14) % 2 ? 0.35 : 1) : 1) * (mode === 'respawn' ? Math.max(0, 1 - modeT / 0.4) : 1) + kas.visible = kMat.opacity > 0.01 + + // Contact shadow on whatever is under Kasimir + let floorY = 0 + for (const c of hazards.crates) if (rx > c.x0 && rx < c.x1 && ry >= c.top - 0.05) floorY = c.top + const overHole = floorY === 0 && overPit(rx, hazards.pits) + blob.visible = !overHole && ry > -0.2 + const lift = Math.max(0, ry - floorY) + blob.position.set(rx, floorY + 0.02, 0.35) + blob.scale.setScalar(Math.max(0.35, 1 - lift * 0.15)) + ;(blob.material as THREE.MeshBasicMaterial).opacity = Math.max(0.2, 1 - lift * 0.18) + + // Camera: critically damped follow with look-ahead in the facing direction + look += (phys.facing * 2.4 - look) * Math.min(1, dt * 1.8) + camX = smoothDamp(camX, rx + look, camVX, 0.32, dt) + const camTargetY = CAM_BASE_Y + Math.max(-2.5, Math.min(2.2, (ry - 0.6) * 0.35)) + camY = smoothDamp(camY, camTargetY, camVY, 0.45, dt) + shake *= Math.exp(-dt * 7) + const sx = shake > 0.002 ? (Math.random() - 0.5) * shake : 0 + const sy = shake > 0.002 ? (Math.random() - 0.5) * shake : 0 + camera.position.x = camX + sx + camera.position.y = camY + sy + camera.lookAt(camX + sx, camY - CAM_LOOK_DROP + sy, 0) + + key.position.set(camX - 10, 20, 14) + key.target.position.set(camX, 0, 0) + + // Zone + goal prompt notifications + const zi = zoneIndexAt(layout, rx) + if (zi !== currentZone) { + currentZone = zi + const z = layout.zones[zi] + safe(() => zoneCb?.(z ? { index: zi, total: layout.zones.length, title: z.title, color: zoneColor(z.colorIndex), locked: z.locked } : null)) + } + const g = playerControl && mode === 'play' && phys.onGround ? goalAt(layout, phys.x) : null + if (g !== currentGoal) { + currentGoal = g + safe(() => goalCb?.(promptFor(g))) + } + + if (mode !== 'wrap') fade = Math.min(1, fade + dt * 2.5) + canvas.style.opacity = fade.toFixed(3) + + world.update(simT, dt, camX, rx, zi) + fx.update(dt) + renderer.render(scene, camera) + labels?.update(camera, camX, rx, ry, width(), height()) + + // stop() may have run inside this frame (e.g. onPickTrack β†’ hide); don't re-arm the loop. + if (started) rafId = requestAnimationFrame(frame) + } + + function attach(): void { + window.addEventListener('resize', resize) + window.addEventListener('keydown', onKeyDown) + window.addEventListener('keyup', onKeyUp) + canvas.addEventListener('pointerdown', onPointerDown) + canvas.addEventListener('pointermove', onPointerMove) + canvas.addEventListener('pointerup', onPointerUp) + canvas.addEventListener('pointercancel', onPointerUp) + canvas.addEventListener('pointerleave', onPointerUp) + } + + function detach(): void { + window.removeEventListener('resize', resize) + window.removeEventListener('keydown', onKeyDown) + window.removeEventListener('keyup', onKeyUp) + canvas.removeEventListener('pointerdown', onPointerDown) + canvas.removeEventListener('pointermove', onPointerMove) + canvas.removeEventListener('pointerup', onPointerUp) + canvas.removeEventListener('pointercancel', onPointerUp) + canvas.removeEventListener('pointerleave', onPointerUp) + keys.left = keys.right = keys.jump = false + pointers.clear() } - return { obstacles, pits } -} - -// --- public API --- - -export interface GameHandle { - start(): void - stop(): void - setPlayerControl(on: boolean): void - onRingCount(cb: (n: number) => void): void - onEvent(cb: (e: 'hit' | 'pickup' | 'goal') => void): void - dispose?(): void -} - -export function createGame( - canvas: HTMLCanvasElement, - model: LevelModel, - opts: { onPickTrack: (trackId: string) => void } -): GameHandle { - // Defensive: if WebGL fails, return safe no-ops - let renderer: THREE.WebGLRenderer - try { - renderer = new THREE.WebGLRenderer({ canvas, antialias: true }) - } catch (_e) { - return { - start() {}, - stop() {}, - setPlayerControl(_on: boolean) {}, - onRingCount(_cb: (n: number) => void) {}, - onEvent(_cb: (e: 'hit' | 'pickup' | 'goal') => void) {}, - dispose() {}, - } - } - - renderer.setPixelRatio(Math.min(devicePixelRatio, 2)) - renderer.shadowMap.enabled = true - renderer.shadowMap.type = THREE.PCFSoftShadowMap - - const scene = new THREE.Scene() - scene.fog = new THREE.Fog(0x06080f, 20, 52) - - const camera = new THREE.PerspectiveCamera(42, canvas.clientWidth / canvas.clientHeight || 1, 0.1, 200) - - // Lighting - scene.add(new THREE.AmbientLight(0x5566aa, 1.1)) - const key = new THREE.DirectionalLight(0xffffff, 1.5) - key.position.set(-6, 14, 9) - key.castShadow = true - key.shadow.mapSize.set(2048, 2048) - key.shadow.camera.near = 1 - key.shadow.camera.far = 70 - key.shadow.camera.left = -40 - key.shadow.camera.right = 40 - key.shadow.camera.top = 24 - key.shadow.camera.bottom = -12 - scene.add(key) - - const kLight = new THREE.PointLight(0x9ab8ff, 2, 16) - scene.add(kLight) - - // Build scene from model - const { markers, zoneMeta, totalLen, obstacleMeshes } = buildScene(scene, model) - - // Merged hazards for physics (all zones combined) - const allHazards = mergeHazards(zoneMeta) - - // Kasimir billboard β€” atlas-animated with static fallback - const kMat = new THREE.MeshBasicMaterial({ transparent: true, alphaTest: 0.3 }) - const kasimir = new THREE.Mesh(new THREE.PlaneGeometry(2.6, 3.6), kMat) - kasimir.position.set(5, GROUND_Y, 0.3) - scene.add(kasimir) - - // Atlas animation state - type FrameRect = { x: number; y: number; w: number; h: number } - type ClipDef = { frames: FrameRect[]; fps: number; loop: boolean } - type FrameMap = { image: string; cell: { w: number; h: number }; clips: Record } - - let atlasLoaded = false - let frameMap: FrameMap | null = null - let atlasTex: THREE.Texture | null = null - let totalCols = 1 - let totalRows = 1 - let animElapsedMs = 0 - let currentClip: ClipName = 'idle' - - // Load atlas texture, then fetch the frame-map JSON - const atlasLoader = new THREE.TextureLoader() - atlasLoader.load( - KASIMIR_ATLAS, - (loadedTex) => { - // Atlas texture loaded β€” now import the frame map - import('./kasimir-frames.json').then((mod) => { - try { - const fm = mod.default as FrameMap - // Compute atlas grid dimensions from cell size + max frame bounds - let maxX = 0 - let maxY = 0 - for (const clip of Object.values(fm.clips)) { - for (const f of (clip as ClipDef).frames) { - if (f.x + f.w > maxX) maxX = f.x + f.w - if (f.y + f.h > maxY) maxY = f.y + f.h - } - } - totalCols = Math.round(maxX / fm.cell.w) - totalRows = Math.round(maxY / fm.cell.h) - loadedTex.colorSpace = THREE.SRGBColorSpace - // UV wrapping: clamp so we never bleed between cells - loadedTex.wrapS = THREE.ClampToEdgeWrapping - loadedTex.wrapT = THREE.ClampToEdgeWrapping - // Enable UV offset/repeat sub-region selection - loadedTex.repeat.set(1 / totalCols, 1 / totalRows) - loadedTex.offset.set(0, 0) - frameMap = fm - atlasTex = loadedTex - kMat.map = loadedTex - kMat.needsUpdate = true - atlasLoaded = true - } catch (_e) { - // JSON parse or structure error β€” fall through to static sprite - try { console.warn('[app-space-game] kasimir-frames.json failed to parse') } catch { /* silent */ } - loadFallbackSprite() - } - }).catch((_e) => { - // Dynamic import failed β€” fall through to static sprite - try { console.warn('[app-space-game] kasimir-frames.json failed to import') } catch { /* silent */ } - loadFallbackSprite() - }) - }, - undefined, - (_err) => { - // Atlas texture load failed β€” fall through to static sprite - try { console.warn('[app-space-game] Kasimir atlas failed to load') } catch { /* silent */ } - loadFallbackSprite() - } - ) - - function loadFallbackSprite(): void { - const fallbackTex = new THREE.TextureLoader().load( - KASIMIR_TEX, - undefined, - undefined, - (_err) => { - try { console.warn('[app-space-game] Kasimir fallback texture also failed to load') } catch { /* silent */ } - } - ) - fallbackTex.colorSpace = THREE.SRGBColorSpace - kMat.map = fallbackTex - kMat.needsUpdate = true - } - - /** Update the atlas UV offset/repeat for the current frame, and facing flip. */ - function updateKasimirUV(facing: 1 | -1): void { - if (!atlasLoaded || !frameMap || !atlasTex) return - - const clip = frameMap.clips[currentClip] - if (!clip) return - - const clips = frameMap.clips as Record - const frameIdx = frameAt(currentClip, animElapsedMs, clips) - const frame = clip.frames[frameIdx] - if (!frame) return - - const col = Math.round(frame.x / frameMap.cell.w) - const row = Math.round(frame.y / frameMap.cell.h) - - atlasTex.repeat.set(1 / totalCols, 1 / totalRows) - // Three.js UV origin is bottom-left; row 0 in the atlas is the top. - atlasTex.offset.set(col / totalCols, 1 - (row + 1) / totalRows) - atlasTex.needsUpdate = true - - // Flip scale.x for facing direction - const absScaleX = Math.abs(kasimir.scale.x) - kasimir.scale.x = facing === -1 ? -absScaleX : absScaleX - } - - // Kasimir shadow blob - const blob = new THREE.Mesh( - new THREE.CircleGeometry(1.1, 24), - new THREE.MeshBasicMaterial({ color: 0, transparent: true, opacity: 0.4 }) - ) - blob.rotation.x = -Math.PI / 2 - blob.position.set(5, 0.03, 0) - scene.add(blob) - - // --- Physics + ring state --- - let physState: PhysicsState = { - x: 5, - y: GROUND_Y, - vy: 0, - onGround: true, - facing: 1, - lastSafeX: 5, - } - let ringState: RingState = ringsReset() - - // Progress tracking for autopilot watchdog - let progress: ProgressTrack = { - lastX: 5, - lastAdvanceT: 0, - nowT: 0, - } - - // Animation event tracking for AnimContext - let lastEvent: AnimContext['lastEvent'] = 'none' - let sinceEventMs = 0 - - let playerControl = false - let rafId = 0 - let started = false - - // --- Callbacks --- - let ringCountCb: ((n: number) => void) | null = null - let eventCb: ((e: 'hit' | 'pickup' | 'goal') => void) | null = null - - function fireRingCount(n: number): void { - try { ringCountCb?.(n) } catch { /* never throw into the page */ } - } - - function fireEvent(e: 'hit' | 'pickup' | 'goal'): void { - try { eventCb?.(e) } catch { /* never throw into the page */ } - } - - // Zone hit tracking for onPickTrack - const goalHit = new Set() - - const clock = new THREE.Clock() - - // --- Player input state --- - // Renderer owns the listeners when player-control is on. - const keys = { right: false, left: false, jump: false } - - function onKeyDown(e: KeyboardEvent): void { - if (e.key === 'ArrowRight') keys.right = true - if (e.key === 'ArrowLeft') keys.left = true - if (e.key === 'ArrowUp' || e.key === ' ') keys.jump = true - } - - function onKeyUp(e: KeyboardEvent): void { - if (e.key === 'ArrowRight') keys.right = false - if (e.key === 'ArrowLeft') keys.left = false - if (e.key === 'ArrowUp' || e.key === ' ') keys.jump = false - } - - // Pointer/touch: tap anywhere on the canvas = jump; hold right half = run right; hold left = run left - let pointerDown = false - let pointerHalf: 'left' | 'right' | null = null - - function onPointerDown(e: PointerEvent): void { - pointerDown = true - const rect = canvas.getBoundingClientRect() - const relX = e.clientX - rect.left - const third = canvas.clientWidth / 3 - if (relX < third) { - pointerHalf = 'left' - } else if (relX > canvas.clientWidth - third) { - pointerHalf = 'right' - } else { - pointerHalf = null // center tap = jump - } - } - - function onPointerUp(): void { - pointerDown = false - pointerHalf = null - } - - /** Derive moveDir and jump from current input state in player mode. */ - function playerInput(): { moveDir: -1 | 0 | 1; jump: boolean } { - let moveDir: -1 | 0 | 1 = 0 - if (keys.right || (pointerDown && pointerHalf === 'right')) moveDir = 1 - else if (keys.left || (pointerDown && pointerHalf === 'left')) moveDir = -1 - const jump = keys.jump || (pointerDown && pointerHalf === null) - return { moveDir, jump } - } - - function resizeToCanvas() { - const w = canvas.clientWidth || canvas.width || 800 - const h = canvas.clientHeight || canvas.height || 600 - camera.aspect = w / h - camera.updateProjectionMatrix() - renderer.setSize(w, h, false) - } - resizeToCanvas() - - // Keep camera aspect + drawing buffer in sync when the window resizes mid-attract - const onResize = () => resizeToCanvas() - - /** Reset all run state (called at start + on wrap). */ - function resetRunState(): void { - physState = { - x: 5, - y: GROUND_Y, - vy: 0, - onGround: true, - facing: 1, - lastSafeX: 5, - } - ringState = ringsReset() - progress = { lastX: 5, lastAdvanceT: 0, nowT: clock.elapsedTime } - lastEvent = 'none' - sinceEventMs = 0 - animElapsedMs = 0 - currentClip = 'idle' - goalHit.clear() - markers.forEach(m => { - m.userData.alive = true - m.visible = true - }) - fireRingCount(0) - } - - function tick() { - const dt = Math.min(clock.getDelta(), 0.05) - const t = clock.elapsedTime - - // Update progress time - progress = { ...progress, nowT: t } - - // Derive input - let input: { moveDir: -1 | 0 | 1; jump: boolean } - - if (!playerControl) { - // Attract mode: autopilot - input = autopilotInput(physState, allHazards) - - // Update progress tracking (x advance > 0.5 units resets wedge timer) - if (physState.x - progress.lastX > 0.5) { - progress = { ...progress, lastX: physState.x, lastAdvanceT: t } - } - - // Watchdog: if stalled, forced-teleport - const wd = watchdog(progress, physState, allHazards) - if (wd.forced) { - physState = wd.next - // Resync progress after watchdog teleport - progress = { lastX: physState.x, lastAdvanceT: t, nowT: t } - } - } else { - // Player mode - input = playerInput() - // Consume jump so holding doesn't re-trigger - if (input.jump && physState.onGround) { - keys.jump = false - } - } - - // --- Physics step --- - const { next: nextState, event: physEvent } = stepPhysics(physState, input, allHazards, dt) - physState = nextState - - // --- Handle physics events --- - if (physEvent === 'hit' || physEvent === 'fell') { - const { next: nextRings } = ringsOnHit(ringState) - ringState = nextRings - fireRingCount(ringState.count) - fireEvent('hit') - lastEvent = physEvent === 'fell' ? 'fell' : 'hit' - sinceEventMs = 0 - physState = respawn(physState) - } - - // --- Wrap at end of level (attract mode loops; player mode resets) --- - if (physState.x > totalLen - 4) { - resetRunState() - return - } - - // --- Coin collection --- - markers.forEach(coin => { - if (!coin.userData.alive) return - coin.rotation.y += dt * 3 - if (Math.abs(coin.position.x - physState.x) < 1.2 && Math.abs(coin.position.y - physState.y) < 1.6) { - coin.userData.alive = false - coin.visible = false - ringState = ringsCollect(ringState) - fireRingCount(ringState.count) - fireEvent('pickup') - lastEvent = 'pickup' - sinceEventMs = 0 - } - }) - - // --- Goal reach check --- - if (playerControl) { - zoneMeta.forEach((zm, zi) => { - const zone = model.zones[zi] - const zoneId = zone?.id - if (zoneId && !goalHit.has(zoneId) && !zm.locked && Math.abs(physState.x - zm.goalX) < 1.3) { - goalHit.add(zoneId) - const trackId = zone.goal.trackId ?? zoneId - fireEvent('goal') - lastEvent = 'goal' - sinceEventMs = 0 - opts.onPickTrack(trackId) - } - }) - } - - // --- Animation --- - sinceEventMs += dt * 1000 - - const animCtx: AnimContext = { - onGround: physState.onGround, - moveDir: (input.moveDir as -1 | 0 | 1), - vy: physState.vy, - lastEvent, - sinceEventMs, - } - const nextClip = selectClip(animCtx) - if (nextClip !== currentClip) { - currentClip = nextClip - animElapsedMs = 0 - } else { - animElapsedMs += dt * 1000 - } - - // --- Update Kasimir mesh --- - kasimir.position.set(physState.x, physState.y, 0.3) - blob.position.set(physState.x, 0.03, 0) - blob.scale.setScalar(1 - (physState.y - GROUND_Y) * 0.12) - kLight.position.set(physState.x, physState.y + 1, 3) - - // Update atlas UV for current animation frame + facing - updateKasimirUV(physState.facing) - - // Camera follow - camera.position.x += (physState.x - camera.position.x) * Math.min(1, dt * 4) - camera.position.y = 3.4 + Math.sin(t * 1.2) * 0.15 - camera.position.z = 13 - camera.lookAt(camera.position.x, 2.4, 0) - - renderer.render(scene, camera) - rafId = requestAnimationFrame(tick) - } - - function attachPlayerInput(): void { - window.addEventListener('keydown', onKeyDown) - window.addEventListener('keyup', onKeyUp) - canvas.addEventListener('pointerdown', onPointerDown) - canvas.addEventListener('pointerup', onPointerUp) - canvas.addEventListener('pointerleave', onPointerUp) - } - - function detachPlayerInput(): void { - window.removeEventListener('keydown', onKeyDown) - window.removeEventListener('keyup', onKeyUp) - canvas.removeEventListener('pointerdown', onPointerDown) - canvas.removeEventListener('pointerup', onPointerUp) - canvas.removeEventListener('pointerleave', onPointerUp) - keys.right = false - keys.left = false - keys.jump = false - pointerDown = false - pointerHalf = null - } - - return { - start() { - if (started) return - started = true - resizeToCanvas() - window.addEventListener('resize', onResize) - clock.start() - resetRunState() - rafId = requestAnimationFrame(tick) - }, - stop() { - if (rafId) { - cancelAnimationFrame(rafId) - rafId = 0 - } - window.removeEventListener('resize', onResize) - detachPlayerInput() - playerControl = false - started = false - }, - setPlayerControl(on: boolean) { - playerControl = on - if (on) { - attachPlayerInput() - } else { - detachPlayerInput() - } - }, - onRingCount(cb: (n: number) => void) { - ringCountCb = cb - }, - onEvent(cb: (e: 'hit' | 'pickup' | 'goal') => void) { - eventCb = cb - }, - dispose() { - // Dispose renderer and GPU resources - window.removeEventListener('resize', onResize) - try { - renderer.dispose() - scene.traverse(obj => { - if (obj instanceof THREE.Mesh) { - obj.geometry?.dispose() - if (Array.isArray(obj.material)) { - obj.material.forEach(m => m.dispose()) - } else { - (obj.material as THREE.Material)?.dispose() - } - } - }) - kMat.dispose() - atlasTex?.dispose() - obstacleMeshes.length = 0 - } catch { - // Best-effort - never throw on dispose - } - }, - } -} + + return { + start() { + if (started) return + started = true + resize() + attach() + resetRun() + camX = phys.x + look + clock.start() + acc = 0 + rafId = requestAnimationFrame(frame) + }, + stop() { + if (rafId) cancelAnimationFrame(rafId) + rafId = 0 + detach() + playerControl = false + started = false + clock.stop() + if (currentGoal) { + currentGoal = null + safe(() => goalCb?.(null)) + } + }, + setPlayerControl(on: boolean) { + playerControl = on + if (on && mode === 'pause') { mode = 'play'; pose = null } + if (!on) { + keys.left = keys.right = keys.jump = false + // Autopilot resumes from here β€” don't let stale progress trip the wedge watchdog. + progress = { lastX: phys.x, lastAdvanceT: simT, nowT: simT } + } + }, + onRingCount(cb) { ringCb = cb }, + onEvent(cb) { eventCb = cb }, + onZone(cb) { zoneCb = cb }, + onGoalPrompt(cb) { goalCb = cb }, + activateGoal, + dispose() { + try { + if (rafId) cancelAnimationFrame(rafId) + detach() + labels?.dispose() + fx.dispose() + scene.traverse(obj => { + const m = obj as THREE.Mesh + if (m.geometry) m.geometry.dispose() + const mat = m.material as THREE.Material | THREE.Material[] | undefined + if (Array.isArray(mat)) mat.forEach(x => x.dispose()) + else mat?.dispose() + }) + atlas?.dispose() + renderer.dispose() + } catch { + // best-effort + } + }, + } +} diff --git a/hugo-apps/src/app-space-game/scene.ts b/hugo-apps/src/app-space-game/scene.ts new file mode 100644 index 000000000..5126fa61a --- /dev/null +++ b/hugo-apps/src/app-space-game/scene.ts @@ -0,0 +1,520 @@ +// Builds the static arcade world from a WorldLayout and returns handles for the +// pieces that animate (coins, flags, chests, gate lamps, clouds, sky tint). + +import * as THREE from 'three' +import type { WorldLayout } from './layout' +import { zoneColor } from './level-model' +import { COIN_Y } from './layout' +import { + groundSideTexture, grassTexture, crateTexture, chestTexture, hazardStripeTexture, windowsTexture, + softDotTexture, cloudTexture, flagTexture, signTexture, seeded, +} from './textures' + +const GROUND_DEPTH = 8 // z extent of the play strip (z ∈ [-5, 3]) +const GROUND_BACK = -5 +const GROUND_FRONT = 3 +const GROUND_THICK = 7 + +export interface CoinView { + mesh: THREE.Mesh + baseY: number + alive: boolean + collectable: boolean +} +export interface FlagView { cloth: THREE.Mesh; base: Float32Array; x: number; locked: boolean; phase: number } +export interface ChestView { lid: THREE.Group; glow: THREE.Sprite; x: number; locked: boolean; open: number } +export interface GateView { lamp: THREE.Mesh; glow: THREE.Sprite; x: number; lit: boolean } + +export interface SceneHandles { + coins: CoinView[] + flags: FlagView[] + chests: ChestView[] + gates: GateView[] + update(t: number, dt: number, camX: number, kasX: number, zoneIndex: number): void +} + +function scaleUV(geo: THREE.BufferGeometry, sx: number, sy: number): THREE.BufferGeometry { + const uv = geo.getAttribute('uv') as THREE.BufferAttribute + for (let i = 0; i < uv.count; i++) uv.setXY(i, uv.getX(i) * sx, uv.getY(i) * sy) + uv.needsUpdate = true + return geo +} + +function solidSpans(layout: WorldLayout, from: number, to: number): Array<[number, number]> { + const pits = [...layout.hazards.pits].sort((a, b) => a.x0 - b.x0) + const spans: Array<[number, number]> = [] + let x = from + for (const p of pits) { + if (p.x0 > x) spans.push([x, p.x0]) + x = Math.max(x, p.x1) + } + if (to > x) spans.push([x, to]) + return spans +} + +/** Zone sky palette: deep night top, zone-tinted horizon. */ +export function zonePalette(colorIndex: number): { top: THREE.Color; horizon: THREE.Color; fog: THREE.Color } { + const accent = new THREE.Color(zoneColor(colorIndex)) + const top = new THREE.Color('#070b1f').lerp(accent, 0.12) + const horizon = new THREE.Color('#2a1f55').lerp(accent, 0.45) + const fog = horizon.clone().lerp(new THREE.Color('#0d1230'), 0.35) + return { top, horizon, fog } +} + +export function buildScene(scene: THREE.Scene, layout: WorldLayout): SceneHandles { + const L0 = -40 + const L1 = layout.length + 40 + const rnd = seeded(2026) + + // ---------- Sky (screen-filling gradient that follows the camera) ---------- + const skyUniforms = { + uTop: { value: new THREE.Color('#070b1f') }, + uHorizon: { value: new THREE.Color('#2a1f55') }, + } + const sky = new THREE.Mesh( + new THREE.PlaneGeometry(400, 160), + new THREE.ShaderMaterial({ + uniforms: skyUniforms, + depthWrite: false, + fog: false, + vertexShader: 'varying vec2 vUv; void main(){ vUv = uv; gl_Position = projectionMatrix * modelViewMatrix * vec4(position,1.0); }', + fragmentShader: + 'uniform vec3 uTop; uniform vec3 uHorizon; varying vec2 vUv;' + + 'void main(){ float t = smoothstep(0.25, 0.85, vUv.y); gl_FragColor = vec4(mix(uHorizon, uTop, t), 1.0); }', + }), + ) + sky.position.set(0, 30, -120) + sky.renderOrder = -10 + scene.add(sky) + + // Stars (far, nearly fixed) + const starGeo = new THREE.BufferGeometry() + const starPos = new Float32Array(900 * 3) + for (let i = 0; i < 900; i++) { + starPos[i * 3] = (rnd() - 0.5) * 360 + starPos[i * 3 + 1] = 12 + rnd() * 70 + starPos[i * 3 + 2] = -110 + } + starGeo.setAttribute('position', new THREE.BufferAttribute(starPos, 3)) + const starMat = new THREE.PointsMaterial({ color: 0xffffff, size: 1.6, sizeAttenuation: false, transparent: true, opacity: 0.85, fog: false }) + const stars = new THREE.Points(starGeo, starMat) + scene.add(stars) + + // Moon with halo + const moon = new THREE.Group() + const halo = new THREE.Sprite(new THREE.SpriteMaterial({ map: softDotTexture('rgba(255,240,200,0.55)', 'rgba(255,240,200,0)'), fog: false, depthWrite: false })) + halo.scale.set(40, 40, 1) + const disc = new THREE.Mesh(new THREE.CircleGeometry(5, 48), new THREE.MeshBasicMaterial({ color: 0xfff1cc, fog: false })) + moon.add(halo, disc) + moon.position.set(40, 48, -105) + scene.add(moon) + + // Clouds (slow drift) + const clouds: THREE.Sprite[] = [] + for (let i = 0; i < 26; i++) { + const m = new THREE.SpriteMaterial({ map: cloudTexture(i + 3), transparent: true, opacity: 0.18 + rnd() * 0.18, depthWrite: false, fog: false }) + const c = new THREE.Sprite(m) + const s = 14 + rnd() * 18 + c.scale.set(s, s * 0.45, 1) + c.position.set(L0 + rnd() * (L1 - L0), 22 + rnd() * 22, -70 - rnd() * 15) + c.userData.speed = 0.4 + rnd() * 0.8 + clouds.push(c) + scene.add(c) + } + + // ---------- Far mountains + mid hills (silhouettes) ---------- + const ridge = (z: number, baseY: number, amp: number, color: string, seed: number, step: number) => { + const r = seeded(seed) + const shape = new THREE.Shape() + shape.moveTo(L0 - 60, -20) + let y = baseY + for (let x = L0 - 60; x <= L1 + 60; x += step) { + y = baseY + Math.sin(x * 0.05 + seed) * amp * 0.5 + (r() - 0.5) * amp + shape.lineTo(x, y) + } + shape.lineTo(L1 + 60, -20) + shape.lineTo(L0 - 60, -20) + const mesh = new THREE.Mesh(new THREE.ShapeGeometry(shape), new THREE.MeshBasicMaterial({ color, fog: false })) + mesh.position.z = z + scene.add(mesh) + } + ridge(-90, 20, 14, '#1b1f4a', 7, 9) + ridge(-68, 10, 8, '#22285c', 9, 6) + + // ---------- Skyline (instanced buildings with lit windows) ---------- + const buildingCount = Math.ceil((L1 - L0) / 3.2) + const bGeo = scaleUV(new THREE.BoxGeometry(1, 1, 1), 1, 2) + const bMat = new THREE.MeshStandardMaterial({ + color: 0x151a36, + roughness: 0.9, + emissive: 0xffffff, + emissiveMap: windowsTexture(3), + emissiveIntensity: 0.32, + }) + const buildings = new THREE.InstancedMesh(bGeo, bMat, buildingCount) + const m4 = new THREE.Matrix4() + const q = new THREE.Quaternion() + for (let i = 0; i < buildingCount; i++) { + const w = 2.4 + rnd() * 2.6 + const h = 6 + rnd() * 14 + m4.compose(new THREE.Vector3(L0 + i * 3.2 + rnd(), h / 2 - 2, -52 - rnd() * 8), q, new THREE.Vector3(w, h, 2.5)) + buildings.setMatrixAt(i, m4) + } + buildings.instanceMatrix.needsUpdate = true + scene.add(buildings) + + // Neon roof strips in each zone's colour + for (const z of layout.zones) { + const col = new THREE.Color(zoneColor(z.colorIndex)).lerp(new THREE.Color('#ffffff'), 0.25) + for (let k = 0; k < 3; k++) { + const bar = new THREE.Mesh( + new THREE.BoxGeometry(3 + rnd() * 3, 0.25, 0.25), + new THREE.MeshBasicMaterial({ color: col }), + ) + bar.position.set(z.x0 + 4 + rnd() * Math.max(1, z.x1 - z.x0 - 8), 8 + rnd() * 8, -49) + scene.add(bar) + } + } + + // ---------- Near trees + lamp posts ---------- + const treeCount = Math.ceil((L1 - L0) / 5) + const trunkGeo = new THREE.CylinderGeometry(0.18, 0.25, 1.6, 6) + const crownGeo = new THREE.ConeGeometry(1.3, 3.2, 7) + const trunkMat = new THREE.MeshStandardMaterial({ color: 0x4a2f22, roughness: 1 }) + const crownMat = new THREE.MeshStandardMaterial({ color: 0x1f6b48, roughness: 0.8, flatShading: true }) + const trunks = new THREE.InstancedMesh(trunkGeo, trunkMat, treeCount) + const crowns = new THREE.InstancedMesh(crownGeo, crownMat, treeCount * 2) + for (let i = 0; i < treeCount; i++) { + const x = L0 + i * 5 + rnd() * 3 + const z = -7.5 - rnd() * 4 + const s = 0.8 + rnd() * 0.7 + m4.compose(new THREE.Vector3(x, 0.8 * s, z), q, new THREE.Vector3(s, s, s)) + trunks.setMatrixAt(i, m4) + m4.compose(new THREE.Vector3(x, (1.6 + 1.3) * s, z), q, new THREE.Vector3(s, s, s)) + crowns.setMatrixAt(i * 2, m4) + m4.compose(new THREE.Vector3(x, (1.6 + 2.6) * s, z), q, new THREE.Vector3(s * 0.75, s * 0.75, s * 0.75)) + crowns.setMatrixAt(i * 2 + 1, m4) + crowns.setColorAt(i * 2, new THREE.Color().setHSL(0.38 + rnd() * 0.06, 0.5, 0.22 + rnd() * 0.08)) + crowns.setColorAt(i * 2 + 1, new THREE.Color().setHSL(0.38 + rnd() * 0.06, 0.5, 0.28 + rnd() * 0.08)) + } + trunks.instanceMatrix.needsUpdate = true + crowns.instanceMatrix.needsUpdate = true + if (crowns.instanceColor) crowns.instanceColor.needsUpdate = true + scene.add(trunks, crowns) + + const lampGlowTex = softDotTexture('rgba(255,214,140,0.9)', 'rgba(255,214,140,0)') + const lampPostMat = new THREE.MeshStandardMaterial({ color: 0x2b3245, metalness: 0.6, roughness: 0.4 }) + const lampPostGeo = new THREE.CylinderGeometry(0.08, 0.1, 4.2, 6) + for (let x = 8; x < layout.length; x += 14) { + const post = new THREE.Mesh(lampPostGeo, lampPostMat) + post.position.set(x, 2.1, -4.6) + const glow = new THREE.Sprite(new THREE.SpriteMaterial({ map: lampGlowTex, transparent: true, depthWrite: false })) + glow.scale.set(2.6, 2.6, 1) + glow.position.set(x, 4.3, -4.5) + const bulb = new THREE.Mesh(new THREE.SphereGeometry(0.2, 10, 8), new THREE.MeshBasicMaterial({ color: 0xffe2a8 })) + bulb.position.set(x, 4.3, -4.6) + scene.add(post, glow, bulb) + } + + // ---------- Ground with real pits ---------- + const sideTex = groundSideTexture() + const grassTex = grassTexture() + const earthMat = new THREE.MeshStandardMaterial({ map: sideTex, roughness: 1 }) + const topMat = new THREE.MeshStandardMaterial({ color: 0x3c9b46, roughness: 0.9 }) + const lipMat = new THREE.MeshStandardMaterial({ map: grassTex, roughness: 0.9 }) + for (const [a, b] of solidSpans(layout, L0, L1)) { + const w = b - a + const geo = scaleUV(new THREE.BoxGeometry(w, GROUND_THICK, GROUND_DEPTH), w / 4, GROUND_THICK / 4) + const block = new THREE.Mesh(geo, [earthMat, earthMat, topMat, earthMat, earthMat, earthMat]) + block.position.set((a + b) / 2, -GROUND_THICK / 2, (GROUND_BACK + GROUND_FRONT) / 2) + block.receiveShadow = true + scene.add(block) + const lip = new THREE.Mesh(scaleUV(new THREE.PlaneGeometry(w + 0.1, 0.5), w / 4, 1), lipMat) + lip.position.set((a + b) / 2, -0.2, GROUND_FRONT + 0.01) + scene.add(lip) + } + // Pit interiors: dark shaft with a faint glow far below + warning stripes at the rims + const stripeTex = hazardStripeTexture() + const pitWallMat = new THREE.MeshBasicMaterial({ color: 0x06040d }) + const pitGlowMat = new THREE.MeshBasicMaterial({ map: softDotTexture('rgba(150,60,255,0.55)', 'rgba(150,60,255,0)'), transparent: true, depthWrite: false }) + for (const p of layout.hazards.pits) { + const w = p.x1 - p.x0 + const back = new THREE.Mesh(new THREE.PlaneGeometry(w, GROUND_THICK), pitWallMat) + back.position.set((p.x0 + p.x1) / 2, -GROUND_THICK / 2, GROUND_BACK + 0.02) + const glow = new THREE.Mesh(new THREE.PlaneGeometry(w * 1.6, 3), pitGlowMat) + glow.position.set((p.x0 + p.x1) / 2, -GROUND_THICK + 1, GROUND_BACK + 0.05) + scene.add(back, glow) + for (const ex of [p.x0, p.x1]) { + const stripe = new THREE.Mesh( + scaleUV(new THREE.BoxGeometry(0.22, 0.08, GROUND_DEPTH), 1, 1), + new THREE.MeshBasicMaterial({ map: stripeTex }), + ) + stripe.position.set(ex + (ex === p.x0 ? -0.11 : 0.11), 0.04, (GROUND_BACK + GROUND_FRONT) / 2) + scene.add(stripe) + } + } + + // Foreground bushes along the front edge (never overlap Kasimir β€” they sit low and in front) + const bushGeo = new THREE.IcosahedronGeometry(0.55, 0) + const bushMat = new THREE.MeshStandardMaterial({ color: 0x2f8f45, flatShading: true, roughness: 0.8 }) + const flowerMat = new THREE.MeshBasicMaterial({ color: 0xffd34d }) + const bushSpots = solidSpans(layout, L0, L1).flatMap(([a, b]) => { + const out: number[] = [] + for (let x = a + 1; x < b - 1; x += 3 + rnd() * 4) out.push(x) + return out + }) + const bushes = new THREE.InstancedMesh(bushGeo, bushMat, Math.max(1, bushSpots.length)) + const flowers = new THREE.InstancedMesh(new THREE.SphereGeometry(0.08, 6, 4), flowerMat, Math.max(1, bushSpots.length)) + bushSpots.forEach((x, i) => { + const s = 0.6 + rnd() * 0.6 + m4.compose(new THREE.Vector3(x, 0.15 * s, GROUND_FRONT - 0.3), q, new THREE.Vector3(s * 1.4, s * 0.7, s)) + bushes.setMatrixAt(i, m4) + m4.compose(new THREE.Vector3(x + 0.2, 0.45 * s, GROUND_FRONT - 0.05), q, new THREE.Vector3(1, 1, 1)) + flowers.setMatrixAt(i, m4) + }) + bushes.count = bushSpots.length + flowers.count = bushSpots.length + bushes.instanceMatrix.needsUpdate = true + flowers.instanceMatrix.needsUpdate = true + scene.add(bushes, flowers) + + // ---------- Crates ---------- + const crateMat = new THREE.MeshStandardMaterial({ map: crateTexture(), roughness: 0.75 }) + for (const c of layout.hazards.crates) { + const w = c.x1 - c.x0 + const crate = new THREE.Mesh(new THREE.BoxGeometry(w, c.top, 1.6), crateMat) + crate.position.set((c.x0 + c.x1) / 2, c.top / 2, 0) + crate.castShadow = true + crate.receiveShadow = true + scene.add(crate) + } + + // ---------- Spikes ---------- + const spikeCones: THREE.Matrix4[] = [] + const plateMat = new THREE.MeshStandardMaterial({ map: stripeTex, roughness: 0.6 }) + for (const s of layout.hazards.spikes) { + const w = s.x1 - s.x0 + const plate = new THREE.Mesh(scaleUV(new THREE.BoxGeometry(w, 0.14, 1.5), w / 1.2, 1), plateMat) + plate.position.set((s.x0 + s.x1) / 2, 0.07, 0) + plate.receiveShadow = true + scene.add(plate) + const n = Math.max(3, Math.round(w / 0.3)) + for (let i = 0; i < n; i++) { + for (const z of [-0.35, 0.35]) { + const x = s.x0 + (i + 0.5) * (w / n) + (z > 0 ? 0.06 : -0.06) + spikeCones.push(new THREE.Matrix4().makeTranslation(x, 0.14 + (s.top - 0.14) / 2, z)) + } + } + } + if (spikeCones.length) { + const cone = new THREE.InstancedMesh( + new THREE.ConeGeometry(0.13, 0.42, 6), + new THREE.MeshStandardMaterial({ color: 0xc9d2de, metalness: 0.85, roughness: 0.25, emissive: 0x551010, emissiveIntensity: 0.4 }), + spikeCones.length, + ) + spikeCones.forEach((mm, i) => cone.setMatrixAt(i, mm)) + cone.instanceMatrix.needsUpdate = true + cone.castShadow = true + scene.add(cone) + } + + // ---------- Checkpoint gates (decorative arch behind the path) ---------- + const stoneMat = new THREE.MeshStandardMaterial({ color: 0x8a8fa3, roughness: 0.7 }) + const gates: GateView[] = [] + const amberGlowTex = softDotTexture('rgba(255,190,80,0.9)', 'rgba(255,190,80,0)') + for (const g of layout.gates) { + const group = new THREE.Group() + for (const dx of [-1.7, 1.7]) { + const p = new THREE.Mesh(new THREE.CylinderGeometry(0.22, 0.28, 4, 10), stoneMat) + p.position.set(dx, 2, 0) + group.add(p) + } + const arch = new THREE.Mesh(new THREE.TorusGeometry(1.7, 0.2, 8, 28, Math.PI), stoneMat) + arch.position.set(0, 4, 0) + group.add(arch) + const lampMat = new THREE.MeshStandardMaterial({ color: 0xffb02e, emissive: 0xffb02e, emissiveIntensity: 0.15 }) + const lamp = new THREE.Mesh(new THREE.SphereGeometry(0.35, 16, 12), lampMat) + lamp.position.set(0, 5.9, 0) + group.add(lamp) + const glow = new THREE.Sprite(new THREE.SpriteMaterial({ map: amberGlowTex, transparent: true, opacity: 0, depthWrite: false, blending: THREE.AdditiveBlending })) + glow.scale.set(3.2, 3.2, 1) + glow.position.set(0, 5.9, 0.2) + group.add(glow) + group.position.set(g.x, 0, -3) + scene.add(group) + gates.push({ lamp, glow, x: g.x, lit: false }) + } + + // ---------- Zone signs ---------- + const postMat = new THREE.MeshStandardMaterial({ color: 0x6b4a2c, roughness: 0.9 }) + layout.zones.forEach((z, i) => { + const color = zoneColor(z.colorIndex) + const post = new THREE.Mesh(new THREE.CylinderGeometry(0.1, 0.12, 2.6, 8), postMat) + post.position.set(z.x0 + 2.5, 1.3, -1.8) + const face = new THREE.Mesh(new THREE.CircleGeometry(0.85, 32), new THREE.MeshStandardMaterial({ map: signTexture(String(i + 1), color), roughness: 0.6 })) + face.position.set(z.x0 + 2.5, 2.9, -1.7) + scene.add(post, face) + }) + + // ---------- Goals: flags (track) and chests (prize / checkpoint) ---------- + const flags: FlagView[] = [] + const chests: ChestView[] = [] + const chestTex = chestTexture() + const poleMat = new THREE.MeshStandardMaterial({ color: 0xd7dde8, metalness: 0.8, roughness: 0.3 }) + const goldMat = new THREE.MeshStandardMaterial({ color: 0xffc94a, metalness: 0.9, roughness: 0.25, emissive: 0x6b4a00, emissiveIntensity: 0.4 }) + layout.zones.forEach((z, i) => { + const goal = z.goal + const color = zoneColor(z.colorIndex) + const gx = goal.x + if (goal.kind === 'track') { + const base = new THREE.Mesh(new THREE.CylinderGeometry(0.7, 0.9, 0.5, 16), stoneMat) + base.position.set(gx, 0.25, -1.2) + const pole = new THREE.Mesh(new THREE.CylinderGeometry(0.09, 0.11, 5, 10), poleMat) + pole.position.set(gx, 2.5, -1.2) + const ball = new THREE.Mesh(new THREE.SphereGeometry(0.24, 16, 12), goldMat) + ball.position.set(gx, 5.1, -1.2) + const clothGeo = new THREE.PlaneGeometry(2.6, 1.6, 16, 4) + clothGeo.translate(1.3, 0, 0) + const cloth = new THREE.Mesh( + clothGeo, + new THREE.MeshStandardMaterial({ map: flagTexture(color, String(i + 1), goal.locked), side: THREE.DoubleSide, roughness: 0.8 }), + ) + cloth.position.set(gx + 0.08, goal.locked ? 2.6 : 4.2, -1.2) + if (goal.locked) cloth.rotation.z = -0.25 + cloth.castShadow = true + scene.add(base, pole, ball, cloth) + flags.push({ cloth, base: Float32Array.from(clothGeo.getAttribute('position').array as Float32Array), x: gx, locked: goal.locked, phase: i * 1.7 }) + } else { + const chest = new THREE.Group() + const bodyMat = new THREE.MeshStandardMaterial({ map: chestTex, color: goal.locked ? 0x8a8a8a : 0xffffff, roughness: 0.55, metalness: 0.15 }) + const body = new THREE.Mesh(new THREE.BoxGeometry(2, 1.2, 1.3), bodyMat) + body.position.y = 0.6 + body.castShadow = true + chest.add(body) + if (!goal.locked) { + const treasure = new THREE.Mesh(new THREE.BoxGeometry(1.7, 0.2, 1.0), goldMat) + treasure.position.set(0, 1.47, 0) + chest.add(treasure) + } + for (const dx of [-0.75, 0.75]) { + const band = new THREE.Mesh(new THREE.BoxGeometry(0.16, 1.24, 1.34), goldMat) + band.position.set(dx, 0.6, 0) + chest.add(band) + } + const lid = new THREE.Group() + lid.position.set(0, 1.2, -0.65) + const lidMesh = new THREE.Mesh(new THREE.CylinderGeometry(0.65, 0.65, 2, 16, 1, false, 0, Math.PI), bodyMat) + lidMesh.rotation.z = Math.PI / 2 + lidMesh.rotation.y = Math.PI / 2 + lidMesh.position.set(0, 0, 0.65) + lid.add(lidMesh) + chest.add(lid) + const glow = new THREE.Sprite(new THREE.SpriteMaterial({ map: amberGlowTex, transparent: true, opacity: 0, depthWrite: false, blending: THREE.AdditiveBlending })) + glow.scale.set(4, 4, 1) + glow.position.set(0, 1.9, 0.3) + chest.add(glow) + if (goal.locked) { + const lock = new THREE.Mesh(new THREE.BoxGeometry(0.4, 0.4, 0.15), goldMat) + lock.position.set(0, 1.0, 0.7) + const shackle = new THREE.Mesh(new THREE.TorusGeometry(0.14, 0.04, 6, 16, Math.PI), goldMat) + shackle.position.set(0, 1.2, 0.7) + chest.add(lock, shackle) + } + const plinth = new THREE.Mesh(new THREE.CylinderGeometry(1.5, 1.7, 0.3, 20), new THREE.MeshStandardMaterial({ color, roughness: 0.6 })) + plinth.position.y = -0.15 + 0.15 + chest.add(plinth) + body.position.y += 0.3 + lid.position.y += 0.3 + chest.position.set(gx, 0, -1.2) + scene.add(chest) + chests.push({ lid, glow, x: gx, locked: goal.locked, open: 0 }) + } + }) + + // ---------- Coins ---------- + const coinGeo = new THREE.TorusGeometry(0.42, 0.13, 12, 28) + const coinMats = { + open: new THREE.MeshStandardMaterial({ color: 0xffc23a, metalness: 0.9, roughness: 0.2, emissive: 0x8a5a00, emissiveIntensity: 0.55 }), + done: new THREE.MeshStandardMaterial({ color: 0x5fe07a, metalness: 0.7, roughness: 0.25, emissive: 0x1d6b2d, emissiveIntensity: 0.6 }), + locked: new THREE.MeshStandardMaterial({ color: 0x777b88, metalness: 0.4, roughness: 0.6, transparent: true, opacity: 0.45 }), + } + const coins: CoinView[] = layout.coins.map(c => { + const mat = !c.available ? coinMats.locked : c.done ? coinMats.done : coinMats.open + const mesh = new THREE.Mesh(coinGeo, mat) + mesh.position.set(c.x, COIN_Y, 0) + mesh.castShadow = c.available + scene.add(mesh) + return { mesh, baseY: COIN_Y, alive: true, collectable: c.available } + }) + + // ---------- Per-frame animation ---------- + const fog = scene.fog as THREE.Fog | null + const target = { top: new THREE.Color(), horizon: new THREE.Color(), fog: new THREE.Color() } + let lastZone = -2 + + function update(t: number, dt: number, camX: number, kasX: number, zoneIndex: number): void { + sky.position.x = camX + stars.position.x = camX * 0.92 + moon.position.x = camX * 0.95 + 40 + starMat.opacity = 0.7 + Math.sin(t * 1.3) * 0.15 + + for (const c of clouds) { + c.position.x += c.userData.speed * dt + if (c.position.x > camX + 160) c.position.x -= 320 + } + + if (zoneIndex !== lastZone && zoneIndex >= 0) { + const p = zonePalette(layout.zones[zoneIndex].colorIndex) + target.top.copy(p.top) + target.horizon.copy(p.horizon) + target.fog.copy(p.fog) + if (lastZone === -2) { + skyUniforms.uTop.value.copy(p.top) + skyUniforms.uHorizon.value.copy(p.horizon) + fog?.color.copy(p.fog) + } + lastZone = zoneIndex + } + const k = Math.min(1, dt * 1.5) + skyUniforms.uTop.value.lerp(target.top, k) + skyUniforms.uHorizon.value.lerp(target.horizon, k) + fog?.color.lerp(target.fog, k) + + for (const c of coins) { + if (!c.alive) continue + c.mesh.rotation.y += dt * 2.6 + c.mesh.position.y = c.baseY + Math.sin(t * 2.2 + c.mesh.position.x * 0.7) * 0.1 + } + + for (const f of flags) { + if (f.locked) continue + const pos = f.cloth.geometry.getAttribute('position') as THREE.BufferAttribute + const arr = pos.array as Float32Array + for (let i = 0; i < pos.count; i++) { + const bx = f.base[i * 3] + const amp = bx * 0.12 + arr[i * 3 + 2] = f.base[i * 3 + 2] + Math.sin(t * 4 + bx * 2.2 + f.phase) * amp + arr[i * 3 + 1] = f.base[i * 3 + 1] + Math.sin(t * 3 + bx * 1.6 + f.phase) * amp * 0.25 + } + pos.needsUpdate = true + f.cloth.geometry.computeVertexNormals() + } + + for (const ch of chests) { + const near = !ch.locked && Math.abs(kasX - ch.x) < 3.2 + ch.open += ((near ? 1 : 0) - ch.open) * Math.min(1, dt * 5) + ch.lid.rotation.x = -ch.open * 1.15 + ;(ch.glow.material as THREE.SpriteMaterial).opacity = ch.locked ? 0 : 0.25 + ch.open * 0.6 + Math.sin(t * 3) * 0.05 + } + + for (const g of gates) { + if (!g.lit && kasX > g.x) g.lit = true + const mat = g.lamp.material as THREE.MeshStandardMaterial + const targetI = g.lit ? 1.4 + Math.sin(t * 4) * 0.2 : 0.15 + mat.emissiveIntensity += (targetI - mat.emissiveIntensity) * Math.min(1, dt * 6) + ;(g.glow.material as THREE.SpriteMaterial).opacity = g.lit ? 0.85 : 0 + } + } + + return { coins, flags, chests, gates, update } +} diff --git a/hugo-apps/src/app-space-game/sound.ts b/hugo-apps/src/app-space-game/sound.ts index 458872665..e3fb0308e 100644 --- a/hugo-apps/src/app-space-game/sound.ts +++ b/hugo-apps/src/app-space-game/sound.ts @@ -8,8 +8,12 @@ export interface SoundHandle { enabled: boolean /** Toggle enabled state. */ toggle(): void - /** Play a short blip sound. No-op if disabled or no AudioContext. */ + /** Play a short blip sound (ring pickup). No-op if disabled or no AudioContext. */ blip(): void + jump(): void + hit(): void + fall(): void + goal(): void } export function createSound(): SoundHandle { @@ -66,29 +70,51 @@ export function createSound(): SoundHandle { }, toggle() { enabled = !enabled + // Toggling is itself a user gesture. + gestureReceived = true }, blip() { - // No-op unless enabled AND a user gesture has occurred - if (!enabled || !gestureReceived) return - try { - const c = getCtx() - if (!c) return - if (c.state === 'suspended') return - // Short sine blip: ~50ms, 880Hz β†’ 440Hz + tone([[880, 440]], 0.08, 'sine', 0.18) + }, + jump() { + tone([[320, 640]], 0.12, 'square', 0.06) + }, + hit() { + tone([[220, 90]], 0.25, 'sawtooth', 0.12) + }, + fall() { + tone([[600, 120]], 0.5, 'triangle', 0.14) + }, + goal() { + tone([[523, 523], [659, 659], [784, 784], [1047, 1047]], 0.1, 'square', 0.07, 0.09) + }, + } + + /** + * Plays a short sequence of sweeps. Each entry is [startHz, endHz]. + * No-op unless enabled AND a user gesture has occurred. NEVER throws. + */ + function tone(sweeps: Array<[number, number]>, dur: number, type: OscillatorType, vol: number, gap = 0): void { + if (!enabled || !gestureReceived) return + try { + const c = getCtx() + if (!c || c.state === 'suspended') return + sweeps.forEach(([f0, f1], i) => { + const t0 = c.currentTime + i * (gap || dur) const osc = c.createOscillator() const gain = c.createGain() osc.connect(gain) gain.connect(c.destination) - osc.type = 'sine' - osc.frequency.setValueAtTime(880, c.currentTime) - osc.frequency.exponentialRampToValueAtTime(440, c.currentTime + 0.05) - gain.gain.setValueAtTime(0.18, c.currentTime) - gain.gain.exponentialRampToValueAtTime(0.001, c.currentTime + 0.08) - osc.start(c.currentTime) - osc.stop(c.currentTime + 0.09) - } catch { - // NEVER throw β€” audio is best-effort - } - }, + osc.type = type + osc.frequency.setValueAtTime(f0, t0) + if (f1 !== f0) osc.frequency.exponentialRampToValueAtTime(f1, t0 + dur * 0.8) + gain.gain.setValueAtTime(vol, t0) + gain.gain.exponentialRampToValueAtTime(0.001, t0 + dur) + osc.start(t0) + osc.stop(t0 + dur + 0.02) + }) + } catch { + // audio is best-effort + } } } diff --git a/hugo-apps/src/app-space-game/textures.ts b/hugo-apps/src/app-space-game/textures.ts new file mode 100644 index 000000000..0b7346430 --- /dev/null +++ b/hugo-apps/src/app-space-game/textures.ts @@ -0,0 +1,282 @@ +// Procedural canvas textures for the arcade scene. Every generator degrades to a +// blank texture when a 2D context is unavailable (tests / exotic browsers). + +import * as THREE from 'three' + +function canvas(w: number, h: number): { cv: HTMLCanvasElement; g: CanvasRenderingContext2D | null } { + const cv = document.createElement('canvas') + cv.width = w + cv.height = h + let g: CanvasRenderingContext2D | null = null + try { g = cv.getContext('2d') } catch { g = null } + return { cv, g } +} + +function finish(cv: HTMLCanvasElement, opts: { repeat?: boolean; srgb?: boolean } = {}): THREE.CanvasTexture { + const t = new THREE.CanvasTexture(cv) + if (opts.srgb !== false) t.colorSpace = THREE.SRGBColorSpace + if (opts.repeat) { + t.wrapS = THREE.RepeatWrapping + t.wrapT = THREE.RepeatWrapping + } + t.anisotropy = 4 + return t +} + +/** Mulberry32 β€” deterministic texture noise so scenes look the same every load. */ +export function seeded(seed: number): () => number { + let s = seed >>> 0 + return () => { + s = (s + 0x6d2b79f5) | 0 + let t = Math.imul(s ^ (s >>> 15), 1 | s) + t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t + return ((t ^ (t >>> 14)) >>> 0) / 4294967296 + } +} + +/** Earth/stone side of the ground with layered strata and pebbles. */ +export function groundSideTexture(): THREE.CanvasTexture { + const { cv, g } = canvas(256, 256) + if (g) { + const rnd = seeded(11) + const grad = g.createLinearGradient(0, 0, 0, 256) + grad.addColorStop(0, '#5b3b2a') + grad.addColorStop(1, '#2a1a14') + g.fillStyle = grad + g.fillRect(0, 0, 256, 256) + for (let band = 0; band < 5; band++) { + g.fillStyle = `rgba(0,0,0,${0.08 + band * 0.03})` + const y = 40 + band * 46 + g.beginPath() + g.moveTo(0, y) + for (let x = 0; x <= 256; x += 16) g.lineTo(x, y + Math.sin(x * 0.05 + band) * 5) + g.lineTo(256, y + 18) + g.lineTo(0, y + 18) + g.fill() + } + for (let i = 0; i < 70; i++) { + const x = rnd() * 256, y = 20 + rnd() * 236, r = 2 + rnd() * 6 + g.fillStyle = `rgba(${150 + rnd() * 60},${120 + rnd() * 50},${100 + rnd() * 40},${0.25 + rnd() * 0.3})` + g.beginPath() + g.ellipse(x, y, r * 1.3, r, rnd() * 3, 0, Math.PI * 2) + g.fill() + } + } + return finish(cv, { repeat: true }) +} + +/** Grass top strip (tileable horizontally). */ +export function grassTexture(): THREE.CanvasTexture { + const { cv, g } = canvas(256, 64) + if (g) { + const rnd = seeded(5) + g.fillStyle = '#3fae4a' + g.fillRect(0, 0, 256, 64) + for (let i = 0; i < 260; i++) { + const x = rnd() * 256 + g.strokeStyle = rnd() > 0.5 ? '#5fd36a' : '#2c8a37' + g.lineWidth = 2 + g.beginPath() + g.moveTo(x, 64) + g.lineTo(x + (rnd() - 0.5) * 8, 64 - 10 - rnd() * 30) + g.stroke() + } + } + return finish(cv, { repeat: true }) +} + +/** Wooden crate face with planks, cross brace and metal corners. */ +export function crateTexture(): THREE.CanvasTexture { + const { cv, g } = canvas(256, 256) + if (g) { + g.fillStyle = '#b5773b' + g.fillRect(0, 0, 256, 256) + for (let i = 0; i < 4; i++) { + g.fillStyle = i % 2 ? '#a96c33' : '#bf8245' + g.fillRect(0, i * 64, 256, 62) + g.fillStyle = 'rgba(60,30,10,0.5)' + g.fillRect(0, i * 64 + 62, 256, 2) + } + g.strokeStyle = '#7a4a1e' + g.lineWidth = 28 + g.beginPath(); g.moveTo(24, 24); g.lineTo(232, 232); g.stroke() + g.strokeStyle = '#8f5826' + g.lineWidth = 18 + g.beginPath(); g.moveTo(24, 24); g.lineTo(232, 232); g.stroke() + g.strokeStyle = '#6b3f17' + g.lineWidth = 24 + g.strokeRect(12, 12, 232, 232) + g.fillStyle = '#9aa6b5' + for (const [x, y] of [[0, 0], [216, 0], [0, 216], [216, 216]]) { + g.fillRect(x, y, 40, 40) + g.fillStyle = '#c9d3df' + g.fillRect(x + 4, y + 4, 32, 6) + g.fillStyle = '#5c6572' + g.beginPath(); g.arc(x + 20, y + 22, 5, 0, Math.PI * 2); g.fill() + g.fillStyle = '#9aa6b5' + } + } + return finish(cv) +} + +/** Treasure chest face: dark red-brown planks with gold trim and studs (distinct from crates). */ +export function chestTexture(): THREE.CanvasTexture { + const { cv, g } = canvas(256, 256) + if (g) { + for (let i = 0; i < 6; i++) { + g.fillStyle = i % 2 ? '#6e2a17' : '#7d321c' + g.fillRect(i * 43, 0, 43, 256) + g.fillStyle = 'rgba(0,0,0,0.35)' + g.fillRect(i * 43 + 41, 0, 2, 256) + } + const gold = g.createLinearGradient(0, 0, 0, 256) + gold.addColorStop(0, '#ffe79a') + gold.addColorStop(0.5, '#e0a52a') + gold.addColorStop(1, '#a8701a') + g.strokeStyle = gold + g.lineWidth = 22 + g.strokeRect(11, 11, 234, 234) + g.fillStyle = gold + g.fillRect(0, 112, 256, 22) + g.fillStyle = '#fff3c4' + for (const [x, y] of [[11, 11], [245, 11], [11, 245], [245, 245], [11, 123], [245, 123]]) { + g.beginPath(); g.arc(x, y, 6, 0, Math.PI * 2); g.fill() + } + } + return finish(cv) +} + +/** Yellow/black hazard stripes for spike bases and pit edges. */ +export function hazardStripeTexture(): THREE.CanvasTexture { + const { cv, g } = canvas(128, 32) + if (g) { + g.fillStyle = '#ffc61a' + g.fillRect(0, 0, 128, 32) + g.fillStyle = '#1a1a1a' + for (let x = -32; x < 160; x += 32) { + g.beginPath() + g.moveTo(x, 32); g.lineTo(x + 16, 0); g.lineTo(x + 32, 0); g.lineTo(x + 16, 32) + g.fill() + } + } + return finish(cv, { repeat: true }) +} + +/** Skyline window grid: lit and unlit windows, used as an emissive map on buildings. */ +export function windowsTexture(seed: number): THREE.CanvasTexture { + const { cv, g } = canvas(128, 256) + if (g) { + const rnd = seeded(seed) + g.fillStyle = '#000' + g.fillRect(0, 0, 128, 256) + for (let y = 8; y < 248; y += 16) { + for (let x = 8; x < 120; x += 16) { + if (rnd() < 0.3) { + const warm = rnd() < 0.7 + g.fillStyle = warm ? `rgba(255,${200 + rnd() * 40},${120 + rnd() * 60},1)` : 'rgba(140,210,255,1)' + g.fillRect(x, y, 9, 10) + } + } + } + } + return finish(cv, { repeat: true }) +} + +/** Soft round blob β€” clouds, glows, sparkles, shadows. */ +export function softDotTexture(inner = 'rgba(255,255,255,1)', outer = 'rgba(255,255,255,0)'): THREE.CanvasTexture { + const { cv, g } = canvas(64, 64) + if (g) { + const grad = g.createRadialGradient(32, 32, 0, 32, 32, 32) + grad.addColorStop(0, inner) + grad.addColorStop(1, outer) + g.fillStyle = grad + g.fillRect(0, 0, 64, 64) + } + return finish(cv) +} + +/** Four-point sparkle star. */ +export function sparkleTexture(): THREE.CanvasTexture { + const { cv, g } = canvas(64, 64) + if (g) { + const grad = g.createRadialGradient(32, 32, 0, 32, 32, 30) + grad.addColorStop(0, 'rgba(255,255,255,1)') + grad.addColorStop(0.25, 'rgba(255,230,140,0.9)') + grad.addColorStop(1, 'rgba(255,200,60,0)') + g.fillStyle = grad + g.beginPath() + g.moveTo(32, 0); g.quadraticCurveTo(36, 28, 64, 32); g.quadraticCurveTo(36, 36, 32, 64) + g.quadraticCurveTo(28, 36, 0, 32); g.quadraticCurveTo(28, 28, 32, 0) + g.fill() + } + return finish(cv) +} + +/** Fluffy cloud built from overlapping soft circles. */ +export function cloudTexture(seed: number): THREE.CanvasTexture { + const { cv, g } = canvas(256, 128) + if (g) { + const rnd = seeded(seed) + for (let i = 0; i < 9; i++) { + const x = 50 + rnd() * 156, y = 60 + (rnd() - 0.5) * 30, r = 26 + rnd() * 30 + const grad = g.createRadialGradient(x, y, 0, x, y, r) + grad.addColorStop(0, 'rgba(255,255,255,0.85)') + grad.addColorStop(1, 'rgba(255,255,255,0)') + g.fillStyle = grad + g.beginPath(); g.arc(x, y, r, 0, Math.PI * 2); g.fill() + } + } + return finish(cv) +} + +/** Flag cloth: zone colour with a light border and a big zone number. */ +export function flagTexture(color: string, label: string, locked: boolean): THREE.CanvasTexture { + const { cv, g } = canvas(256, 160) + if (g) { + g.fillStyle = locked ? '#5a5f6b' : color + g.fillRect(0, 0, 256, 160) + g.fillStyle = 'rgba(255,255,255,0.18)' + for (let i = 0; i < 256; i += 32) g.fillRect(i, 0, 16, 160) + g.strokeStyle = 'rgba(255,255,255,0.85)' + g.lineWidth = 8 + g.strokeRect(6, 6, 244, 148) + g.fillStyle = '#ffffff' + g.font = 'bold 92px system-ui, sans-serif' + g.textAlign = 'center' + g.textBaseline = 'middle' + g.fillText(locked ? 'πŸ”’' : label, 128, 84) + } + return finish(cv) +} + +/** Round sign face for the zone number at the start of each zone. */ +export function signTexture(text: string, color: string): THREE.CanvasTexture { + const { cv, g } = canvas(256, 256) + if (g) { + g.fillStyle = '#f2e3c4' + g.beginPath(); g.arc(128, 128, 120, 0, Math.PI * 2); g.fill() + g.lineWidth = 16 + g.strokeStyle = color + g.stroke() + g.fillStyle = color + g.font = 'bold 130px system-ui, sans-serif' + g.textAlign = 'center' + g.textBaseline = 'middle' + g.fillText(text, 128, 138) + } + return finish(cv) +} + +/** Vertical sky gradient (top β†’ horizon). */ +export function skyTexture(top: string, mid: string, horizon: string): THREE.CanvasTexture { + const { cv, g } = canvas(4, 256) + if (g) { + const grad = g.createLinearGradient(0, 0, 0, 256) + grad.addColorStop(0, top) + grad.addColorStop(0.55, mid) + grad.addColorStop(1, horizon) + g.fillStyle = grad + g.fillRect(0, 0, 4, 256) + } + return finish(cv) +} diff --git a/hugo-apps/src/app-space-game/types.ts b/hugo-apps/src/app-space-game/types.ts index 19c47ce7e..01d40ee23 100644 --- a/hugo-apps/src/app-space-game/types.ts +++ b/hugo-apps/src/app-space-game/types.ts @@ -80,8 +80,10 @@ export interface Goal { trackId: string } -export interface Obstacle { x: number; w: number; h: number } // x in coin-index units -export interface Pit { x: number; w: number } // x in coin-index units +// x in coin-index units (hazards sit between coins, e.g. 1.5); w/h in world units. +// crate = solid block you must jump over or onto; spike = low hazard that costs rings. +export interface Obstacle { x: number; w: number; h: number; kind: 'crate' | 'spike' } +export interface Pit { x: number; w: number } export interface Zone { id: string diff --git a/hugo/static/images/app-space-game/kasimir-atlas.png b/hugo/static/images/app-space-game/kasimir-atlas.png index 05cb3103770c0b72302985a18b2bb82ef017e65d..f79c89626b904087361b39b967f835592cd59fc3 100644 GIT binary patch literal 872686 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%0A`DF1)(w4Rj$s=aBp~WJ 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

l(eY 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 z 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ℑrN}JDe=Q`AR|_w=~at-97XlVkYKO`a1WV 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�+wMt_3?Qdca!sZd!x|8B!| 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�K~ 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�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