# Change Request CR-003 — Map Art **Project:** Deadhead: Space Freight Tycoon **Applies to:** a build with Phases 1–10, CR-001 (Faster Start), and CR-002 (Open Markets) complete **Do this:** as **Phase 10C**, after Phase 10B and before Phase 11 **Related docs:** `implementation-plan.md`, `change-request-faster-start.md`, `change-request-open-markets.md`, `DECISIONS.md` > **Numbering note (added when implemented):** the plan's §0.5 already uses 29–34 (CR-001) and 35–36 (CR-002). This CR's decisions 0.5.29–0.5.33 are recorded as **0.5.37 Map art pipeline**, **0.5.38 Constant screen size**, **0.5.39 Child bodies**, **0.5.40 Background** and **0.5.41 Fallback**. References to them in §5 and §6 mean the renumbered decisions. --- ## 1. Why The Phase 10 system map uses placeholder circles. The user has generated real art and placed it in the map assets folder (expected: `apps/web/public/assets/map/`). This change replaces the placeholders with that art and tightens how the map draws, while keeping everything that moves or changes state (positions, orbits, routes, labels, locks, badges) drawn in code. ## 2. The art ### What the user provided - `bg.png` — the space background. No planets in it. - One transparent sprite per body: Sun, Earth, Moon, Mars, Ceres Station, Jupiter. - Possibly a small cloud-platform badge for Jupiter. - One transparent top-down sprite per ship model, nose pointing **up**: Courier, Freighter, Bulk Hauler, Company Courier. Source images are large (likely 1024×1024 or bigger) and may have empty padding around the subject. ### What is drawn in code (not art) Orbit rings, route lines, location labels, the undiscovered dim and lock, the HQ (bay) marker, idle-ship count badges, selection highlights. ### Expected logical asset ids | Asset id | Used for | |---|---| | `bg` | Background | | `sun` | Sun | | `body-earth`, `body-moon`, `body-mars`, `body-ceres`, `body-jupiter` | Locations | | `badge-cloud-platform` | Jupiter Cloud Platform marker (optional) | | `ship-courier`, `ship-freighter`, `ship-bulk-hauler`, `ship-company-courier` | Ships | The actual file names in the folder may differ. Step 10C.1 maps them. ## 3. Rules for this change - All rules in `implementation-plan.md` §0.1 apply: steps in order, one commit per step, each step's test must pass. - **Never modify the user's source images.** All resizing and conversion writes to a separate generated folder. - Display sizes, offsets, and visual tuning values are presentation settings, not game numbers. They live in one map style config file in `apps/web`, not in `packages/content`. Sprite keys (which asset belongs to which location or ship model) **do** live in `packages/content`. - Visual quality can't be fully tested automatically. Steps marked "manual check" end with a screenshot (desktop and phone width) committed to `docs/screenshots/` and mentioned in `progress.md` for the user to review. - Update `progress.md` after every step. Stop for the user's review at the end of Phase 10C. ## 4. Decisions Record these in `DECISIONS.md`. - **0.5.29 Map art pipeline.** Source art lives in the map assets folder and is never edited. A script generates optimized copies (trimmed, resized at 1x and 2x, background converted to WebP) into a generated folder that the app loads. - **0.5.30 Constant screen size.** Body and ship sprites keep a roughly constant on-screen size as the player zooms, scaled only between a configured minimum and maximum factor. Positions still use the compressed radial scale from Step 10.1. - **0.5.31 Child bodies.** The Moon (and any future location whose position is "parent plus small offset") is drawn at a fixed screen-space offset from its parent, not at its scaled real position, so it never overlaps the parent at any zoom. - **0.5.32 Background.** The background is not tiled. It is scaled to cover the viewport with a margin and moves with gentle parallax, clamped so its edges never show. - **0.5.33 Fallback.** If any asset is missing or fails to load, the map draws the old placeholder for that item and logs one warning. The map never fails to render because of art. ## 5. Steps — Phase 10C ### Step 10C.1 — Inventory and asset manifest - List the files in the map assets folder. Map each file to the logical asset ids in §2. Record the mapping in a manifest file in `apps/web` (asset id → source file name). - If a file name is ambiguous or an expected asset is missing, record it in `DECISIONS.md` and continue: missing assets use the fallback (0.5.33). If more than one file could match the same id, stop and ask the user. - Add a `sprite` key to each location and ship model in `packages/content` (for example Earth → `body-earth`, Freighter → `ship-freighter`). Add the optional `badge` key to Jupiter Cloud Platform. - **Test:** A test asserts every location and ship model in content has a sprite key, and every sprite key exists in the manifest. A second test asserts every file named in the manifest exists on disk. ### Step 10C.2 — Asset optimization script - Add a script (using `sharp`) that reads the manifest and writes to a generated folder: - Body and badge sprites: trim transparent padding, then export PNG at 256 px and 512 px on the long side. - Ship sprites: trim, then export PNG at 128 px and 256 px. - Background: convert `bg.png` to WebP at no more than 2048 px on the long side. - Run the script as part of the web build and as a standalone command. Commit the generated output so builds don't depend on `sharp` at deploy time. - **Test:** Running the script produces every expected output file at the right dimensions. Running it twice produces identical files. For every sprite (not the background), the four corner pixels of the **source** image are fully transparent; if not, the test fails with a message naming the file and saying its background isn't transparent (this catches a painted-on checkerboard). The background output is under 1 MB. ### Step 10C.3 — Map style config - Create one map style config in `apps/web` holding: on-screen base size per sprite (Sun ~64 px, Jupiter ~48 px, Earth and Mars ~32 px, Ceres ~22 px, Moon ~18 px, ships ~24 px); zoom scale limits for sprites (for example 0.75× to 1.75×); Moon screen offset from Earth (distance and angle); badge offset; minimum tap target size (44 px); parallax factor; background cover margin; colors for orbit rings, routes, labels, HQ marker, and badges. - **Test:** The config validates against a small schema (all sizes positive, min scale ≤ max scale, parallax between 0 and 1). ### Step 10C.4 — Asset loading and fallback - Load the generated assets with PixiJS's asset loader as one bundle. Choose the 2x variants when the device pixel ratio is 2 or more. - Show a small loading state on the map until the bundle resolves. On any per-asset failure, use the placeholder for that item and log one warning per asset id. - **Test:** With all assets mocked as loaded, the scene uses textures for every location and ship. With one asset mocked as failed, that item uses the placeholder, the rest use textures, and exactly one warning is logged. With device pixel ratio 2, the 2x variants are requested. ### Step 10C.5 — Background - Draw the background per decision 0.5.32: scaled to cover the viewport plus the configured margin, offset by camera pan × parallax factor, and clamped so no edge is ever visible. It does not zoom with the map (or zooms only slightly). - **Test:** Unit test of the background placement function: for a range of viewport sizes, pans, and zooms, the background rectangle always fully contains the viewport. Manual check: panning shows gentle parallax with no visible edges or seams; screenshot committed. ### Step 10C.6 — Bodies, orbits, and labels - Draw the Sun at the center and each location's sprite at its compressed-scale position, sized per decisions 0.5.30 and 0.5.31. - Draw a thin, faint orbit ring for each body that orbits the Sun, at its current compressed radius. Draw a small ring around Earth for the Moon. - Draw the Jupiter cloud-platform badge at the configured offset on Jupiter's edge, if the asset exists. - Draw each location's name under it with a dark halo for contrast. - Every location's tap target is at least the configured minimum size, even if its sprite is smaller. - **Test:** Unit tests: the sprite scale function stays within the configured limits at extreme zooms; the Moon's screen position is exactly the configured offset from Earth's at several zoom levels and dates; tap targets are never smaller than the minimum. Manual check: at default zoom on a phone-width screen, all bodies are visible, nothing overlaps, and labels are readable; screenshot committed. ### Step 10C.7 — Location state overlays - Undiscovered locations: sprite desaturated (color matrix filter) and dimmed, with a code-drawn lock icon. This replaces the CR-001 Step 10A.16 lock rendering; keep its tap panel (undiscovered text and reachability list). - Locations with a player bay: a small code-drawn HQ marker under the label. - Locations with idle ships: a count badge (for example "2") at the configured badge offset. Idle ships are **not** drawn individually, so ships never pile up on a planet. - **Test:** Scene test with seeded state: undiscovered locations have the filter and lock; bay locations have the HQ marker; a location with two idle ships shows a badge reading 2 and no individual idle ship sprites. Manual check with screenshot. ### Step 10C.8 — Ships in flight - Each in-flight ship uses its model's sprite, positioned with the sim interpolation every frame (as in Step 10.2), rotated to face its direction of travel (the art points up, so add the matching angle offset). - Draw each route as a faint line from origin to destination, brighter for the remaining part ahead of the ship. - If several ships are within a few pixels of each other, nudge them apart perpendicular to their route so each is visible and tappable. - The selected ship gets a highlight ring. - **Test:** Unit tests: the rotation for a ship moving right is 90° from the art's up direction, and moving down is 180°; with three ships at the same position on the same route, their screen positions are distinct and at least the minimum tap size apart. Manual check: ships move smoothly and face where they're going; screenshot committed. ### Step 10C.9 — Performance - Use one texture per asset (no per-frame texture creation). Pause the PixiJS ticker when the Map route is not mounted or the page is hidden, and resume on return. - **Test:** Unmounting the Map route stops the ticker; a hidden-page event pauses it and a visible event resumes it. Manual check: on a mid-range phone, panning and zooming stay smooth with five locations and ten ships in flight; note the result in `progress.md`. ### Step 10C.10 — Plan amendments and regression - Apply the amendments in §6 to `implementation-plan.md`. - Run the full suite. - **Test:** CI is green. Manual check: the user reviews the committed screenshots (desktop and phone) of the tutorial map, a mid-game map with ships in flight, and a map with undiscovered locations. ## 6. Amendments to `implementation-plan.md` | Step | Change | |---|---| | 0.5 | Add decisions 0.5.29–0.5.33. | | 10.1 | Bodies use sprites, constant screen size, Moon offset (10C.6). | | 10.2 | Ships use rotated sprites; idle ships shown as count badges (10C.7, 10C.8). | | 10.3 | Tap targets meet the minimum size (10C.6). | | CR-001 10A.16 | Lock rendering replaced by 10C.7; panel behavior unchanged. | | 11.1 | The service worker precaches the generated map assets. | ## 7. Out of scope Animated sprites or sprite sheets, engine trail particles, planet rotation, day/night shading, zoom-dependent detail levels, new art beyond what the user provided, and anything else in `implementation-plan.md` §0.3.