Skip to content

Commit 3871d7f

Browse files
Fix Split the Wound power-up and expand cheat sheet
Split the Wound: - Add split-wound-select phase for choosing second track - Apply curse to both tracks at half strength - Half-strength curses don't delete tracks or set permanent targets Cheat sheet: - Add complete mutation table (d100) - Add complete target curse table (d100) - Add complete mix curse table (d100) - Highlight important/special entries - Add scrollable table styling Also: - Improve Room 1 mutation logging to show original roll
1 parent 9277a81 commit 3871d7f

File tree

6 files changed

+225
-28
lines changed

6 files changed

+225
-28
lines changed

src/game/logic.ts

Lines changed: 44 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -381,6 +381,18 @@ export function selectApplyLastCurseTarget(trackIndex: number): void {
381381
});
382382
}
383383

384+
export function selectSplitWoundTarget(trackIndex: number): void {
385+
const state = getState();
386+
if (!state?.currentCurse) return;
387+
388+
addLogEntry(`Split the Wound: Also applying half-strength curse to Track ${trackIndex + 1}`);
389+
390+
updateState({
391+
phase: 'curse-result',
392+
pendingCurseTargets: [...state.pendingCurseTargets, trackIndex],
393+
});
394+
}
395+
384396
function rollMixCurse(): void {
385397
const state = getState();
386398
if (!state) return;
@@ -427,17 +439,24 @@ export function acceptCurse(): void {
427439

428440
if (state.currentCurse.type === 'Target Curse') {
429441
const targetEntry = curseEntry as TargetCurseEntry;
442+
const isHalfStrength = state.splitWoundActive;
443+
const curseText = isHalfStrength
444+
? `${state.currentCurse.effect} (Half Strength)`
445+
: state.currentCurse.effect;
430446

431447
for (const targetIdx of state.pendingCurseTargets) {
432448
if (targetIdx >= 0 && targetIdx < tracks.length) {
449+
// Half strength curses don't delete tracks
450+
const shouldDelete = targetEntry.mechanics?.deleteTrack && !isHalfStrength;
451+
433452
tracks[targetIdx] = {
434453
...tracks[targetIdx],
435-
curses: [...tracks[targetIdx].curses, state.currentCurse.effect],
436-
deleted: targetEntry.mechanics?.deleteTrack ? true : tracks[targetIdx].deleted,
454+
curses: [...tracks[targetIdx].curses, curseText],
455+
deleted: shouldDelete ? true : tracks[targetIdx].deleted,
437456
};
438-
addLogEntry(`Curse applied to Track ${targetIdx + 1}`);
457+
addLogEntry(`Curse applied to Track ${targetIdx + 1}${isHalfStrength ? ' (Half Strength)' : ''}`);
439458

440-
if (targetEntry.mechanics?.becomesCurseTarget) {
459+
if (targetEntry.mechanics?.becomesCurseTarget && !isHalfStrength) {
441460
curseTargetTrackIndex = targetIdx;
442461
addLogEntry(`Track ${targetIdx + 1} is now the target of all future curses`);
443462
}
@@ -504,6 +523,7 @@ export function acceptCurse(): void {
504523
pendingCurseTargets: [],
505524
pendingTargetCurseRolls: remainingRolls,
506525
painShiftActive: remainingRolls > 0 ? state.painShiftActive : false,
526+
splitWoundActive: false,
507527
...(nextPhase ? { phase: nextPhase } : {}),
508528
});
509529

@@ -537,11 +557,13 @@ function rollSingleMutation(mode: string, room: number, isSecondMutation = false
537557

538558
if (entry.mechanics?.roomOneRule && room === 1) {
539559
if (entry.mechanics.roomOneRule === 'reroll') {
560+
addLogEntry(`Mutation Roll: ${r}${entry.text}`);
540561
addLogEntry('Room 1: Re-rolling mutation');
541562
return rollSingleMutation(mode, room, isSecondMutation);
542563
}
543564
if (entry.mechanics.roomOneRule === 'no-mutation') {
544-
addLogEntry('Room 1: No mutation');
565+
addLogEntry(`Mutation Roll: ${r}${entry.text}`);
566+
addLogEntry('Room 1: No mutation applies');
545567
return { roll: r, effect: 'No Mutation.' };
546568
}
547569
}
@@ -815,7 +837,23 @@ export function usePowerUp(type: string): void {
815837
rollTargetCurse();
816838
return;
817839
case 'split':
818-
addLogEntry('Power-Up: Split the Wound - Curse applied at half strength');
840+
if (state.currentCurse?.type === 'Target Curse' && state.pendingCurseTargets.length > 0) {
841+
const availableTracks = state.tracks
842+
.map((t, i) => i)
843+
.filter(i => !state.tracks[i].deleted && i !== state.roomLockTrack && !state.pendingCurseTargets.includes(i));
844+
845+
if (availableTracks.length > 0) {
846+
addLogEntry('Power-Up: Split the Wound - Select second track for half-strength curse');
847+
updates.splitWoundActive = true;
848+
updates.phase = 'split-wound-select';
849+
updateState(updates);
850+
return;
851+
} else {
852+
addLogEntry('Power-Up: Split the Wound - No other tracks available, curse applied at half strength to original target');
853+
}
854+
} else {
855+
addLogEntry('Power-Up: Split the Wound - Curse applied at half strength');
856+
}
819857
break;
820858
case 'breath':
821859
if (!state.usedOneLastBreath) {

src/game/state.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ export function createInitialState(mode: GameMode, manualTrackType: boolean): Ga
6262
conditionalPowerUp: false,
6363
powerUpBlockedThisRoom: false,
6464
oneLastBreathPending: false,
65+
splitWoundActive: false,
6566
};
6667
}
6768

src/game/types.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,8 @@ export type Phase =
1212
| 'compose'
1313
| 'powerup-roll'
1414
| 'next-room'
15-
| 'room-lock-select';
15+
| 'room-lock-select'
16+
| 'split-wound-select';
1617

1718
export type CurseTargetMethod =
1819
| 'previous'
@@ -86,6 +87,7 @@ export interface GameState {
8687
conditionalPowerUp: boolean;
8788
powerUpBlockedThisRoom: boolean;
8889
oneLastBreathPending: boolean;
90+
splitWoundActive: boolean;
8991
}
9092

9193
export type PowerUpType = 'redirect' | 'lock' | 'painshift' | 'split' | 'breath';

src/index.html

Lines changed: 108 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -150,29 +150,116 @@ <h3>Curse Target (2 rolls)</h3>
150150
<p><strong>First:</strong> 01-20: Previous, 21-40: Oldest, 41-60: Loudest, 61-80: Quietest, 81-95: Choice, 96-100: Two Targets</p>
151151
<p><strong>Second:</strong> 01-32: Track Before, 33-66: That Track, 67-100: Track After</p>
152152

153-
<h3>Key Mutations</h3>
154-
<ul>
155-
<li>63-64: Roll twice, apply both</li>
156-
<li>71-72: Abandon Track Type</li>
157-
<li>85-86: 5-minute timer</li>
158-
<li>93-94: Take Target Curse instead</li>
159-
<li>97-98: Roll 80+ = delete Track</li>
160-
</ul>
153+
<h3>Mutations (d100)</h3>
154+
<div class="table-scroll">
155+
<table>
156+
<tr><th>Roll</th><th>Effect</th></tr>
157+
<tr><td>01-02</td><td>Sum the Track to mono.</td></tr>
158+
<tr><td>03-04</td><td>Track must be monophonic.</td></tr>
159+
<tr><td>05-06</td><td>Limited to one octave or four slices.</td></tr>
160+
<tr><td>07-08</td><td>Tracks/Slices must be fixed velocity.</td></tr>
161+
<tr><td>09-10</td><td>Track must loop one bar over and over.</td></tr>
162+
<tr><td>11-12</td><td>May only play on beats 1 and 3.</td></tr>
163+
<tr><td>13-14</td><td>May only play on beats 2 and 4.</td></tr>
164+
<tr><td>15-16</td><td>Must be played or sequenced in triplets.</td></tr>
165+
<tr><td>17-18</td><td>Must not be quantized; must be played live.</td></tr>
166+
<tr><td>19-20</td><td>Must be quantized to 1/16 steps.</td></tr>
167+
<tr><td>21-22</td><td>Notes must be sequenced in step sequence.</td></tr>
168+
<tr><td>23-24</td><td>Notes/slices can't be edited once performed or programmed.</td></tr>
169+
<tr><td>25-26</td><td>Cannot be re-sequenced after Room resolves. Can only delete notes/slices.</td></tr>
170+
<tr><td>27-28</td><td>Must be altered and resampled before processing.</td></tr>
171+
<tr><td>29-30</td><td>Shorten envelope to be percussive.</td></tr>
172+
<tr><td>31-32</td><td>Must use one sound or note only.</td></tr>
173+
<tr><td>33-34</td><td>Cannot use the same note or slice twice.</td></tr>
174+
<tr><td>35-36</td><td>Notes must be played in strict order.</td></tr>
175+
<tr><td>37-38</td><td>Every note must repeat once before another may be played.</td></tr>
176+
<tr><td>39-40</td><td>There can be no silence in the Track.</td></tr>
177+
<tr><td>41-42</td><td>Must pass through reverb before composing.</td></tr>
178+
<tr><td>43-44</td><td>Must pass through delay before composing.</td></tr>
179+
<tr><td>45-46</td><td>May not use effects.</td></tr>
180+
<tr><td>47-48</td><td>May use only one effect.</td></tr>
181+
<tr><td>49-50</td><td>Must be side-chained to the previous Track.</td></tr>
182+
<tr><td>51-52</td><td>Must be noise-gated.</td></tr>
183+
<tr><td>53-54</td><td>Must pass through a low-pass filter (1 kHz).</td></tr>
184+
<tr><td>55-56</td><td>Must be detuned slightly (±10 cents).</td></tr>
185+
<tr><td>57-58</td><td>You can't audition sounds. Pick blind before composing.</td></tr>
186+
<tr><td>59-60</td><td>All effects must be added and tuned before composing.</td></tr>
187+
<tr><td>61-62</td><td>Mute all rhythms and the metronome for the duration of the Room.</td></tr>
188+
<tr class="highlight"><td>63-64</td><td>Roll twice. Apply both Mutations.</td></tr>
189+
<tr><td>65-66</td><td>Only apply effects that have already been used.</td></tr>
190+
<tr><td>67-68</td><td>Must include one deliberate wrong note.</td></tr>
191+
<tr><td>69-70</td><td>No Mutation.</td></tr>
192+
<tr class="highlight"><td>71-72</td><td>Track must abandon its intended Track Type.</td></tr>
193+
<tr><td>73-74</td><td>Solo this Track for the duration of the Room.</td></tr>
194+
<tr><td>75-76</td><td>Only use effects that haven't been used.</td></tr>
195+
<tr><td>77-78</td><td>Once Room is finalized, volume is locked.</td></tr>
196+
<tr><td>79-80</td><td>No Mutation.</td></tr>
197+
<tr class="highlight"><td>81-82</td><td>Purpose of Track changes to match previous Room. If Room One, re-roll.</td></tr>
198+
<tr class="highlight"><td>83-84</td><td>Repeat last Room's Mutation. If first Room, no Mutation.</td></tr>
199+
<tr class="highlight"><td>85-86</td><td>Once you begin composing, the Room finalizes in five minutes.</td></tr>
200+
<tr><td>87-88</td><td>Must control same amount of notes as previous Track.</td></tr>
201+
<tr><td>89-90</td><td>Compose Track while muted. Select sounds/patches before unmuting. Unmute ends Room.</td></tr>
202+
<tr class="highlight"><td>91-92</td><td>Each note requires deleting one note from another Track. If Room One, no Mutation.</td></tr>
203+
<tr class="highlight"><td>93-94</td><td>Take a Target Curse instead.</td></tr>
204+
<tr><td>95-96</td><td>Once a note or slice is placed, it can't be moved—only deleted.</td></tr>
205+
<tr class="highlight"><td>97-98</td><td>Roll Mutations 80 or higher = delete Track. If Room One, no Mutation.</td></tr>
206+
<tr><td>99-100</td><td>No Mutation.</td></tr>
207+
</table>
208+
</div>
161209

162-
<h3>Key Target Curses</h3>
163-
<ul>
164-
<li>33-36: Force Room + Double Mutation</li>
165-
<li>45-48: Track becomes permanent curse target</li>
166-
<li>57-60: Delete Track</li>
167-
<li>93-96: Apply last Curse to another Track</li>
168-
<li>97-100: Re-roll twice</li>
169-
</ul>
210+
<h3>Target Curses (d100)</h3>
211+
<div class="table-scroll">
212+
<table>
213+
<tr><th>Roll</th><th>Effect</th></tr>
214+
<tr><td>01-04</td><td>Mute until end of Run.</td></tr>
215+
<tr><td>05-08</td><td>Collapse to mono.</td></tr>
216+
<tr><td>09-12</td><td>1/32 quantize.</td></tr>
217+
<tr><td>13-16</td><td>Pan hard left or right.</td></tr>
218+
<tr><td>17-20</td><td>Track must remain constant in final arrangement.</td></tr>
219+
<tr><td>21-24</td><td>Resample, Chop, Reverse, Re-sequence.</td></tr>
220+
<tr><td>25-28</td><td>Pitch shift one octave.</td></tr>
221+
<tr><td>29-32</td><td>Apply Flanger or Chorus.</td></tr>
222+
<tr class="highlight"><td>33-36</td><td>Force a Room. Next Room will have two Mutations.</td></tr>
223+
<tr><td>37-40</td><td>Turn Track down -6 dB.</td></tr>
224+
<tr><td>41-44</td><td>Strip all effects.</td></tr>
225+
<tr class="highlight"><td>45-48</td><td>This Track is now target of all future Curses until end of Run.</td></tr>
226+
<tr><td>49-52</td><td>Over-compress.</td></tr>
227+
<tr><td>53-56</td><td>When discarding Run, roll d100. On 51+, force another Room.</td></tr>
228+
<tr class="highlight"><td>57-60</td><td>Delete Track. You can't replace its role.</td></tr>
229+
<tr><td>61-64</td><td>No re-sequencing or volume change.</td></tr>
230+
<tr><td>65-68</td><td>Remove half the notes/chops.</td></tr>
231+
<tr><td>69-72</td><td>Loop a short segment.</td></tr>
232+
<tr><td>73-76</td><td>Erase all notes/slices except first and last.</td></tr>
233+
<tr><td>77-80</td><td>This Track can't loop back to back.</td></tr>
234+
<tr><td>81-84</td><td>Delete the first bar of the Track.</td></tr>
235+
<tr><td>85-88</td><td>Delete the last bar of the Track.</td></tr>
236+
<tr><td>89-92</td><td>Cannot edit Track rest of Run.</td></tr>
237+
<tr class="highlight"><td>93-96</td><td>Apply last Curse to another Track of your choosing. If first Curse, ignore.</td></tr>
238+
<tr class="highlight"><td>97-100</td><td>Re-roll twice.</td></tr>
239+
</table>
240+
</div>
170241

171-
<h3>Key Mix Curses</h3>
172-
<ul>
173-
<li>01: This is the last Room</li>
174-
<li>92-100: Roll three Target Curses</li>
175-
</ul>
242+
<h3>Mix Curses (d100, only on 99-100 curse check)</h3>
243+
<div class="table-scroll">
244+
<table>
245+
<tr><th>Roll</th><th>Effect</th></tr>
246+
<tr class="highlight"><td>01</td><td>This is the last Room. If Room One or Two, re-roll.</td></tr>
247+
<tr><td>02-07</td><td>Delay on Entire Mix.</td></tr>
248+
<tr><td>08-14</td><td>No quantize for rest of Run. All notes performed live.</td></tr>
249+
<tr><td>15-21</td><td>1/32 quantize for rest of Run.</td></tr>
250+
<tr><td>22-28</td><td>Decrease BPM by 10.</td></tr>
251+
<tr><td>29-35</td><td>Increase BPM by 10.</td></tr>
252+
<tr><td>36-42</td><td>Reverb on Entire Mix.</td></tr>
253+
<tr><td>43-49</td><td>Resample Entire Mix. Disable all other Tracks.</td></tr>
254+
<tr><td>50-56</td><td>Tracks can no longer resample unless instructed by Curse/Mutation.</td></tr>
255+
<tr><td>57-63</td><td>Adjust your Monitoring. Cannot change for rest of Run.</td></tr>
256+
<tr><td>64-70</td><td>You cannot adjust volume of previous Tracks.</td></tr>
257+
<tr><td>71-77</td><td>Automation is now banned.</td></tr>
258+
<tr><td>78-84</td><td>Mix must be summed to mono.</td></tr>
259+
<tr><td>85-91</td><td>Only previously used effects can be used until end of Run.</td></tr>
260+
<tr class="highlight"><td>92-100</td><td>Roll three Target Curses.</td></tr>
261+
</table>
262+
</div>
176263

177264
<h3>End Condition</h3>
178265
<p>Run ends when you declare it over with no forced Rooms. If rules followed, you win!</p>

src/styles.css

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -363,6 +363,46 @@ select {
363363
margin-bottom: 0.5rem;
364364
}
365365

366+
.modal-body .table-scroll {
367+
max-height: 300px;
368+
overflow-y: auto;
369+
margin-bottom: 1rem;
370+
border: 1px solid var(--border);
371+
border-radius: 4px;
372+
}
373+
374+
.modal-body table {
375+
width: 100%;
376+
border-collapse: collapse;
377+
font-size: 0.8rem;
378+
}
379+
380+
.modal-body th,
381+
.modal-body td {
382+
padding: 0.4rem 0.5rem;
383+
text-align: left;
384+
border-bottom: 1px solid var(--border);
385+
}
386+
387+
.modal-body th {
388+
background: var(--surface2);
389+
position: sticky;
390+
top: 0;
391+
font-weight: bold;
392+
}
393+
394+
.modal-body tr:last-child td {
395+
border-bottom: none;
396+
}
397+
398+
.modal-body tr.highlight {
399+
background: rgba(255, 107, 53, 0.15);
400+
}
401+
402+
.modal-body tr.highlight td {
403+
color: var(--accent);
404+
}
405+
366406
@media (max-width: 600px) {
367407
.container {
368408
padding: 0.5rem;

src/ui/render.ts

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import {
88
acceptCurse,
99
selectCurseTarget,
1010
selectApplyLastCurseTarget,
11+
selectSplitWoundTarget,
1112
selectRoomLockTarget,
1213
rollMutation,
1314
acceptMutation,
@@ -404,6 +405,34 @@ function renderRoom(): void {
404405
});
405406
break;
406407

408+
case 'split-wound-select':
409+
const splitAvailableTracks = state.tracks
410+
.map((t, i) => ({ track: t, index: i }))
411+
.filter(({ track, index }) => !track.deleted && index !== state.roomLockTrack && !state.pendingCurseTargets.includes(index));
412+
413+
content.innerHTML = `
414+
<div class="roll-result">
415+
<div class="roll-text">Split the Wound: ${state.currentCurse?.effect}</div>
416+
<p class="muted" style="margin-top: 0.5rem;">Original target: Track ${state.pendingCurseTargets.map(i => i + 1).join(', ')}</p>
417+
</div>
418+
<h3 style="margin-top: 1rem;">Select second track for half-strength curse</h3>
419+
<div class="track-select-list">
420+
${splitAvailableTracks.map(({ track, index }) => `
421+
<button class="track-select-btn" data-index="${index}">
422+
Room ${track.room}: ${track.type}
423+
</button>
424+
`).join('')}
425+
</div>
426+
`;
427+
document.querySelectorAll<HTMLButtonElement>('.track-select-btn').forEach(btn => {
428+
btn.onclick = () => {
429+
const index = parseInt(btn.dataset.index || '0', 10);
430+
selectSplitWoundTarget(index);
431+
render();
432+
};
433+
});
434+
break;
435+
407436
case 'next-room':
408437
content.innerHTML = `
409438
<h3>Room ${state.room} Complete</h3>

0 commit comments

Comments
 (0)