# Bug Report: Separate Wallets Silently Reset to Shared When Client Clicks "New Farmer"

**Game Version:** Stardew Valley 1.6.15 build 24356  
**SMAPI Version:** 4.5.2  
**Platform:** Linux (Unix 6.1.0.21) -- headless host via modded server framework  
**Game Mode:** Multiplayer (Host + Client via IP/LAN)  
**Severity:** High -- Breaks intended game mode silently  

> **Scope Note:** This has been reproduced consistently in our headless host setup. We have **not** tested whether the same issue occurs in standard listen-server (in-game host) mode.

---

## 1. Summary

When the host creates a co-op farm and selects **"Separate Wallets"**, the setting is silently reverted to **"Shared Wallets"** as soon as a connecting client clicks **"New Farmer"** in the farmhand selection menu. The host receives no warning, and the change persists for the rest of the session.

---

## 2. Steps to Reproduce (Our Environment)

1. **Host:** Launch headless host via modded server framework.
2. **Host:** Create a new co-op world and select **"Separate Wallets"**.
3. **Client:** Connect via IP.
4. **Client:** In the farmhand selection menu, click **"New Farmer"**.
5. **Host:** Wallet mode has been reset to **Shared**.

---

## 3. Root Cause Analysis

### 3.1 The Proxy Property

`Farmer.useSeparateWallets` is a proxy property that reads/writes `FarmerTeam.useSeparateWallets` (a `NetBool`):

**File:** `StardewValley/Farmer.cs`

```csharp
[XmlElement("useSeparateWallets")]
public bool useSeparateWallets
{
    get => this.teamRoot.Value.useSeparateWallets.Value;
    set => this.teamRoot.Value.useSeparateWallets.Value = value;
}
```

### 3.2 The Team-Level Field

The actual synchronized field is `FarmerTeam.useSeparateWallets`, declared as a `NetBool`:

**File:** `StardewValley/FarmerTeam.cs`

```csharp
public readonly NetBool useSeparateWallets = new NetBool();
```

It is registered in `NetFields` for network synchronization:

```csharp
this.NetFields.SetOwner(this).AddField(...)
    .AddField(this.useSeparateWallets, "useSeparateWallets")
```

### 3.3 The Critical Bug: Client-Side Mutation of Host's Team State

When a client clicks **"New Farmer"**, the following chain executes:

**File:** `StardewValley/Menus/FarmhandMenu.cs`

```csharp
public void Activate()
{
    Game1.game1.loadForNewGame(false);
    Game1.player = this.Farmer;
    this.menu.client.sendPlayerIntroduction();
}
```

`loadForNewGame(false)` triggers `CharacterCustomization` constructor:

**File:** `StardewValley/Menus/CharacterCustomization.cs`

```csharp
public CharacterCustomization(LocalizedContentManager content = null)
{
    ...
    Game1.player.team.useSeparateWallets.Value = false;
    ...
}
```

At this moment:
- `Game1.player` is the **newly created farmhand** on the client side.
- `farmhand.teamRoot` still points to the **host's `FarmerTeam` instance** (by reference, assigned during `Multiplayer.addPlayer`).

Therefore, the client is **directly mutating the host's team state on its own machine**.

### 3.4 Network Delta Sync Corrupts the Host

Because `useSeparateWallets` is a `NetBool`, the mutation marks it `Dirty`. The client sends a delta sync to the host.

**File:** `Netcode/NetBool.cs`

```csharp
protected override void ReadDelta(BinaryReader reader, NetVersion version)
{
    bool newValue = reader.ReadBoolean();
    if (version.IsPriorityOver(this.ChangeVersion))
    {
        base.setInterpolationTarget(newValue);
    }
}
```

**File:** `Netcode/NetFieldBase.cs`

```csharp
protected void setInterpolationTarget(T newValue)
{
    T oldValue = this.value;
    if (!this.InterpolationWait || base.Root == null || !this.setUpInterpolation(oldValue, newValue))
    {
        this.cleanSet(newValue);  // <-- Direct field assignment, no setter
        return;
    }
    ...
}
```

**File:** `Netcode/NetFieldBase.cs`

```csharp
protected void cleanSet(T newValue)
{
    T oldValue = this.value;
    this.targetValue = newValue;
    this.value = newValue;        // <-- Direct field write
    this.previousValue = default(T);
    base.NeedsTick = false;
    ...
}
```

The host receives the delta, `setInterpolationTarget(false)` is called, and `cleanSet(false)` permanently overwrites the host's `useSeparateWallets` to `false`.

### 3.5 Why This Is a Design Bug

`useSeparateWallets` is a **team-level** setting, not a **player-level** setting. Yet it is exposed as a writable property on every `Farmer` instance via `[XmlElement]`. This creates two independent corruption vectors:

1. **XML Deserialization:** Farmhand save files contain `<useSeparateWallets>false</useSeparateWallets>`. When the host loads the save, the setter writes this `false` into the host's `FarmerTeam`.

2. **Network Delta Sync:** Client-side initialization of a new farmhand mutates the shared `FarmerTeam` reference, and the dirty `NetBool` propagates the `false` value back to the host.

---

## 4. Call Stack at Time of Corruption

### Vector 1: XML Deserialization (Save Load)

```
StardewValley.Farmer.set_useSeparateWallets
Microsoft.Xml.Serialization.GeneratedAssembly.XmlSerializationReaderSaveGame.Read171_Farmer
Microsoft.Xml.Serialization.GeneratedAssembly.XmlSerializationReaderSaveGame.Read287_SaveGame
System.Xml.Serialization.XmlSerializer.Deserialize
StardewValley.SaveSerialization.SaveSerializer.Deserialize
StardewValley.SaveGame.TryReadSaveFile
```

### Vector 2: Network Sync (Client Join)

```
Netcode.NetBool.ReadDelta
Netcode.NetFieldBase.ReadFull
Netcode.NetRefBase.deserialize
Netcode.NetRefBase.ReadFull
StardewValley.Multiplayer.readFarmer
StardewValley.Multiplayer.playerIntroduction
```

### Vector 3: NetRoot.Clone (Player Quit / Save)

```
StardewValley.Farmer.set_useSeparateWallets
Microsoft.Xml.Serialization.GeneratedAssembly.XmlSerializationReaderFarmer.Read172_Farmer
Netcode.NetRefBase.deserialize
Netcode.NetRefBase.ReadFull
Netcode.NetRoot.Clone
```

---

## 5. Suggested Fix

### Option A: Guard the Setter (Minimal Change)

Prevent non-host players from writing to the team setting:

**File:** `StardewValley/Farmer.cs`

```csharp
[XmlElement("useSeparateWallets")]
public bool useSeparateWallets
{
    get => this.teamRoot.Value.useSeparateWallets.Value;
    set
    {
        if (Game1.IsMultiplayer && !this.IsMainPlayer)
            return; // Farmhands cannot override host team setting
        this.teamRoot.Value.useSeparateWallets.Value = value;
    }
}
```

### Option B: Remove from Farmer Serialization (Cleaner Design)

Since `useSeparateWallets` is a team-level setting, it should only be serialized on `FarmerTeam`, not on individual `Farmer` instances:

**File:** `StardewValley/Farmer.cs`

```csharp
// Remove [XmlElement] and make it a read-only computed property
public bool useSeparateWallets => this.teamRoot.Value.useSeparateWallets.Value;
```

**File:** `StardewValley/FarmerTeam.cs`

```csharp
[XmlElement("useSeparateWallets")]
public readonly NetBool useSeparateWallets = new NetBool();
```

### Option C: Fix Client-Side Initialization (Most Targeted)

Prevent `CharacterCustomization` from mutating the host's team state. During `loadForNewGame(false)` on a client, `Game1.player.team` should be a temporary local `FarmerTeam`, not the host's shared instance:

**File:** `StardewValley/Menus/FarmhandMenu.cs`

```csharp
public void Activate()
{
    // Ensure farmhand uses a temporary team during local initialization
    var originalTeam = this.Farmer.teamRoot;
    this.Farmer.teamRoot = new NetRoot<FarmerTeam>(new FarmerTeam());
    
    Game1.game1.loadForNewGame(false);
    
    // Restore host team reference before sending introduction
    this.Farmer.teamRoot = originalTeam;
    Game1.player = this.Farmer;
    this.menu.client.sendPlayerIntroduction();
}
```

---

## 6. Patch (Temporary Workaround)

A temporary Harmony-based patch intercepts `NetFieldBase.setInterpolationTarget` on the host side, blocking any network delta that attempts to flip `useSeparateWallets` from `true` to `false`:

```csharp
using HarmonyLib;
using Netcode;
using StardewModdingAPI;
using StardewValley;
namespace SeparateWalletFix;

public class ModEntry : Mod
{
public static IMonitor? StaticMonitor;
    public override void Entry(IModHelper helper)
    {
        StaticMonitor = Monitor;
        var harmony = new Harmony(ModManifest.UniqueID);

        // Core Defense: NetFieldBase.setInterpolationTarget
        var setInterp = AccessTools.Method(typeof(NetFieldBase<bool, NetBool>), "setInterpolationTarget", new[] { typeof(bool) });
        if (setInterp != null)
            harmony.Patch(setInterp, prefix: new HarmonyMethod(typeof(Patcher), nameof(Patcher.SetInterpolationTargetPrefix)));
    }
}

public static class Patcher
{
    public static bool SetInterpolationTargetPrefix(NetFieldBase<bool, NetBool> __instance, bool newValue)
    {
        if (!Game1.IsMasterGame) return true;
        if (!ReferenceEquals(__instance, Game1.player?.team?.useSeparateWallets)) return true;
        if (__instance.Value && !newValue)
        {
            ModEntry.StaticMonitor?.Log(
                "[SWF] Defense triggered: useSeparateWallets attempted to change from true to false. Sync blocked.",
                LogLevel.Trace);
            return false;
        }
        return true;
    }
}
```

This single interception point is sufficient because:
- **Network sync** (`ReadDelta`) always routes through `setInterpolationTarget`.
- **Local host operations** (e.g., visiting Lewis to toggle wallets) bypass `setInterpolationTarget` entirely -- they call `NetBool.Set()` directly, which is unaffected by this patch.

> **GitHub (AutoServerPro):** [https://github.com/LinHan0lhd/StardewValleyMods/tree/main/AutoServerPro]  
> **GitHub (SeparateWalletFix):** [https://github.com/LinHan0lhd/StardewValleyMods/tree/main/SeparateWalletFix]

---
**Thank you for your time. A fix would be greatly appreciated by the multiplayer community.**