11 KiB
AGENTS.md
These requirements apply to every screen, feature, game system, and code change in this repository.
Product priority
AYN Thor Android app is the primary product. Browser build is a secondary access path for players without an AYN Thor. Do not weaken the Thor experience to simplify the browser version; provide an adaptive browser fallback instead.
Game asset workflow
game_assets/is an ignored local source library. Never load or import a runtime asset directly from it.- When adopting a model, animation, texture, audio file, or other game asset from
game_assets/, copy it into the trackedsrc/assets/game/runtime library first. Preserve its path relative togame_assets/. - Use
pnpm assets:import <path-within-game_assets>for the copy. Use--replaceonly to deliberately update an existing runtime copy. Copy required companion files and license/attribution notices with assets that are not self-contained. - Reference only
src/assets/game/copies from game code. Keepgame_assets/as source material for future imports, conversions, and authoring work.
AYN Thor display and layout targets
Design and test against both physical displays in landscape orientation. Physical panel resolution and Android CSS/layout viewport are different concepts; record and validate both.
| Display | Panel specification | Android CSS/layout viewport |
|---|---|---|
| Main / top | 6-inch AMOLED, 1920 x 1080 physical pixels, 120 Hz | Approximately 960 x 540 CSS pixels |
| Secondary / bottom | 3.92-inch AMOLED, 1240 x 1080 physical pixels, 60 Hz | Approximately 620 x 540 CSS pixels |
1920 x 1080and1240 x 1080describe physical framebuffer/panel pixels. They are not the UI layout viewports.- All Thor UI sizing, breakpoints, typography, hit targets, navigation geometry, and visual QA must use the Android CSS/layout viewport: approximately
960 x 540for the main display and620 x 540for the secondary display. - Use physical resolution only for render-buffer quality, pixel density, screenshot validation, and GPU/performance decisions.
- Ship gameplay at a stable, locked 60 FPS on both displays. The main panel can refresh at 120 Hz, but 60 FPS is the required game target.
- Treat the displays as two independent surfaces with different aspect ratios, not one continuous
3160 x 1080canvas. - Use each display's own container or viewport dimensions for layout and typography. Never size bottom-screen UI from the top screen or combined page viewport.
- Approximate CSS viewports reflect current Android scaling. Runtime code must still measure actual viewport size, device pixel ratio, safe insets, and Android system-bar insets rather than assuming those values are constant.
- Keep critical controls and information inside safe bounds. No clipping, overlap, unreadably small text, or off-screen focus state at either target size.
- Any new full-screen view, menu, modal, HUD, overlay, loading state, and error state must be checked at both CSS/layout viewports and their corresponding physical framebuffer resolutions.
Dual-screen behavior
- Use both Thor screens whenever the second display can add useful information or controls without duplicating clutter.
- Top screen owns primary 3D gameplay, world presentation, cinematics, and information needed while looking at the action.
- Bottom screen should host contextual/tactical UI such as party state, abilities, maps, inventory, quests, chat, loadouts, and settings where appropriate.
- Actions and state must stay synchronized across displays. Moving a feature to the bottom screen must not hide required feedback from the top screen.
- Keep rendering and update work independent so an expensive bottom-screen UI update cannot stall top-screen gameplay.
- Support display attach, detach, recreation, sleep, and resume without losing game state or duplicating input handlers.
- Browser/single-display mode must preserve all features through responsive panels, tabs, drawers, or overlays. Dual-screen layout remains preferred on Thor.
Controller-first navigation
Every screen must be fully operable with the built-in controller. Touch, mouse, and keyboard are optional alternate inputs.
- No action may require tapping, clicking, hovering, or focusing the canvas/DOM first.
- Do not depend on browser/DOM focus for gamepad routing. Poll and route controller input through one app-level input service, then send semantic actions to gameplay and UI.
- Every interactive item must be reachable in a deterministic order and show a clear visible focus state.
- Directional navigation must match spatial layout. Define explicit neighbors when automatic spatial navigation would be ambiguous.
- Provide controller paths for confirm, cancel/back, tab or section changes, scrolling, sliders, dialogs, tooltips, pause, and return to gameplay.
- Trap navigation inside an open modal, then restore focus to the element that opened it when the modal closes.
- Choose a safe default focus target whenever a screen opens or the active display changes. Never leave focus nowhere or behind an overlay.
- Prevent held buttons and analog drift from causing uncontrolled repeats. Use dead zones, initial repeat delay, repeat rate, debouncing, and one-shot press edges as appropriate.
- Clear held-input state on controller disconnect, app pause, visibility change, screen recreation, and display handoff.
- Keep controller mappings centralized and remappable. UI prompts must reflect the active controller mapping.
- Avoid text entry for required flows where possible. When required, provide an Android/controller-accessible input path.
- Browser fallback must not require a click to focus individual controls or the game canvas. Browser/OS restrictions may require the browser itself to be active and may require one initial user gesture before the Gamepad API is exposed; no game code may add further focus requirements.
Modular game architecture
Build every game mechanic as a reusable, composable module. Repeated mechanic code is a defect.
- Separate game rules and state from rendering, React components, display routing, input devices, audio, persistence, and networking.
- Expose mechanics through small typed interfaces and semantic commands/events. Do not let a mechanic read DOM state or hard-code a specific screen.
- Make abilities, effects, enemies, encounters, items, quests, and UI bindings data-driven when behavior differs mainly by configuration.
- Prefer shared systems and composition over copied handlers, large conditional components, or deep inheritance.
- Keep one authoritative state model. Both displays render projections of shared state rather than maintaining competing copies.
- Put shared timing, cooldown, targeting, resource, status-effect, damage, healing, and validation logic in domain modules usable by players, AI, previews, tooltips, and tests.
- Inject platform-specific adapters for Android, browser, storage, display, input, and network behavior.
- Add focused tests for reusable mechanics and regression tests for bugs. A mechanic should be testable without launching the renderer or mounting UI.
- Before adding code, search for an existing system that can be extended. If similar logic appears twice, extract the common behavior unless doing so would create a less coherent abstraction.
Performance requirements
Stable locked 60 FPS is a release requirement, not a best-effort goal.
- Treat the AYN Thor Lite configuration (Snapdragon 865, 8 GB RAM) as the performance floor unless project requirements explicitly change.
- Maintain a 16.67 ms total frame budget during real gameplay, including simulation, scripting, rendering, UI, and cross-display synchronization.
- Profile representative gameplay on physical Thor hardware. Desktop-only measurements are insufficient for performance-sensitive changes.
- Measure before and after substantial changes. Track frame time, 1% lows, long frames, CPU time, GPU time, JavaScript heap, native memory, draw calls, triangles, texture memory, and garbage-collection pauses where tooling permits.
- Do not allocate objects, arrays, closures, materials, textures, or temporary vectors in hot per-frame paths when reusable storage or pooling is practical.
- Dispose GPU, audio, event-listener, timer, subscription, and display resources deterministically when their owner unloads.
- Use culling, LOD, instancing, batching, compressed textures, bounded particle counts, and asset streaming/lazy loading where they measurably help.
- Keep simulation work bounded. Avoid unbounded searches, per-frame full-collection scans, unnecessary polling, and update loops for invisible or inactive entities.
- Prevent avoidable React renders. Subscribe to narrow state slices, memoize only where measured or structurally useful, batch updates, and throttle non-critical bottom-screen telemetry.
- Load heavy screens and assets on demand, but prefetch predictable transitions when memory budget allows.
- Avoid duplicate assets and duplicate state in memory across displays. Share immutable resources where platform APIs allow.
- Favor algorithms and data layouts that reduce CPU and RAM together. Do not trade a large permanent memory increase for a tiny unmeasured CPU gain.
- Performance fixes must preserve deterministic rules, input responsiveness, visual clarity, and controller accessibility.
Verification scope
- Verify the changed code and its direct dependencies. Do not run tests, visual QA, or controller checks for unrelated screens or systems by default.
- For a screen change, check that screen's applicable display layouts, browser fallback, and controller path only.
- For a reusable mechanic or domain change, run its focused tests plus direct consumers affected by the change.
- Expand to broader regression or full-suite verification only for shared foundations, cross-cutting changes, risky refactors, release validation, or when explicitly requested.
- State what was verified and any deliberately unverified scope in the handoff.
Definition of done
A screen or mechanic is not complete until:
- Its main-display UI is designed and verified at the approximately
960 x 540Android CSS/layout viewport, with rendering validated against the1920 x 1080physical panel. - Its applicable secondary UI is designed and verified at the approximately
620 x 540Android CSS/layout viewport, with rendering validated against the1240 x 1080physical panel. - It has a complete single-display browser fallback.
- Every action is reachable and understandable using only a controller, with no click-to-focus step.
- Shared game logic is modular, typed, and testable outside the renderer/UI.
- It introduces no known resource leak, unbounded work, or avoidable hot-path allocation.
- Representative Thor hardware sustains the locked 60 FPS target, or the change includes measured evidence and an explicit approved exception.