# Submission rules Requirements a game must satisfy to be accepted. A submission that violates any **MUST** is rejected without review; the QA queue is not a linting service. Applies to internal and third-party submissions alike. --- ## 1. Content **MUST NOT** contain: sexual content, graphic violence, gambling or simulated gambling with real-money framing, hate speech, or anything targeting minors inappropriately. The portal is open to all ages by default. **MUST** declare an age rating: `all`, `7+`, `12+`, or `16+`. **MUST** be original work, or work the submitter holds the rights to. This includes art, audio and fonts — not just code. Asset licences are declared in the manifest and are checked. **MUST NOT** use third-party trademarks, characters or branding without written permission. --- ## 2. Sizing and layout This section exists because the portal decides how much room a game gets, and that amount is not known in advance. It changes with the device, the orientation, and whether the player is in fullscreen. **MUST fill its container and nothing more.** The root element is `width: 100%; height: 100%`. It **MUST NOT** carry `min-width`, `min-height`, `aspect-ratio`, or any fixed pixel dimension. This is the single most common failure we see. A rule such as: ```css .my-game-wrap { height: 100%; min-height: 480px; } /* WRONG */ ``` looks harmless and is not: on a phone the play area is often shorter than 480px, `min-height` wins over `height`, and the game silently renders taller than its frame. Everything drawn near the bottom — a player ship, a HUD, a button — is clipped away, and input coordinates stop matching what the player sees. **MUST declare a minimum playable size in the manifest instead:** ```json "minSize": { "width": 320, "height": 480 } ``` The shell guarantees the game gets at least that. Where the device cannot provide it, the shell scales the frame down rather than letting the game overflow. Declaring the minimum is how a game asks for space; CSS is not. **MUST NOT use `position: fixed`.** Inside an iframe it anchors to the frame, not the screen, and behaves differently again in fullscreen. Use `position: absolute` within the root, which is what is almost always meant. **MUST implement `windowResized` completely.** Resizing the canvas is not enough: every entity positioned relative to the old dimensions has to be recomputed. A ship parked at `height - 60` stays at the old offset after a shrink and ends up off-screen permanently. Rotate the device mid-game and verify the result — this is checked in review. **MUST NOT depend on pinch zoom.** Zoom is suppressed during play, because a shifted visual viewport desynchronises touch input from what is on screen. ### Is shipping CSS reasonable at all? Yes, but for a narrower purpose than most submissions assume. Legitimate uses are DOM overlays a game owns — a game-over panel, a pause menu, a HUD built from elements rather than drawn. What is not legitimate is CSS that negotiates with the container. Sizing is a contract between the game and the shell, and that contract is expressed in the manifest, where the shell can read it. CSS cannot be read or reasoned about by the shell; it can only be discovered when something looks wrong on someone's phone. Concretely: all styles **MUST** be scoped under a game-specific class, and **MUST NOT** target `html`, `body`, or `:root` with dimensions, overflow, or positioning. --- ## 3. Technical **MUST** load and use the arcade SDK (`docs/developers/ARCADE-SDK.md`), calling at minimum `init`, `loadingFinished`, `gameplayStart`, `gameplayStop` and `gameOver`. **MUST** be fully playable standalone, by opening its `index.html` directly. **MUST** handle `bananai:restart` without a page reload. **MUST NOT** make network requests to any origin other than the one it is served from. No external analytics, no external fonts, no CDN-loaded libraries. Bundle everything. **MUST** reference portal files by root-relative path — `/sdk/v0/bananai-sdk.js`, never `https://bananai.games/sdk/v0/bananai-sdk.js`. Your game is served from the portal's own origin, so the root-relative form always resolves; the absolute form is a *cross-origin* request everywhere the portal is not live production — on your machine, on the preview domain, and in `npm run check`, which fails it as an external request. It also breaks the day the portal changes domain, and that day is coming. This rule is checked automatically. **MUST NOT** use `localStorage` for anything other than its own settings and local high scores, namespaced under `game::`. **MUST work in private browsing.** Incognito restricts `localStorage`, and an unguarded write throws. Wrap every storage access in try/catch and degrade to not saving, rather than to a broken game. **MUST NOT let its own scrolling reach the parent page.** A game document that scrolls inside its frame will chain that scroll outwards on iOS, and the player ends up moving the portal instead of playing. In practice this follows from sizing correctly, but it is checked directly. **MUST NOT** attempt to access `window.parent`, `document.cookie`, or navigate the top-level window. The sandbox blocks this; attempting it is grounds for rejection. **MUST** be responsive: fill the container, handle resize, work from 320px wide upward, in both orientations. **MUST** support touch input, with `touchStarted` / `touchMoved` / `touchEnded` returning `false` so the page does not scroll underneath. **MUST** run at a stable 30fps minimum on a mid-range 2021 Android phone. We test on real hardware, not a throttled desktop. **SHOULD** stay under 15 MB total. Above 25 MB requires justification. **SHOULD** reach interactive within 3 seconds on a 4G connection. --- ## 4. Behaviour **MUST NOT** autoplay audio before the first user interaction. Browsers block it and it is hostile regardless. **MUST** provide a mute control if it has audio at all. **MUST NOT** gate progress behind `rewardedBreak()`, which always resolves `false` here. Treat rewards as an optional bonus that may never arrive. **MUST NOT** display advertising of any kind, including house ads for the developer's other games or cross-promotion to external sites. **MUST NOT** collect personal data. No forms asking for email, name or age. **MAY** assume it has the keyboard. The shell focuses the game's frame once it has loaded, and re-focuses it after fullscreen and after any click that lands on the shell chrome, so key events are delivered to the game's own document. Listen on the game's `window` or `document` — a listener on a specific element only fires if that element is what actually holds focus. --- ## 5. Submission package ``` / ├── bananai.json manifest (see below) ├── index.html entry point ├── assets/ │ └── thumb.png catalogue thumbnail, square, 400×400 └── js/ └── vendor/ bundled libraries ``` **The thumbnail ships inside the game folder**, at `assets/thumb.png` (or `.svg`, or `.webp`), and is declared in the manifest. It is not handed over separately and does not live in the portal's shared assets. This follows from the folder being self-contained: a submission is one directory that can be dropped in, moved, or removed as a unit. A thumbnail kept elsewhere is a second thing to remember, and the thing people forget when a game is withdrawn. It must stay legible at roughly 180px, which is its real size in the grid. Check it at that size, not at full resolution. SVG is preferred where the artwork suits it — a few KB, sharp at any density — but a PNG is the right call for detailed or photographic art. `bananai.json`: ```json { "id": "gemburst", "title": "Gem Burst", "version": "1.0.0", "category": "arcade", "ageRating": "all", "description": { "it": "...", "en": "..." }, "instructions": { "it": "...", "en": "..." }, "controls": ["mouse", "touch"], "orientation": "any", "minSize": { "width": 320, "height": 480 }, "sdkVersion": "0.1.1", "maxScoreRate": 500, "author": { "name": "...", "email": "...", "url": "..." }, "licences": [ { "asset": "audio/pop.ogg", "source": "...", "licence": "CC0" } ] } ``` `sdkVersion` is the **full** version — `0.1.1`, not `0.1`. The patch digit is not cosmetic: in 0.1.0 the two advertising methods were local stubs that resolved without contacting the portal, so a game vendoring 0.1.0 can never show an ad or earn from one, and nothing about it looks broken. `0.1` does not say which of the two you shipped, which makes the one question review needs to answer unanswerable. State what the package actually contains; declaring a version you have not shipped is worse than shipping an old one. `maxScoreRate` is the theoretical maximum points per second. The backend uses it to reject implausible submissions, so an inflated value is a red flag in review, not a way to be safe. --- ## 6. Review process 1. **Automated checks** — manifest schema, bundle size, no external requests, SDK calls present, sandbox compliance, and `npm run check`, which loads the game at five viewport sizes and verifies the canvas matches its frame with no internal scrolling. Runs on submission; failures come back within minutes. 2. **Internal QA** — see `docs/developers/QA-CHECKLIST.md`. Target turnaround: 5 working days. 3. **Outcome** — approved, or rejected with specific reasons. Resubmission is unlimited. Approved builds are published by the developer, not automatically. Publishing is a separate, explicit action. --- ## 7. Rights and takedown The developer retains full ownership. Submission grants Bananai a non-exclusive licence to host and display the game. Either party may withdraw a game with 30 days' notice. We may remove a game immediately if a rights claim, security issue or rule violation is found — with a written explanation and a right of reply. **We do not claim exclusivity.** A game published here can be published anywhere else, which is precisely why the SDK ships ad-API shims: portability is a feature we offer developers, not a leak we tolerate.