# Astra Craft

![icon](pwa-512x512.png "Astra Craft Icon")

## Part 1 — Player guide

Build a world together from your phone or desktop. Astra Craft is a first-person creative game: explore gentle hills, lakes and scattered trees, then place or remove blocks freely. The landscape is 128 × 128 blocks with a 48-block build ceiling. Pigs, cows and chickens wander nearby.

### Move, look and build

![Portrait controls with movement, building, jump, look and eight block choices](guide-controls.png)

- **MOVE**, on the left: Drag the pad to walk forward, backward or sideways.
- **LOOK**, on the right: Drag to turn the camera. You can also drag an unobstructed part of the world.
- **Cube −**: Remove the block under the crosshair.
- **Cube +**: Place the selected material beside the targeted block.
- **↑**: Jump while walking; hold to rise while flying.
- **↓**: Hold to descend while flying. Disabled when walking.
- **Walking / Flying**: Tap to switch between walking and creative flight.
- **Eight block swatches**: Choose Grass, Sand, Wood, Stone, Coral, Sunflower, Sky or Lavender. Supplies are unlimited.

Aim the centre crosshair at a block within eight blocks. The **red outline** marks the block you can remove; the **green outline** previews where a new block will appear. Choose a swatch, then press Cube +. A disabled action means there is no reachable target or valid placement space. You cannot place blocks inside yourself or another known player.

Walking includes gravity, jumping and smooth automatic steps up one-block ledges. Walls, trees and ceilings stop you in both walking and flying modes. Water supports your feet: walk across its surface without sinking. Animals are scenery; there is no combat or inventory to manage.

### Play in either orientation

![Landscape game with Move and building on the left, Look and jump on the right](guide-landscape.jpg)

In portrait, the controls sit below the world. In landscape, including desktop browsers, the world fills the viewport and the controls overlay its edges. Move and building controls stay on the left; looking and jump/flight controls stay on the right so you can move and jump together. Block choices stay along the bottom, leaving the centre clear for aiming.

### Keyboard controls

- **W, A, S, D**: Move.
- **Arrow keys**: Look.
- **Space**: Jump / fly up.
- **Shift**: Fly down.
- **F**: Toggle walking / flying.
- **E / Q**: Place / remove.
- **1–8**: Select a block.

### Invite friends and customize your player

![World options: fullscreen, change player name, invite players](guide-options.png)

Open **Options** at the top of the game:

1. **Enter fullscreen** uses the whole display where supported. Use the same option to leave fullscreen.
2. **Music on / Music off** toggles the gentle generated soundtrack. Your choice is saved on this browser; movement sounds and animal calls stay on.
3. **Change player name** lets you edit your name, top color and pants color. Press **Save player** to apply them. Other players see your textured block character with your name above its head; your own view stays first person.
4. **Invite players** opens the standard RCWeb QR panel. Friends scan it or use **Copy invite link** to join the same world. Everyone runs the same app; no separate control app is needed.

Sound begins after your first tap or key press. Soft footsteps follow your movement, with different sounds on stone and water. Placing a block makes a soft tap; removing one makes a short crumble. Jumping and landing have short effects, and nearby pigs, cows and chickens occasionally call. Flying is quiet. Audio pauses while the app is hidden.

### Install Astra Craft

Use your browser's **Install app** or **Add to Home Screen** option to give Astra Craft its own app icon. On iPhone or iPad, use **Share → Add to Home Screen**. Open the world you want to keep before installing: the app's launch link remembers that room.

Installed launches request landscape fullscreen, with an Astra Craft icon and matching dark launch background. Browsers that do not support those settings use their own app display and orientation behavior. Installation requires HTTPS (or localhost for development). An internet connection is required to launch; no offline app cache is added.

### Save and return

Changes save automatically on this browser, separately for each room. Keep or bookmark the invite link to return to the same world. Your position and walking/flying mode are saved too.

Connected players can build at the same time. Changes to different blocks are retained; if two players change the same block, one result wins consistently. Offline edits merge when you reconnect.

World saves live in participating browsers, not on the server. Clearing browser data removes that browser's copy; another connected player with a saved copy can restore the shared edits. Watch the save status for storage errors.

## Part 2 — Implementation guide for agents

This section describes the current implementation and the design needed to build a similar symmetric RCWeb game. Use the source files as the authority for exact constants. This app explicitly targets modern JavaScript, WebGL, Pointer Events and modern mobile browsers.

### Files and startup

- [index.html](index.html): App shell, controls, dialogs, icons, texture preloads and script order.
- [webmanifest.json](webmanifest.json): Install name, description, icons, screenshot and landscape fullscreen launch. RCWeb fills in the app ID, room-specific start URL and scope; the manifest link is set after connection establishes `rc.room`.
- [style.css](style.css): Portrait panel, landscape overlays, safe areas and compact options.
- [script.js](script.js): Integration, input, render loop, chunk meshes, saves and RCWeb callbacks.
- [world.js](world.js): Deterministic terrain, voxel registers, migrations, raycast and player physics.
- [graphics.js](graphics.js): Lighting, procedural sky, water and adaptive render resolution.
- [foliage.js](foliage.js): Batched grass tufts and leaf sprays.
- [avatars.js](avatars.js): Textured characters, tintable outfits, nameplates and interpolation.
- [animals.js](animals.js): Roaming simulation, animal meshes and coordinator synchronization.
- [audio.js](audio.js): Original 84 BPM looping score, synthesized movement, building and animal effects, proximity/cadence limits and saved music preference. Uses one gesture-unlocked Web Audio context, at most 16 voices and 262,840 bytes of reusable mono effect buffers; no audio files or libraries are downloaded. A 60 ms audio-clock scheduler queues 180 ms ahead, skips missed windows and stops while music is off or the app is hidden. Completed sources disconnect. Effects remain independent of the music gain. Footsteps use actual grounded travel distance; animal calls use local rendered positions, fade over 18 blocks and play at most once every 5–7 seconds.

Serve the directory at `http://localhost:8080/astra-craft/?r=test-0001`. App edits take effect on refresh without compiling or restarting Java. Test room IDs must contain 4–11 ASCII letters, digits or hyphens; invalid room IDs can be replaced by setup, separating test clients.

Load `/assets/core/comms.js` first, followed by the repository's QR library and Three.js r146, then the app files in the order shown in `index.html`. No new external dependencies are needed. Register callbacks before `rc.connect()`. Initialize room-dependent state inside `rc.onConnected()`; setup values such as `rc.room` and `rc.client` are not available synchronously.

The globally reachable `window.astraCraft` exposes `receiveV3`, `requestV3`, `presenceV3` and `animalsV3`. Normal sends use:

```javascript
rc.sendFunctionCall('astra-craft', 'astraCraft.receiveV3',
    room, rc.client, editBatch);
```

Receivers check room, sender and payload shape before applying state. RCWeb transports executable function calls within a trusted room; payload validation is not a security boundary against malicious participants.

### Voxel model and concurrent edits

The base world is generated deterministically into a `Uint8Array` of 128 × 128 × 48 cells. The linear index is:

```text
key = x + 128 * (z + 128 * y)
```

Material IDs are air 0, the eight selectable materials 1–8, leaves 9, water 10, snow 11 and earth 12. Terrain includes gentle hills, lakes and 43 trees; trunk generation stops below the leaf canopy. Generated terrain is reconstructed locally rather than transmitted.

Only edits are stored and shared, as per-voxel last-writer-wins registers:

```text
[voxelKey, materialId, counter, actorId]
```

Each page creates a random actor ID distinct from its RCWeb client ID. Local edits increment a Lamport clock; merges advance that clock to the largest received counter. Compare registers by counter, then lexicographic actor ID, then material ID as a final deterministic tie-break. Keep air edits as tombstones so a removed block cannot reappear from an older snapshot.

This retains concurrent edits to different cells and converges on one value for a contested cell without relying on synchronized wall clocks. Validate integer bounds, material IDs, safe positive counters and actor format before merging. Report invalid records visibly.

Broadcast local edits immediately. Joining and reconnecting clients request peer snapshots and share retained edits. Snapshots use batches of at most 128 records spaced 25 ms apart, with jittered, throttled responses. Visibility resume and periodic 12-second repair exchanges recover missed updates. Versioned callbacks prevent older clients from interpreting the larger world's indices; participants should refresh together after upgrades.

### Persistence and migration

The room save key is `astra-craft:v3:<room>`. Its JSON structure is:

```javascript
{
    version: 3,
    edits: [[voxelKey, materialId, counter, actorId]],
    camera: [x, eyeY, z, yaw, pitch],
    flying: false,
    animals: [/* nine animal state records */]
}
```

Debounce saves by 300 ms, also saving periodically and on blur, hidden visibility and page exit. Merge existing stored voxel edits before writing to avoid same-browser tabs overwriting each other's builds. Merge incoming `storage` events as well. Surface parse, validation and storage failures in the UI.

Migrations translate v1's 32 × 32 × 24 and v2's 80 × 80 × 48 indices into v3 coordinates, preserving edits and air tombstones without deleting old saves. Edits override regenerated terrain.

Names use shared `rcwebName`; validated outfit colors use `astra-craft:outfit` with `top` and `pants` hex colors. Neither belongs in voxel edit registers. Animal snapshots are saved alongside edits but follow their own coordinator protocol.

### Controls, collision and camera

Use Pointer Events and independent movement/look pads so both thumbs work simultaneously. Keep Remove/Place beside Move and Jump/Down beside Look. Apply the overlay layout to every landscape aspect ratio, not only small screens. Controls retain accessible names even when their visible content is an icon.

The player uses an eye height of 1.62, body height of 1.8 and collision radius of 0.3 blocks. Axis-separated AABB movement slides along walls. Subdivide movement into distances no greater than 0.15 blocks and physics timesteps no greater than 0.016 seconds to prevent tunneling. Walking applies gravity and jumping; flying bypasses gravity but retains collision. Water remains solid to this model.

Step-up changes collision position immediately to clear a one-block ledge, then exponentially decays a visual camera offset to make the rise smooth. If a remote edit encloses the player, recover into clear space. DDA voxel raycasting finds a target and adjacent free cell within eight blocks. Placement checks both local and known remote player bodies.

Reserve the save-status line's height and truncate its text. Resize the WebGL drawing buffer only when the game viewport dimensions actually change: changing “Saving” to “Saved on this browser” must not reflow the canvas and cause a mobile flash. Close the options dialog when entering fullscreen so its modal backdrop cannot intercept gameplay input.

### Terrain rendering, lighting and water

Build exposed faces only, grouped into 16 × 16 horizontal chunks spanning the world's height. Separate opaque terrain and water geometry; attach foliage to its supporting chunk. Mark edited chunks and affected boundary neighbors dirty. Rebuild across frames with a 4 ms budget checked between chunks, disposing replaced geometry. One chunk can exceed that budget, so avoid treating it as a hard frame-time guarantee.

Terrain shares a textured Phong material with subtle bump shading and vertex contact shading calculated from neighboring voxels. Hemisphere light, warm directional sunlight, ACES tone mapping at exposure 0.92 and distance fog provide depth without washing out textures. A 1024-pixel soft shadow map covers a snapped window around the player. Cache static shadows; invalidate them for world changes and refresh moving-character shadows at most every 150 ms.

The sky uses continuous three-dimensional directional noise with wind-driven coordinates, a sun disc and halo. It has no texture seams or image download. Sky and water share the same procedural sky-color function. Water combines animated wave normals, sun glints and angle-dependent Fresnel reflections with transparent turquoise shallows. Chunk geometry supplies water-column depth for absorption; submerged terrain receives a depth tint and animated caustic light patterns. Water remains walkable. Transparency disables depth writes and uses the existing scene render, with no screen-space reflection sampling, mirrored scene, terrain reflection or reflection render target. Depth tint follows contiguous water columns; the generated lakes are only one block deep.

Request native multisample anti-aliasing when creating the renderer. Foliage uses alpha-to-coverage on supported multisampled devices. There is one main scene render, plus shadow rendering when invalidated.

### Textures and vegetation

Runtime textures:

- [material-atlas.webp](material-atlas.webp): 1024 × 1024 pixels; 508,294 bytes.
- [foliage-atlas.webp](foliage-atlas.webp): 512 × 512 pixels; 198,994 bytes.
- [animal-atlas.webp](animal-atlas.webp): 512 × 512 pixels; 53,212 bytes.

These imagegen-derived atlases total 760,500 bytes. Mipmaps and filtering retain detail while limiting distant shimmer. Match preload and texture-loader anonymous CORS settings so each image downloads once.

Retain original PNGs for authoring, but load only WebP at runtime. Generation instructions are in [material-atlas-prompt.md](material-atlas-prompt.md), [foliage-atlas-prompt.md](foliage-atlas-prompt.md) and [animal-atlas-prompt.md](animal-atlas-prompt.md).

Deterministic coordinate hashes distribute clusters of two or three grass tufts on exposed grass, with varied height, orientation and green tint. Crossed alpha-tested planes add height; larger leaf sprays and three angled crown planes soften exposed canopy edges. Vertex color darkens the roots, and a shared shader clock bends grass tips and gently moves leaves. Batch both into chunk geometry rather than creating an object per blade. Grass fades over 32–44 blocks and leaves over 68–80 blocks. Decorations receive lighting and shadows, have no collision or network state, and rebuild when supporting blocks change.

### Players and roaming animals

Remote avatars combine box geometry with locally generated 32 × 32 textures. Neutral grayscale clothing textures multiply the selected top and pants colors. Paint hair and facial features on the head cube instead of overlapping hair geometry, which causes depth flicker. Measure name text before sizing its canvas and sprite, use fixed padding, and render cleaned names as plain text.

Presence updates are limited to 10 Hz with background heartbeats. Validate positions, outfits and sequence numbers; interpolate position and shortest-angle yaw, animate legs from movement and remove stale players after 15 seconds. Dispose character geometry, materials and textures when players leave, including replaced name maps.

The herd contains three pigs, three cows and three chickens. Animals alternate seeded random walks and pauses, avoid water and blocked ground, and recover when edits obstruct their surroundings. A shared texture atlas and merged body geometry reduce draw calls; moving legs remain separate, and animals beyond 48 blocks are culled.

One active client coordinates the herd: the lowest eligible client ID among recently active peers. A joining client waits 1.5 seconds before becoming eligible; absent coordinators expire after six seconds. The coordinator simulates at 20 Hz and broadcasts snapshots at most 5 Hz. Followers interpolate accepted states rather than independently choosing destinations.

Each animal state is `[x, feetY, z, yaw, walkingFlag, rngState, remainingSeconds]`. Validate the nine-record snapshot and sequence number, accept state from the current coordinator, and preserve random/timer state for handoff and saves. Herd state is separate from voxel registers. This is peer coordination with eventual convergence after peers reconnect, not consensus during a network partition.

### Performance and verification

Adaptive resolution starts at a device-pixel-ratio cap of 1.25 for coarse pointers or 1.5 for fine pointers. Sustained frame times above 17.5 ms reduce resolution; longer stable periods below 16.9 ms restore it gradually, with a minimum ratio of 0.6. This targets 60 fps but cannot guarantee it on every device. Check real phones as well as desktop emulation.

Inspect `astraCraft.graphicsStats()` for frame rate, pixel ratio, texture loading, anti-aliasing and the sky reflection mode. Run the existing behavior checks from the repository root:

```powershell
node src/main/apps/app/astra-craft/world.test.cjs
node src/main/apps/app/astra-craft/graphics.test.cjs
node src/main/apps/app/astra-craft/foliage.test.cjs
node src/main/apps/app/astra-craft/avatars.test.cjs
node src/main/apps/app/astra-craft/animals.test.cjs
node src/main/apps/app/astra-craft/audio.test.cjs
```

For browser verification, open two clients in the same valid room. Check simultaneous edits to different and identical blocks, late join, reconnect, reload and same-browser storage merging. Check walking, smooth step-up, water support and collisions in flight; then verify player names/outfits and herd coordinator handoff. Test portrait and desktop/phone landscape controls, dialog dismissal after fullscreen, and that save-status changes do not resize the world.

The compact screenshots in Part 1 were captured from the running app: a 412 × 915 portrait viewport for the controls and options, and a 640 × 360 landscape viewport.
