Skip to content

feat: studio-quality Sprint J polish - #44

Merged
IETGLIM merged 2 commits into
mainfrom
feat/studio-quality-sprint-j
Jul 28, 2026
Merged

feat: studio-quality Sprint J polish#44
IETGLIM merged 2 commits into
mainfrom
feat/studio-quality-sprint-j

Conversation

@IETGLIM

@IETGLIM IETGLIM commented Jul 28, 2026

Copy link
Copy Markdown
Owner

Summary

  • Act 4–5 live hints (last_poem, blind_spot, final_code, machine_confession, echo_of_vladimir, night_before_dawn)
  • Industrial damp sheen (factory oil + chk_campfire_night ash ring); Photo Mode session gallery (in-memory, max 6)
  • WorldMap travel confirm shows active quest titles; stuck recovery auto-focuses nearest NPC ring
  • Quest board ↔ journal deep-link; env LOD tighten + locomotion/FOV feel; ring juice on stuck recovery

Test plan

  • npm run typecheck
  • npm run lint
  • npm run test:unit
  • Expand Act 4/5 quests in journal — live cues for last_poem / blind_spot / final_code
  • Travel on World Map to quest-marked scene — confirm dialog lists quest titles
  • Photo Mode: capture several stills — gallery thumbnails appear; click restores preview
  • Trigger stuck recovery near NPC — distance ring reappears on nearest/target NPC
  • Journal «Доска» / board «В журнал» open the other panel

Base: main (post #43). No merge unless green and simple — user will merge.

Act 4-5 live quest hints, industrial damp sheen, photo session gallery, travel confirm quest context, stuck NPC ring refocus, board-journal deep-links, and env LOD/locomotion feel.
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

@vercel

vercel Bot commented Jul 28, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
volodka Ignored Ignored Jul 28, 2026 9:59pm
@coderabbitai

coderabbitai Bot commented Jul 28, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Summary by CodeRabbit

  • New Features

    • Added a Photo Mode gallery for browsing up to six recent captures and selecting one for preview.
    • Added active quest previews to travel confirmation dialogs.
    • Added quick navigation between the quest board and journal.
    • Expanded contextual quest hints across Acts 4 and 5.
    • Improved interaction recovery indicators when NPC interactions become stuck.
  • Improvements

    • Enhanced damp-floor and puddle visuals in industrial and campfire scenes.
    • Tuned scene detail distances, player rotation, sprint FOV, and movement feel.

Walkthrough

The PR adds damp industrial scene materials, quest navigation and contextual hints, stuck-interaction recovery feedback, photo capture history with gallery selection, environment LOD adjustments, and player movement tuning.

Changes

Scene rendering and environment tuning

Layer / File(s) Summary
Industrial damp material contract
src/engine/graphics/wetStreetScenes.ts, src/engine/graphics/wetStreetScenes.test.ts
Adds industrial damp scene classification and floor/oil material settings with coverage for supported and unsupported scenes.
Damp scene visual integration
src/components/3d/AbandonedFactoryVisual.tsx, src/components/3d/ChkForestZorgeVisual.tsx, src/components/3d/SceneColliderSelector.tsx
Applies damp materials to factory floors and puddles, and conditionally renders a damp campfire surface.
Environment LOD profiles
src/engine/lod/distanceLod.ts, src/engine/lod/distanceLod.test.ts
Retunes scene clutter and decorative distances with exact profile assertions.

Quest navigation and contextual hints

Layer / File(s) Summary
Quest panel and travel navigation
src/components/game/QuestsPanel.tsx, src/components/game/questBoard/QuestBoardPanel.tsx, src/components/game/WorldMap.tsx, src/engine/questBoard/questBoardConstants.ts
Adds navigation between quest panels and displays active quest titles in travel confirmation.
Act 4 contextual hint generators
src/engine/guidedStory/act4QuestHints.ts, src/engine/guidedStory/act4QuestHints.test.ts
Adds objective- and scene-aware hints for two Act 4 quests.
Act 5 contextual hint generators
src/engine/guidedStory/act5QuestHints.ts, src/engine/guidedStory/act5QuestHints.test.ts
Adds four objective-based Act 5 hint generators and tests.
Quest journal hint integration
src/hooks/questJournalHint.ts, src/hooks/questJournalHint.test.ts
Routes additional quest ids to the new live contextual hint helpers.

Interaction stuck recovery

Layer / File(s) Summary
NPC recovery focus resolution
src/engine/interaction/stuckRecoveryNpcFocus.ts, src/engine/interaction/stuckRecoveryNpcFocus.test.ts
Selects a preferred or nearest NPC and emits recovery interaction hint data.
Recovery event wiring
src/engine/interaction/interactionSession.ts, src/engine/interaction/emergencyInteractionReset.ts
Invokes NPC focus emission during watchdog and emergency reset recovery.
Recovery ring display
src/components/game/hud/parts/InteractionDistanceRing.tsx
Restores the interaction ring after stuck recovery with an extended timeout.

Photo capture gallery

Layer / File(s) Summary
Capture history storage
src/engine/photo/photoCaptureHistory.ts, src/engine/photo/photoCaptureHistory.test.ts
Adds bounded newest-first capture history and lookup helpers.
Photo controller history flow
src/components/game/photoMode/usePhotoModeController.ts
Records captures, preserves scene metadata, and selects historical captures for preview.
Photo gallery UI
src/components/game/photoMode/PhotoModeViewfinder.tsx, src/components/game/photoMode/PhotoModePanel.tsx, src/engine/photo/photoModeConstants.ts
Renders selectable thumbnails and wires gallery state and labels.

Player feel tuning

Layer / File(s) Summary
Movement and sprint tuning
src/engine/player/playerConstants.ts
Updates rotation speeds and sprint FOV parameters.

Estimated code review effort: 4 (Complex) | ~60 minutes

Poem

I’m a rabbit with a damp little view,
Quest boards now hop where journals do too.
Photos stack neatly, six in a row,
Lost NPC rings find where to go.
Forests gleam softly beneath moonlight bright—
I twitch my nose and declare it all right!

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 53.13% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title is generic and doesn't clearly identify the main change set, though it is related to the sprint polish theme. Use a more specific title naming the primary features, such as act hints, photo gallery, travel confirm, and stuck-recovery UI.
✅ Passed checks (3 passed)
Check name Status Explanation
Description check ✅ Passed The description accurately matches the PR scope and summarizes the implemented polish features and test plan.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/studio-quality-sprint-j
🔧 Fix failing CI
  • Fix failing CI in branch feat/studio-quality-sprint-j

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Updated (UTC)
❌ Deployment failed
View logs
volodka2 b5a9411 Jul 28 2026, 09:48 PM

@coderabbitai coderabbitai 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.

Actionable comments posted: 8

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/components/game/photoMode/PhotoModeViewfinder.tsx`:
- Around line 340-342: Update the capture history gallery container in
PhotoModeViewfinder so it uses a different vertical position from the scene-name
block anchored at bottom-6. Preserve its existing horizontal positioning and
behavior while assigning a separate bottom spacing slot that prevents overlap.
- Around line 341-355: Remove the conflicting role="listitem" from each native
thumbnail button in the captureHistory mapping within PhotoModeViewfinder; keep
the buttons’ native semantics and existing click behavior intact.

In `@src/components/game/photoMode/usePhotoModeController.ts`:
- Around line 126-130: Update selectHistoryCapture to store entry.filter in
PhotoPreviewData alongside the dataUrl and timestamp, then update
PhotoModeViewfinder and this hook’s download/share paths to use the selected
preview filter instead of the current filterPreset. Preserve current-filter
behavior for newly captured photos and ensure historical captures preview,
download, and share with their stored filter.
- Around line 56-57: Update the capture flow around capturePhotoStill and
captureScreenshot to snapshot filterPreset and sceneName before the async
capture begins, then use those snapshots when creating the history entry. Add
sceneName to captureScreenshot’s dependency list, and remove the render-time
sceneNameRef.current assignment and any metadata reads that can observe newer
values.

In `@src/engine/interaction/stuckRecoveryNpcFocus.test.ts`:
- Around line 42-52: Update the test for resolveNearestNpcForRingFocus to pass a
non-null, unknown recovered target ID that is absent from groups, while
retaining the assertions that Maria is selected as the nearest NPC and has the
expected label.

In `@src/engine/interaction/stuckRecoveryNpcFocus.ts`:
- Around line 69-76: Update the interaction hint emission in the stuck-recovery
NPC focus flow to use a re-approach message when focus.distance exceeds
NPC_INTERACTION_QUERY_RANGE, rather than reporting the NPC as being in the
interaction zone. Preserve the existing in-range message for distances within
the query range and keep the remaining hint fields unchanged.

In `@src/engine/photo/photoCaptureHistory.ts`:
- Around line 23-25: Update the capture ID generation in the photo capture
history function to use a collision-resistant identifier independent of
history.length, such as a monotonic counter or UUID, while preserving the
existing cap-${timestamp}- style only if uniqueness remains guaranteed. Add a
regression test covering two captures created within the same millisecond when
history is full, verifying their IDs differ.

In `@src/engine/player/playerConstants.ts`:
- Around line 58-62: Update RUN_FOV_SPEED_MIN in the player movement constants
to match the actual sprint threshold, preventing normal movement from receiving
FOV boost; retain RUN_FOV_SPEED_FULL above this minimum so full intensity
remains reached only at higher sprint speeds.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 06a508a5-492e-4ad0-8638-8527277faddd

📥 Commits

Reviewing files that changed from the base of the PR and between d80efb4 and b5a9411.

📒 Files selected for processing (29)
  • src/components/3d/AbandonedFactoryVisual.tsx
  • src/components/3d/ChkForestZorgeVisual.tsx
  • src/components/3d/SceneColliderSelector.tsx
  • src/components/game/QuestsPanel.tsx
  • src/components/game/WorldMap.tsx
  • src/components/game/hud/parts/InteractionDistanceRing.tsx
  • src/components/game/photoMode/PhotoModePanel.tsx
  • src/components/game/photoMode/PhotoModeViewfinder.tsx
  • src/components/game/photoMode/usePhotoModeController.ts
  • src/components/game/questBoard/QuestBoardPanel.tsx
  • src/engine/graphics/wetStreetScenes.test.ts
  • src/engine/graphics/wetStreetScenes.ts
  • src/engine/guidedStory/act4QuestHints.test.ts
  • src/engine/guidedStory/act4QuestHints.ts
  • src/engine/guidedStory/act5QuestHints.test.ts
  • src/engine/guidedStory/act5QuestHints.ts
  • src/engine/interaction/emergencyInteractionReset.ts
  • src/engine/interaction/interactionSession.ts
  • src/engine/interaction/stuckRecoveryNpcFocus.test.ts
  • src/engine/interaction/stuckRecoveryNpcFocus.ts
  • src/engine/lod/distanceLod.test.ts
  • src/engine/lod/distanceLod.ts
  • src/engine/photo/photoCaptureHistory.test.ts
  • src/engine/photo/photoCaptureHistory.ts
  • src/engine/photo/photoModeConstants.ts
  • src/engine/player/playerConstants.ts
  • src/engine/questBoard/questBoardConstants.ts
  • src/hooks/questJournalHint.test.ts
  • src/hooks/questJournalHint.ts
Comment on lines +340 to +342
{captureHistory.length > 0 && onSelectHistory && (
<div
className="absolute bottom-6 left-6 sm:left-10 pointer-events-auto flex flex-col gap-1.5"

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Move the gallery away from the scene label.

The gallery uses bottom-6 left-6, the same anchor as the existing scene-name block at Lines 198–208. It will cover the scene name in the viewfinder; give the gallery a separate vertical slot.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/game/photoMode/PhotoModeViewfinder.tsx` around lines 340 -
342, Update the capture history gallery container in PhotoModeViewfinder so it
uses a different vertical position from the scene-name block anchored at
bottom-6. Preserve its existing horizontal positioning and behavior while
assigning a separate bottom spacing slot that prevents overlap.
Comment on lines +341 to +355
<div
className="absolute bottom-6 left-6 sm:left-10 pointer-events-auto flex flex-col gap-1.5"
role="list"
aria-label={PHOTO_MODE_LABELS.galleryTitle}
>
<span className="text-[8px] font-mono uppercase tracking-wider" style={{ color: accent(0.55) }}>
{PHOTO_MODE_LABELS.galleryTitle}
</span>
<div className="flex items-center gap-1.5">
{captureHistory.slice(0, 6).map((entry) => (
<button
key={entry.id}
type="button"
role="listitem"
onClick={() => onSelectHistory(entry)}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== locate file =="
fd -a 'PhotoModeViewfinder.tsx' . || true

echo "== relevant lines =="
file="$(fd 'PhotoModeViewfinder.tsx' . | head -n1 || true)"
if [ -n "${file:-}" ]; then
  nl -ba "$file" | sed -n '320,375p'
fi

echo "== search role/listitem usage in file =="
if [ -n "${file:-}" ]; then
  rg -n 'role="listitem"|<button|role="list"|onSelectHistory|captureHistory' "$file"
fi

echo "== package/accessibility deps =="
for f in package.json deno.json pnpm-lock.yaml .npmrc .eslintrc* eslint.config.*; do
  [ -f "$f" ] && { echo "--- $f"; sed -n '1,220p' "$f"; }
done

Repository: IETGLIM/Volodka

Length of output: 306


🏁 Script executed:

#!/bin/bash
set -euo pipefail

file="/home/jailuser/git/src/components/game/photoMode/PhotoModeViewfinder.tsx"

echo "== relevant lines =="
awk '{printf "%6d\t%s\n", NR, $0}' "$file" | sed -n '320,375p'

echo "== search relevant symbols in file =="
grep -nE 'role="listitem"|role="list"|<button|onSelectHistory|captureHistory' "$file" || true

echo "== package/accessibility deps =="
for f in /home/jailuser/git/package.json /home/jailuser/git/deno.json /home/jailuser/git/pnpm-lock.yaml /home/jailuser/git/.npmrc /home/jailuser/git/.eslintrc /home/jailuser/git/.eslintrc.js /home/jailuser/git/.eslintrc.json /home/jailuser/git/eslint.config.js; do
  if [ -f "$f" ]; then
    echo "--- $f"
    sed -n '1,220p' "$f"
  fi
done

Repository: IETGLIM/Volodka

Length of output: 12913


🌐 Web query:

WAI-ARIA listitem role button role allowed ancestors implicit semantics listitem invalid container

💡 Result:

Under WAI-ARIA and HTML accessibility specifications, the listitem role requires specific ancestral context, and the button role imposes restrictions on its descendants. Ancestral Requirements for listitem: The listitem role must be contained within, or owned by, an element with the role list or group [1]. In native HTML, an li element is implicitly exposed as a listitem only when it is a direct child of a ul, ol, or menu element [2][3]. If an li element is used outside of these required parent elements, it is typically exposed as a generic role (non-semantic) [2][3]. Implicit Semantics and Invalid Containers: If a listitem is placed in a container that does not provide the required list or group context, it fails to fulfill the semantic requirement of a list item [1]. Conversely, if an li element is assigned an explicit role (e.g., role=button) while inside a properly structured list (a parent element with role=list), this is generally disallowed by the ARIA in HTML specification because the list element only permits specific accessibility children (typically listitem or group) [4][5]. Interaction with the button Role: 1. Ancestors: There are no specific "listitem-like" ancestral requirements for a button, but a button cannot be a direct child of certain elements that do not support interactive content or where it would violate the accessibility tree structure [4]. 2. Descendants: A critical restriction exists for the button role: it cannot contain other interactive elements or semantic children that the accessibility API cannot represent [6]. Browsers automatically apply role=presentation to all descendants of a button element, effectively stripping them of their native or explicit semantic roles [6]. Invalid Usage Patterns: Attempting to nest a button inside a listitem (e.g.,

  • ...
  • ) is generally valid HTML structure, as a button is a valid descendant of an li element. However, attempting to change the role of the list item itself to a button (e.g.,
  • ) inside a list is invalid because the list container expects listitem or group children, not buttons [4]. If an li element is used as an orphan (outside of a list), it is treated as generic; in that specific case, it may be possible to assign it other roles, but this is poor practice [2][4][7]. Always prefer native HTML elements (e.g., ul/li or button) over applying ARIA roles to generic containers [8][6][7].

    Citations:


    🌐 Web query:

    ARIA listitem role native button semantics aria-role on button listitem invalid

    💡 Result:

    Applying role=button directly to an li element is invalid and semantically contradictory [1][2][3]. According to the W3C ARIA in HTML specification, an li element functioning as a list item (within a ul, ol, or menu) must not be assigned the button role [1][4]. Assigning role=button to an li creates a semantic conflict where the browser treats the element as a list item, but screen readers may incorrectly announce it as a button, leading to unpredictable or degraded user experiences [2][3]. Furthermore, a native li lacks the built-in keyboard interactivity (focus management and activation via Space/Enter) expected of a button, requiring developers to manually implement these features—an approach that is error-prone and violates best practices [5][2]. To implement this correctly, follow these recommended patterns: 1. Place a native button inside the li: This preserves the list semantics while providing standard button functionality [2].

  • Action
  • 2. Use alternative structures if it is not a true list: If your items are not part of a semantic list, do not use li/ul structures [2]. Use native button elements directly, or div/span elements with role=button only as a last resort (which would then require manual tabindex="0" and keyboard event handling) [5][2]. 3. Use specific roles for menus: If the list items are intended to be interactive menu actions, use role=menu on the container and role=menuitem on the items (typically applied to li or other elements) [6][7]. Always prioritize native HTML elements (like ) over overriding roles with ARIA, as native elements provide essential accessibility features by default [5][3].

    Citations:


    🌐 Web query:

    MDN ARIA button role descendants semantic children presentation descendants button role

    💡 Result:

    In the context of WAI-ARIA, the button role is defined as a role that does not support semantic children [1][2][3]. Because accessibility APIs cannot represent semantic elements (such as headings, lists, or tables) contained within a button, browsers automatically apply the presentation role to all descendant elements of any button [1][2][3]. As a result of this automatic transformation, any semantic elements nested inside a button are hidden from assistive technology, effectively becoming presentational [1][2]. For example, a heading element nested inside a div with role="button" is treated as if it had an explicit role="presentation," meaning it is ignored by screen readers and other accessibility tools [1][2]. Furthermore, according to the ARIA in HTML specification, buttons (including elements with an explicit role="button") must not contain interactive content or elements with a tabindex attribute specified [4][5][6]. While buttons are permitted to contain phrasing content, the underlying accessibility tree flattens the hierarchy, removing the semantic structure of any children [1][2]. Developers are strongly advised against nesting interactive elements or complex structures within buttons to avoid unexpected behavior and accessibility issues, as these descendants will not be properly exposed to users of assistive technologies [7][8].

    Citations:


    🌐 Web query:

    WAI-ARIA listitem role allowed parent roles button element native HTML aria role listitem on button

    💡 Result:

    The WAI-ARIA listitem role requires an accessibility parent role of list or group [1][2][3]. Specifically, an element with role listitem is only meaningful when it is contained inside or owned by an element with the role list [1][2]. Regarding the use of aria roles on specific elements: 1. Native HTML LI Elements: If an li element is a child of a list element (ul, ol, or menu) that is exposed as a list, the li element has an implicit role of listitem [4][5]. Under the ARIA in HTML specification, no role other than listitem is permitted on an li in this context [6][5]. Specifying role=button on an li that is correctly nested within a list is invalid and creates a semantic conflict, as the browser continues to treat it as a list item while assistive technologies are instructed to treat it as a button [7][8]. This practice is strongly discouraged [7][5]. 2. If the parent list element has had its implicit list role removed or overwritten (e.g., via role=none or role=presentation), the li element may be permitted to have other roles, provided those roles are allowed by its ancestors [9][6]. 3. General Best Practice: If you need a button, you should use the native HTML button element [7][10][8]. If you must use a different element, you should generally avoid overriding list items with button roles [7][8]. Instead, place a button element inside the list item, or use a structure that natively supports the desired role [7][8].

    Citations:


    🌐 Web query:

    "role=listitem" "<button>" button semantics

    💡 Result:

    Using role="button" on an element with role="listitem" (or an

  • element) is invalid, creates semantic conflicts, and is considered an accessibility anti-pattern [1][2]. The ARIA specification and ARIA in HTML guidelines generally prohibit overriding the listitem role when an element is correctly nested within a list structure (such as a
      or
        ). Applying role="button" to a list item confuses assistive technologies, as the element attempts to convey two different, incompatible semantics: being a structural item in a list and being an interactive trigger [2]. Key reasons to avoid this pattern include: 1. Semantic Conflict: Screen readers may struggle to interpret the element correctly. You are telling the browser the element is a button, yet it remains structurally trapped within a list context [2]. 2. Lack of Native Functionality: Unlike a native
  • element, which provides built-in keyboard support (Enter/Space to activate), focusability, and form integration, an
  • with role="button" provides none of these [3][2]. You would be required to manually add tabindex="0" and complex JavaScript event handlers to mimic button behavior, which is error-prone and often leads to accessibility gaps [3][2]. 3. Structural Integrity: Custom stylesheets or assistive tools that expect valid list structures may malfunction if list items are repurposed as widgets [1]. Recommended Alternatives: - Place a native
  • inside the list item: If you need a list of interactive actions, use the standard
      /
    • structure and put a
    element inside each
  • [2]. This maintains the list structure while providing the full functionality and accessibility of a real button. - Use a list container without list semantics: If the items are not truly a list, use a generic container (like a
    or
      with role="none") and mark up the buttons appropriately [2][4]. - Native elements first: Always prefer native HTML elements (
  • , , etc.) over overriding roles on generic elements or structural elements like
  • [3][5][2]. Top results: [3], [1], [2], [4]

    Citations:


  • Keep gallery thumbnails exposed as buttons, not list items.

    role="listitem" is invalid on a native <button> and can suppress/override button semantics. Wrap each thumbnail in an <li role="listitem"> with a native button inside, or remove the conflicting ARIA role from the buttons.

    🧰 Tools
    🪛 React Doctor (0.9.1)

    [warning] 343-343: Screen reader users get more reliable semantics from <ul> than role="list", so use <ul> instead.

    Use the matching HTML element when one exists so browsers and assistive tech get native semantics.

    (prefer-tag-over-role)


    [warning] 354-354: Screen reader users can't operate this interactive <button> because role listitem says it isn't, so remove the role or use a different element.

    Do not give an interactive element a role that says it is not interactive.

    (no-interactive-element-to-noninteractive-role)

    🤖 Prompt for AI Agents
    Verify each finding against current code. Fix only still-valid issues, skip the
    rest with a brief reason, keep changes minimal, and validate.
    
    In `@src/components/game/photoMode/PhotoModeViewfinder.tsx` around lines 341 -
    355, Remove the conflicting role="listitem" from each native thumbnail button in
    the captureHistory mapping within PhotoModeViewfinder; keep the buttons’ native
    semantics and existing click behavior intact.
    

    Source: Linters/SAST tools

    Comment on lines +56 to +57
    const sceneNameRef = useRef(sceneName);
    sceneNameRef.current = sceneName;

    Copy link
    Copy Markdown

    Choose a reason for hiding this comment

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

    🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

    🧩 Analysis chain

    🏁 Script executed:

    #!/bin/bash
    set -euo pipefail
    
    echo "== files =="
    git ls-files | rg '(^|/)usePhotoModeController\.ts$|PhotoMode|photoMode' || true
    
    echo "== target outline =="
    ast-grep outline src/components/game/photoMode/usePhotoModeController.ts || true
    
    echo "== target relevant source =="
    cat -n src/components/game/photoMode/usePhotoModeController.ts | sed -n '1,180p'
    
    echo "== related photo capture usages/tests =="
    rg -n "capturePhotoStill|dataUrl|history|sceneNameRef|filterPreset|sceneName" src -S || true

    Repository: IETGLIM/Volodka

    Length of output: 28394


    🏁 Script executed:

    #!/bin/bash
    set -euo pipefail
    
    python3 - <<'PY'
    from pathlib import Path
    p=Path('src/components/game/photoMode/usePhotoModeController.ts')
    s=p.read_text()
    checks={
     "capture_photo_still_call": "const result = await capturePhotoStill(filterPreset);" in s,
     "result_data_in_snapshot": "const historyEntry: PhotoHistoryEntry = { dataUrl: result.dataUrl, title: sceneName, filter: filterPreset }" in s,
     "scene_ref_render_assignment": "sceneNameRef.current = sceneName;" in s,
     "scene_ref_callback_dependency": ", sceneNameRef" in s or ", sceneName" in s,
    }
    for k,v in checks.items():
        print(f"{k}: {v}")
    PY

    Repository: IETGLIM/Volodka

    Length of output: 287


    Snapshot capture metadata before the async operation.

    capturePhotoStill(filterPreset) captures with the filter at invocation time, so pairing result.dataUrl with mutable filterPresetRef.current and sceneNameRef.current in the history entry can attach newer metadata to an older capture. Snapshot filterPreset and sceneName before starting the capture, pass those snapshots into the history entry, add sceneName to captureScreenshot dependencies, and remove the render-time sceneNameRef.current = sceneName.

    Also applies to lines 101-110.

    🧰 Tools
    🪛 React Doctor (0.9.1)

    [error] 57-57: This ref is mutated during render. React can replay or discard render work, so the mutation can leak from UI that never commits.

    Move ref writes into an event handler or effect. Render must stay pure because React can replay or discard it. The predictable null-guarded lazy initialization pattern remains supported.

    (no-ref-current-in-render)

    🤖 Prompt for AI Agents
    Verify each finding against current code. Fix only still-valid issues, skip the
    rest with a brief reason, keep changes minimal, and validate.
    
    In `@src/components/game/photoMode/usePhotoModeController.ts` around lines 56 -
    57, Update the capture flow around capturePhotoStill and captureScreenshot to
    snapshot filterPreset and sceneName before the async capture begins, then use
    those snapshots when creating the history entry. Add sceneName to
    captureScreenshot’s dependency list, and remove the render-time
    sceneNameRef.current assignment and any metadata reads that can observe newer
    values.
    

    Source: Linters/SAST tools

    Comment on lines +126 to +130
    const selectHistoryCapture = useCallback((entry: PhotoCaptureHistoryEntry) => {
    setPreview({ dataUrl: entry.dataUrl, timestamp: entry.timestamp });
    bumpPreviewTimer();
    setLiveAnnouncement(`${PHOTO_MODE_LABELS.gallerySelect}: ${entry.sceneName}`);
    }, [bumpPreviewTimer]);

    Copy link
    Copy Markdown

    Choose a reason for hiding this comment

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

    🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

    Preserve the selected capture’s filter.

    Selecting an older entry stores only its URL and timestamp. PhotoModeViewfinder.tsx:304 and this hook’s download/share paths then use the current filterPreset, so selecting a noir capture while the mode is neon previews and exports it with the wrong filter. Store entry.filter in PhotoPreviewData and use that value for preview, download, and share.

    🤖 Prompt for AI Agents
    Verify each finding against current code. Fix only still-valid issues, skip the
    rest with a brief reason, keep changes minimal, and validate.
    
    In `@src/components/game/photoMode/usePhotoModeController.ts` around lines 126 -
    130, Update selectHistoryCapture to store entry.filter in PhotoPreviewData
    alongside the dataUrl and timestamp, then update PhotoModeViewfinder and this
    hook’s download/share paths to use the selected preview filter instead of the
    current filterPreset. Preserve current-filter behavior for newly captured photos
    and ensure historical captures preview, download, and share with their stored
    filter.
    
    Comment on lines +42 to +52
    it('falls back to nearest registered npc', () => {
    groups.set('albert', {
    getWorldPosition: (v) => { v.set(5, 0, 0); },
    });
    groups.set('maria', {
    getWorldPosition: (v) => { v.set(1, 0, 0); },
    });
    const focus = resolveNearestNpcForRingFocus(null);
    expect(focus?.npcId).toBe('maria');
    expect(focus?.label).toBe('Мария');
    });

    Copy link
    Copy Markdown

    Choose a reason for hiding this comment

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

    🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

    Exercise the missing recovered-target fallback.

    Line 49 passes null, so this does not verify the documented case where a recovered target ID is present but no longer registered. Pass an unknown ID here (or add a case) and retain the nearest-NPC assertion.

    -    const focus = resolveNearestNpcForRingFocus(null);
    +    const focus = resolveNearestNpcForRingFocus('missing-npc');
    📝 Committable suggestion

    ‼️ IMPORTANT
    Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

    Suggested change
    it('falls back to nearest registered npc', () => {
    groups.set('albert', {
    getWorldPosition: (v) => { v.set(5, 0, 0); },
    });
    groups.set('maria', {
    getWorldPosition: (v) => { v.set(1, 0, 0); },
    });
    const focus = resolveNearestNpcForRingFocus(null);
    expect(focus?.npcId).toBe('maria');
    expect(focus?.label).toBe('Мария');
    });
    it('falls back to nearest registered npc', () => {
    groups.set('albert', {
    getWorldPosition: (v) => { v.set(5, 0, 0); },
    });
    groups.set('maria', {
    getWorldPosition: (v) => { v.set(1, 0, 0); },
    });
    const focus = resolveNearestNpcForRingFocus('missing-npc');
    expect(focus?.npcId).toBe('maria');
    expect(focus?.label).toBe('Мария');
    });
    🤖 Prompt for AI Agents
    Verify each finding against current code. Fix only still-valid issues, skip the
    rest with a brief reason, keep changes minimal, and validate.
    
    In `@src/engine/interaction/stuckRecoveryNpcFocus.test.ts` around lines 42 - 52,
    Update the test for resolveNearestNpcForRingFocus to pass a non-null, unknown
    recovered target ID that is absent from groups, while retaining the assertions
    that Maria is selected as the nearest NPC and has the expected label.
    
    Comment on lines +69 to +76
    eventBus.emit('interaction:hint', {
    label: focus.label,
    key: 'E',
    description: 'Снова в зоне взаимодействия',
    type: 'npc',
    distance: focus.distance,
    maxRange: Math.max(focus.distance + 0.5, NPC_INTERACTION_QUERY_RANGE),
    });

    Copy link
    Copy Markdown

    Choose a reason for hiding this comment

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

    🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

    Do not report an out-of-range NPC as interactable.

    The fallback has no distance limit, and Line 75 explicitly supports distant NPCs; therefore Line 72 can falsely say the player is “in the interaction zone.” Use a re-approach message when focus.distance > NPC_INTERACTION_QUERY_RANGE.

    Proposed fix
    +  const inRange = focus.distance <= NPC_INTERACTION_QUERY_RANGE;
       eventBus.emit('interaction:hint', {
         label: focus.label,
         key: 'E',
    -    description: 'Снова в зоне взаимодействия',
    +    description: inRange
    +      ? 'Снова в зоне взаимодействия'
    +      : 'Подойдите ближе для взаимодействия',
    📝 Committable suggestion

    ‼️ IMPORTANT
    Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

    Suggested change
    eventBus.emit('interaction:hint', {
    label: focus.label,
    key: 'E',
    description: 'Снова в зоне взаимодействия',
    type: 'npc',
    distance: focus.distance,
    maxRange: Math.max(focus.distance + 0.5, NPC_INTERACTION_QUERY_RANGE),
    });
    const inRange = focus.distance <= NPC_INTERACTION_QUERY_RANGE;
    eventBus.emit('interaction:hint', {
    label: focus.label,
    key: 'E',
    description: inRange
    ? 'Снова в зоне взаимодействия'
    : 'Подойдите ближе для взаимодействия',
    type: 'npc',
    distance: focus.distance,
    maxRange: Math.max(focus.distance + 0.5, NPC_INTERACTION_QUERY_RANGE),
    });
    🤖 Prompt for AI Agents
    Verify each finding against current code. Fix only still-valid issues, skip the
    rest with a brief reason, keep changes minimal, and validate.
    
    In `@src/engine/interaction/stuckRecoveryNpcFocus.ts` around lines 69 - 76, Update
    the interaction hint emission in the stuck-recovery NPC focus flow to use a
    re-approach message when focus.distance exceeds NPC_INTERACTION_QUERY_RANGE,
    rather than reporting the NPC as being in the interaction zone. Preserve the
    existing in-range message for distances within the query range and keep the
    remaining hint fields unchanged.
    
    Comment on lines +23 to +25
    id: `cap-${entry.timestamp}-${history.length}`,
    };
    return [next, ...history].slice(0, Math.max(1, max));

    Copy link
    Copy Markdown

    Choose a reason for hiding this comment

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

    🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

    Make capture IDs collision-resistant.

    Once history is full, two captures with the same millisecond timestamp both generate cap-${timestamp}-6. Duplicate React keys can make gallery selection ambiguous. Use a monotonic counter or UUID independent of history.length, and add a same-timestamp regression test.

    🤖 Prompt for AI Agents
    Verify each finding against current code. Fix only still-valid issues, skip the
    rest with a brief reason, keep changes minimal, and validate.
    
    In `@src/engine/photo/photoCaptureHistory.ts` around lines 23 - 25, Update the
    capture ID generation in the photo capture history function to use a
    collision-resistant identifier independent of history.length, such as a
    monotonic counter or UUID, while preserving the existing cap-${timestamp}- style
    only if uniqueness remains guaranteed. Add a regression test covering two
    captures created within the same millisecond when history is full, verifying
    their IDs differ.
    
    Comment on lines +58 to +62
    export const RUN_FOV_BOOST = 3.5;
    /** Player speed (m/s) at which FOV boost is at full intensity. */
    export const RUN_FOV_SPEED_FULL = 5.5;
    export const RUN_FOV_SPEED_FULL = 5.25;
    /** Player speed (m/s) at which FOV boost begins. */
    export const RUN_FOV_SPEED_MIN = 2.5;
    export const RUN_FOV_SPEED_MIN = 2.35;

    Copy link
    Copy Markdown

    Choose a reason for hiding this comment

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

    🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

    Keep the FOV boost sprint-only.

    With RUN_FOV_SPEED_MIN = 2.35, normal movement around 4 m/s already receives approximately a 2° boost, while src/engine/camera/strategies/explorationStrategy.ts documents this effect as engaging only during sprinting. Set the minimum threshold to the actual sprint threshold and keep the full-intensity threshold above it.

    🤖 Prompt for AI Agents
    Verify each finding against current code. Fix only still-valid issues, skip the
    rest with a brief reason, keep changes minimal, and validate.
    
    In `@src/engine/player/playerConstants.ts` around lines 58 - 62, Update
    RUN_FOV_SPEED_MIN in the player movement constants to match the actual sprint
    threshold, preventing normal movement from receiving FOV boost; retain
    RUN_FOV_SPEED_FULL above this minimum so full intensity remains reached only at
    higher sprint speeds.
    
    @IETGLIM
    IETGLIM merged commit 058e7ee into main Jul 28, 2026
    2 checks passed
    Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

    Labels

    None yet

    1 participant