Merging multiple new experiment features (classes, advantaged pay, clean aborts)#263
Merging multiple new experiment features (classes, advantaged pay, clean aborts)#263kodacapo wants to merge 49 commits into
Conversation
- Add family: 4 option to mongoose.connect() to force IPv4 (localhost was resolving to IPv6 ::1 which MongoDB wasn't listening on) - Modernize unit-utils.js to use async/await and handle connection states - Update test helper functions to use modern Mongoose promise API instead of deprecated callback syntax All 287 tests now pass (was 127 passing, 21 failing). Coverage improved from 43% to 52%. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
…antageicon, pfishvalue) - Add pisadvantaged URL parameter (boolean: true/false/1/0, defaults to false) - Add padvantageicon URL parameter for advantage status icon display - Add pfishvalue URL parameter for per-participant fish value override - Pass pParams through socket events from client to Fisher constructor - Refactor Fisher to use params object consistently (no duplicate properties) - Display both pClassIcon and pAdvantageIcon with space between them - Show icons even when showFisherNames is false - Fix tests to use params object structure Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Split the "Disable profit columns" checkbox into three separate toggles: - Disable season profit column (default: off) - Disable overall profit column (default: off) - Disable profit diff column (default: on) Each toggle independently controls its column's visibility in the game view. Backward compatible with existing microworlds that use the legacy profitDisplayDisabled field. All 316 tests passing. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Fix pay gap calculation: compare effective fish values instead of relying on pHasAdvantage flag, which was not being set correctly - Fix column hide functions: use attribute selector instead of iterating st.fishers (which isn't populated when setupOcean runs) - Remove references to non-existent #profit-season-th and #profit-total-th - Add Pay Gap localization key (info_payGap) to all 8 languages - Display positive gap values with + prefix in blue, negative in red Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
…ors in the profit gap column
… with the green background color for "You"
… test assertions
Use querySelectorAll instead of querySelector in the jQuery mock so
attribute selectors like $('[id$="-profit-season"]') operate on all
matching elements. Also add css() to the mock and remove assertions
for #profit-season-th and #profit-total-th which don't exist in the
actual template.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
New microworld settings section "Fisher Classes" with: - Enable toggle to activate the feature - List of class names (array of strings) - Number of fishers per class (object indexed by class name) - Emojis for each class (object indexed by class name) - EXPLAIN button with modal documentation Validation ensures counts match class names and sum to total fishers. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Server-side addFisher() now validates and assigns the default fisher class when fClass is missing or invalid, matching the client-side validateFisherClass() logic. Previously, fishers joining without URL parameters had fClass=null on the server, so status broadcasts never included a valid class for emoji lookup. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…ale/Male Fisher class names are now validated case-insensitively and normalized to their canonical (capitalized) form in microworld config, client-side URL param parsing, and server-side addFisher(). Default class names changed from Class A/Class B to Female/Male with ♀︎/♂︎ emojis. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…e sub-controls - Add fish value pay gap, advantage/no-advantage emoji fields to microworld config - Change profit gap column from "Disable" (default on) to "Enable" (default off) - Show/hide sub-controls for catch intentions, fisher classes, and fisher advantage - Implement advantage pay gap in fisher earnings (server-side) - Use microworld emojis for advantage display instead of URL parameter - Rename phasadvantage URL param to fhasadvantage, gate on fisherAdvantageEnabled - Remove pfishvalue/padvantageicon URL params and unicodeToChar (now from microworld) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…cy profitDisplayDisabled Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Allow experimenters to assign a class (e.g., Female/Male) and advantage status to each bot. Transpose the bot configuration table so properties are rows and bots are columns. Validate bot class assignments against class count limits and adjust classesNeeded to account for bot slots. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Wrap fisher name emojis in fixed-width spans so names align regardless of emoji choice. Validate that advantage, no-advantage, and class emojis are each a single character. Rename advantageIcon to advantageEmoji. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Require fish value pay gap to be at least 0.01 when fisher advantage is enabled. Update fisher classes and fisher advantage explanation pages with accurate descriptions of how the features work. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Shows a waiting-room table during ocean setup phase displaying each fisher's lobby status (reading rules / ready and waiting / missing) and how long they've been waiting. The current player's row shows "You". Bot fishers receive staggered timestamps so they can't be identified. All labels and headers are wired into the translation system (8 languages). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Clamp readyMins/entryMins to max(0,min(99,...)) to guard against negative times from client/server clock skew. Emit lobbyStatus to the room after removeFisherFromOcean so remaining fishers see the departed fisher removed immediately. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Implements configurable timeouts for the read-rules and lobby-wait phases: ocean tracks per-fisher timers, notifies clients via abortPrompt/forceAbort events, and handles keepReading/proceedToLobby/ keepWaiting/abortFish responses. Adds readRulesTimeout and lobbyWaitTimeout params to microworld model and config UI, with localization strings for all new prompts. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Display wait times in mm:ss format (capped at 59:59), updating every second - Randomize bot entry/ready times to rand(k*60, (k+1)*60) seconds before ocean creation to avoid suspiciously uniform gaps - Remove setFisherNotifier call that belongs to clean-abort branch (was causing a crash when any human joined) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
… modal - Microworld UI: wrap abort settings in Enable Clean Abort checkbox with EXPLAIN button and show/hide; reset read/wait timeouts to 0 when disabled - Validation: range checks for abort fields (read/wait 0-10 min, prompt 10-60 sec, max timeouts 1-20) with error messages instead of silent clamping - Fix save function to store integer defaults instead of null for abort fields - Force-abort modal with OK button and countdown when forceAbort event received - Refactor duplicate countdown logic into shared startCountdown() helper - Remove spurious maxTimeouts truthy guard in onPlayerTimeout - Add /explain-clean-abort page and route - Lobby status: mm:ss wait time format, random bot entry offsets - Update status_wait localization to 'Please wait in the lobby' in all 8 languages - Rename section label 'Abort / timeout' to 'Clean Abort' Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
ocean.fisherClassesEnabled was not checked when computing classEmoji for bots, so bots with fClass in their params displayed class icons regardless of whether the feature was enabled. Human fishers were unaffected because validateFisherClass() already clears pParams.fClass when the toggle is off. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add abortReadingRulesText, abortLobbyWaitText, forceAbortText fields to microworld model; experimenters can supply per-dialog messages or leave blank to use the auto-translated system default - Rename #abort-modal to #maybe-abort-modal to distinguish from force-abort - When the maybe-abort prompt countdown expires, show force-abort modal instead of silently aborting - Interleave timeout value and message fields in the microworld editor - Fix readRulesTimeout/lobbyWaitTimeout showing blank when value is 0 - Update EXPLAIN page to document new message fields and revised prompt flow Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
When a participant opens a second tab or refreshes, the new socket now takes over the existing fisher slot rather than adding a duplicate entry. The stale socket is displaced (evicted from the room and notified), so it stops receiving broadcasts and its disconnect no longer evicts the fisher. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
When fisher-classes are enabled, a departing human's class slot was never returned to classesNeeded, preventing new fishers of that class from joining. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Dumps the fish database, zips it, and uploads to Dropbox via rclone. Skips backup if no new runs since the last backup. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Remove the [ -n "$LATEST_ID" ] guard so an empty ID still matches an empty last-saved ID, preventing daily backups when nothing has changed. Also use print() + tail -1 for reliable mongosh output instead of REPL formatting. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add HTML element creation support to jQuery mock ($('<span>') etc.) so
updateFishers() tests don't crash with invalid CSS selector errors
- Add missing lobby message keys to langs.en mock so renderLobbyTable()
tests render correct status strings
- Widen bot timestamp ranges in ocean.test.js to match the randomized
arrival formula rather than expecting fixed offsets
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
| es['info_payGap'] = 'Pay Gap'; | ||
| fr['info_payGap'] = 'Pay Gap'; | ||
| pt['info_payGap'] = 'Pay Gap'; | ||
| ko['info_payGap'] = 'Pay Gap'; |
There was a problem hiding this comment.
Would it be possible to get translations for these?
| function computeProfitGap(fisher) { | ||
| var otherFishValue = getOtherClassFishValue(fisher); | ||
| var hypotheticalMoney = fisher.totalFishCaught * otherFishValue; | ||
| return (fisher.money - hypotheticalMoney).toFixed(2); |
There was a problem hiding this comment.
If I understand correctly, this formula wouldn't account for fishing costs on the hypothetical side. If I spent $5 departing to sea, casting for fish, etc., that's reflected in the money that I have, but not in hypotheticalMoney.
The way to get around that is to only get the difference of fish values without considering the fishing costs (which are the same for both classes). Something like:
function computeProfitGap(fisher) {
var diff = getEffectiveFishValue(fisher) - getOtherClassFishValue(fisher);
return (fisher.totalFishCaught * diff).toFixed(2);
}
|
Of course. Don’t know how that happened; I got translations for all the other new ones. - - - /-/ a n sDr. Johannes A. ***@***.*** Jul 5, 2026, at 01:01, Jorge Aranda ***@***.***> wrote:
@jorgearanda commented on this pull request.
In public/js/localization.js:
@@ -257,6 +257,134 @@ fr['info_overall'] = 'En tout';
pt['info_overall'] = 'Total';
ko['info_overall'] = '총';
+en['info_payGap'] = 'Pay Gap';
+cn['info_payGap'] = 'Pay Gap';
+ct['info_payGap'] = 'Pay Gap';
+de['info_payGap'] = 'Pay Gap';
+es['info_payGap'] = 'Pay Gap';
+fr['info_payGap'] = 'Pay Gap';
+pt['info_payGap'] = 'Pay Gap';
+ko['info_payGap'] = 'Pay Gap';
Would it be possible to get translations for these?
—Reply to this email directly, view it on GitHub, or unsubscribe.You are receiving this because you authored the thread.Message ID: ***@***.***>
|
|
Good point!Your fix sounds very reasonable. But there is a more general question here. Does an advantaged fisher only benefit wrt fish value, or also wrt fishing costs?Becca and Richardt have not used fishing costs in their experiments so that never came up in the discussions around advantage. I wonder if a percentage P would be more general (value+P%,costs-P%)?Either that or more url parameters. I’ll confer with Becca and Richardt; what’s your thinking about this?- - - /-/ a n sDr. Johannes A. ***@***.*** Jul 5, 2026, at 01:04, Jorge Aranda ***@***.***> wrote:
@jorgearanda commented on this pull request.
In public/js/fish.js:
+// Get the fish value used by the "other class" of fisher (with or without advantage).
+function getOtherClassFishValue(currentFisher) {
+ if (!ocean.fisherAdvantageEnabled) return ocean.fishValue;
+ var currentHasAdvantage = currentFisher.params && currentFisher.params.fHasAdvantage;
+ if (currentHasAdvantage) {
+ return ocean.fishValue;
+ } else {
+ return ocean.fishValue + (ocean.fishValuePayGap || 0);
+ }
+}
+
+// Compute profit gap: actual money minus hypothetical money with other class's fish value
+function computeProfitGap(fisher) {
+ var otherFishValue = getOtherClassFishValue(fisher);
+ var hypotheticalMoney = fisher.totalFishCaught * otherFishValue;
+ return (fisher.money - hypotheticalMoney).toFixed(2);
If I understand correctly, this formula wouldn't account for fishing costs on the hypothetical side. If I spent $5 departing to sea, casting for fish, etc., that's reflected in the money that I have, but not in hypotheticalMoney.
The way to get around that is to only get the difference of fish values without considering the fishing costs (which are the same for both classes). Something like:
function computeProfitGap(fisher) {
var diff = getEffectiveFishValue(fisher) - getOtherClassFishValue(fisher);
return (fisher.totalFishCaught * diff).toFixed(2);
}
—Reply to this email directly, view it on GitHub, or unsubscribe.You are receiving this because you authored the thread.Message ID: ***@***.***>
|
Yes, that would be the key question. As it is set up, they would only benefit from fish values—if fish costs are different, that would also need to be set up in the microworld settings. I'm interested in learning what Rebecca and Richardt have to say. |
|
Becca and Richardt do not need any cost reductions in the system. They have already defined their next experiments, and are publishing based on the definitions, so they don’t care if anything gets added wrt costs, as long as nothing changes wrt fish value pay gaps.
But I agree with your observation that experiment designers with advantaged fishers should have the ability to define costs reductions also, so I’m now in the middle of implementing those, in parallel to the pay gap stuff.
Here’s the final plan:
Updated plan
⎿ /plan to preview
⏺ User approved Claude's plan
⎿ Plan saved to: ~/.claude/plans/lazy-giggling-salamander.md · /plan to edit
Add cost-reduction parameters to Fisher Advantage
Context
The "Fisher Advantage" section in the microworld config page (views/microworld.pug) already lets experimenters give advantaged fishers a higher fish value via "Fish value pay gap" (fishValuePayGap). The user wants three more advantage parameters, mirroring the three
cost fields in the "Economics" section:
- Cost to attempt to fish (costCast)
- Cost to set sail (costDeparture)
- Cost per second at sea (costSecond)
Each gets a paired "cost reduction" value: the amount by which that cost is reduced for advantaged fishers. This follows the same shape as the existing pay-gap field (a checkbox-gated section, a model field, a form input, server-side logic in fisher.js, and
client-side display in fish.js).
Design decision (confirmed with user): a cost reduction may not exceed its corresponding base cost. This is validated on the config form, the same way fishValuePayGap is validated today — it blocks saving with an error message rather than silently clamping at
runtime.
Implementation
1. Data model — src/models/microworld-model.js
Add three Number fields to params, next to fishValuePayGap:
costCastReduction: Number,
costDepartureReduction: Number,
costSecondReduction: Number,
2. Form — views/microworld.pug
In the "Fisher Advantage" section (~line 311-345), add three new .row.fisher-advantage-option.hide blocks right after the existing "Fish value pay gap" row (~line 321-326), following the exact same markup pattern (label + tooltip <a> + .col-md-6 numeric input,
.to-disable class, min='0', step='0.01', value='0.00'):
- #cost-cast-reduction — label "Cost to attempt to fish reduction", tooltip "Reduction in the cost to attempt to fish for advantaged fishers"
- #cost-departure-reduction — label "Cost to set sail reduction", tooltip "Reduction in the cost to set sail for advantaged fishers"
- #cost-second-reduction — label "Cost per second at sea reduction", tooltip "Reduction in the cost per second at sea for advantaged fishers"
3. Client admin JS — public/js/microworld.js
- Tooltip init: add .tooltip() calls for the 3 new tooltip anchors near line 62-64.
- Validation (inside the existing if ($('#enable-fisher-advantage').prop('checked')) block, ~line 361-374, alongside the pay-gap check): for each of the three, validate the reduction is a non-negative number and does not exceed the corresponding base cost
(cost-cast, cost-departure, cost-second), e.g. "The cost to attempt to fish reduction cannot exceed the cost to attempt to fish."
- Save (~line 469-490): add mw.costCastReduction = $('#cost-cast-reduction').val(); and the other two.
- Load (~line 643-647, 669-671): add $('#cost-cast-reduction').val(mw.params.costCastReduction || 0); and the other two.
4. Server engine logic — src/engine/fisher.js
Add a small helper to replace the inline advantage check already duplicated at line 190-191:
this.hasAdvantage = function () {
return this.ocean.microworld.params.fisherAdvantageEnabled &&
this.params && this.params.fHasAdvantage;
};
Use it at the three cost sites — since the form guarantees reduction ≤ base cost, no runtime clamping is needed, just a straight subtraction:
- goToSea (line 179-183): reduce costDeparture by costDepartureReduction when advantaged.
- tryToFish (line 185-201): reduce costCast by costCastReduction when advantaged (also simplify the existing pay-gap check to use hasAdvantage()).
- runBot (line 208-209): reduce costSecond by costSecondReduction when advantaged.
Pattern for each: var cost = baseCost - (this.hasAdvantage() ? (reductionParam || 0) : 0);
5. Client game display — public/js/fish.js
updateCosts() (line 359-393) already computes an advantage-adjusted displayFishValue. Apply the same pattern to the three cost lines: when ocean.fisherAdvantageEnabled && pParams.fHasAdvantage, subtract the corresponding reduction before displaying
costDeparture/costCast/costSecond.
6. Documentation modal — views/explain-fisher-advantage.pug
Add three dt/dd entries under "Parameters" (after the existing "Fish value pay gap" entry) describing the new cost-reduction fields and the "cannot exceed the base cost" validation rule.
7. Tests
- src/engine/fisher.test.js: add cases for goToSea/tryToFish/runBot verifying an advantaged fisher pays the reduced cost, and a non-advantaged fisher (or advantage disabled) pays the full cost.
- public/js/fish.test.js: extend the existing updateCosts() tests (around line 969-1009) with advantage-adjusted cost cases, mirroring the existing fishValuePayGap display tests.
- public/js/microworld.test.js (if it covers form validation) or manual check: confirm saving is blocked when a reduction exceeds its base cost.
No new localization strings are needed — the microworld config page and its labels (like the existing cost labels) are English-only/unlocalized, unlike the participant-facing info_* keys in localization.js.
Verification
- Kill the dev server before testing (per project convention — tests bind port 8080).
- npm test — confirm 0 failures, including new fisher.test.js/fish.test.js cases.
- Manual check: npm run dev, create/edit a microworld with fisher advantage enabled, set base costs and reductions (try one exceeding its base cost to confirm the validation error), join the game as an advantaged fisher (&fhasadvantage) and a regular one, confirm
displayed and actually-deducted costs differ correctly.
Thanks,
- - - /-/ a n s
… On Jul 6, 2026, at 01:18, Jorge Aranda ***@***.***> wrote:
jorgearanda
left a comment
(jorgearanda/fish#263)
<#263 (comment)>
Good point!Your fix sounds very reasonable. But there is a more general question here. Does an advantaged fisher only benefit wrt fish value, or also wrt fishing costs?
Yes, that would be the key question. As it is set up, they would only benefit from fish values—if fish costs are different, that would also need to be set up in the microworld settings. I'm interested in learning what Rebecca and Richardt have to say.
—
Reply to this email directly, view it on GitHub <#263?email_source=notifications&email_token=ABRJ7CKIZMW3NNLT3DWRXO35DMZCHA5CNFSNUABFM5UWIORPF5TWS5BNNB2WEL2JONZXKZKDN5WW2ZLOOQXTIOBYHEZTINJQG42KM4TFMFZW63VGMF2XI2DPOKSWK5TFNZ2KYZTPN52GK4S7MNWGSY3L#issuecomment-4889345074>, or unsubscribe <https://github.com/notifications/unsubscribe-auth/ABRJ7CJMPGJCQG25QGYO4ZL5DMZCHAVCNFSNUABDKJSXA33TNF2G64TZHMZDEMJYHEZTOO2JONZXKZJ3GQ2TSOJTGE4TMMZWUF3AE>.
You are receiving this because you authored the thread.
|
Advantaged fishers can now also pay reduced cast, departure, and per-second-at-sea costs, mirroring the existing fish-value pay gap. Reductions cannot exceed their base cost (validated on the config form).
computeProfitGap() summed seasonData with a plain for-in loop, but seasonData[0] is always null (seasons are 1-indexed). Accessing .actualCasts on that null threw, aborting updateFishers() partway through the render loop - so only the first-processed fisher showed and everyone else (including the "you" row) silently disappeared. Add a totalCasts running total on Fisher, maintained the same way as totalFishCaught/totalDepartures/totalSecondsAtSea, so the client can read it directly instead of re-deriving it from season history. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Three new features, mostly driven by experiments where Fish is invoked from, and returns to, a subject provider such as Gorilla.
(1) The "Class" feature allows subjects to be associated with one of several classes (female/male, progressive/conservative, poor/average/rich, etc). Subjects' class associations are displayed using class-specific emojis. The "Class" feature is implemented using URL params and microworld settings. See the "EXPLAIN" button on the Microworld definition page.
(2) The "Advantage" feature allows experimenters to assign different payments to classes. The "Advantage" feature is implemented using URL params and microworld settings. See the "EXPLAIN" button on the Microworld definition page.
(3) The "Clean Abort" feature uses timeouts to ask the user whether to continue reading the instructions when they first connect to Fish, or continue to wait for additional fishers to join before a Fish simulation can start. The experimenter has full control over the number, the durations and the displayed messages of timeouts, and the URL to where aborting subjects are directed. The "Clean Abort" feature is implemented using microworld settings. See the "EXPLAIN" button in the Microworld definition page.