Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 7 additions & 6 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,13 @@

## Unreleased
- Made built-in Cria entity fields matter in the generated starter playtest:
pickup `amount` awards score; enemy `patrol` moves horizontally at its
`speed` interval; enemy `damage` spends multiple lives. Defaults now give
authored pickups one point and enemies one speed/damage unit; START restores
entity positions and all derived state. The behavior probe covers score,
patrol, damage, game-over, and checkpoint reset while custom renamed fields
remain metadata-only.
pickup `amount` awards score; pickup `respawn` restores a collected pickup
after finite-life loss while reversing its score and objective count; enemy
`patrol` moves horizontally at its `speed` interval; enemy `damage` spends
multiple lives. Defaults now give authored pickups one point and enemies one
speed/damage unit; START restores entity positions and all derived state. The
behavior probe covers score, pickup respawn, patrol, damage, game-over, and
checkpoint reset while custom renamed fields remain metadata-only.
- Made ELIS prereleases directly deployable through `/ztash this`: the binary
workflow now publishes an immutable two-platform `ztash-release.json` bound
to the source commit, exact archive sizes, and SHA-256 hashes, with focused
Expand Down
8 changes: 4 additions & 4 deletions MEMORY.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,10 @@ Run `bash scripts/verify.sh`. It builds in `ReleaseSafe`, uses dummy SDL drivers
- Full third-party ports are staged under `ports/` until content completeness, license isolation, deterministic package/simulator gates, and named physical-device proof. `demos/mr-rescue/` is the sole explicit physical-validation exception and remains unapproved; all 100 Hex-a-Hop levels follow only after named-board approval.
- `.elisworld` schema v7 adds bounded starting lives, metadata/checkpoint
trigger behavior, and the named built-in starter behaviors `enemy.speed`,
`enemy.patrol`, `enemy.damage`, and `pickup.amount`; v6 added the
reach-goal/collect-all objective rule, v5 added cardinal/blob terrain, and v4
added project-defined names plus four typed fields per entity slot. These edits
use project snapshots in the same ordered history as map edits. History
`enemy.patrol`, `enemy.damage`, `pickup.amount`, and `pickup.respawn`; v6
added the reach-goal/collect-all objective rule, v5 added cardinal/blob terrain,
and v4 added project-defined names plus four typed fields per entity slot.
- These edits use project snapshots in the same ordered history as map edits. History
revisions remain monotonic across snapshots so dirty state cannot collide after
save; project/settings writes use collision-free atomic temporary files, and
dirty close requests require Save and Exit or explicit discard. V1 and v2 load
Expand Down
7 changes: 4 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -187,9 +187,10 @@ over the same authoritative project and command history:
Save/Discard guard for mouse, keyboard, and controller users;
- a chrome-free validated map preview, plus an interactive starter playtest that
moves through collision, renders typed entities, counts and scores pickups,
patrols authored enemies, applies enemy damage, enforces authored objectives,
spends finite lives, and respawns at authored checkpoint triggers without
mutating the saved project;
restores authored respawning pickups after finite-life loss, patrols authored
enemies, applies enemy damage, enforces authored objectives, spends finite
lives, and respawns at authored checkpoint triggers without mutating the saved
project;

Open a project using the game manifest and palette source as its asset workspace:

Expand Down
21 changes: 12 additions & 9 deletions STUDIO.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,9 @@ world. Arrow buttons move one cell, solid cells block movement, and pickup
entities are counted and award their authored `amount` score. Built-in entity
field names also drive bounded behavior: enemy `patrol` moves horizontally at
the authored `speed` turn interval and `damage` spends that many lives.
Patrolling enemies bounce from blocked or occupied cells. The authored
Collected pickups with `respawn` set to `1` return after a finite-life loss;
their score and objective count are reversed so collect-all objectives remain
honest. Patrolling enemies bounce from blocked or occupied cells. The authored
objective either wins at the goal immediately or keeps it locked until every
pickup is collected. Starting lives can be unlimited or finite; in finite mode
game-over occurs at zero, and checkpoint-enabled trigger entities become the
Expand Down Expand Up @@ -122,9 +124,9 @@ editor map preview, not a claim that game-specific mechanics are running.
fields with defaults and bounds. The schema editor renames types and fields,
adds or removes trailing fields, cycles field kinds, and edits defaults/ranges
through the shared undo/redo history. The starter playtest recognizes the
built-in behavior names `enemy.speed`, `enemy.patrol`, `enemy.damage`, and
`pickup.amount`; custom names remain data-only. Non-decoration entities on
collision produce a warning.
built-in behavior names `enemy.speed`, `enemy.patrol`, `enemy.damage`,
`pickup.amount`, and `pickup.respawn`; custom names remain data-only.
Non-decoration entities on collision produce a warning.
Removing or reenabling a trailing field initializes instances from its validated
default; undo restores their previous schema and distinct values. Failed entity
placement leaves the project and pending undo gesture unchanged.
Expand Down Expand Up @@ -245,11 +247,12 @@ editor frame.

`--playtest-smoke` generates the same temporary interactive runtime wrapper,
copies the selected tilesets, runs objective, finite-life, game-over,
checkpoint-respawn, pickup-score, patrol, and enemy-damage probes plus one real
simulator screenshot, verifies the frame, and cleans the wrapper directory before
returning. The generated starter loop exercises movement, collision, entity
rendering, pickup scoring, patrol behavior, both goal rules, both trigger modes,
feedback, and full reset wiring; it does not replace hand-authored game logic.
checkpoint-respawn, pickup-score, pickup-respawn, patrol, and enemy-damage
probes plus one real simulator screenshot, verifies the frame, and cleans the
wrapper directory before returning. The generated starter loop exercises
movement, collision, entity rendering, pickup scoring and respawn, patrol
behavior, both goal rules, both trigger modes, feedback, and full reset
wiring; it does not replace hand-authored game logic.
The toolbar PLAYTEST button and F9 use the interactive form and return to Cria
after the simulator closes.

Expand Down
2 changes: 1 addition & 1 deletion scripts/studio_smoke.sh
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ env SDL_VIDEODRIVER=dummy \
--playtest-smoke \
--capture="$work/studio.bmp" \
--smoke 2>&1 | tee "$work/studio.log"
grep -q 'Cria generated playtest: pass (objectives, lives, checkpoints, entity behaviors)' "$work/studio.log"
grep -q 'Cria generated playtest: pass (objectives, lives, checkpoints, entity behaviors, pickup respawn)' "$work/studio.log"

test -s "$work/starter.elisworld"
test -s "$work/starter.lua"
Expand Down
33 changes: 18 additions & 15 deletions src/studio_app.zig
Original file line number Diff line number Diff line change
Expand Up @@ -1068,6 +1068,17 @@ const Studio = struct {
\\ end
\\end
\\
\\local function respawn_collected_pickups()
\\ for index, entity in ipairs(entities) do
\\ if entity.kind == "pickup" and collected[index] and
\\ entity_field(entity, "respawn", 0) == 1 then
\\ collected[index] = nil
\\ pickup_count = pickup_count - 1
\\ score = score - math.max(0, entity_field(entity, "amount", 1))
\\ end
\\ end
\\end
\\
\\local function lose_life(damage)
\\ damage = math.max(1, damage or 1)
\\ if starting_lives > 0 then
Expand All @@ -1079,6 +1090,7 @@ const Studio = struct {
\\ end
\\ player_x = respawn_x
\\ player_y = respawn_y
\\ if starting_lives > 0 then respawn_collected_pickups() end
\\end
\\
\\remember_entity_positions()
Expand Down Expand Up @@ -1191,27 +1203,18 @@ const Studio = struct {
\\
\\ width, height = 5, 1
\\ solid = {}
\\ entities = { { kind = "enemy", x = 2, y = 0, fields = { 1, 1, 1, 0 } } }
\\ entities = { { kind = "pickup", x = 1, y = 0, fields = { 0, 2, 1, 0 } }, { kind = "enemy", x = 2, y = 0, fields = { 0, 0, 1, 0 } } }
\\ spawn = { x = 0, y = 0 }
\\ goal = { x = 4, y = 0 }
\\ goal_rule = "reach"
\\ starting_lives, trigger_rule = 3, "metadata"
\\ pickup_total = 1
\\ remember_entity_positions()
\\ reset()
\\ try_move(1, 0)
\\ assert(entities[1].x == 3 and lives == 3)
\\ try_move(1, 0)
\\ assert(player_x == 2 and entities[1].x == 4)
\\
\\ entities = { { kind = "enemy", x = 1, y = 0, fields = { 0, 0, 2, 0 } } }
\\ spawn = { x = 0, y = 0 }
\\ goal = { x = 3, y = 0 }
\\ starting_lives, trigger_rule = 3, "metadata"
\\ remember_entity_positions()
\\ reset()
\\ try_move(1, 0)
\\ assert(player_x == 0 and lives == 1 and not game_over)
\\ assert(score == 2 and pickup_count == 1)
\\ try_move(1, 0)
\\ assert(game_over and lives == -1)
\\ assert(lives == 2 and player_x == 0 and score == 0 and pickup_count == 0 and not collected[1])
\\
\\ width, height = 4, 1
\\ solid = {}
Expand Down Expand Up @@ -1376,7 +1379,7 @@ const Studio = struct {
self.notice = .playtest_failed;
return;
}
std.debug.print("Cria generated playtest: pass (objectives, lives, checkpoints, entity behaviors)\n", .{});
std.debug.print("Cria generated playtest: pass (objectives, lives, checkpoints, entity behaviors, pickup respawn)\n", .{});
} else {
const argv = [_][]const u8{ runtime_path, root };
if (!runPlaytestRuntime(self.allocator, io, root, &argv)) {
Expand Down
Loading