Skip to content

Commit b622f50

Browse files
committed
Preserve legacy progress through daily migration
2 parents 232643e + 2ffc51e commit b622f50

4 files changed

Lines changed: 44 additions & 6 deletions

File tree

index.html

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

270270
// Interaction targets reserve their tile so the player stops facing them.
271271
const npcOnTile = isTileOccupiedByNPC(tx, ty, null);
272-
const fragmentOnTile = getUncollectedFragmentAt(tx, ty);
272+
const fragmentOnTile = getActiveFragmentAt(tx, ty);
273273
if (npcOnTile || fragmentOnTile || !isWalkable(tx, ty)) {
274274
// BLOCKED
275275
player.state = 'BLOCKED';
276276
player.bumpDir = dir;
277277
player.bumpProgress = 0;
278+
if (fragmentOnTile && !fragmentOnTile.discovered) showStatus('SIGNAL INTERFERENCE · SCAN');
278279
return;
279280
}
280281

@@ -867,7 +868,7 @@ <h2 id="journal-title">SIGNAL LOG</h2>
867868
// PATHFINDING
868869
// ══════════════════════════════════════════════════════
869870
function findPath(sx,sy,ex,ey){
870-
const isPlayerWalkable = (x, y) => isWalkable(x, y) && !getUncollectedFragmentAt(x, y);
871+
const isPlayerWalkable = (x, y) => isWalkable(x, y) && !getActiveFragmentAt(x, y);
871872
return findWorldPath(sx, sy, ex, ey, isPlayerWalkable, { allowNearest: false });
872873
}
873874

@@ -1046,6 +1047,14 @@ <h2 id="journal-title">SIGNAL LOG</h2>
10461047
}
10471048

10481049
function resetQuest() {
1050+
const occupiedSignalSite = signalFragments.some(fragment => (
1051+
entityOccupiesTile(player, fragment.x, fragment.y) ||
1052+
isTileReserved(npcs, fragment.x, fragment.y, null)
1053+
));
1054+
if (occupiedSignalSite) {
1055+
showStatus('CLEAR SIGNAL SITES BEFORE RESTART', 2.5);
1056+
return false;
1057+
}
10491058
for (const fragment of signalFragments) {
10501059
fragment.discovered = false;
10511060
fragment.collected = false;
@@ -1058,6 +1067,7 @@ <h2 id="journal-title">SIGNAL LOG</h2>
10581067
updateJournal();
10591068
showStatus('SIGNAL SEARCH RESTARTED', 2);
10601069
metrics.increment('replays');
1070+
return true;
10611071
}
10621072

10631073
function requestQuestReset(confirmReset) {
@@ -1092,6 +1102,7 @@ <h2 id="journal-title">SIGNAL LOG</h2>
10921102
journalDialog.showModal();
10931103
});
10941104
journalClose.addEventListener('click', () => journalDialog.close());
1105+
journalDialog.addEventListener('close', () => canvas.focus({ preventScroll: true }));
10951106
function updateSoundButton() {
10961107
const muted = feedback.isMuted();
10971108
soundButton.classList.toggle('muted', muted);

src/progress.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@
5252

5353
function scopeProgressToChallenge(progress, challengeDate) {
5454
const normalized = parseProgress(progress);
55+
if (!normalized.challengeDate) return { ...normalized, challengeDate };
5556
if (normalized.challengeDate === challengeDate) return normalized;
5657
return {
5758
...defaultProgress(),

tests/progress.test.cjs

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,15 +48,24 @@ test('progress normalizes lists and ignores unknown fields', () => {
4848
});
4949

5050
test('version one saves migrate resident discoveries', () => {
51-
assert.deepEqual(parseProgress({
51+
const migrated = parseProgress({
5252
version: 1,
53+
discoveredFragments: ['field-coil'],
54+
collectedFragments: ['field-coil'],
5355
discoveredNPCs: ['OVERSEER'],
5456
complete: true,
55-
}), {
57+
});
58+
assert.deepEqual(migrated, {
5659
...defaultProgress(),
60+
discoveredFragments: ['field-coil'],
61+
collectedFragments: ['field-coil'],
5762
discoveredNPCs: ['OVERSEER'],
5863
complete: true,
5964
});
65+
assert.deepEqual(scopeProgressToChallenge(migrated, '2026-07-16'), {
66+
...migrated,
67+
challengeDate: '2026-07-16',
68+
});
6069
});
6170

6271
test('daily progress restores only for the matching challenge date', () => {

tests/runtime-smoke.test.cjs

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ test('runtime initializes and renders a frame', () => {
5656
'sound-button': element(),
5757
'share-button': element(),
5858
};
59+
elements.game.focus = () => { listeners.canvasFocused = true; };
5960
const sandbox = {
6061
LittleAndroidLogic,
6162
LittleAndroidContent,
@@ -101,24 +102,29 @@ test('runtime initializes and renders a frame', () => {
101102
{ ...keyboardTargets },
102103
{ game: true, canvas: true, control: false },
103104
);
105+
listeners.close();
106+
assert.equal(listeners.canvasFocused, true);
104107

105108
const retrieval = vm.runInContext(`
106109
const fragment = signalFragments[0];
107110
player.tileX = fragment.x;
108111
player.tileY = fragment.y - 1;
109112
player.renderX = player.tileX;
110113
player.renderY = player.tileY;
114+
playerStartMove('south');
115+
const hiddenBlocked = player.state;
116+
player.state = 'IDLE';
111117
triggerScanPulse();
112118
updateScanPulse(1);
113119
playerStartMove('south');
114120
const blocked = player.state;
115121
player.state = 'IDLE';
116122
performAction();
117-
({ discovered: fragment.discovered, blocked, collected: fragment.collected });
123+
({ discovered: fragment.discovered, hiddenBlocked, blocked, collected: fragment.collected });
118124
`, sandbox);
119125
assert.deepEqual(
120126
{ ...retrieval },
121-
{ discovered: true, blocked: 'BLOCKED', collected: true },
127+
{ discovered: true, hiddenBlocked: 'BLOCKED', blocked: 'BLOCKED', collected: true },
122128
);
123129

124130
const resetBehavior = vm.runInContext(`
@@ -142,6 +148,17 @@ test('runtime initializes and renders a frame', () => {
142148
`, sandbox);
143149
assert.deepEqual({ ...completion }, { complete: true, collected: 3 });
144150
assert.match(elements.quest.textContent, /^FACTORY ONLINE · D\d{4}$/);
151+
152+
const replay = vm.runInContext(`
153+
player.tileX = signalFragments[0].x;
154+
player.tileY = signalFragments[0].y;
155+
const replayRefused = resetQuest();
156+
player.tileX = 4;
157+
player.tileY = 6;
158+
const replayAccepted = resetQuest();
159+
({ refused: replayRefused, accepted: replayAccepted, collected: signalFragments.filter(fragment => fragment.collected).length });
160+
`, sandbox);
161+
assert.deepEqual({ ...replay }, { refused: false, accepted: true, collected: 0 });
145162
});
146163

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

0 commit comments

Comments
 (0)