Skip to content

Add Battle Tanks power‑up achievements and authoritative per‑player statistics - #69

Merged
bashmohandes merged 2 commits into
masterfrom
codex/add-battle-tanks-achievements-and-statistics-jjkwov
Aug 16, 2026
Merged

bashmohandes merged 2 commits into
masterfrom
codex/add-battle-tanks-achievements-and-statistics-jjkwov

Conversation

@bashmohandes

Copy link
Copy Markdown
Owner

Motivation

  • Introduce stable, measurable Battle Tanks achievements for new power‑up mechanics and ensure their conditions are unambiguous and server‑verifiable.
  • Track per‑player, authoritative power‑up statistics inside core mechanics so achievements are driven by game logic rather than client UI signals.
  • Validate and normalize Battle Tanks result payloads to keep local submissions untrusted while allowing trusted online results to provide authoritative achievement data.

Description

  • Added eleven new Battle Tanks achievement catalog entries and extended the matcher with narrow operators (lengthGte, includes, gt) so array membership and compound boundaries can be expressed declaratively in server/achievements.js.
  • Extended the Battle Tanks core to record per‑player statistics inside authoritative mechanics in battle-tanks/scripts/game.js, including powerUpsAcquired, powerUpsUsed, powerUpTypesUsed (internal Set, serialized bounded array), shieldDamageAbsorbed, healthRestored, invisibilityActivations, laserRicochetHits, laserSelfDamage, homingHits, heavyProjectileMaxDamage, and poweredHits; these counters are updated only by pickup/activation, healing, shield absorption, weapon resolution and reset on match/rematch.
  • Ensured snapshots serialize Set fields safely and included the bounded statistics in snapshot to be used by server rooms and result normalization.
  • Included the local signed‑in player’s normalized power‑up statistics in submitResult in battle-tanks/scripts/app.js while forcing invisibilityActivations to 0 for local results to prevent local spoofing.
  • Added strict validation and normalization for Battle Tanks power‑up statistics in server/accounts.js, including known ID validation, deduplication, bounded lengths, numeric range checks, and cross‑field invariants (for example powerUpsUsed <= powerUpsAcquired, powerUpTypesUsed.length <= powerUpsUsed, and local invisibilityActivations === 0), and preserved the trusted online path for authoritative results.
  • Propagated authoritative statistics into rooms via server/battle-tanks-rooms.js: extended room.stats to track per‑side power‑up aggregates, introduced syncPowerStatistics to fill room.stats from room.game.statistics, and included these fields when finish calls recordResult so only server‑derived values are persisted for online matches.
  • Added and updated unit tests to cover the new achievements, validation rules, room behavior, and core statistic attribution in tests/achievements.test.js, tests/accounts.test.js, tests/battle-tanks.test.js, and tests/battle-tanks-rooms.test.js.

Testing

  • Ran npm test (full test suite); all automated tests passed (155 tests passing).
  • Ran npm run check (static checks); it completed successfully.
  • Updated unit tests include tests/achievements.test.js, tests/accounts.test.js, tests/battle-tanks.test.js, and tests/battle-tanks-rooms.test.js, and they were executed as part of the test runs and validated the new achievement boundaries, server validation, authoritative room statistics, and reset/rematch behavior.

Codex Task

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 2d0879205a

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

canvas.addEventListener('pointerdown',event=>{if(mode==='online'||state.phase!=='aiming')return;dragging=true;canvas.setPointerCapture(event.pointerId);});canvas.addEventListener('pointermove',event=>{if(!dragging||state.phase!=='aiming')return;const rect=canvas.getBoundingClientRect(),logicalX=(event.clientX-rect.left)*WIDTH/rect.width,tank=state.tanks[state.activePlayer],bounds=tankBounds(state,state.activePlayer);tank.x=Math.max(bounds.min,Math.min(bounds.max,logicalX-TANK_W/2));tank.y=tankYAt(state,tank);collectPickup(state,state.activePlayer);sync();render();});canvas.addEventListener('pointerup',()=>dragging=false);canvas.addEventListener('pointercancel',()=>dragging=false);
function optionalResultStatistics(statistics,side){if(!statistics)return {};const weapons=statistics.weapons?.[side],splashDamage=statistics.splashDamage?.[side],healing=statistics.healing?.[side],powerUps=statistics.powerUps?.[side];return { ...(weapons&&typeof weapons==='object'?{weapons}:{ }), ...(Number.isSafeInteger(splashDamage)?{splashDamage}:{ }), ...(Number.isSafeInteger(healing)?{healing}:{ }), ...(Number.isSafeInteger(powerUps)?{powerUps}:{ }) };}
async function submitResult(){if(state.resultSubmitted||state.phase!=='game-over'||state.draw||!window.Arcade?.user())return;state.resultSubmitted=true;const damageTaken=state.damageTaken?.[0]||0,details={mode:'local',winner:state.winner+1,turns:state.shots,shots:state.shots,hits:state.hits,seconds:Math.max(1,Math.min(7200,Math.round((Date.now()-state.startedAt)/1000))),damageTaken,...optionalResultStatistics(state.statistics,0)};try{await window.Arcade.record({game:'battletanks',won:state.winner===0,details});}catch{/* Saving an arcade result must never block a local rematch. */}}
function optionalResultStatistics(statistics,side){if(!statistics)return {};const result={},fields=['powerUpsAcquired','powerUpsUsed','shieldDamageAbsorbed','healthRestored','invisibilityActivations','laserRicochetHits','laserSelfDamage','homingHits','heavyProjectileMaxDamage','poweredHits'],weapons=statistics.weapons?.[side];if(weapons&&typeof weapons==='object')result.weapons=weapons;for(const name of fields)if(Number.isSafeInteger(statistics[name]?.[side]))result[name]=statistics[name][side];const types=statistics.powerUpTypesUsed?.[side];result.powerUpTypesUsed=Array.isArray(types)?[...new Set(types.filter(id=>POWER_UP_CATALOG[id]))]:[];return result;}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Serialize Set-based power-up types for local results

In local matches, state.statistics.powerUpTypesUsed[side] is a Set, but this helper accepts only arrays and therefore always submits powerUpTypesUsed: []. A signed-in player who legitimately uses three distinct types locally can never unlock the new “Full Deck” achievement; handle the core Set here as well as the array form received in snapshots.

Useful? React with 👍 / 👎.

Comment thread server/accounts.js
Comment on lines +179 to +180
if (powerUpsUsed > powerUpsAcquired || powerUpTypesUsed.length > powerUpsUsed || counters.invisibilityActivations > powerUpsUsed || counters.healthRestored > powerUpsUsed * 35 || counters.shieldDamageAbsorbed > powerUpsUsed * 60 || counters.invisibilityActivations > powerUpTypesUsed.includes('invisibility') * powerUpsUsed || (details.mode === 'local' && counters.invisibilityActivations !== 0)) throw new Error('Invalid Battle Tanks power-up statistics.');
if (counters.laserRicochetHits > (optional.weapons?.laser || 0) || counters.homingHits > (optional.weapons?.homing || 0) || counters.heavyProjectileMaxDamage > 0 && !(optional.weapons?.['heavy-shell'] > 0)) throw new Error('Invalid Battle Tanks weapon statistics.');

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Require power-up use before accepting powered hits

For an untrusted local submission, poweredHits is constrained only by shots, so a payload with powerUpsAcquired: 0, powerUpsUsed: 0, an empty type list, and poweredHits: 2 is accepted and unlocks “Power Player” on a win even though that state is impossible in the game. Add a cross-field invariant tying positive powered hits to at least one used power-up or applicable powered weapon/effect.

Useful? React with 👍 / 👎.

@bashmohandes
bashmohandes merged commit d273bed into master Aug 16, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant