This commit is contained in:
Nicholas Butzke
2026-08-02 02:20:02 -04:00
parent 61669627db
commit 922983429e
226 changed files with 34032 additions and 18521 deletions
+85
View File
@@ -0,0 +1,85 @@
# Character Art Direction
## North star
Papaya Shooter is **urban anime first, expressive utility tech second**.
Grace Howard from *Zenless Zone Zero* is a directional reference for the
relationship between character and machinery: confident, readable streetwear
and personality remain dominant while a few believable tools explain
extraordinary movement. It is not a costume, silhouette, or prop set to copy.
The working ratio is:
```text
90% character / urban anime
10% functional mechanics / technology
```
The ratio is perceptual rather than a polygon count. At combat distance, the
player should identify face, hair, body language, clothing silhouette, team
colour and weapon before noticing a mechanism. Technology earns attention only
when its related action begins.
## The 90%: character and city
- Strong hair, face, garment and pose silhouettes carry recognition.
- Clothing uses broad graphic panels, asymmetry, layered streetwear and one
clear accent rhythm instead of dense sci-fi segmentation.
- Materials stay readable: cloth looks soft, rubberized trim looks matte, and
skin/hair keep their anime value grouping.
- Decals, seams and accessories describe taste, job and attitude before lore.
- Animation sells confidence and intent. The body leads; hardware follows.
Avoid full exosuit coverage, exposed machinery on every limb, uniform emissive
strips, or greebles spread evenly across the body. Those invert the ratio and
turn a character into a vehicle.
## The 10%: hero tools
Each character may express the same movement verbs through a different form
language, but the function and timing remain legible.
### Jump jets
Jump jets support bunny-hopping, air correction and quick direction changes.
They may live at heels, calves, a belt, coat hardware or a compact backpack.
The visual event is a short deployment, a graphic thrust wedge, and a fast
retraction. The audio is mostly air/thrust with one soft actuator detail.
### Grappling hook
The grapple needs three readable beats: launch, latch, and loaded reel. Its
housing may be wrist-mounted, weapon-mounted, hip-carried or deployed by a
small companion device. Cable direction and tension matter more than housing
detail. The mechanism stays quiet enough that the body swing remains the hero.
### Thrust dash
Dashing is a decisive impulse, not sustained flight. Thrusters may appear at
the waist, shoulder blades, boots or weapon rig. Use a clean ignition core,
directional air wedge and brief afterimage. Do not add a rattling engine loop;
the device should be visually dormant again by the recovery pose.
## Shared visual and audio law
- **Idle:** technology is folded, dark and quiet.
- **Anticipation:** one seam opens, nozzle pivots, or latch arms.
- **Action:** a single bright shape and clean thrust/mechanical cue explains
the verb.
- **Recovery:** the tool retracts quickly and character animation takes focus.
- **Sound ratio:** roughly 90% motion, thrust and body; 10% relay, latch or
servo. No constant rattle, scrape wall, or narrow high-frequency chirp.
- **VFX ratio:** one authored wedge/ring/line is preferable to many particles.
This law lets characters share technology without looking mass-produced.
Function, gameplay colour and timing stay consistent; placement, casing,
fasteners, deployment motion and wear pattern belong to the individual.
## Review questions
1. Does the silhouette still read as a person before it reads as equipment?
2. Can the movement tool be understood during its active animation?
3. Does the hardware disappear from attention when inactive?
4. Is every mechanical sound attached to a visible or gameplay-relevant cause?
5. Would removing half the greebles improve the character? If yes, remove them.
+254
View File
@@ -0,0 +1,254 @@
# Graphical Fidelity Direction
**Primary reference: [Sakura Crossing](https://github.com/Kenton-GMI/sakura-crossing)**
(MIT) — a procedural Japanese suburb in three.js, rendered to look like a
hand-painted 2D anime background. This is the target image for the project, with
one deliberate departure: **the line work is slightly harder and thicker.**
Secondary reference: [The Long Silence](https://github.com/achimala/TheLongSilence).
The transferable lesson there is not its realistic-space rendering. It is that
one authored visual law is reused everywhere, expensive detail is paid for only
where it can be seen, and every art decision is checked in repeatable captures.
Character-specific decisions are governed by
[CHARACTER_ART_DIRECTION.md](CHARACTER_ART_DIRECTION.md): 90% urban anime
character read, 10% selectively deployed utility technology.
## The cel law
Four layered techniques, ported from the reference. `scenes/maps/sakura_crossing`
is the map built to demonstrate all four; `scenes/maps/sakura_palette.gd` is the
palette port.
**1. Quantised toon lighting with a hue-shifted shadow**
`assets/shaders/sakura_cel.gdshader`. Direct light is snapped to one of 25 flat
values from a hand-authored ramp, sampled at `dot(N,L) * 0.5 + 0.5`. Then:
```
irradiance = band * mix(shadow_tint, vec3(1.0), band)
```
The band drives its own tint, so full light is untinted and the darkest band is
almost pure cool violet. **That hue shift in shadow is most of what separates
"anime cel" from "low-poly 3D"** — a wall whose shade is merely 40% of its lit
colour reads as an unlit polygon. High-key ramps (`RAMP_SOFT`, `RAMP_SOFT3`)
exist for pale masses like blossom that must stay light on the shadow side.
Faceted shading is on by default and is not a shortcut: a quantised ramp over
smooth normals puts a *curved* band boundary across a curved surface, which
reads as an airbrushed gradient no matter how few steps it has.
**2. Screen-space ink from the SECOND difference of linear depth**
`assets/shaders/ink_edge.gdshader`. This replaced a conventional first-difference
edge detect, and the distinction is the single most important one in the file. A
first difference measures depth *slope*, which is large wherever a surface is
oblique to the camera — so the road, every roof and every long wall carried ink,
and the threshold had to be raised until it smeared. A second difference measures
*curvature*, which is exactly zero across any planar surface however oblique. It
therefore fires only on real silhouettes and real creases, and the threshold can
be low enough to catch a distant railing.
Convex curvature inks strongly, concave faintly — the outside line of a shape is
heavy, the contact line inside it is light, which is what an animator draws.
Two things to know when tuning it: `ink_hardness` also sets the **noise floor**,
because a grazing ground plane quantises in the depth buffer into a staircase and
a staircase has a large second difference at every step. Too soft and the road
comes out finely hatched. And anything smaller than the line width — petals,
distant wires — will be inked *solid*, so sub-pixel scatter belongs in the
transparent pass where the ink cannot see it.
**3. Inverted-hull outlines** on hero props only — `toon_outline.gdshader`,
applied via `LevelMaterials.outline()`.
**4. A four-light anime rig**`LevelEnvironment._build_light_rig`. A warm
quantised key (the only shadow caster), a **strong** cool bounce from the
opposite quarter at roughly a third of the key, a weak violet up-light for
undersides, and a violet-ground hemisphere carried by ambient. The bounce is the
important one: it puts a distinct, cool, non-black value on every shadow plane,
so a surface turning away from the sun turns *into another colour* rather than
into darkness.
The colour grade is a **split-tone** — cool violet into the darks, warm paper
white into the lights — and it lives at the end of the ink pass rather than in
`Environment.adjustment`, so it can be a split rather than a single saturation
number. Environment's adjustment stage is therefore disabled; enabling both
grades the frame twice.
## Lettering and trim
The reference is emphatic that *"every sign, fascia, lantern and price strip is
drawn at runtime — there is not one image asset in `src/`."* That is not a
technical footnote. A Japanese street is **made of lettering**, and the first
build of Sakura Crossing had correct forms, correct colour and no type on it
anywhere; it read as an architectural massing study.
`scenes/maps/sakura_signage.gd` is the answer. Godot's equivalent of the
reference's Canvas2D is **`TextMesh`** — real geometry from a font — which suits
this renderer better than a drawn texture would, because the whole map is
untextured flat colour and, more importantly, **geometry gets inked**. Every
glyph picks up the screen-space outline exactly as the buildings do, which is
what seats the lettering *in* the picture rather than on top of it.
Settings that matter, both measured:
- **`depth = 0`.** Extruded text is 4 500 verts for four kanji; flat is 2 100.
The extrusion's side walls are most of the mesh and are never seen on a sign
fixed to a wall. Flat glyphs sit a centimetre proud of their board instead.
- **`curve_step = 8`.** The 0.5 default is 43 000 verts for three signs; 8 is
13 700 and looks identical at any distance a sign is read from.
The font is a **system stack** — the same one the reference uses (`Yu Gothic`,
`Meiryo`, `Hiragino Kaku Gothic ProN`, `MS Gothic`). Ships no font file; if none
resolve, signs go blank and nothing crashes.
**Ink or no ink is an art decision, per object.** Raised lettering on a sign
board wants the outline — it is an object standing off a wall. Paint on the
ground does not: road markings were inked at first and came out *hollow*,
because a stroke of 止まれ at a grazing angle is a few pixels wide, the ink fired
on both edges and the two outlines met in the middle and ate the letter. Pass
`inked: false` and the glyphs go to the transparent pass where the ink cannot
see them. Same mechanism as the petals — **anything thinner than the line width
gets inked solid.**
Trim is the other half. A cel image is judged on its *third* tier of structure,
and the first build had almost none: no sills, no downpipes, no meters, no
manholes, no tactile dots. All of it is render-only and most of it is a box —
none of it needs to be clever, it needs to be **there**.
One layout lesson worth keeping: unit width is the character of a street. Eight
shop units across this map made each one 19.5 m wide, which is a department
store, not a shop — one awning and one noren stretched across sixty feet of
frontage. Six-to-ten-metre units give twenty awning colours, twenty names and
twenty doorways per side, and that rhythm is most of what a 商店街 *is*.
Cost: ~5 300 mesh instances, 6.85 ms/frame at 1080p in the heaviest view.
## Image contract
Every gameplay frame should have:
- A readable dark, mid and light band with less than 2% practical clipping.
- One dominant warm/cool lighting relationship, not white ambient everywhere.
- Dark contact at feet, props and building bases.
- Large, medium and small visual structure, in that order.
- Ink weight that follows importance: character silhouette, rigid accessory,
architecture crease, then tertiary texture.
- Quiet traversal space around loud signs, characters and combat VFX.
- Surface detail that disappears cleanly before it becomes sub-pixel noise.
The current automatic guardrail is:
```powershell
python tools/levels.py <capture-directory> --inset 8 --gate
```
`debug/fidelity_probe.gd` is the deterministic look-development scene.
`debug/visual_capture.gd` remains the real-game regression capture.
## Lighting and post
- Keep the sun as the form key and ambient as a low-energy colour bed.
- Use tight SSAO only for contact. Broad grey AO fights flat cel bands.
- Keep cast shadow and form shadow separate; cast shadows may be deeper and
harder than a character or prop's terminator.
- Let only authored emissives cross the glow threshold. White paint must never
bloom.
- Preserve a filmic shoulder and measure clipping instead of judging it from a
single monitor.
- The first-person weapon uses a camera-relative warm key and cool fill. Its
graphic read must not change when the player turns in world space.
## Texturing and materials
`LevelMaterials.SURFACE_LAW` is the shared construction language:
- `wall`: storey-scale seams plus a restrained base grade.
- `ground`: larger paving divisions with no vertical grade.
- `panel`: tight machined divisions for metal and kiosks.
- `trim`: grounding grade without a grid.
These are semantic materials, not colour presets. Do not apply one noise field
to every asset. Foliage, vehicles and character silhouettes stay quiet unless
their own authored texture calls for detail.
The next texture pass should add a small reusable decal sheet for:
- one or two edge chips;
- drain and service markings;
- poster tears and tape corners;
- sparse hazard chevrons;
- directional pavement paint.
Place decals by composition and gameplay route. Random coverage is not detail;
it is visual noise.
## Modeling
Build and review assets in three reads:
1. Silhouette at combat distance.
2. Primary planes and construction breaks at traversal distance.
3. Fasteners, bevels and decals only at inspection distance.
For the city kit:
- Give rooflines and street corners deliberate silhouette interruptions.
- Prefer modeled awnings, frames, vents and cable bundles over high-frequency
material noise.
- Reuse a small vocabulary of construction dimensions so adjacent modules feel
manufactured together.
- Merge static pieces by material only after their panel coordinates are stable;
seams should continue across parts meant to read as one assembly.
- Add LOD or visibility ranges before adding tertiary geometry across all 100
city blocks.
## Character and weapon animation
Anime fidelity comes from pose design and timing more than interpolation count:
- Let the body lead every movement verb. Jump jets, grapple hardware and dash
thrusters deploy only long enough to explain the motion, then return visual
focus to the character.
- Gameplay anticipation must be short but unmistakable.
- Key combat silhouettes need a readable held pose before recovery.
- Accelerate through impact, hold the strongest pose, then use a clean recovery
arc instead of uniformly smooth motion.
- Keep weapon, support hand and head intent aligned; the existing hold-profile
checks should remain mandatory.
- Judge locomotion from the gameplay camera, not only in an animation viewer.
- Use `debug/anim_capture.gd`, `debug/fp_weapon_capture.gd` and the hold/pose
checks whenever animation assets change.
Secondary cloth and hair should lag the torso and settle quickly enough not to
obscure aim or team recognition. Their job is to sell acceleration, not to run
an independent simulation showcase.
## VFX
- Reserve the brightest values for hits, muzzle flashes and abilities.
- Shape effects as graphic wedges, rings and speed lines before adding soft
particles.
- Use colour to communicate gameplay class consistently.
- Fade detail by projected size. A distant effect should become one clean spark,
not many sub-pixel particles.
- Keep screen-space distortion local and brief; the HUD and target silhouette
must remain readable.
## Performance and review
Fidelity features should degrade by resolution or distance before being
disabled outright. Panel seams and sky clouds are analytic and band-limited;
future decals and tertiary geometry should use visibility ranges.
Review sets should include:
- deterministic fidelity probe, key and grazing angles;
- Neon Alley street, spawn and aerial views;
- one bright and one dark character skin;
- each first-person weapon family;
- run, slide, dash, reload and one ability impact;
- a low render-scale capture to expose shimmer and sub-pixel detail.
Compare the set as a group. A change is not an improvement if one hero frame is
better while combat readability or another map regresses.
+90 -53
View File
@@ -1,67 +1,104 @@
# Sound Design Plan
# Sound Design
The current sounds are procedurally generated sine/noise blends
(`generate_sounds.py`) — functional, but robotic. This doc is the path to
real sound design with zero budget.
Papaya Shooter's mix is **stylized impact, comfortable repetition**: graphic
and energetic enough to match the cel-shaded action, but deliberately free of
the clipped white-noise and narrow resonant tones that make a fast shooter
fatiguing.
## What's in place now
Character movement follows the same 90/10 rule as
`docs/CHARACTER_ART_DIRECTION.md`: about 90% clean motion/thrust/body and 10%
actuator, latch or servo. Mechanics explain a verb; they do not become a
constant texture over the character.
`AudioManager` (autoload, `globals/audio_manager.gd`) provides the plumbing
good sound design needs:
## Runtime mix
- **Bus layout**: `Master ← SFX ← {Weapons, Footsteps, UI}`, `Master ← Music`
— created automatically at boot, so volume sliders and effects (EQ,
compression, reverb zones) can target categories.
- **Variation playback**: drop `footstep_01.wav`, `footstep_02.wav`,
`footstep_03.wav` into `assets/sounds/` and `AudioManager.play_3d("footstep", pos)`
picks one at random.
- **Pitch randomization**: every playback is pitched ±6% by default — the
single cheapest trick to stop repeated sounds sounding fake.
- **Pooled 3D players**: `play_3d()` is safe to call every frame; no node
churn, no cut-off management.
`AudioManager` owns registration, aliases, variation, routing and pooled
playback. The hierarchy is:
```gdscript
# anywhere in gameplay code:
AudioManager.play_3d("ak47_fire", muzzle.global_position)
AudioManager.play_ui("hit_confirm")
```text
Master (hard limiter)
├── SFX
│ ├── Weapons (light parallel compression)
│ ├── Explosions (slower body compression)
│ ├── Impacts
│ ├── Footsteps
│ ├── Movement
│ ├── Ambience
│ ├── UI
│ └── Wind
└── Music
```
## Replacing the procedural sounds (priority order)
The limiter is only a safety net for coincident shots/explosions. Assets are
mastered below full scale, and category gain establishes the hierarchy before
the limiter is reached.
Sound is ~50% of "game feel" in an FPS. Replace in this order:
Legacy event ids such as `m4_fire`, `explosion`, `wind` and `ui_hover` resolve
to premium families in `globals/audio_manager.gd`. This preserves gameplay API
compatibility while keeping source filenames out of weapon and UI code.
1. **Weapon fire** (heard constantly): layered = body (low thump) + crack
(mid transient) + tail (room reflection). Sonniss GDC packs have complete
layered gunshots. One file per weapon is fine to start; add `_01.._03`
variations later.
2. **Hit confirm + kill confirm**: short, bright, satisfying (this is the
"touchdown" pillar in the design doc).
3. **Footsteps**: 4+ variations minimum, quieter than you think, on the
`Footsteps` bus so they can be ducked while firing.
4. **Movement verbs**: slide (cloth+concrete scrape), dash (air whoosh),
wall-run loop, jump/land (land intensity scaled by fall speed).
5. **Ambience**: a quiet room tone per map kills the "dead air" feeling.
6. **UI**: menu hover/click/equip from a Kenney UI pack.
## Asset construction
Where to get files: see [ASSET_SOURCES.md](ASSET_SOURCES.md#sounds) —
Sonniss GDC bundles first, Kenney CC0 packs second, Freesound (CC0 filter)
for gaps.
Run:
## Conventions
```powershell
python tools/build_audio_assets.py
python tools/audio_audit.py
```
- Files: `assets/sounds/<event>[_NN].wav` — 44.1 kHz, 16-bit, mono for 3D
positional sounds, stereo only for UI/music.
- Ids are auto-registered from filenames at boot (`ak47_fire.wav`
`"ak47_fire"`). Names containing `fire`/`reload` route to the Weapons bus,
`footstep` to Footsteps, everything else to SFX
(see `BUS_HINTS` in audio_manager.gd).
- Keep source loudness consistent: normalize weapon shots to about -6 dBFS
peak, footsteps/foley around -18 dBFS.
The builder is deterministic except for downloading the pinned CC0 menu track.
It:
## Later polish (cheap wins, in order)
- retains the transient of the CC0 firearm recordings;
- layers a low-passed body and a short, dark reflection tail;
- reduces the abrasive 35 kHz region and controls peaks;
- creates four concrete, metal, wood and glass footstep variations;
- constructs a 48.5-second speed-wind master plus softened scrape, wall-run and
grapple loops from recordings;
- constructs mono mechanical projectile loops for correct 3D localization;
- layers low relay/clunk UI hover/click/confirm/error/equip feedback;
- masters the CC0 menu loop to a conservative music level.
1. Distance low-pass on the Weapons bus (far gunfire sounds muffled).
2. A short duck (sidechain) on Footsteps/ambience when local weapon fires.
3. Reverb `AudioEffectReverb` per-map on SFX for indoor spaces.
4. Surface-dependent footsteps: raycast down, map material → sound id
(`footstep_metal`, `footstep_concrete`).
`tools/audio_audit.py` gates family coverage, 48 kHz format, mono/stereo intent,
clipping, DC offset, headroom, loop duration, seam continuity, spectral
flatness, high-frequency centroid and the explosion/firearm loudness hierarchy.
## Gameplay behavior
- Footsteps raycast the actual floor collider and use its
`acoustic_material`; brick and untagged structure map to concrete.
- Local and remote footsteps use the same surface families. Remote steps remain
positional and pass through material-aware occlusion.
- Slides switch recorded friction gestures if the floor material changes and
scale restrained pitch/gain with speed.
- Wall-run friction scales with speed and gets a restrained wood/metal
resonance shift.
- Speed wind is a narrowed, filtered field recording and fades in above 12 m/s
with a stronger velocity-pitch relationship. Its 48.5-second cycle prevents
a short gust cadence from becoming audible. There is no global noise bed.
- Jump and double-jump are compact jump-jet impulses. Dash is an equally brief
thrust-jet cue; clean air and low engine body dominate both.
- Grapple launch/latch/reel use distinct air, padded latch and restrained motor
cues. The reel stays below the character movement and never becomes a
continuous metallic foreground.
- Automatic weapon players keep eight overlapping voices; a new shot no longer
cuts the previous shot's body and tail.
- Explosion pitch and gain scale with blast radius, while reflections and
occlusion locate the source in the environment. Masters carry a measured
body advantage over firearms and a dark industrial debris layer.
- UI hover has a short cooldown so crossing dense menus does not become a buzz.
All UI feedback is built from softened relay/clunk gestures with 8 ms
transient ramps and a two-pole 2.4 kHz ceiling.
- Menu music fades in and is owned by the menu scene, so it stops cleanly when
a match begins.
## Source and naming conventions
Licenses and download pages are recorded in `assets/sounds/SOURCES.md`.
- Positional one-shots and projectile loops: 48 kHz, 16-bit mono WAV.
- Player-local movement beds: 48 kHz, 16-bit stereo WAV.
- Music: 48 kHz stereo Ogg Vorbis.
- Variations: `<event>_00.wav`, `<event>_01.wav`, etc.
- Runtime event families, bus gains and pitch ranges live in
`AudioManager.EVENT_TUNING`.