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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions arcade.css

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 9 additions & 1 deletion arcade.js
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,8 @@
};
const scoreMessages = ['The leaderboard just felt that!', 'New legend status unlocked!', 'That record never stood a chance!', 'History, officially rewritten!'];
const notificationQueue = [];
const notifiedAchievementIds = new Set();
let notifiedAchievementUserId = null;
let showingNotification = false;
const showNextNotification = () => {
const notification = notificationQueue.shift();
Expand Down Expand Up @@ -273,7 +275,10 @@
notificationQueue.push(...notifications);
if (!showingNotification) showNextNotification();
};
const showUnlocks = unlocked => enqueueNotifications(unlocked.map(detail => ({ type: 'achievement', detail })));
const showUnlocks = unlocked => {
const fresh = unlocked.filter(detail => { if (!detail?.id || notifiedAchievementIds.has(detail.id)) return false; notifiedAchievementIds.add(detail.id); return true; });
Comment thread
bashmohandes marked this conversation as resolved.
enqueueNotifications(fresh.map(detail => ({ type: 'achievement', detail })));
};
const showTopScore = topScore => { if (topScore) enqueueNotifications([{ type: 'top-score', detail: topScore }]); };
const notifyResult = result => {
if (!result || typeof result !== 'object') return result;
Expand Down Expand Up @@ -304,6 +309,8 @@
const settleAuthentication = value => { const waiters = authenticationWaiters; authenticationWaiters = []; waiters.forEach(resolve => resolve(value)); };
const saveManager = game && window.ArcadeSaveManager ? window.ArcadeSaveManager.create({ game, api, user: () => currentUser, signIn: requestAuthentication }) : null;
const render = () => {
const userId = currentUser?.id ?? null;
if (userId !== notifiedAchievementUserId) { notifiedAchievementUserId = userId; notifiedAchievementIds.clear(); }
account.replaceChildren();
account.append(appearanceButton);
if (game && audio) { updateAudioControls(); account.append(audioButton); }
Expand Down Expand Up @@ -334,6 +341,7 @@
signIn: requestAuthentication,
saves: saveManager,
record: async result => { if (!currentUser) return null; return notifyResult(await api('/api/results', { method: 'POST', body: JSON.stringify(result) })); },
checkpoint: async checkpoint => { if (!currentUser) return null; return notifyResult(await api('/api/achievement-checkpoints', { method: 'POST', body: JSON.stringify(checkpoint) })); },
achievements: loadAchievements,
notifyAchievements: showUnlocks,
notifyResult,
Expand Down
20 changes: 19 additions & 1 deletion battle-tanks/scripts/app.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 7 additions & 0 deletions docs/adr/0003-results-and-achievements.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,10 @@ flowchart LR
client-reported results still require plausibility validation and catalog changes
are deployments rather than data administration.

Irrevocable single-run milestones may be evaluated before a game ends through
a rate-limited checkpoint API. The API accepts bounded game facts rather than
achievement IDs, applies game-specific consistency checks, and evaluates only
catalog entries explicitly marked for live unlocks. It does not write game
results or advance cumulative completion rules; final result processing remains
the fallback for clients that do not send checkpoints.

6 changes: 6 additions & 0 deletions docs/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,12 @@ sequenceDiagram
A-->>G: toast notifications
```

Irrevocable in-run milestones use the same validation and catalog path through
a bounded achievement checkpoint. Checkpoints can unlock only catalog entries
marked as live; they never create results, scores, leaderboard entries, or
cumulative finish progress. Terminal conditions such as wins and completed-run
counts remain exclusively in the result flow.

Registration/login uses scrypt passcode hashes. A random session token is sent
only in an HttpOnly, SameSite=Strict cookie; only its SHA-256 hash is stored.
Profile changes revoke existing sessions and rotate the current one. Public
Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "javascript-playground",
"version": "1.1.0",
"version": "1.2.0",
"private": true,
"description": "Browser games with authoritative online multiplayer",
"main": "server/index.js",
Expand Down
Loading