Bläddra i källkod

Use the authored sunburn object as the sole escape hazard

Millie 3 dagar sedan
förälder
incheckning
a81edf372b

+ 11 - 1
docs/mission-ember-apex-cooling-and-scorch.md

@@ -5,7 +5,7 @@
 - Cooling doors open after the surge stops. Visual/mechanical intervals remain 14 seconds closed, 6 seconds surging, 10 seconds exposed.
 - The user confirmed those visuals/mechanics align, but hears the surge at cooling-door opening. The next live test requested another four seconds of lead. Audio sequences now pre-roll at closed-window second 4, ten seconds before the visual surge. This is playtest-based compensation, not a recovered native delay parameter. Pending audio is phase/region/generation guarded and cancelled on reset, core destruction and deposit.
 - Initial beam: snapping to the resting endpoint still showed the incomplete beam in the first user screenshot. Native device position/power both reached 1. The new candidate seeks the driven endpoint once on laser creation, then animates to the resting endpoint so authored animation events can run. Duplicate presence and late presence after deposit cannot restart it. The target is the second user screenshot's thin, continuous beam. Needs visual confirmation.
-- Escape damage: the live test showed no scorch after the extra thermal attachment was removed. Deposit now replaces the pipe attachment with one rail-scoped instance of the proven burn (80C1D9E0), and explicitly disables SUNBURN_DAMAGE_OBJECT. Escape completion detaches it. This restores the working attachment without running both sources; visual/damage confirmation remains a live-test requirement.
+- Escape damage: after the user confirmed doubled damage persisted, the scripted rail-wide burn was removed. Deposit disables the climb hop-on and enables only the placed `SUNBURN_DAMAGE_OBJECT` (type 4, Apex slot 17, model 80B82486). Initialization and escape completion disable the object; checkpoint hazard reconciliation selects its state. No scripted damage multiplier or second escape attachment is applied. Native damage/volume behavior still needs live confirmation.
 - Escape ship: wait for native object presence, unlock, power on, seek closed, animate open. Duplicate presence does not restart it. Flight remains subject to visual testing.
 
 ## Escape trigger evidence
@@ -68,3 +68,13 @@ The full-screen surge effect is still unresolved. The new guide points to author
 ## Follow-up scorch correction
 
 The user still observes doubled scorch with the rail burn enabled and the sunburn object disabled. Deposit now explicitly disables the pipe hop-on, waits 250 ms, then attaches the rail burn. Hazard mode is idempotent, so repeated callbacks do not issue additional attachment revisions. This addresses a possible overlapping attachment during an enabled-to-enabled filter change; the native cause and damage rate still require visual confirmation. The sunburn object remains disabled.
+
+## Installed a8d3064 live result (17:37 run)
+
+The intro started and skipped successfully. At t=251282 the exact escape trigger fired. Retirement requested eight keys from source region 0 at t=251748, observed the baseline at t=251756, and completed native cleanup at t=253805. Native travel was armed at t=253815 and started/synchronized to packed region 1 with command token 2 at t=253897–253898. This proves the final-roster correction fixed the empty cleanup request.
+
+At t=253908 the native sense probe reported a record-chain cycle; subsequent native asserts reported a stalled `network_send` job. The read-only capture `build/first-encounter-audit/reactor-runtime-20260906-174128` contains zero registry groups and no movie controller. No ending cinematic-start incident arrived. Successful old-group removal therefore does not by itself resolve the subsequent world teardown/record-chain failure. Preserve this distinction: playback readiness has not yet been reached. The fixed snapshot is `build/first-encounter-audit/ending-freeze-a8d3064-20260906-1741.log`; the continuous capture is `bookend-live-20260906-173744.log`. Scorch damage/appearance cannot be verified from these logs.
+
+## Native-only escape hazard
+
+The detach/delayed-reattach experiment did not resolve the reported doubled damage. It is superseded: escape now enables SUNBURN_DAMAGE_OBJECT and leaves REACTOR_COFFIN_INTERIOR_THERMAL_HOP_ON disabled. The latter is retained only for the climb pipes before deposit. The SDK exposes the sunburn as a placed type-4 object, not a standalone type-60 trigger volume; its precise damage implementation has not been recovered. Tests assert activation at deposit, no enabled scripted damage attachment during escape, duplicate callback idempotence, and deactivation at the escape trigger. The ending transition freeze remains unresolved.

+ 8 - 13
scripts/mission_ember/apex.lua

@@ -106,6 +106,8 @@ return function(m, a, ending)
         local wanted = mode or "off"
         if s:variable("ember.apex.hazard_mode") == wanted then return end
         c:set_variable("ember.apex.hazard_mode", wanted)
+        -- The placed native sunburn object owns escape damage and its authored bounds.
+        a.objects(c, {"SUNBURN_DAMAGE_OBJECT"}, mode == "escape")
         if mode == "climb" then
             -- The five narrow authored pipe volumes on the way up to the deposit; their heights
             -- track the climb the mother-brain dialogue volumes walk through, from z~172 at
@@ -122,13 +124,8 @@ return function(m, a, ending)
                     a.slot(c, "REACTOR_MOTHER_BRAIN_HOT_PIPES_03_TRIGGER_VOLUME"),
                     a.slot(c, "SLOT_0005_80B3C09F"), a.slot(c, "SLOT_0006_80B3C09F"),
                     a.slot(c, "SLOT_0008_80B3C09F")}}, true)
-        elseif mode == "escape" then
-            -- The deposit callback already detached the climb burn on an earlier tick.
-            -- Attach one rail-scoped burn. The sunburn prop
-            -- alone did not deliver damage in the live test; do not run both sources.
-            a.effect(c, s, "REACTOR_COFFIN_INTERIOR_THERMAL_HOP_ON",
-                "AOD_REACTOR_RAIL_TOP_OBJECT_FILTER", rail_filter(c), true)
         else
+            -- End the climb attachment; never add a second rail-wide damage effect.
             a.effect(c, s, "REACTOR_COFFIN_INTERIOR_THERMAL_HOP_ON",
                 "AOD_REACTOR_RAIL_TOP_OBJECT_FILTER", rail_filter(c), false)
         end
@@ -184,6 +181,7 @@ return function(m, a, ending)
     local function initialize_devices(c, s)
         if s:variable("ember.apex.devices_ready") then return end
         c:set_variable("ember.apex.devices_ready", true)
+        a.objects(c, {"SUNBURN_DAMAGE_OBJECT"}, false)
         unlock(c, "SECURITY_DOOR_DEVICE")
         for _, side in ipairs(sides) do
             for _, part in ipairs({"DOOR_A", "DOOR_B"}) do unlock(c, "REACTOR_CLAMSHELL_" .. side .. "_" .. part .. "_DEVICE") end
@@ -216,16 +214,13 @@ return function(m, a, ending)
         function(c, s)
             if phase(s) ~= 5 then return end
             set(c, 6)
-            -- Deliver an explicit detach before publishing the rail attachment. A direct
-            -- enabled-to-enabled filter change can leave the previous burn attached.
-            hazards(c, s, nil)
-            c:start_timer("ember.apex.hazards", 250)
+            -- Disable the climb burn and enable only the authored escape hazard.
+            hazards(c, s, "escape")
             a.checkpoint(c, 0, 0x45920385, "escape")
             a.device(c, "MOTHER_BRAIN_CONSOLE_DEVICE", true)
             a.device(c, "MOTHER_BRAIN_ENGINE_LEFT_DEVICE", true)
             a.device(c, "MOTHER_BRAIN_ENGINE_RIGHT_DEVICE", true)
             a.objects(c, {"REACTOR_GETAWAY_SHIP_OBJECT"}, true)
-            a.objects(c, {"SUNBURN_DAMAGE_OBJECT"}, false)
             -- Object publication is not an entity-creation receipt. Start its device from
             -- A.object once the ship is present, so the initial movement is not lost.
             c:clear_variable("ember.apex.ship_started")
@@ -371,8 +366,8 @@ return function(m, a, ending)
         end
         if e.timer_name == "ember.apex.hazards" then
             -- The climb pipes burn while the cell is being carried up (phase 5), well before
-            -- the deposit. Escape (phase 6) replaces it with the rail-scoped burn, so
-            -- entering it detaches the climb burn. A checkpoint reset also detaches it.
+            -- the deposit. Escape (phase 6) uses only the native sunburn object.
+            -- Entering escape or resetting detaches the climb burn.
             if s:variable("ember.region") == 0 then
                 local p = phase(s)
                 if p == 5 then hazards(c, s, "climb")

+ 18 - 7
tests/mission_ember_routes_test.lua

@@ -390,20 +390,24 @@ assert(vars['ember.carry.apex.done'] and not vars['ember.carry.apex.held'])
 local before=#calls;use('MOTHER_BRAIN_INTERACT_OBJECT');assert(#calls==before)
 call(R.dispatch,'object',c,s,event('MOTHER_BRAIN_CARRY_OBJECT',{generation=3,present=false,alive=false}))
 assert(not timers['ember.carry.recover.apex.3'],'consumed final cell respawned')
--- Deposit must publish a disabled revision before the delayed rail attachment.
+-- Deposit disables the scripted climb burn; native sunburn alone owns escape damage.
 local detachedBeforeRail=false
 for i=depositStart+1,#calls do
     if calls[i][1]=='set_mission_effect' and calls[i][3].enabled==false then detachedBeforeRail=true end
 end
-assert(detachedBeforeRail,'deposit must detach the pipe burn before applying escape scorch')
--- Escape replaces the climb attachment with one scoped burn and disables the other source.
-timer('ember.apex.hazards')
+assert(detachedBeforeRail,'deposit must detach the pipe burn before escape')
+-- No scripted rail burn is attached during escape.
+call(R.dispatch,'timer',c,s,{timer_name='ember.apex.hazards'})
 local escapeEffect
 for i=#calls,1,-1 do
     if calls[i][1]=='set_mission_effect' then escapeEffect=calls[i][3];break end
 end
-assert(escapeEffect and escapeEffect.enabled==true and escapeEffect.filter~=nil,
-    'escape must attach the rail burn after deposit')
+assert(escapeEffect and escapeEffect.enabled==false,
+    'escape must leave the additional scripted burn disabled')
+for i=depositStart+1,#calls do
+    assert(not (calls[i][1]=='set_mission_effect' and calls[i][3].enabled),
+        'deposit must not add a second damage attachment')
+end
 local afterRail=#calls
 call(R.dispatch,'timer',c,s,{timer_name='ember.apex.hazards'})
 assert(#calls==afterRail,'duplicate hazard callback must not reattach scorch')
@@ -413,7 +417,7 @@ for i=depositStart+1,#calls do
         sunburnState=calls[i][3].active
     end
 end
-assert(sunburnState==false,'escape must not stack the sunburn prop with the rail burn')
+assert(sunburnState==true,'escape must activate its authored sunburn object')
 -- The weapon is dead once the cell is in: powered off, but still installed. Deactivating the
 -- ring objects would take the beam and its surrounding structure out of the world entirely.
 assert(vars['ember.apex.beam']==false,'the beam must stop firing after the deposit')
@@ -449,6 +453,13 @@ reset_check('escape',function()
     assert(vars['ember.apex.beam']==false,'escape restart must leave the beam off')
 end)
 trigger('APEX_DIRECTIVE_REACTOR_RAILS_ESCAPE_PLAYER_TRIGGER')
+local sunburnAfterEscape
+for i=#calls,1,-1 do
+    if calls[i][1]=='set_object_active' and calls[i][2]==slotDefs[m.Slot.SUNBURN_DAMAGE_OBJECT].name then
+        sunburnAfterEscape=calls[i][3].active;break
+    end
+end
+assert(sunburnAfterEscape==false,'escape completion must disable native sunburn')
 -- A selected bookend is not loaded merely because the player holds Apex's base world.
 local beforeArrival=#calls
 call(R.client,c,s,{held_region_index=0})