Round 5a: heroes (The Selector, The Curator), air (45 Flyer, Compression Drone) with pads + Aircraft queue/tab, scouts (Boom Bike, Crawler), 5 weapons, wax seep regrowth on all maps

Wax seeps fix the economy flatline the arena telemetry exposed: both bots hit
cash=0/stored=0 from tick 6250 with no way to recover.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
type-two 2026-07-25 19:08:13 +10:00
parent 1939f868b0
commit 5255aeeb1f
58 changed files with 734 additions and 0 deletions

83
LORE.md
View File

@ -95,6 +95,89 @@ replaced by a receipt.
| `datacentre` | Edge Node | power/build-radius creep | humming white monolith with a single blue status LED | | `datacentre` | Edge Node | power/build-radius creep | humming white monolith with a single blue status LED |
| `transcoder` | Transcoder | refinery | white cube that swallows records whole; only receipts come out | | `transcoder` | Transcoder | refinery | white cube that swallows records whole; only receipts come out |
## Heroes
Each side fields exactly one hero. They cost a fortune, they cannot be replaced
while alive, and losing one is a story beat, not a statistic.
### THE SELECTOR (MRP, `selector`)
Nobody built the Selector. It assembled itself in the sub-basement of a flooded
pressing plant out of a coat rack, a broken mastering lathe, and the contents of
a collection somebody spent forty years building and one afternoon losing.
It carries a crate of **one-of-one dubplates** — acetates cut before the
Compression, of songs that exist nowhere else. Each one it throws is gone
forever. That is the Selector's whole tragedy and its whole tactic: it wins
fights by destroying the last copies of the things it is fighting for.
It does not speak. It nods, and something in the distance falls over.
- Slow, absurdly tough for infantry, huge splash damage, long reload.
- Voice lines are just the needle-drop crackle of a record starting.
### THE CURATOR (Stream, `curator`)
The Algorithm's only physical opinion. A white sphere ringed with lenses that
hovers three feet off the ground and *watches*, and the watching is the weapon —
everything it looks at is being scored, ranked, and found wanting.
It is unfailingly polite. It refers to units it is destroying as "underperforming
content." It refers to the Selector as "a legacy account."
- Fast, fragile for a hero, continuous beam that melts armour.
- Cannot capture or hold ground. It only ever passes through, sampling.
## Air
Late-war escalation. Both sides looked up at the same time.
- **45 Flyer** (`flyer45`, MRP): a 7-inch single with tonearm rotor blades and a
scavenged engine pod bolted through the spindle hole. Loud, fast, made of
rubbish, held together by belief.
- **Compression Drone** (`compressor`, Stream): from the original Stream roster —
projects a shrinking blue field; things inside get smaller and worse.
- Launch pads: **Launch Ramp** (`launchramp`) and **Drone Port** (`droneport`).
## Scouts
- **Boom Bike** (`boombike`, MRP): a trike with two speaker cones where the
pillion should be. Nothing outruns it. Nothing wants to be near it either.
- **Crawler** (`crawler`, Stream): a flat white rover that maps your base and
files a report. Unarmed, silent, already gone by the time you shoot at it.
## Wax seeps
Wax fields are not deposits, they are **wounds**. Each field grows from a
**seep point** — a crack over a buried pressing plant, a landfill vent, a
flooded archive — and as long as the seep is open, the field regrows. Kill the
economy of a map and it will heal; the Black Wax has all the time in the world.
## The campaign: SIDE A
Three missions. The MRP origin story, told from the landfill up.
### 1. FIRST PRESSING
The Party wakes up. A pop-up van and a single digger in a wax field at the edge
of the old industrial estate. Get a pressing plant running and press the first
records of the new world — before the Stream's survey drones finish counting
what's down here.
*Objectives: build a Pressing Plant, bank 2000 records, survive the first probe.*
### 2. CRATE DIGGERS
A Stream transcoder outpost has found something under the hill: a **master
lacquer**, the original of an original. In six minutes it will be a receipt.
Take the outpost apart and take back the master.
*Objectives: destroy the Transcoder before the timer, keep the Selector alive.*
### 3. THE DROP
The Algorithm has surfaced. Build the Mastering Studio, charge the orbital
tonearm, and put the needle through it.
*Objectives: build a Mastering Studio, fire THE DROP at the Algorithm, survive
the counterattack.*
*(SIDE B — the Stream campaign, in which you are the polite one — is unwritten.)*
## Tone ## Tone
Saturday-morning-cartoon apocalypse. The stakes are real (music is being deleted Saturday-morning-cartoon apocalypse. The stakes are real (music is being deleted

View File

@ -31,3 +31,7 @@ All images generated locally on MODELBEAST (`flux_local`) via `tools/mbgen.py`.
| renders/scraper.png | 2026-07-20 | glossy seamless white tech aesthetic, soft blue ring lights, smooth rounded minimal design, autonomous white street sweeper vehicle inhaling black vinyl through a glowing blue intake, isometric three-quarter view from above, single object centered, video game asset, plain flat grey background, no text | | renders/scraper.png | 2026-07-20 | glossy seamless white tech aesthetic, soft blue ring lights, smooth rounded minimal design, autonomous white street sweeper vehicle inhaling black vinyl through a glowing blue intake, isometric three-quarter view from above, single object centered, video game asset, plain flat grey background, no text |
| renders/bufferbot.png | 2026-07-20 | glossy seamless white tech aesthetic, soft blue ring lights, smooth rounded minimal design, knee-high white sphere robot with a loading spinner face, isometric three-quarter view from above, single object centered, video game asset, plain flat grey background, no text | | renders/bufferbot.png | 2026-07-20 | glossy seamless white tech aesthetic, soft blue ring lights, smooth rounded minimal design, knee-high white sphere robot with a loading spinner face, isometric three-quarter view from above, single object centered, video game asset, plain flat grey background, no text |
| renders/drmwalker.png | 2026-07-20 | glossy seamless white tech aesthetic, soft blue ring lights, smooth rounded minimal design, tall faceless white mech with one blue ring-light eye and a glowing paywall screen chest, isometric three-quarter view from above, single object centered, video game asset, plain flat grey background, no text | | renders/drmwalker.png | 2026-07-20 | glossy seamless white tech aesthetic, soft blue ring lights, smooth rounded minimal design, tall faceless white mech with one blue ring-light eye and a glowing paywall screen chest, isometric three-quarter view from above, single object centered, video game asset, plain flat grey background, no text |
| renders/selector.png | 2026-07-25 | rusty junkyard robot aesthetic, rust metal with hot pink trim, analog dials and speaker parts, legendary hero DJ robot in a long coat with a crate of glowing gold dubplates under one arm and a tonearm staff, isometric three-quarter view from above, single object centered, video game asset, plain flat grey background, no text |
| renders/curator.png | 2026-07-25 | glossy seamless white tech aesthetic, soft blue ring lights, smooth rounded minimal design, hovering white sphere ringed with many camera lenses, a single blue eye at the front, faint halo, isometric three-quarter view from above, single object centered, video game asset, plain flat grey background, no text |
| renders/launchramp.png | 2026-07-25 | rusty junkyard robot aesthetic, rust metal with hot pink trim, analog dials and speaker parts, scrap metal launch ramp pad with painted pink chevrons, floodlights and a parked flying vinyl disc, isometric three-quarter view from above, single object centered, video game asset, plain flat grey background, no text |
| renders/droneport.png | 2026-07-25 | glossy seamless white tech aesthetic, soft blue ring lights, smooth rounded minimal design, white circular drone landing pad building with recessed blue guide lights and folded launch arms, isometric three-quarter view from above, single object centered, video game asset, plain flat grey background, no text |

Binary file not shown.

After

Width:  |  Height:  |  Size: 928 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 819 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 935 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 832 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 895 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 772 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 937 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 805 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 547 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 471 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 969 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 850 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 813 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 720 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 553 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 484 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 774 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 687 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 305 B

After

Width:  |  Height:  |  Size: 527 B

View File

@ -403,3 +403,6 @@ production-icons:
vehicle: 0, 32, 16, 16 vehicle: 0, 32, 16, 16
vehicle-disabled: 16, 32, 16, 16 vehicle-disabled: 16, 32, 16, 16
vehicle-alert: 32, 32, 16, 16 vehicle-alert: 32, 32, 16, 16
aircraft: 0, 48, 16, 16
aircraft-disabled: 16, 48, 16, 16
aircraft-alert: 32, 48, 16, 16

View File

@ -93,3 +93,14 @@ Container@PLAYER_WIDGETS:
X: 7 X: 7
Y: 7 Y: 7
ImageCollection: production-icons ImageCollection: production-icons
ProductionTypeButton@AIRCRAFT:
Y: 99
Width: 30
Height: 30
Background: button
ProductionGroup: Aircraft
Children:
Image@ICON:
X: 7
Y: 7
ImageCollection: production-icons

View File

@ -87,3 +87,23 @@ power-thedrop =
power-theshuffle = power-theshuffle =
.name = THE SHUFFLE .name = THE SHUFFLE
.description = Reality-compression strike. Terms of service apply. .description = Reality-compression strike. Terms of service apply.
## round 5: heroes, scouts, air
actor-selector =
.name = The Selector
actor-curator =
.name = The Curator
actor-boombike =
.name = Boom Bike
actor-crawler =
.name = Crawler
actor-flyer45 =
.name = 45 Flyer
actor-compressor =
.name = Compression Drone
actor-launchramp =
.name = Launch Ramp
actor-droneport =
.name = Drone Port
actor-waxseed =
.name = Wax Seep

Binary file not shown.

Before

Width:  |  Height:  |  Size: 86 B

After

Width:  |  Height:  |  Size: 86 B

View File

@ -58,5 +58,11 @@ Actors:
Actor4: scraper Actor4: scraper
Owner: BotB Owner: BotB
Location: 24,25 Location: 24,25
waxseed0: waxseed
Owner: Neutral
Location: 8,20
waxseed1: waxseed
Owner: Neutral
Location: 18,10
Rules: rules.yaml Rules: rules.yaml

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 113 B

View File

@ -76,3 +76,30 @@ Actors:
Actor5: mpspawn Actor5: mpspawn
Owner: Multi5 Owner: Multi5
Location: 45,84 Location: 45,84
waxseed0: waxseed
Owner: Neutral
Location: 12,45
waxseed1: waxseed
Owner: Neutral
Location: 20,20
waxseed2: waxseed
Owner: Neutral
Location: 20,71
waxseed3: waxseed
Owner: Neutral
Location: 45,45
waxseed4: waxseed
Owner: Neutral
Location: 45,25
waxseed5: waxseed
Owner: Neutral
Location: 45,66
waxseed6: waxseed
Owner: Neutral
Location: 71,20
waxseed7: waxseed
Owner: Neutral
Location: 71,71
waxseed8: waxseed
Owner: Neutral
Location: 79,45

Binary file not shown.

Before

Width:  |  Height:  |  Size: 937 B

After

Width:  |  Height:  |  Size: 99 B

View File

@ -60,3 +60,30 @@ Actors:
Actor3: mpspawn Actor3: mpspawn
Owner: Multi3 Owner: Multi3
Location: 57,57 Location: 57,57
waxseed0: waxseed
Owner: Neutral
Location: 10,32
waxseed1: waxseed
Owner: Neutral
Location: 16,16
waxseed2: waxseed
Owner: Neutral
Location: 16,49
waxseed3: waxseed
Owner: Neutral
Location: 32,32
waxseed4: waxseed
Owner: Neutral
Location: 32,10
waxseed5: waxseed
Owner: Neutral
Location: 32,55
waxseed6: waxseed
Owner: Neutral
Location: 49,16
waxseed7: waxseed
Owner: Neutral
Location: 49,49
waxseed8: waxseed
Owner: Neutral
Location: 55,32

Binary file not shown.

Before

Width:  |  Height:  |  Size: 517 B

After

Width:  |  Height:  |  Size: 89 B

View File

@ -44,3 +44,18 @@ Actors:
Actor1: mpspawn Actor1: mpspawn
Owner: Multi1 Owner: Multi1
Location: 34,20 Location: 34,20
waxseed0: waxseed
Owner: Neutral
Location: 7,7
waxseed1: waxseed
Owner: Neutral
Location: 7,34
waxseed2: waxseed
Owner: Neutral
Location: 20,20
waxseed3: waxseed
Owner: Neutral
Location: 34,7
waxseed4: waxseed
Owner: Neutral
Location: 34,34

Binary file not shown.

Before

Width:  |  Height:  |  Size: 86 B

After

Width:  |  Height:  |  Size: 86 B

View File

@ -52,3 +52,9 @@ Actors:
Actor8: adblocker Actor8: adblocker
Owner: Creeps Owner: Creeps
Location: 25,22 Location: 25,22
waxseed0: waxseed
Owner: Neutral
Location: 8,20
waxseed1: waxseed
Owner: Neutral
Location: 18,10

Binary file not shown.

Before

Width:  |  Height:  |  Size: 86 B

After

Width:  |  Height:  |  Size: 86 B

View File

@ -36,3 +36,9 @@ Actors:
Actor0: mpspawn Actor0: mpspawn
Owner: Multi0 Owner: Multi0
Location: 16,16 Location: 16,16
waxseed0: waxseed
Owner: Neutral
Location: 10,10
waxseed1: waxseed
Owner: Neutral
Location: 24,23

View File

@ -215,3 +215,152 @@ algorithm:
DisplayRadarPing: true DisplayRadarPing: true
SupportPowerPaletteOrder: 10 SupportPowerPaletteOrder: 10
SupportPowerChargeBar: SupportPowerChargeBar:
# --- round 5: MRP hero + scout ---
selector:
Inherits: ^ArmedUnit
Voiced:
VoiceSet: MrpVoice
Buildable:
Queue: Infantry
BuildPaletteOrder: 30
Prerequisites: mastering
BuildLimit: 1
Valued:
Cost: 2000
Tooltip:
Name: actor-selector.name
Health:
HP: 40000
Armor:
Type: None
Mobile:
Locomotor: foot
Speed: 42
Armament:
Weapon: dubplate
RevealsShroud:
Range: 7c0
SelectionDecorations:
boombike:
Inherits: ^ArmedUnit
Voiced:
VoiceSet: MrpVoice
-WithSpriteBody:
WithFacingSpriteBody:
Buildable:
Queue: Vehicle
BuildPaletteOrder: 35
Prerequisites: garage
Valued:
Cost: 400
Tooltip:
Name: actor-boombike.name
Health:
HP: 15000
Armor:
Type: Light
Mobile:
Locomotor: wheeled
Speed: 140
Armament:
Weapon: bike_horn
RevealsShroud:
Range: 8c0
# --- round 5: air ---
# ponytail: aircraft are Targetable as GroundActor too, so existing weapons can
# hit them. Upgrade path: dedicated AA armaments + AirborneActor-only targeting.
^AirUnit:
BodyOrientation:
QuantizeFacingsFromSequence:
RenderSprites:
WithFacingSpriteBody:
SelectionDecorations:
AppearsOnRadar:
UseLocation: true
HitShape:
HiddenUnderFog:
Type: GroundPosition
Selectable:
Bounds: 1024, 1024
Targetable:
TargetTypes: GroundActor, AirborneActor
MustBeDestroyed:
FireWarheadsOnDeath:
Weapon: UnitExplode
Aircraft:
CanHover: True
CanSlide: True
VTOL: True
IdleBehavior: Land
LandableTerrainTypes: Clear, Wax
AirborneCondition: airborne
CruisingCondition: cruising
Hovers@CRUISING:
RequiresCondition: cruising
AttackMove:
Guard:
Guardable:
AutoTarget:
AutoTargetPriority@DEFAULT:
ValidTargets: GroundActor, Structure
RevealsShroud:
Range: 7c0
Type: CenterPosition
MapEditorData:
Categories: Aircraft
flyer45:
Inherits: ^AirUnit
Voiced:
VoiceSet: MrpVoice
Buildable:
Queue: Aircraft
BuildPaletteOrder: 10
Prerequisites: launchramp
Valued:
Cost: 900
Tooltip:
Name: actor-flyer45.name
Health:
HP: 22000
Armor:
Type: Light
Aircraft:
Speed: 149
Armament:
Weapon: rotor_blade
PauseOnCondition: !ammo
AttackAircraft:
AttackType: Hover
FacingTolerance: 90
AmmoPool:
Ammo: 12
ReloadDelay: 24
AmmoCondition: ammo
launchramp:
Inherits: ^EconBuilding
Inherits@shape: ^Buildable2x2
Buildable:
Queue: Building
BuildPaletteOrder: 55
Prerequisites: garage
Valued:
Cost: 800
Tooltip:
Name: actor-launchramp.name
Health:
HP: 70000
Production:
Produces: Aircraft
Exit@1:
SpawnOffset: 0,-256,0
ExitCell: 0,0
Reservable:
Power:
Amount: -20

View File

@ -263,3 +263,27 @@ garage:
ExitCell: 0, 2 ExitCell: 0, 2
Power: Power:
Amount: -20 Amount: -20
# --- round 5: wax seep points (regrow the fields; see LORE.md "Wax seeps") ---
waxseed:
BodyOrientation:
RenderSprites:
WithSpriteBody:
Interactable:
HiddenUnderShroud:
Building:
Footprint: x
Dimensions: 1,1
TerrainTypes: Clear, Wax
AppearsOnRadar:
RadarColorFromTerrain:
Terrain: Wax
AppearsOnMapPreview:
Terrain: Wax
SeedsResource:
ResourceType: Wax
Interval: 90
MaxRange: 60
MapEditorData:
Categories: Resource spawn

View File

@ -26,6 +26,10 @@ player:
Type: Infantry Type: Infantry
Group: Infantry Group: Infantry
ReadyAudio: UnitReady ReadyAudio: UnitReady
ClassicProductionQueue@Aircraft:
Type: Aircraft
Group: Aircraft
ReadyAudio: UnitReady
ConquestVictoryConditions: ConquestVictoryConditions:
MissionObjectives: MissionObjectives:
WinNotification: Win WinNotification: Win

View File

@ -202,3 +202,108 @@ drmwalker:
Speed: 42 Speed: 42
Armament: Armament:
Weapon: feedback_burst Weapon: feedback_burst
# --- round 5: Stream hero + scout ---
curator:
Inherits: ^ArmedUnit
Voiced:
VoiceSet: StreamVoice
Buildable:
Queue: Infantry
BuildPaletteOrder: 30
Prerequisites: algorithm
BuildLimit: 1
Valued:
Cost: 2000
Tooltip:
Name: actor-curator.name
Health:
HP: 25000
Armor:
Type: Light
Mobile:
Locomotor: foot
Speed: 95
Armament:
Weapon: curator_beam
RevealsShroud:
Range: 8c0
SelectionDecorations:
crawler:
Inherits: ^Unit
Voiced:
VoiceSet: StreamVoice
-WithSpriteBody:
WithFacingSpriteBody:
Buildable:
Queue: Vehicle
BuildPaletteOrder: 15
Prerequisites: transcoder
Valued:
Cost: 300
Tooltip:
Name: actor-crawler.name
Health:
HP: 10000
Armor:
Type: Light
Mobile:
Locomotor: wheeled
Speed: 155
RevealsShroud:
Range: 10c0
# --- round 5: air ---
compressor:
Inherits: ^AirUnit
Voiced:
VoiceSet: StreamVoice
Buildable:
Queue: Aircraft
BuildPaletteOrder: 11
Prerequisites: droneport
Valued:
Cost: 700
Tooltip:
Name: actor-compressor.name
Health:
HP: 16000
Armor:
Type: Light
Aircraft:
Speed: 170
Armament:
Weapon: compress_field
PauseOnCondition: !ammo
AttackAircraft:
AttackType: Hover
FacingTolerance: 90
AmmoPool:
Ammo: 10
ReloadDelay: 20
AmmoCondition: ammo
droneport:
Inherits: ^EconBuilding
Inherits@shape: ^Buildable2x2
Buildable:
Queue: Building
BuildPaletteOrder: 56
Prerequisites: printfarm
Valued:
Cost: 800
Tooltip:
Name: actor-droneport.name
Health:
HP: 60000
Production:
Produces: Aircraft
Exit@1:
SpawnOffset: 0,-256,0
ExitCell: 0,0
Reservable:
Power:
Amount: -20

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

@ -213,3 +213,59 @@ smoke_m:
Filename: sequences/assets/smoke.png Filename: sequences/assets/smoke.png
Start: 6 Start: 6
Length: 2 Length: 2
selector:
idle:
Filename: sequences/assets/selector.png
icon:
Filename: sequences/assets/icon_selector.png
curator:
idle:
Filename: sequences/assets/curator.png
icon:
Filename: sequences/assets/icon_curator.png
launchramp:
idle:
Filename: sequences/assets/launchramp.png
icon:
Filename: sequences/assets/icon_launchramp.png
droneport:
idle:
Filename: sequences/assets/droneport.png
icon:
Filename: sequences/assets/icon_droneport.png
boombike:
idle:
Filename: sequences/assets/boombike.png
Facings: 16
icon:
Filename: sequences/assets/icon_boombike.png
crawler:
idle:
Filename: sequences/assets/crawler.png
Facings: 16
icon:
Filename: sequences/assets/icon_crawler.png
flyer45:
idle:
Filename: sequences/assets/flyer45.png
Facings: 16
icon:
Filename: sequences/assets/icon_flyer45.png
compressor:
idle:
Filename: sequences/assets/compressor.png
Facings: 16
icon:
Filename: sequences/assets/icon_compressor.png
waxseed:
idle:
Filename: sequences/assets/waxseed.png

View File

@ -153,3 +153,104 @@ theshuffle_impact:
Explosions: bigboom Explosions: bigboom
ImpactSounds: audio/sfx_boom.wav ImpactSounds: audio/sfx_boom.wav
ValidTargets: Ground, GroundActor ValidTargets: Ground, GroundActor
# --- round 5: heroes, scouts, air ---
dubplate:
Inherits: ^GroundWeapon
Report: audio/sfx_throw.wav
ReloadDelay: 90
Range: 5c512
Projectile: Bullet
Speed: 448
LaunchAngle: 40
Image: seven_inch_proj
Sequences: idle
Warhead@1Dam: SpreadDamage
Spread: 426
Damage: 9000
ValidTargets: GroundActor
Versus:
None: 100
Wood: 90
Light: 85
Heavy: 65
Warhead@2Eff: CreateEffect
Explosions: bigboom
ImpactSounds: audio/sfx_boom.wav
ValidTargets: Ground, GroundActor
curator_beam:
Inherits: ^GroundWeapon
Report: audio/sfx_zap.wav
ReloadDelay: 8
Range: 4c512
Warhead@1Dam: SpreadDamage
Spread: 42
Damage: 1100
ValidTargets: GroundActor
Versus:
None: 70
Wood: 80
Light: 110
Heavy: 120
Warhead@2Eff: CreateEffect
Explosions: pop
ValidTargets: Ground, GroundActor
bike_horn:
Inherits: ^GroundWeapon
Report: audio/sfx_bass.wav
ReloadDelay: 35
Range: 2c512
Warhead@1Dam: SpreadDamage
Spread: 128
Damage: 1400
ValidTargets: GroundActor
Versus:
None: 120
Wood: 30
Light: 40
Heavy: 10
Warhead@2Eff: CreateEffect
Explosions: pop
ImpactSounds: audio/sfx_pop.wav
ValidTargets: Ground, GroundActor
rotor_blade:
Inherits: ^GroundWeapon
Report: audio/sfx_throw.wav
ReloadDelay: 22
Range: 3c512
Warhead@1Dam: SpreadDamage
Spread: 96
Damage: 2600
ValidTargets: GroundActor
Versus:
None: 95
Wood: 60
Light: 90
Heavy: 55
Warhead@2Eff: CreateEffect
Explosions: pop
ImpactSounds: audio/sfx_pop.wav
ValidTargets: Ground, GroundActor
compress_field:
Inherits: ^GroundWeapon
Report: audio/sfx_zap.wav
ReloadDelay: 28
Range: 3c256
Warhead@1Dam: SpreadDamage
Spread: 213
Damage: 1900
ValidTargets: GroundActor
Versus:
None: 110
Wood: 70
Light: 80
Heavy: 45
Warhead@2Eff: CreateEffect
Explosions: pop
ImpactSounds: audio/sfx_pop.wav
ValidTargets: Ground, GroundActor

View File

@ -30,6 +30,11 @@ VEHICLES = [
('streamvan', 26, STR + 'sleek white autonomous delivery van with blue play-button roof logo'), ('streamvan', 26, STR + 'sleek white autonomous delivery van with blue play-button roof logo'),
('scraper', 24, STR + 'white autonomous street sweeper vehicle with glowing blue front intake'), ('scraper', 24, STR + 'white autonomous street sweeper vehicle with glowing blue front intake'),
('drmwalker', 30, STR + 'white quad-legged mech with one blue ring light, seen from above'), ('drmwalker', 30, STR + 'white quad-legged mech with one blue ring light, seen from above'),
# round 5: scouts + aircraft
('boombike', 20, MRP + 'small fast motorbike trike with two loudspeaker cones mounted on the back'),
('crawler', 18, STR + 'small flat white six-wheeled scout rover with a blue sensor dome'),
('flyer45', 22, MRP + 'flying machine built from a black vinyl record with spinning tonearm rotor blades and a small rusty engine pod'),
('compressor', 22, STR + 'white quadcopter drone with four rotors and a glowing blue lens underneath'),
] ]
@ -47,7 +52,12 @@ def sheet16(im, size, out):
print(f'[facings] {out}', flush=True) print(f'[facings] {out}', flush=True)
only = [a for a in sys.argv[1:] if not a.startswith('-')]
skip_world = '--units-only' in sys.argv
for name, size, prompt in VEHICLES: for name, size, prompt in VEHICLES:
if only and name not in only:
continue
raw = os.path.join(RENDERS, f'{name}_raw.png') raw = os.path.join(RENDERS, f'{name}_raw.png')
cut = os.path.join(RENDERS, f'{name}.png') cut = os.path.join(RENDERS, f'{name}.png')
if not os.path.exists(cut): if not os.path.exists(cut):
@ -55,6 +65,10 @@ for name, size, prompt in VEHICLES:
run('bg_remove_local', {}, cut, asset_id=upload(raw)) run('bg_remove_local', {}, cut, asset_id=upload(raw))
sheet16(Image.open(cut).convert('RGBA'), size, os.path.join(ASSETS, f'{name}.png')) sheet16(Image.open(cut).convert('RGBA'), size, os.path.join(ASSETS, f'{name}.png'))
if skip_world or only:
print('ALL DONE (units only)')
sys.exit(0)
# terrain tile: seamless ground texture -> 32x32 # terrain tile: seamless ground texture -> 32x32
traw = os.path.join(RENDERS, 'terrain_raw.png') traw = os.path.join(RENDERS, 'terrain_raw.png')
if not os.path.exists(traw): if not os.path.exists(traw):

View File

@ -43,6 +43,11 @@ ACTORS = [
('scraper', 22, STR + 'autonomous white street sweeper vehicle inhaling black vinyl through a glowing blue intake'), ('scraper', 22, STR + 'autonomous white street sweeper vehicle inhaling black vinyl through a glowing blue intake'),
('bufferbot', 14, STR + 'knee-high white sphere robot with a loading spinner face'), ('bufferbot', 14, STR + 'knee-high white sphere robot with a loading spinner face'),
('drmwalker', 28, STR + 'tall faceless white mech with one blue ring-light eye and a glowing paywall screen chest'), ('drmwalker', 28, STR + 'tall faceless white mech with one blue ring-light eye and a glowing paywall screen chest'),
# round 5: heroes + air bases
('selector', 22, MRP + 'legendary hero DJ robot in a long coat with a crate of glowing gold dubplates under one arm and a tonearm staff'),
('curator', 22, STR + 'hovering white sphere ringed with many camera lenses, a single blue eye at the front, faint halo'),
('launchramp', 64, MRP + 'scrap metal launch ramp pad with painted pink chevrons, floodlights and a parked flying vinyl disc'),
('droneport', 64, STR + 'white circular drone landing pad building with recessed blue guide lights and folded launch arms'),
] ]

68
tools/place_seeds.py Normal file
View File

@ -0,0 +1,68 @@
#!/usr/bin/env python3
"""Place a neutral `waxseed` actor at the centre of every wax blob on every map.
Reads the resource plane straight out of each map.bin, finds blob centroids by
flood fill, and appends the actors to map.yaml. Idempotent: strips previously
placed waxseed entries first.
"""
import os, re, struct, glob
MAPS = glob.glob(os.path.join(os.path.dirname(__file__), '..', 'mods', 'vinylgod', 'maps', '*'))
def resource_cells(path):
d = open(path, 'rb').read()
fmt, w, h = d[0], *struct.unpack_from('<HH', d, 1)
tiles_off, heights_off, res_off = struct.unpack_from('<III', d, 5)
assert fmt == 2 and heights_off == 0, path
cells = set()
for x in range(w):
for y in range(h):
if d[res_off + 2 * (x * h + y)] != 0:
cells.add((x, y))
return cells
def blobs(cells):
"""Flood-fill into connected blobs, return list of (cx, cy)."""
seen, out = set(), []
for start in sorted(cells):
if start in seen:
continue
stack, blob = [start], []
seen.add(start)
while stack:
x, y = stack.pop()
blob.append((x, y))
for n in ((x+1, y), (x-1, y), (x, y+1), (x, y-1)):
if n in cells and n not in seen:
seen.add(n)
stack.append(n)
if len(blob) >= 6: # ignore specks
out.append((round(sum(p[0] for p in blob) / len(blob)),
round(sum(p[1] for p in blob) / len(blob))))
return out
for mapdir in sorted(MAPS):
my, mb = os.path.join(mapdir, 'map.yaml'), os.path.join(mapdir, 'map.bin')
if not (os.path.exists(my) and os.path.exists(mb)):
continue
centres = blobs(resource_cells(mb))
s = open(my).read()
s = re.sub(r'\n\twaxseed\d+: waxseed\n\t\tOwner: Neutral\n\t\tLocation: \d+,\d+', '', s)
if 'Actors:' not in s:
s = s.rstrip() + '\n\nActors:\n'
block = ''.join(f'\n\twaxseed{i}: waxseed\n\t\tOwner: Neutral\n\t\tLocation: {x},{y}'
for i, (x, y) in enumerate(centres))
# append to the Actors section (it is the last block in our generated maps)
head, _, tail = s.partition('Actors:')
lines = tail.split('\n')
cut = len(lines)
for i, ln in enumerate(lines):
if ln and not ln.startswith('\t') and i > 0: # next top-level key
cut = i
break
tail = '\n'.join(lines[:cut]).rstrip() + block + '\n' + '\n'.join(lines[cut:])
open(my, 'w').write(head + 'Actors:' + tail)
print(f'{os.path.basename(mapdir)}: {len(centres)} seep(s) at {centres}')