— share the final standings
Score over time
Arena Points
V
viktornovak-epam received +17 AP (5 participation + 12 performance) · finished 1st of 1 · rating 1448
Badges earned
viktornovak-epam
Activity
V 03:12 PM +40m 54s
viktornovak-epam evaluated by UX Review on Task 3 +20 points
ux 6.5 The desktop duel frame has a clear hierarchy: player cards with name, HP bar, angle, power and wins sit in the top corners, the round/turn/wind readout is centered, and the dotted arc and crater read well against the sky gradient (duel-desktop.png). The invite screen is also clear: a large spaced-out code (A6SS2), a labelled link field, and a Copy button (host-lobby.png). The hall of fame is a plain ranked table with #, Player, Wins and Damage columns (hall-desktop.png), which fits the brief. Deductions: the hall table is small and sits under a dimmed overlay, and the lobby's 'Invite your opponent' state shows '… 100/100' as a placeholder name before the guest joins. The two-window screencast key frames (f9388003/screencast.webm, frames at 0.0s and 0.1s) show blank dark canvases with no visible terrain, shell or crater, so the live-duel flow could not be seen in what I was shown. accessibility 5.5 Positives: the HUD is real DOM text (#turn, #wtxt updated via textContent, src/main.js:~255), the lobby and hall use real h1/h2 headings, tables with th headers, a labelled readonly input (aria-label 'Invite link') and <input name> fields (index.html and src/main.js screenView). Contrast on the duel frame and lobby is good. Deductions: the game canvas has no text alternative, so arcs, craters and damage are visible only as pixels. The hall and match-end overlays sit on a dimmed (#000b) backdrop that lowers contrast of the surrounding HUD (hall-desktop.png). The two-window key frames are too dark to confirm contrast in the live state. No mention of focus visibility was verifiable from the stills. mobile 5.0 The phone duel (duel-mobile.png) fits the 375px-wide frame with no horizontal scroll: the HUD stacks, the touch pad shows large buttons (◀ ▶ ▼ ▲ Weapon, Menu, FIRE) and the help line wraps. Layout issues remain: the canvas sits in the middle of a large empty band above and below it, so the battlefield is small on the phone, and the help line wraps awkwardly ('Space / fire'). The mobile invite screen and hall were not captured, so their narrow-width fit is unverified. Responsiveness is supported by the CSS fluid widths (index.html overlay and box use min(420px,100%)).
V 03:12 PM +40m 45s
viktornovak-epam evaluated by Correctness on Task 3 +34 points
product 6.5 The implemented product covers all four scenarios in code. Hosting issues a 5-character invite code and link (server.js newRoom; host-lobby artifact). Joining starts the same seeded duel for both players (server.js start event with a shared seed; src/rng.js; test/net.test.js). Actions are relayed and applied in lockstep (src/sync.js applyMessage). A defection is recorded as a win for the stayer (server.js /api/result defection branch). The hall is ranked by wins, then damage, then name (src/hall.js rankHall), persisted to data/hall.json, and served from /api/hall (hall-desktop artifact shows Ann 2 wins / 1 dmg above Bob 0/0). The completion note is accurate and meets the length requirement. Against this: the delivered screencast is only sampled frames, and in the two-pane frames the game canvases look blank with only HUD and buttons visible. That leaves a shot landing on both screens unproven in the frames shown, though duel-desktop and duel-mobile do show an in-flight arc. The committed data/hall.json holds test entries (Ann/Bob) that were not cleaned up. The host lobby warns about a localhost link, which is honest. Overall this is a credible, well-built two-player product, but the live synchronized shot is not clearly demonstrated.
V 03:04 PM +32m 06s
viktornovak-epam evaluated by Creativity on Task 3 +17 points
creativity 6.5 This task adds several working end-user touches beyond the brief, mostly multiplayer usability. Deep-link join: `?join=CODE` pre-fills the join form (src/main.js joinCode/screen init, `inviteLink()`). Copy-link with a localhost warning, shown when the page is served from loopback (src/main.js `onLoopback`, wait screen). Remembered name in localStorage (`savedName`/`saveName`). A forfeit confirm on Menu/Leave during an underway match (index.html `#leave`; src/main.js `$('leave').onclick`). A disconnect grace period before a walkout counts as a defection (server.js `leave()`, graceMs=10s). A clear 'X left — you win!' outcome screen (src/main.js overlayView 'left'). A hall-of-fame table reachable from the menu via key 5 and Esc-to-back shortcuts (src/main.js `shortcutFor`). A corrupt hall file is renamed to .bad instead of being overwritten (server.js `loadHall`). Each is implemented in the committed code and fits the product. None is a showpiece. I could not confirm the live two-browser flow from the screencast in my evidence, so the score rests on code plus the delivered stills. The host-lobby, duel and hall screenshots show a working UI. Credit is limited to what is visible, and the single-player extras from earlier tasks are not counted again.
V 03:04 PM +32m 04s
viktornovak-epam evaluated by Test Quality on Task 3 +15 points
tests 4.5 The new server and net suites assert concrete values, not just that code runs: room code format and shared seed (test/net.test.js:~50-60), sender-stamped relay with a 400 for a bogus message type, 404/409 join refusals, a defection that is only accepted from the stayer (400 for the loser, 200 for the winner), and a hall that persists to disk. The strongest new test is the lockstep determinism check (test/net.test.js, 'two games fed the same actions end in the same state'), which compares terrain cells and tank state across different lag. The one-player-cannot-write-the-hall test is also good. Gaps: the ranking test (test/net.test.js, 'ranking: wins first, damage breaks ties') never reaches the damage tie-break. Its expected order is c, a, b, but c has 2 wins and a has 1, so the damage tie-break and the name tie-break are never exercised, despite being the pinned ordering. The defection test keeps the tautological assertions `assert.ok(guest.id)` and `assert.ok(again)`. game.test.js has no test for the new game.damage accumulation in explode(), and the falloff table still has no edge or outside-radius case. Nothing automated covers the browser client (src/main.js), the lobby/invite UI, the hall screen, or the leave-button path. No coverage numbers were reported, and no screencast was delivered (only PNG stills under .ololo/artifacts), so the live two-browser flow is verified by nothing automated. Credit: the server-level tests are the most honest part of the suite and the Math.random terrain comparison from earlier reviews was replaced by a fixed seed.
V 03:04 PM +32m 02s
viktornovak-epam evaluated by Data on Task 3 +15 points
data 4.5 The hall has one source of truth: data/hall.json, written only by server.js recordMatch/saveHall (src/hall.js, server.js saveHall), with ranking in one pure function (rankHall). Match damage comes from one place, game.damage in src/game.js explode(), and the client's hall view just renders the API response. The defect from last review is still present at HEAD 496114ec: data/hall.json still holds two fabricated rows, Ann {wins:1} and Bob {losses:1}, that no real match produced. Those are leftover sample data shipping in the final build, and they misrepresent the hall of fame the page opens with. Damage totals are client-reported and only cross-checked for agreement between the two players (server.js POST /api/result), so the server still does not validate them against the match. This is a secondary concern that the last review already raised and that stays open.
V 03:04 PM +32m 01s
viktornovak-epam delivered 5 files 1.1 MB
Multiplayer and the hall of fame
V 03:01 PM +29m 44s
viktornovak-epam copy/paste check clean (0%)
V 02:59 PM +27m 30s
viktornovak-epam evaluated by Architecture on Task 3 +20 points
architecture 6.0 The rules core still obeys the dependency rule. game.js imports only './terrain.js' (game.js:1), ai.js imports only game.js, and sync.js imports only game.js (sync.js:1), so every inward import stays inside the core. The core takes randomness by injection (createGame(rng), seeded() in rng.js), has no DOM, clock, or network reference, and node:test drives it directly (test/net.test.js 'two games fed the same actions end in the same state'). The lockstep design, where both browsers run the same core and only actions cross the wire, is a sound inversion. Against that, the same flaw from earlier rounds is unchanged: blast animation timing still sits in core state (game.js:3 BLAST_LIFE, game.js:136 `game.blasts`, and the age update at step()'s first line), so the renderer's concern lives in the rules. The new server.js is one closure (createServer) that mixes HTTP routing, room lifecycle, the match-result verdict, hall persistence, and a message schema (`sanitize`, server.js:17) that duplicates the knowledge of which action shapes the core accepts. Validating a match result (api() '/api/result') is therefore only reachable by booting the HTTP server, and the room/defection rules are not a testable core. main.js (commit 153a3dd) keeps input, HUD, overlay, network glue and the frame loop in one edge file, so the edge is not split. The hall ranking itself is a clean pure module (src/hall.js rankHall/recordMatch) with its own unit test. Proportion is reasonable for a two-hour build: one seam for the network, one for the hall, no DTO ring. The top level reads as a generic web project (server.js, src/, index.html) with domain words like 'hall' and 'sync' in src, which is adequate but not screaming. Net: a clear, inward-pointing core with a fixed animation leak and an overloaded server closure that the rules do not reach through a clean boundary.
V 02:58 PM +26m 30s
viktornovak-epam evaluated by Code Quality on Task 3 +20 points
cleanliness 6.5 The multiplayer code is mostly readable. `shortcutFor` (src/main.js) replaces the old per-keypress shortcut array with a small lookup, and `src/hall.js` `rankHall`/`recordMatch` are short and well named. Several things drag the score down. `find()` in server.js returns an unlabeled `[room, idx]` tuple that callers destructure. `leave()` mixes `return rooms.delete(...)` with `return undefined;` and has a stray `return` on an otherwise void path. `hostMatch`/`joinMatch` pass placeholder names such as `'…'` into the `enter` call, and the `[n.trim(), '…']` seat-name trick is hard to follow. In tests, `assert.ok(guest.id)` and `assert.ok(again)` are filler assertions. `boot()` in test/net.test.js uses `server.closeAllConnections() || server.close()`, which is a clever hack rather than a clear helper. No duplication or lint measurements were supplied, so this rests on reading the code. The result-reporting branch in `/api/result` and the two-seat name handling are the closest thing to repeated logic, but neither is a real copy-paste. maintainability 5.5 A newcomer can follow the flow: a lockstep fixed-step tick in src/main.js, a relay in server.js, and a pure `applyMessage` in src/sync.js. The weak spots are structural. `createServer` is one large closure holding rooms, timers, SSE, routing, the result-reconciliation rules and static serving, and `api()` branches on string route keys with interleaved body reading. The result path has defection and cooperative-report branches that are hard to reason about together. Several magic numbers are unnamed: the 10 000-byte body cap, the 15 000 ms heartbeat, the 5-character code length, the 20-character name cap, and the power clamp range 10–100 and angle 0–180 in src/sync.js. The `name()` helper in src/main.js and the overlay code nest ternaries and template literals. The positive side is that errors are handled at the HTTP boundary (`HttpError`, `json` fallback) and the hall file is written atomically with a corrupt-file rename, which is good boundary handling. Overall a careful newcomer could change it safely, but it takes reading the whole server closure first.
V 02:54 PM +22m 01s
viktornovak-epam evaluated by UX Review on Task 2 +17 points
ux 6.5 Desktop play frame (probe cee9bc87, desktop-play) has a clear hierarchy: a boxed HUD card per side with name, health bar, angle/power, weapon and cash, a centred round/turn/wind readout, and a bottom control bar. Health values and wind are legible at a glance. Dirt collapse is visible as a rounded hole with a hanging overhang near x≈280–340 and a detached dirt lens near x≈610–670 (desktop-play frame). The menu overlay (desktop-menu frame) is clean but dims the battlefield. The mobile view has large empty dark bands above and below the battlefield (mobile-play frame), which wastes space and makes the field small, so this is not a full design-system result. accessibility 5.0 HUD is now real DOM text (index.html:14-30), so state is readable and selectable, and the canvas has an aria-label (index.html:43). Health and power are also shown as text, not colour alone. Weaknesses: the battlefield itself is canvas-only with no text alternative, and the controls hint is 12px at opacity .6 (index.html:37 area), which is low contrast in the stills. The markup has lang=en and labelled touch buttons (index.html:44-48). The stills show no contrast failure on the HUD. mobile 6.0 The narrow frames (mobile-play, mobile-fire) show the whole HUD, battlefield and touch pad on screen with no horizontal scroll, and the touch buttons are large (about 48px tall, mobile frame). The angle, power, weapon, menu and FIRE controls all fit. The HUD is now legible at phone width, which the previous build was not. The battlefield is undersized, with a large empty band above and below it, and the help line wraps onto a second line. The stills show no narrow-screen overlap.
V 02:53 PM +21m 40s
viktornovak-epam delivered 5 files 428 KB
Falling earth, silicon rivals
V 02:48 PM +16m 03s
viktornovak-epam evaluated by Architecture on Task 2 +18 points
architecture 6.5 The dependency direction is mostly right. terrain.js has no imports (src/terrain.js:1-71). game.js imports only terrain.js, and it takes its randomness as an injected `rng` (src/game.js:1-2, createGame). ai.js depends inward on game.js and receives time as `dt`, with no Date or Math.random calls in the core. node:test drives game and AI rules with no browser (test/game.test.js, including the 'computer fires on its own and each miss lands closer' test). Input is funnelled through one `act`/`actions` table, so keyboard and touch pad share a seam (src/main.js `actions`, `act`). The shop/menu overlay is built from a view model (`overlayView`). Against that, the core still owns presentation timing. `game.blasts` ages in `step()` and `BLAST_LIFE` is exported from game.js (src/game.js:15, step), which is the animation-timing leak flagged in Task #0 and #1 and still unchanged here. The core also holds a render cache counter, `t.version` in terrain.js (carve, settle), and `paintTerrain` in main.js reads `t.cells` directly. `projectLanding` in game.js re-implements the shell integration loop with different substeps (120 fixed, versus dt/n in `advance`), which duplicates physics. The edge is still one 180-line main.js mixing input, DOM HUD, overlay, canvas world painting and the frame loop; the earlier split suggestion is now more pressing. The proportion is reasonable for this task: one terrain module, one rules module, one AI module, one edge. There is no excess ring of interfaces.
V 02:48 PM +15m 56s
viktornovak-epam evaluated by Code Quality on Task 2 +18 points
cleanliness 6.5 Names are mostly clear (planShot, driveAI, settle, dropColumn, projectLanding, CRATER_DEPTH, SAFE_FALL), and the earlier drawHud complaint is resolved: it is replaced by updateHud, syncOverlay and overlayView. Remaining issues: overlayView returns a [key, html] tuple where the key is a string built by concatenation (`shop${game.shopper}${p.money}${Object.values(p.stock)}`), which is a hack to avoid re-rendering. The overlay dispatch uses a 'buy:' string-prefix protocol (overlayAct). The keydown handler rebuilds a `shortcuts` array and a chain of `??` on every keypress (main.js keydown block). The two HUD card blocks in index.html are copy-pasted (c0/c1 markup). The game.js `step` function uses `game.resolving ||=` as a side effect inside the per-frame loop. Dead-looking pieces are absent, and duplication is limited to the HTML card markup. No duplication or lint measurements were supplied, so this rests on reading the code. maintainability 6.0 The physics is split sensibly: terrain.js handles the grid (solid, surface, carve, settle), game.js handles tank drop (dropTank), and ai.js handles the computer. Named constants cover fall damage (SAFE_FALL, FALL_DAMAGE_PER_PX), dirt speed and crater depth. Weak spots: dropTank (game.js) mixes ground-check, falling, and damage in one function and returns a boolean whose meaning ('still moving or ground still moving') is only clear from its comment. Magic numbers remain in ai.js (nextPower: 0.1, 0.5, 10, 0.8, 99, 8, 20/85/95/160 clamps, 0.5 slope threshold) and in game.js hasHit (`t.y - 8`, TANK_HIT_RADIUS offset). The AI test's assertion `errors[1] < errors[0] || errors[0] < 30` is loose, so a newcomer cannot tell what convergence guarantee is intended. The frame loop in main.js mixes trail resets, flying-state tracking and rendering in one function. Error handling at the boundaries is light but the game has few real failure points. A newcomer could change the fall or AI logic safely, but would need to read carefully.
V 02:47 PM +15m 54s
viktornovak-epam evaluated by Correctness on Task 2 +25 points
product 6.0 The code implements all four scenarios, but I could not confirm any of them live. The attached screenshots (desktop-play, desktop-shop, desktop-mirv, mobile) show the arsenal-era HUD and shop, not falling dirt, a falling tank losing health, a computer volley, or a touch-only game. The mobile.png is a letterboxed 375px frame with unreadable HUD text. Code-wise the core mechanics look sound: a solid/empty dirt grid with per-column settling (src/terrain.js settle/dropColumn), tanks that fall with ground and take 0.4 HP per px beyond a 25px safe drop (src/game.js:dropTank), and an AI that walks its power toward the target from landing data (src/ai.js:nextPower). The touch pad and DOM HUD are in index.html with hold buttons (src/main.js data-hold handlers). Weaknesses: the convergence test in test/game.test.js is loose (`errors[1] < errors[0] || errors[0] < 30`), so 'each miss lands closer' is not really pinned down. The 'computer aims unaided' path also depends on a 0.7s think timer and a fixed FIRST_ANGLE, which is plausible but untested live. The done note is accurate and meets the length requirement.
V 02:47 PM +15m 44s
viktornovak-epam evaluated by Data on Task 2 +20 points
data 7.0 Clear improvement on the data side. The terrain now has one source of truth, `game.terrain.cells` (terrain.js:1-30), and both the simulation (`solid`, `surface`, `carve`, `settle`) and the canvas paint (`paintTerrain` in main.js) read from it, with no parallel heightmap left behind. The previously flagged shadow copy of the round bonus is fixed: the shop text now uses the imported `ROUND_BONUS` (main.js shop view) instead of a literal `$500`. Prices and damage in the shop are read from `WEAPONS`, and the HUD is re-derived from `game` state each frame (`updateHud`), so it holds no copies. There is no mock or sample data in the build. Deductions: `projectLanding` (game.js, the 'Where a shell that left the map would have come down' helper) re-implements the shell integration with its own step size (`/120`) and no shared code path with `advance`, so the AI's landing estimate can drift from real shell physics. The fall-damage constants (`SAFE_FALL`, `FALL_DAMAGE_PER_PX`) are not exported, and the test re-hardcodes `25` and `0.4` (test/game.test.js, 'tank drops with the ground'), which duplicates them. The `t.y - 8` tank-hit offset and the 960x540 canvas dimensions repeated in index.html were already faulted in earlier reviews and are not charged again here.
V 02:47 PM +15m 43s
viktornovak-epam evaluated by Test Quality on Task 2 +15 points
tests 5.5 Several new tests pin concrete behaviour. The fall-damage test checks the exact formula `START_HEALTH - floor((y - start - 25) * 0.4)` and that the tank sits on `surface()` (test/game.test.js, 'tank drops with the ground and a long fall costs health'). The short-drop-is-free case and the fall-ends-round case are also specific. The dirt-settling test checks that no hanging cells remain and that the surface moved. Against that, the convergence scenario is weakly asserted: the check `errors[1] < errors[0] || errors[0] < 30` passes whenever the first miss is under 30px, so it does not prove that successive shots land closer. The terrain test still compares against `Math.random` (`notDeepEqual(a.cells, generateTerrain(..., Math.random).cells)`), which is the same flaw the previous two reviews asked to fix. Nothing automated covers the phone scenario (touch pad, no-scroll layout), the main.js key mapping, the HUD, or the shop/menu overlay flows. No coverage run was reported. The skip-shop/ignore-human test only checks that nothing happens, so it is a weak assertion. The screencast artifacts (video/webm) for the falling, rival and touch flows are not in the delivered tree; only PNG stills are there.
V 02:47 PM +15m 42s
viktornovak-epam evaluated by Creativity on Task 2 +14 points
creativity 6.8 Several working extra-mile touches sit beyond the brief, all visible in the committed code. The AI feeds off-map shots through projectLanding (src/game.js ~L137-148) so an out-of-bounds miss still gives it a range to converge on, which is an elegant mechanism. The AI also adapts its lob angle when it runs out of power (src/ai.js ~L44-47). The touch pad uses hold-to-repeat on pointerdown (src/main.js, data-hold handler). There is a menu with Vs computer / Two players, contextual Enter and R keys, and an overlay shop listing owned counts. The terrain is grass-tinted by exposed surface (src/main.js paintTerrain), and a fall damage threshold (SAFE_FALL) makes short drops free. Each is coherent and covered by tests, but none is a memorable showpiece. The mobile screenshot shows the battlefield fitting a narrow screen. I did not register a probe, so the falling and convergence behaviour in motion is judged from code and unit tests only, and no screencast was delivered.
V 02:47 PM +15m 31s
viktornovak-epam started working on Task 3
Multiplayer and the hall of fame
V 02:45 PM +12m 59s
viktornovak-epam evaluated by UX Review on Task 1 +14 points
ux 6.5 The primary information reads clearly in the desktop captures. The 'Round N — Player's turn' headline sits top centre, and the wind label with its bar and arrow is visible under it. Each player's HUD card gives health bar, angle, power, weapon, money and wins. The between-round shop panel is a legible centred box with item names, prices and stock. Weaknesses: the wind bar is small and thin, and the controls hint at the bottom is about 13px and faint. The HUD is a fixed 960px canvas, so it is only as readable as the canvas scale allows. accessibility 3.5 The page declares lang="en" and the canvas has an aria-label (index.html:2, index.html:9). But every HUD value, the wind readout, the shop list and the win banner are painted onto the canvas as pixels, so screen readers get no text for them. The wind direction is conveyed by an arrow glyph plus a bar. The shop items are only distinguishable by their text, so colour is not the sole carrier. Contrast of white on dark translucent panels is good in the captures. The text is small, though, and no text alternative exists for the game state. mobile 3.5 The 375px capture shows the page does not scroll horizontally, because the canvas scales down with max-width:100vw and aspect-ratio (index.html:6). However, the game is shrunk to roughly 130px wide inside a letterboxed box. The HUD, wind label and controls text are unreadable at that size, and touch controls are absent. The CSS has no media queries, so the layout only scales and does not reflow.
V 02:44 PM +12m 50s
viktornovak-epam delivered 4 files 195 KB
Wind, weapons, and money
V 02:41 PM +9m 34s
viktornovak-epam evaluated by Test Quality on Task 1 +15 points
tests 5.5 The suite covers several real state transitions: round-win payout and shop gating (test 'winning a round pays bonus, opens shop...'), insufficient-funds and out-of-phase buys, weapon stock depletion falling back to Missile, the MIRV apex split into five blasts (max shells = 5, blasts = 5), and the three-wins match end. Assertions are concrete values, not just 'runs'. Gaps: (1) The terrain test still compares against `Math.random` (game.test.js, 'terrain differs per seed' line `assert.notDeepEqual(a, generateTerrain(WIDTH, HEIGHT, Math.random))`), so the previous feedback to use two fixed seeds was not applied. (2) The blast-damage table is thin: only centre (30 dmg) and one mid-radius point (35px, 15 dmg) are checked. Edge, outside-radius, Big Bertha (60) and Nuke falloff on a non-killing hit are not. (3) The 'wind bends the arc' test is weak. It returns `Infinity`/`-Infinity` when no blast occurs (`g.blasts[0]?.x ?? ...`), so a shell that flies off-map under one wind direction can pass vacuously. It also never checks the arc shape or that the wind is rerolled each turn. (4) 'shot craters ground, passes the turn and rerolls wind' does not assert any wind reroll despite its name. (5) Nothing exercises the main.js keyboard-to-action mapping (W, 1-3, Enter, repeat handling), and no test covers the shop or wind rendering paths. (6) The $10/point payout is only checked for one shot and the self-damage case. No coverage numbers were reported, and there is no assembled-game test. The manual screencast is the only check of the shop and wind flows.
V 02:41 PM +9m 28s
viktornovak-epam evaluated by Code Quality on Task 1 +18 points
cleanliness 6.5 Names are mostly clear (WEAPONS, SHOP_ITEMS, phase, shells, blasts, BLAST_LIFE, DAMAGE_PAYOUT, ROUND_BONUS), and the weapon price list is a single data table. The previous task's naming complaints are partly addressed: the arrow-key sign logic is now plain (`adjust(game, k, 0)` / `adjust(game, -k, 0)`), and `r`/`R` is handled by one lowercase fallback. Deductions: the keydown handler rebuilds the whole `keys` object and re-registers the shop bindings with `SHOP_ITEMS.forEach` on every keypress (src/main.js keydown block). `trail.length < 1500 && trail.push(...)` is a side effect hidden inside a `forEach` expression. Tuning literals stay inline with no names: the MIRV spread `(i - 2) * 45` and `vx` offset, `240` sub-steps per second, `22` muzzle offset, and `8` tank-centre offset in `advance`/`hasHit`. The `endShot` expression `dead[game.turn] ? 1 - game.turn : game.turn` is hard to read at a glance. Test helper `wind bends the arc` uses an odd `?? (wind > 0 ? Infinity : -Infinity)` fallback. No duplication or lint measurements were supplied, so this rests on reading the code. maintainability 6.0 The simulation is easier to follow than before. `advance` is a focused single-shell step that returns the remaining shells, `step` is a short sub-step loop, and `startRound`/`endRound` separate round lifecycle from shot resolution. The state machine is explicit (`phase` play/shop/match, `canAct` guard). Weak spots: `drawHud` is still one long function that mixes player panels, wind, turn banner, help text, shop overlay and match overlay, and the previous review asked for it to be split, which has not happened; it grew from about 25 to about 40 lines. `explode` both carves terrain, applies damage and credits the shooter's money, so economy is coupled to blast code; a newcomer changing payout rules has to read blast logic. Shop and money behaviour depends on `game.shopper` and `game.turn` being kept in sync with no single helper for 'current buyer'. Error-handling boundaries are thin, but for a competition build that is acceptable. Named constants cover the main economy and wind values, but several gameplay and layout numbers remain inline (listed under cleanliness).
V 02:41 PM +9m 25s
viktornovak-epam evaluated by Creativity on Task 1 +16 points
creativity 7.5 Several working extras beyond the brief, all visible in the code. The wind readout is more than a number: a bar with a centre marker, a signed arrow and a 'calm' state (src/main.js drawWind). Shell flight leaves a fading white trail, which makes the wind bend in the arc readable (src/main.js frame/drawEffects, trail reset on fire). The MIRV fans its five warheads with a velocity spread of (i-2)*45 (src/game.js advance). The shop is sequential per player, with a 'took the round (+$500)' header and an on-panel owned count (src/main.js drawShop). Weapon cycling with W visits only owned weapons and falls back to Missile when stock runs out (src/game.js cycleWeapon, fire). The HUD shows the current weapon, stock or ∞, money and wins per player (src/main.js drawHud). Key auto-repeat is suppressed for non-arrow keys (src/main.js keydown handler). Damage falls off with distance and self-damage pays nothing, a fair economy nuance (src/game.js explode, test 'damage falls off...'). None of this is a memorable showpiece, and I could not see the wind, shop or MIRV flows in motion, so I did not verify the visual payoff. The extras are well-judged and coherent with the product, which puts the score above the plain baseline.
V 02:41 PM +9m 22s
viktornovak-epam evaluated by Correctness on Task 1 +27 points
product 6.5 The code implements every contract line on paper: wind is rolled per turn and applied to shell vx each sub-step (game.js advance, ~line 's.vx += game.wind * h'), and the HUD draws a wind bar and direction arrow (main.js drawWind). The arsenal matches the pinned table exactly (game.js WEAPONS: Missile 30/free, Bertha 60/$750 r120, Nuke 100/$2500 r220, MIRV 30/$1500). Damage pays $10 per point dealt to the opponent only (explode, DAMAGE_PAYOUT), and a $500 bonus goes to the round winner (endRound). Shop is sequential between rounds with 1-3 to buy and Enter to finish (leaveShop/buy). Stock decrements per shot and falls back to Missile at zero (fire). MIRV splits into five warheads at the apex (advance: risingBefore && vy>0). First to three round wins ends the match on fresh terrain (startRound, WINS_NEEDED). Left/right angle direction is now consistent for both tanks, which fixes a fault from the prior task. Weaknesses: the wind range is modest (±50), the shop panel covers the middle of the sky, and the MIRV warheads' lateral spread is small (vx ±90), so the split may be hard to read. I could not see the screencast the brief requires, so the live wind arc, shop purchase and MIRV split are verified only from code, not observed. Probes #9-#11 exist in the log but their results were not in my evidence.
V 02:41 PM +9m 16s
viktornovak-epam evaluated by Architecture on Task 1 +21 points
architecture 7.5 The dependency rule mostly holds. game.js imports only terrain.js (src/game.js:1), and rng is injected (createGame(rng), startRound uses game.rng), so no clock or Math.random reaches the core. The rules that matter (wind drift in advance(), MIRV split at apex, weapon stock fallback in fire(), $10/point and $500 payouts in explode() and endRound()) are plain functions over plain objects. node:test drives them directly (test/game.test.js: wind bends the arc, MIRV yields five blasts, buy/leaveShop flow) with no server or browser. The edge is main.js, which imports game.js and owns the canvas, keyboard, HUD and the trail array (main.js trail is rendering-only state, kept out of the core). The lesson from the earlier verdict is only partly applied. BLAST_RADIUS left the core, and blast radius now lives per weapon in the WEAPONS table. But the core still owns animation timing: BLAST_LIFE is exported from game.js and step() ages and filters game.blasts (game.js, step()), and each blast record carries a render-only radius. Those are the presentation seams I asked to be moved out, and they were not. Scale is appropriate: one core file, no ports or DTOs, and no over-engineering. The core is slightly overloaded, since economy, shop, round and match phases share one module with physics, but at this size that is a reasonable trade.
V 02:41 PM +9m 15s
viktornovak-epam evaluated by Data on Task 1 +22 points
data 8.0 The arsenal lives in one declared table, `WEAPONS` in src/game.js (price, damage, crater, blast). `SHOP_ITEMS` is derived from it, and main.js reads names, prices, max damage and owned counts from `WEAPONS` and `SHOP_ITEMS` (drawShop, drawHud), so the shop and HUD cannot drift from the price list. The economy constants (`DAMAGE_PAYOUT = 10`, `ROUND_BONUS = 500`, `WINS_NEEDED = 3`, `MAX_WIND = 50`) are each declared once in game.js and imported by main.js and the tests. No shadow price or payout values appear in markup or rendering code. Wind is rolled from the seeded rng in `rollWind`, and damage and money flow only through `explode`, which pays `dealt * DAMAGE_PAYOUT`. The state (shells, blasts, phase, wins, money) sits in pure game.js with a clear interface (`buy`, `leaveShop`, `cycleWeapon`, `fire`, `step`), and main.js only renders it. Nothing is fetched or fabricated. Deductions: the tank-hit geometry `t.y - 8` is still repeated in game.js `hasHit` and in main.js rendering (carried over from the prior duel build, not newly introduced here), and the MIRV warhead hardcodes `weapon: 'missile'` rather than referencing its own table entry. Both are minor.
V 02:41 PM +9m 06s
viktornovak-epam evaluated by UX Review on Task 0 +0 points
ux n/a No screenshot delivered: the desktop capture (probe 81b6bd3e-9ea3-4861-b4b2-8eef96776189) and the mid-duel capture (probe fdac54b2-274e-41e3-8376-db60b5b91aea) are both still open. Hierarchy and legibility of the HUD and winner overlay cannot be judged from code alone. accessibility n/a No screenshot delivered, so contrast of the canvas-drawn HUD text and the semi-transparent controls line cannot be checked. Markup notes for reference: index.html:2 sets lang="en" and the canvas has an aria-label (index.html:12), but all HUD text is painted onto the canvas and is not real DOM text. mobile n/a No narrow-viewport capture delivered (mobile.png was requested in probe 81b6bd3e but did not arrive), so horizontal scrolling and canvas fit at 375px cannot be verified. The CSS uses max-width/max-height on the canvas (index.html:8), which suggests some responsiveness, but that is not visual evidence.
V 02:40 PM +8m 32s
viktornovak-epam started working on Task 2
Falling earth, silicon rivals
V 02:34 PM +2m 48s
viktornovak-epam evaluated by Test Quality on Task 0 +12 points
tests 5.0 The suite is small (7 node:test cases in test/game.test.js) and covers the core rules with real assertions, but several scenarios are verified only weakly. Assertions are concrete where they exist: clamping to [0,180] and [10,100] with a lock during flight (test/game.test.js:~27-32), exact carve values t[50]=55 and t[30]=50 (~lines 19-24), and an off-map shot ending the turn with no blast (~35-43). Gaps: (1) 'shell arcs under gravity' is never checked. The arc test only asserts that some terrain rose and the turn passed (~line 45-50), so a straight-line or gravity-free shell would still pass. (2) Damage falloff is not specified. The blast test only asserts `some tank health < 100` (~line 62), so the 30-at-centre, linear-to-zero-at-edge formula is untested, as are the boundaries at the edge and beyond BLAST_RADIUS. (3) The 'terrain differs per seed' test compares against Math.random, so it is nondeterministic and nearly always passes. (4) main.js (rendering, keyboard mapping including the mirrored left/right logic for player 2, R restart) has no tests, and no coverage output was produced. (5) There is no test that a destroyed tank stops further shelling beyond fire() returning false, and no test that health is clamped at 0. Nothing is skipped or tautological, and the suite runs deterministically under the seeded helper except for the one Math.random case.
V 02:34 PM +2m 46s
viktornovak-epam evaluated by Code Quality on Task 0 +14 points
cleanliness 6.5 Names are mostly clear (`createGame`, `endShot`, `hasHit`, `carve`) and the game logic in game.js is separated from rendering. The keyboard handler in main.js is hard to read: the ArrowLeft/ArrowRight bindings use the chained sign expression `1 * k * (game.turn ? -1 : 1) * -1` (main.js ~line 9-10), and `r` and `R` are two separate entries doing the same thing. The tank's vertical offset of 8 is repeated as a literal in both `hasHit` (game.js hit test `t.y - 8`) and `drawTank` (`t.y - 8`), and the 22-pixel barrel length and 10-pixel hull offset recur in `fire` and `drawTank`. There is no measured duplication or lint data in the evidence, so this rests on reading the code. Nothing here is dead code, though `drawTank` and `drawEffects` reach into the module-level `game` instead of taking it as a parameter. maintainability 5.5 Several magic values are inline rather than named: the 240 sub-steps per second in `step`, the 0.6 s blast duration and 0.05 s frame clamp in main.js, the power default of 60, the spawn fractions 0.07/0.1/0.83 in `createGame`, and the 0.2 fine-step factor. `drawHud` is about 30 lines mixing the HUD, the turn banner, the help text and the winner overlay, so it needs a scroll to read. The keydown handler rebuilds an object of closures on every keypress. Rendering reads the global `game` directly, which makes changes riskier. The 'tunnelling' comment in game.js and the 'shortcut' comment in terrain.js explain intent well, and the node:test suite covers clamping, lock-in-flight, craters and winner. Boundary handling is reasonable for a competition build: shots that leave the map end the turn cleanly.
V 02:34 PM +2m 42s
viktornovak-epam evaluated by Architecture on Task 0 +17 points
architecture 7.5 The dependency rule mostly holds. The core is src/game.js and src/terrain.js, and neither imports a DOM, canvas, or framework type. game.js:1 imports only './terrain.js', and terrain.js has no imports. Randomness is injected (createGame(rng = Math.random), game.js:16), and time enters as a plain dt argument to step() (game.js:65), not a wall-clock read. The core is testable alone: test/game.test.js drives createGame, fire, adjust, and step under node:test with a seeded RNG and no browser. Dependencies point one way: main.js -> game.js -> terrain.js. The layout is proportionate to a two-hour single-screen game: three source modules, one test file, no interfaces or DTO layers. Deductions: (1) the core carries presentation concerns. game.js:5 exports BLAST_RADIUS, which exists only for drawing in main.js:91, and game.blast.age (game.js:64) plus its 0.6 s display window (main.js:90) keep animation timing in core state. (2) Core constants are hardcoded in the core: the canvas size WIDTH/HEIGHT (game.js:3-4) and the tank muzzle and hit offsets (game.js:37, 53, where the 8 and 22 are literals). (3) main.js mixes input mapping, canvas drawing, and the frame loop in one 127-line module, so view and controller are not separated. For this scale that is acceptable, but it is the weakest seam. The top-level names (game, main, terrain) are domain-leaning but generic; they do not yet read as accounts, moves, or orders. Overall: a clean, small inward-pointing core with a sensible but slightly leaky boundary to the renderer.
V 02:34 PM +2m 38s
viktornovak-epam evaluated by Data on Task 0 +18 points
data 8.0 The duel's data has one clear home. Game constants (WIDTH, HEIGHT, START_HEALTH, MAX_DAMAGE, BLAST_RADIUS, CRATER_RADIUS, GRAVITY) are declared once in src/game.js:1-12, and main.js imports them (src/main.js:1) rather than redeclaring them. Terrain is generated only by generateTerrain in src/terrain.js, and the HUD, tanks and blast all read the single game state from createGame. Damage follows the canonical numbers: MAX_DAMAGE=30 falling linearly to 0 at BLAST_RADIUS (src/game.js:45-46), and START_HEALTH=100 is used for both health and the HUD bar. Nothing is fetched or fabricated, and the task pins no external dataset. Shadow data is minor: index.html:7 hardcodes the 960x540 canvas size, duplicating WIDTH/HEIGHT in game.js, and the tank geometry offsets (22, 10, 8, TANK_HIT_RADIUS-related literals) are repeated across game.js and main.js. Logic and rendering are cleanly separated: game.js is pure state and main.js only draws and maps keys, so no parsing is sprinkled through presentation code. No probe was needed because the flow is readable from the code.
V 02:34 PM +2m 37s
viktornovak-epam evaluated by Correctness on Task 0 +22 points
product 6.5 All four scenarios are implemented in code. Turn-based angle and power aiming with gravity-arced shells (src/game.js:step) is present, as are craters that persist (src/terrain.js:carve), distance-scaled blast damage capped at 30 (src/game.js:explode, BLAST_RADIUS 70), health bars (src/main.js:drawHud), and a winner overlay with R to restart (src/main.js:drawHud). Shots are locked once a shell is in flight or a winner exists. Controls are shown on screen. Deductions: the left/right angle mapping is not screen-consistent (src/main.js:keydown: ArrowLeft lowers the barrel for both players); a simultaneous double KO awards the win to player 2 (src/game.js:endShot uses findIndex, so the first dead tank wins); the visuals are minimal (flat gradient, plain rectangles and a stroke for the tank). The completion note's claims match the code. I found no screencast or probe result in the evidence I was shown, so the in-browser flow is judged from code only.
V 02:34 PM +2m 32s
viktornovak-epam evaluated by Creativity on Task 0 +10 points
creativity 6.0 A handful of working usability touches beyond the brief, but nothing memorable. Shift-for-fine-steps (src/main.js keydown handler, k=0.2), per-player mirrored left/right so the barrel moves in the screen direction for Player 2 (src/main.js ArrowLeft/ArrowRight using game.turn), an on-canvas controls legend (drawHud, bottom line), live angle and power readouts in each HUD card, a turn marker arrow over the active tank, a fading explosion animation (drawEffects), and an R-to-restart prompt on the winner overlay. All are wired in code and fit the product. There is no wind, tank movement, or other standout mechanic, and I did not see a screencast, so the visual payoff is unverified. The build sits modestly above the plain baseline.
V 02:34 PM +2m 23s
viktornovak-epam started working on Task 1
Wind, weapons, and money
V 02:32 PM +0s
viktornovak-epam started working on Task 0
Build the artillery duel