Skip to content

Commit 2ffc51e

Browse files
committed
Merge final gameplay safeguards into feedback polish
2 parents 455f4df + 21642af commit 2ffc51e

2 files changed

Lines changed: 32 additions & 4 deletions

File tree

index.html

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -252,12 +252,13 @@ <h2 id="journal-title">SIGNAL LOG</h2>
252252

253253
// Interaction targets reserve their tile so the player stops facing them.
254254
const npcOnTile = isTileOccupiedByNPC(tx, ty, null);
255-
const fragmentOnTile = getUncollectedFragmentAt(tx, ty);
255+
const fragmentOnTile = getActiveFragmentAt(tx, ty);
256256
if (npcOnTile || fragmentOnTile || !isWalkable(tx, ty)) {
257257
// BLOCKED
258258
player.state = 'BLOCKED';
259259
player.bumpDir = dir;
260260
player.bumpProgress = 0;
261+
if (fragmentOnTile && !fragmentOnTile.discovered) showStatus('SIGNAL INTERFERENCE · SCAN');
261262
return;
262263
}
263264

@@ -848,7 +849,7 @@ <h2 id="journal-title">SIGNAL LOG</h2>
848849
// PATHFINDING
849850
// ══════════════════════════════════════════════════════
850851
function findPath(sx,sy,ex,ey){
851-
const isPlayerWalkable = (x, y) => isWalkable(x, y) && !getUncollectedFragmentAt(x, y);
852+
const isPlayerWalkable = (x, y) => isWalkable(x, y) && !getActiveFragmentAt(x, y);
852853
return findWorldPath(sx, sy, ex, ey, isPlayerWalkable, { allowNearest: false });
853854
}
854855

@@ -1024,6 +1025,14 @@ <h2 id="journal-title">SIGNAL LOG</h2>
10241025
}
10251026

10261027
function resetQuest() {
1028+
const occupiedSignalSite = signalFragments.some(fragment => (
1029+
entityOccupiesTile(player, fragment.x, fragment.y) ||
1030+
isTileReserved(npcs, fragment.x, fragment.y, null)
1031+
));
1032+
if (occupiedSignalSite) {
1033+
showStatus('CLEAR SIGNAL SITES BEFORE RESTART', 2.5);
1034+
return false;
1035+
}
10271036
for (const fragment of signalFragments) {
10281037
fragment.discovered = false;
10291038
fragment.collected = false;
@@ -1035,6 +1044,7 @@ <h2 id="journal-title">SIGNAL LOG</h2>
10351044
updateQuestUI();
10361045
updateJournal();
10371046
showStatus('SIGNAL SEARCH RESTARTED', 2);
1047+
return true;
10381048
}
10391049

10401050
function requestQuestReset(confirmReset) {
@@ -1069,6 +1079,7 @@ <h2 id="journal-title">SIGNAL LOG</h2>
10691079
journalDialog.showModal();
10701080
});
10711081
journalClose.addEventListener('click', () => journalDialog.close());
1082+
journalDialog.addEventListener('close', () => canvas.focus({ preventScroll: true }));
10721083
function updateSoundButton() {
10731084
const muted = feedback.isMuted();
10741085
soundButton.classList.toggle('muted', muted);

tests/runtime-smoke.test.cjs

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ test('runtime initializes and renders a frame', () => {
5454
'journal-close': element(),
5555
'sound-button': element(),
5656
};
57+
elements.game.focus = () => { listeners.canvasFocused = true; };
5758
const sandbox = {
5859
LittleAndroidLogic,
5960
LittleAndroidContent,
@@ -98,24 +99,29 @@ test('runtime initializes and renders a frame', () => {
9899
{ ...keyboardTargets },
99100
{ game: true, canvas: true, control: false },
100101
);
102+
listeners.close();
103+
assert.equal(listeners.canvasFocused, true);
101104

102105
const retrieval = vm.runInContext(`
103106
const fragment = signalFragments[0];
104107
player.tileX = fragment.x;
105108
player.tileY = fragment.y - 1;
106109
player.renderX = player.tileX;
107110
player.renderY = player.tileY;
111+
playerStartMove('south');
112+
const hiddenBlocked = player.state;
113+
player.state = 'IDLE';
108114
triggerScanPulse();
109115
updateScanPulse(1);
110116
playerStartMove('south');
111117
const blocked = player.state;
112118
player.state = 'IDLE';
113119
performAction();
114-
({ discovered: fragment.discovered, blocked, collected: fragment.collected });
120+
({ discovered: fragment.discovered, hiddenBlocked, blocked, collected: fragment.collected });
115121
`, sandbox);
116122
assert.deepEqual(
117123
{ ...retrieval },
118-
{ discovered: true, blocked: 'BLOCKED', collected: true },
124+
{ discovered: true, hiddenBlocked: 'BLOCKED', blocked: 'BLOCKED', collected: true },
119125
);
120126

121127
const resetBehavior = vm.runInContext(`
@@ -139,6 +145,17 @@ test('runtime initializes and renders a frame', () => {
139145
`, sandbox);
140146
assert.deepEqual({ ...completion }, { complete: true, collected: 3 });
141147
assert.equal(elements.quest.textContent, 'FACTORY ONLINE');
148+
149+
const replay = vm.runInContext(`
150+
player.tileX = signalFragments[0].x;
151+
player.tileY = signalFragments[0].y;
152+
const replayRefused = resetQuest();
153+
player.tileX = 4;
154+
player.tileY = 6;
155+
const replayAccepted = resetQuest();
156+
({ refused: replayRefused, accepted: replayAccepted, collected: signalFragments.filter(fragment => fragment.collected).length });
157+
`, sandbox);
158+
assert.deepEqual({ ...replay }, { refused: false, accepted: true, collected: 0 });
142159
});
143160

144161
test('NPC movement rejects a destination reserved earlier in the frame', () => {

0 commit comments

Comments
 (0)