# 1914 Offensive a Outrance

**URL:** <https://forum.vassalengine.org/t/1914-offensive-a-outrance/86530>\
**Category:** Technical Support & Bugs\
**Created:** [August 27, 2025, 1:34pm UTC](https://forum.vassalengine.org/t/1914-offensive-a-outrance/86530 "2025-08-27T13:34:16Z")\
**Posts on this page:** 20\
**Page:** 1

<div class="post-metadata">

**Author:** ![Toadkillerdog](https://forum.vassalengine.org/user_avatar/forum.vassalengine.org/toadkillerdog/32/2722_2.png) [@Toadkillerdog](https://forum.vassalengine.org/u/Toadkillerdog)\
**Post date:** [August 27, 2025, 1:34pm UTC](https://forum.vassalengine.org/t/1914-offensive-a-outrance/86530/1 "2025-08-27T13:34:16Z")

</div>

Anyone know how to remove the offending custom code? I tried but failed.

> **[1914: Offensive à outrance - Module Library - Vassal](https://vassalengine.org/library/projects/1914_Offensive_%C3%A0_outrance)**
>
> Vassal is an open-source game engine for building and playing online adaptations of board games and card games.

---

<div class="post-metadata">

**Author:** ![uckelman](https://forum.vassalengine.org/user_avatar/forum.vassalengine.org/uckelman/32/10020_2.png) [@uckelman](https://forum.vassalengine.org/u/uckelman)\
**Post date:** [August 27, 2025, 1:46pm UTC](https://forum.vassalengine.org/t/1914-offensive-a-outrance/86530/2 "2025-08-27T13:46:36Z")

</div>

The error dialog should tell you what classes are the problem. What does it say?

---

<div class="post-metadata">

**Author:** ![Toadkillerdog](https://forum.vassalengine.org/user_avatar/forum.vassalengine.org/toadkillerdog/32/2722_2.png) [@Toadkillerdog](https://forum.vassalengine.org/u/Toadkillerdog)\
**Post date:** [August 27, 2025, 1:59pm UTC](https://forum.vassalengine.org/t/1914-offensive-a-outrance/86530/3 "2025-08-27T13:59:59Z")

</div>

It did, but I have no idea how to remove them. I tried editing the build file, but I must’ve messed something up as no images will tile.

Removed classes, methods, and fields in C:\personal\games\Offensive\_a\_outrance\_v4.vmod  
(used by =\> removed item, version when removed

net.cantab.hayward.george.OCS.MapOverride =\> VASSAL.build.module.PlayerRoster.addSideChangeListener(VASSAL.build.module.PlayerRoster$SideChangeListener), 3.6.0

---

<div class="post-metadata">

**Author:** ![uckelman](https://forum.vassalengine.org/user_avatar/forum.vassalengine.org/uckelman/32/10020_2.png) [@uckelman](https://forum.vassalengine.org/u/uckelman)\
**Post date:** [August 27, 2025, 2:02pm UTC](https://forum.vassalengine.org/t/1914-offensive-a-outrance/86530/4 "2025-08-27T14:02:11Z")

</div>

Have you contacted the owner of the project about this problem?

---

<div class="post-metadata">

**Author:** ![Toadkillerdog](https://forum.vassalengine.org/user_avatar/forum.vassalengine.org/toadkillerdog/32/2722_2.png) [@Toadkillerdog](https://forum.vassalengine.org/u/Toadkillerdog)\
**Post date:** [August 27, 2025, 2:02pm UTC](https://forum.vassalengine.org/t/1914-offensive-a-outrance/86530/5 "2025-08-27T14:02:39Z")

</div>

Not yet. But it was over 7 years ago.

---

<div class="post-metadata">

**Author:** ![uckelman](https://forum.vassalengine.org/user_avatar/forum.vassalengine.org/uckelman/32/10020_2.png) [@uckelman](https://forum.vassalengine.org/u/uckelman)\
**Post date:** [August 27, 2025, 2:03pm UTC](https://forum.vassalengine.org/t/1914-offensive-a-outrance/86530/6 "2025-08-27T14:03:24Z")

</div>

Maybe try that. If the project’s owner fixes the problem, then it will be fixed for everyone.

---

<div class="post-metadata">

**Author:** ![Toadkillerdog](https://forum.vassalengine.org/user_avatar/forum.vassalengine.org/toadkillerdog/32/2722_2.png) [@Toadkillerdog](https://forum.vassalengine.org/u/Toadkillerdog)\
**Post date:** [August 27, 2025, 2:04pm UTC](https://forum.vassalengine.org/t/1914-offensive-a-outrance/86530/7 "2025-08-27T14:04:19Z")

</div>

Information is hidden, so it doesn’t appear that I can.

---

<div class="post-metadata">

**Author:** ![uckelman](https://forum.vassalengine.org/user_avatar/forum.vassalengine.org/uckelman/32/10020_2.png) [@uckelman](https://forum.vassalengine.org/u/uckelman)\
**Post date:** [August 27, 2025, 2:04pm UTC](https://forum.vassalengine.org/t/1914-offensive-a-outrance/86530/8 "2025-08-27T14:04:40Z")

</div>

Other threads about the same module are here:

- [Getting an error message for '1914 Offensive a' outrance'](https://forum.vassalengine.org/t/getting-an-error-message-for-1914-offensive-a-outrance/78563)
- [Defunct Custom Code - Offensive a Outrance](https://forum.vassalengine.org/t/defunct-custom-code-offensive-a-outrance/76898)

---

<div class="post-metadata">

**Author:** ![uckelman](https://forum.vassalengine.org/user_avatar/forum.vassalengine.org/uckelman/32/10020_2.png) [@uckelman](https://forum.vassalengine.org/u/uckelman)\
**Post date:** [August 27, 2025, 2:05pm UTC](https://forum.vassalengine.org/t/1914-offensive-a-outrance/86530/9 "2025-08-27T14:05:20Z")

</div>

> [@Toadkillerdog](#):
>
> Information is hidden, so it doesn’t appear that I can.

Would you be more specific? Where are you seeing this?

---

<div class="post-metadata">

**Author:** ![JoelCFC25](https://forum.vassalengine.org/user_avatar/forum.vassalengine.org/joelcfc25/32/2687_2.png) [@JoelCFC25](https://forum.vassalengine.org/u/JoelCFC25)\
**Post date:** [August 27, 2025, 2:05pm UTC](https://forum.vassalengine.org/t/1914-offensive-a-outrance/86530/10 "2025-08-27T14:05:53Z")

</div>

This approach isn’t going to bear fruit. The user pdietrich–by his own admission at one point–modified an OCS module including all of George Hayward’s code without any underlying knowledge of the code itself or how to modify/support it going forward.

I’m guessing there’s a way to programmatically find all the references in a buildfile and strip them out, but at least viewed in the Editor you can see evidence of custom code like these circled items (there are likely many, many more). Anything you can double click and not see any properties or config. The map windows themselves seem to be custom as well, their configs don’t look like what’s standard.

 ![image](https://forum.vassalengine.org/uploads/default/original/2X/4/4890cedbca5d41bed5d031ba8d7b7f3a7118fa16.png)

---

<div class="post-metadata">

**Author:** ![uckelman](https://forum.vassalengine.org/user_avatar/forum.vassalengine.org/uckelman/32/10020_2.png) [@uckelman](https://forum.vassalengine.org/u/uckelman)\
**Post date:** [August 27, 2025, 2:09pm UTC](https://forum.vassalengine.org/t/1914-offensive-a-outrance/86530/11 "2025-08-27T14:09:06Z")

</div>

Someone needs to rebuild this module if it’s ever going to work with the current version of Vassal…

---

<div class="post-metadata">

**Author:** ![JoelCFC25](https://forum.vassalengine.org/user_avatar/forum.vassalengine.org/joelcfc25/32/2687_2.png) [@JoelCFC25](https://forum.vassalengine.org/u/JoelCFC25)\
**Post date:** [August 27, 2025, 2:23pm UTC](https://forum.vassalengine.org/t/1914-offensive-a-outrance/86530/12 "2025-08-27T14:23:23Z")

</div>

Some CSW conversations that included comments from pdietrich:

[June 2022](https://talk.consimworld.com/WebX?14@@.1dd42715/3631)  
[December 2022](https://talk.consimworld.com/WebX?14@@.1dd42715/3697)

---

<div class="post-metadata">

**Author:** ![cholmcc](https://forum.vassalengine.org/user_avatar/forum.vassalengine.org/cholmcc/32/8319_2.png) [@cholmcc](https://forum.vassalengine.org/u/cholmcc)\
**Post date:** [August 27, 2025, 10:21pm UTC](https://forum.vassalengine.org/t/1914-offensive-a-outrance/86530/13 "2025-08-27T22:21:55Z")

</div>

> [@JoelCFC25](#):
>
> This approach isn’t going to bear fruit.

Actually, all hope is not lost.

First off, if you unpack the module, you will see that the Java sources custom modules is in the sub-directory `source` (kudo’s to @pdietrich2 for providing the sources). You can ignore the sub-directory `source/com` as that code isn’t used in the module (in fact, you should probably remove that sub-directory).

Secondly, the file `source/net/cantab/hayward/george/OCS/edits.required` shows you the translation from standard Vassal elements to the custom elements. The file contains [`sed`](https://www.gnu.org/software/sed/manual/sed.html) regular expression substitutions, and the script `source/net/cantab/hayward/george/OCS/exec.edits` shows you have these are executed. If you reverse these regular expressions - e.g.,

```auto
s/VASSAL.build.module.BasicCommandEncoder/net.cantab.hayward.george.OCS.BasicCommandEncoderOverride/g

```

to

```auto
s/net.cantab.hayward.george.OCS.BasicCommandEncoderOverride/VASSAL.build.module.BasicCommandEncoder/g

```

and then execute `source/net/cantab/hayward/george/OCS/exec.edits` then that will revert the module to the standard Vassal elements.

Of course, that will mean a loss of functionality in the module.

So, a better way is to fix the custom code and recompile it (see also [Simple Workflow](https://forum.vassalengine.org/t/creating-custom-classes/84963#p-168041-a-namesimple-workflowa-simple-workflow-3)). Assuming you have installed VASSAL in `/opt/vassal/current` (on Linux), do

```auto
cd source
javac -cp /opt/vassal/current/lib/Vengine.jar --release 11 `find net -name "*,java"` 

```

and when that compiles cleanly, copy the generated `.class` files (with directory structure) to the root of the unpacked archive

```auto
tar -cf - `find . -name "*.class"` | tar -xf - -C ..

```

Of course, any errors in the custom code need to be fixed first:

```auto
net/cantab/hayward/george/OCS/RegionOverride.java:255: error: cannot find symbol
    Labeler.drawLabel(g, getLocalizedConfigureName(), posX, posY + labelOffset, f, Labeler.CENTER,
                                                                                          ^
  symbol: variable CENTER
  location: class Labeler
net/cantab/hayward/george/OCS/RegionOverride.java:256: error: cannot find symbol
                      Labeler.TOP, fg, bg, fg);
                             ^
  symbol: variable TOP
  location: class Labeler
net/cantab/hayward/george/OCS/RegionOverride.java:255: warning: [removal] drawLabel(Graphics,String,int,int,Font,int,int,Color,Color,Color) in Labeler has been deprecated and marked for removal
    Labeler.drawLabel(g, getLocalizedConfigureName(), posX, posY + labelOffset, f, Labeler.CENTER,
           ^
net/cantab/hayward/george/OCS/MapOverride.java:176: warning: [removal] mapCoordinates(Point) in Map has been deprecated and marked for removal
                    Point p = mapCoordinates(activeViewport.getRealViewPosition());
                              ^
net/cantab/hayward/george/OCS/MapOverride.java:192: warning: [removal] mapCoordinates(Point) in Map has been deprecated and marked for removal
                    Point p = mapCoordinates(activeViewport.getRealViewPosition());
                              ^
net/cantab/hayward/george/OCS/MapOverride.java:293: warning: [removal] mapCoordinates(Point) in Map has been deprecated and marked for removal
            Point p = mapCoordinates(e.getPoint());
                      ^
net/cantab/hayward/george/OCS/MapOverride.java:363: warning: [removal] componentCoordinates(Point) in Map has been deprecated and marked for removal
            p = componentCoordinates(p);
                ^
net/cantab/hayward/george/OCS/MapOverride.java:992: warning: [removal] launchButton in Map has been deprecated and marked for removal
        GameModule.getGameModule().getToolBar().add(launchButton);
                                                    ^
net/cantab/hayward/george/OCS/MapOverride.java:995: error: cannot find symbol
                    new IntConfigurer(MAIN_WINDOW_HEIGHT, null, new Integer(-1));
                                      ^
  symbol: variable MAIN_WINDOW_HEIGHT
  location: class MapOverride
net/cantab/hayward/george/OCS/MapOverride.java:997: warning: [removal] ComponentSplitter in VASSAL.tools has been deprecated and marked for removal
            final ComponentSplitter splitter = new ComponentSplitter();
                  ^
net/cantab/hayward/george/OCS/MapOverride.java:997: warning: [removal] ComponentSplitter in VASSAL.tools has been deprecated and marked for removal
            final ComponentSplitter splitter = new ComponentSplitter();
                                                   ^
net/cantab/hayward/george/OCS/MapOverride.java:1006: warning: [removal] mainWindowDock in Map has been deprecated and marked for removal
            mainWindowDock = splitter.splitBottom(
            ^
net/cantab/hayward/george/OCS/MapOverride.java:1032: error: cannot find symbol
        PlayerRoster.addSideChangeListener(this);
                    ^
  symbol: method addSideChangeListener(MapOverride)
  location: class PlayerRoster
net/cantab/hayward/george/OCS/Parsing/ScenarioBuilder.java:53: warning: [removal] isMacOSX() in Info has been deprecated and marked for removal
        return Info.isMacOSX() ? new MacOSXMenuManager() : new MyMenuManager();
                   ^
net/cantab/hayward/george/OCS/Parsing/ScenarioBuilder.java:59: error: no suitable constructor found for ArchiveWriter(ZipFile)
        final ArchiveWriter archive = new ArchiveWriter(new ZipFile(
                                      ^
    constructor ArchiveWriter.ArchiveWriter(String) is not applicable
      (argument mismatch; ZipFile cannot be converted to String)
    constructor ArchiveWriter.ArchiveWriter(FileArchive) is not applicable
      (argument mismatch; ZipFile cannot be converted to FileArchive)
net/cantab/hayward/george/OCS/Parsing/ScenarioBuilder.java:61: warning: [removal] BasicModule in VASSAL.launch has been deprecated and marked for removal
        GameModule.init(new BasicModule(archive));
                            ^
net/cantab/hayward/george/OCS/Parsing/ScenarioBuilder.java:61: warning: [removal] BasicModule(DataArchive) in BasicModule has been deprecated and marked for removal
        GameModule.init(new BasicModule(archive));
                        ^
net/cantab/hayward/george/OCS/DragHandlerReplace.java:273: warning: [removal] makeDragCursor(double) in DragHandlerNoImage has been deprecated and marked for removal
        protected void makeDragCursor(double zoom) {
                       ^
net/cantab/hayward/george/OCS/DragHandlerReplace.java:274: warning: [removal] makeDragCursor(double) in DragHandlerNoImage has been deprecated and marked for removal
            super.makeDragCursor(zoom);
                 ^
net/cantab/hayward/george/OCS/DragHandlerReplace.java:308: warning: [removal] mapCoordinates(Point) in Map has been deprecated and marked for removal
                    updateRange(baseMap.mapCoordinates(p));
                                       ^
net/cantab/hayward/george/OCS/HexGridNumberingOverride.java:155: error: cannot find symbol
    int alignment = Labeler.TOP;
                           ^
  symbol: variable TOP
  location: class Labeler
net/cantab/hayward/george/OCS/HexGridNumberingOverride.java:158: error: cannot find symbol
      alignment = Labeler.CENTER;
                         ^
  symbol: variable CENTER
  location: class Labeler
net/cantab/hayward/george/OCS/HexGridNumberingOverride.java:189: error: cannot find symbol
                          Labeler.CENTER,
                                 ^
  symbol: variable CENTER
  location: class Labeler
net/cantab/hayward/george/OCS/HexGridNumberingOverride.java:185: warning: [removal] drawLabel(Graphics,String,int,int,Font,int,int,Color,Color,Color) in Labeler has been deprecated and marked for removal
        Labeler.drawLabel(g2d, getName(getRow(gridp), getColumn(gridp)),
               ^
net/cantab/hayward/george/OCS/HexGridNumberingOverride.java:207: error: cannot find symbol
                          Labeler.CENTER,
                                 ^
  symbol: variable CENTER
  location: class Labeler
net/cantab/hayward/george/OCS/HexGridNumberingOverride.java:203: warning: [removal] drawLabel(Graphics,String,int,int,Font,int,int,Color,Color,Color) in Labeler has been deprecated and marked for removal
        Labeler.drawLabel(g2d, getName(getRow(gridp), getColumn(gridp)),
               ^
net/cantab/hayward/george/OCS/HexGridNumberingOverride.java:331: error: getRawRow(Point) in HexGridNumberingOverride cannot override getRawRow(Point) in HexGridNumbering
  protected int getRawRow(Point p) {
                ^
  attempting to assign weaker access privileges; was public
net/cantab/hayward/george/OCS/HexGridNumberingOverride.java:382: warning: [removal] Logger in VASSAL.tools.logging has been deprecated and marked for removal
              Logger.log(e1);
              ^
Note: Some input files use or override a deprecated API.
Note: Recompile with -Xlint:deprecation for details.
Note: Some messages have been simplified; recompile with -Xdiags:verbose to get full output
10 errors
18 warnings

```

Most of these seem relatively simple to fix, e.g., `Labeler.CENTER` is now `'c'`, and `PlayerRoster.addSideChangeListener` seems now to be `PlayerRoster.getInstance().addSideChangeListenerToInstance`. Other issues may take a bit more thought. Note, warnings can be ignored for now, although deprecation warnings will be needed to be addressed for the module to work with future Vassal releases.

Yours,  
_Christian_

---

<div class="post-metadata">

**Author:** ![baborzeterrible](https://forum.vassalengine.org/letter_avatar_proxy/v4/letter/b/85f322/32.png) [@baborzeterrible](https://forum.vassalengine.org/u/baborzeterrible)\
**Post date:** [October 25, 2026, 12:13pm UTC](https://forum.vassalengine.org/t/1914-offensive-a-outrance/86530/14 "2026-10-25T12:13:37Z")

</div>

Hello guys,

I recently acquired a physical copy of OaO and thus encountered the broken vassal module issue.

I claim no skills in java so I brute forced the module bug with a llm (Opus 5.5, Anthropic).

I then also got it to add the scenarios (race to the sea and battle for lorraine).

Where can I post it? is there any restriction policy in using llm for vassal modules?

---

<div class="post-metadata">

**Author:** ![baborzeterrible](https://forum.vassalengine.org/letter_avatar_proxy/v4/letter/b/85f322/32.png) [@baborzeterrible](https://forum.vassalengine.org/u/baborzeterrible)\
**Post date:** [October 25, 2026, 5:24pm UTC](https://forum.vassalengine.org/t/1914-offensive-a-outrance/86530/15 "2026-10-25T17:24:56Z")

</div>

Some more details from my previous post.

## Concerning the bug fixing, here is the pb analysis + fix report from the LLM (I did not understand a single part tbh):

### **Root cause**

The module’s legacy-piece converter re-decodes every palette piece, and VASSAL 3.7 now sends palette pieces through that same converter, so each conversion starts another one without end.

 ![recursion-loop-diagram](https://forum.vassalengine.org/uploads/default/original/2X/7/7534f7e274ba5be4daa8ab7cb5ab9009d24358fa.png)

• The converter. _BasicCommandEncoderOverride.decode()_ handles every “add piece” command. If the decoded piece’s outermost trait is not an OCS counter, it calls _Statics.convertOldPiece()_ to upgrade a pre-3.0 counter. To find a match, f_indMatching()_ calls _buildPiecePtrs()_, which decodes every Piece Palette slot.  
• What changed in VASSAL. VASSAL 3.2 built palette pieces without going through the module’s command encoder. VASSAL 3.7 decodes palette slots (_PieceSlot.getPiece_) and prototype definitions (_PrototypeDefinition.getPiece_) through _GameModule.decode()_, which calls the module’s encoder.  
• The trigger. Three palette slots have a non-OCS trait as their outermost trait: GE\_10\_ED and FR\_Chasseur\_bde (a marker layer on top) and the Note marker (a plain label). Decoding one of them calls the converter, which rebuilds the palette list, which decodes those slots again. The nested call finds the list half-built, matches nothing, clears it (_thePieces = null_) and rebuilds it, so the cache never stops the recursion.  
• Why the 4.0.1 update missed it. The 4.0.1 changes made the code compile against VASSAL 3.7 but did not change this logic; the failure only appears at runtime.

### **The fix**

The converter now runs only for genuine old pieces from a saved game, and can never call itself again; 4.0.2 ships this change in the module’s own code.  
In _BasicCommandEncoderOverride.decode()_ the legacy conversion is attempted only when all of these hold:  
• the piece has a real id (palette slots and prototype definitions are decoded with id null);  
• no trait of the piece is an OCS counter, and its innermost piece is a _BasicPiece_ with artwork (plain markers such as Note are skipped);  
• no other conversion is already running (a re-entrancy counter);  
• if the conversion throws, the piece is used unchanged instead of crashing.

## Concerning the new stuff, some screenshots :

- the scenario selection

 ![Screenshot 2026-10-25 180459](https://forum.vassalengine.org/uploads/default/original/2X/a/a16c1a4ac832a8add71aaef1f8901af49c85e1d7.png)

- the battle for lorraine setup, including the special location enabling victory for each side :

- The race to the sea, including the public note reminding special rules, victory conditions and so on, as well as the turn track where now every reinforcements are located

 ![Screenshot 2026-10-25 180917](https://forum.vassalengine.org/uploads/default/original/2X/2/2e3ed7ec0fc4596d4c7f792eaffe6a0c79b4c3dc.jpeg)

---

<div class="post-metadata">

**Author:** ![uckelman](https://forum.vassalengine.org/user_avatar/forum.vassalengine.org/uckelman/32/10020_2.png) [@uckelman](https://forum.vassalengine.org/u/uckelman)\
**Post date:** [October 25, 2026, 8:51pm UTC](https://forum.vassalengine.org/t/1914-offensive-a-outrance/86530/16 "2026-10-25T20:51:17Z")

</div>

I would be extremely leery of any changes made to this code without a human understanding them. There’s no way to be sure they’re correct without that. I would not want to sink my time into a game of this length and complexity without some assurance that the module code does the right thing and won’t corrupt my ongoing game.

---

<div class="post-metadata">

**Author:** ![cholmcc](https://forum.vassalengine.org/user_avatar/forum.vassalengine.org/cholmcc/32/8319_2.png) [@cholmcc](https://forum.vassalengine.org/u/cholmcc)\
**Post date:** [October 25, 2026, 11:22pm UTC](https://forum.vassalengine.org/t/1914-offensive-a-outrance/86530/17 "2026-10-25T23:22:18Z")

</div>

> [@baborzeterrible](#):
>
> Where can I post it? is there any restriction policy in using llm for vassal modules?

It is @pdietrich2 that owns the [1914: Offensive à outrance](https://vassalengine.org/library/projects/1914_Offensive_%C3%A0_outrance) project, so only @pdietrich2 will be able to upload or grant permission for others to upload.

As @JoelCFC25 pointed out above, @pdietrich2 is no expert in the code.

> The 4.0.1-broken upload was done by me so that others may try to pick it up and fix the issue.

From your “PB(?) analysis + fix report”, I gather that the main problem is in the user class `BasicCommandEncoderOverride`, and, what it does that make the module crash Vassal, is that it creates an infinite recursion.

To better understand it, perhaps you can post the user code pre and post LLM “fix”.

> [@uckelman](#):
>
> I would not want to sink my time into a game of this length and complexity without some assurance that the module code does the right thing and won’t corrupt my ongoing game.

I think the point about an ongoing game is likely moot.

The module has not worked for some time, and an update to the module will likely be incompatible with _any_ previous saves or logs.

From the above quoted report, it sounds like the problem is really only there when the module tries to read old saves or logs, and that the custom `BasicCommandEncoderOverride` is only there to facilitate updates from older saves or logs.

Perhaps a fix is to remove this custom `BasicCommandEncoderOverride` from the module. Of course, any `.vsav` or `.vlog` in the module would need to be removed. Set-ups would then need to be recreated.

> [@uckelman](#):
>
> I would be extremely leery of any changes made to this code without a human understanding them.

I share that sentiment, but not so much with respect to existing games, but rather because there’s really no way to make sure such changes do what they are supposed to do.

Yours,  
_Christian_

---

<div class="post-metadata">

**Author:** ![baborzeterrible](https://forum.vassalengine.org/letter_avatar_proxy/v4/letter/b/85f322/32.png) [@baborzeterrible](https://forum.vassalengine.org/u/baborzeterrible)\
**Post date:** [October 26, 2026, 7:48am UTC](https://forum.vassalengine.org/t/1914-offensive-a-outrance/86530/18 "2026-10-26T07:48:35Z")

</div>

> [@cholmcc](#):
>
> To better understand it, perhaps you can post the user code pre and post LLM “fix”.

I asked for the minimum amount of change to not waste any of your time, it may apparently work by replacing a line.

In _src/net/cantab/hayward/george/OCS/BasicCommandEncoderOverride.java_

Replacing the line 286 :

`if ( !(p instanceof OcsCounter) && !(p instanceof StackOverride)) {`

By

`if (id != null && !(p instanceof OcsCounter) && !(p instanceof StackOverride)) {`

“It breaks the loop because VASSAL 3.7 decodes palette slots and prototype definitions with the id null, while pieces from a saved game or log always have a real id. With the check, palette pieces never reach convertOldPiece(), so the palette rescan inside the conversion can’t start another conversion. Genuine old counters in saved games are still converted as before”

The llm for my 4.0.2 version added three safeguards:

- counter stop a conversion from ever starting another one;
- if a conversion throws, the piece is used unchanged instead of crashing the load;
- plain markers with no artwork, such as Note, are skipped, so they stop printing “Not an OCS Counter” lines in the chat),

plus other fixes like the zoom that resets the camera location, but we can start from the above I suppose.

---

<div class="post-metadata">

**Author:** ![cholmcc](https://forum.vassalengine.org/user_avatar/forum.vassalengine.org/cholmcc/32/8319_2.png) [@cholmcc](https://forum.vassalengine.org/u/cholmcc)\
**Post date:** [October 26, 2026, 4:34pm UTC](https://forum.vassalengine.org/t/1914-offensive-a-outrance/86530/19 "2026-10-26T16:34:34Z")

</div>

> [@baborzeterrible](#):
>
> I asked for the minimum amount of change to not waste any of your time, it may apparently work by replacing a line.

Actually, if you can give the source files _before_ and _after_ the LLM fix _in full_, it is easy enough to get differences by using appropriate tools (`diff -u`).

That would be more informative and help evaluate the code.

Yours,  
_Christian_

---

<div class="post-metadata">

**Author:** ![cholmcc](https://forum.vassalengine.org/user_avatar/forum.vassalengine.org/cholmcc/32/8319_2.png) [@cholmcc](https://forum.vassalengine.org/u/cholmcc)\
**Post date:** [October 26, 2026, 9:16pm UTC](https://forum.vassalengine.org/t/1914-offensive-a-outrance/86530/20 "2026-10-26T21:16:50Z")

</div>

The custom code used in the module is [`github:uckelman/tite-ocs`](https://github.com/uckelman/tite-ocs.git), of which only the `src/net` sub-directory is used.

The changes made by the LLM that makes the module “work” are

```patch
diff --git a/src/net/cantab/hayward/george/OCS/BasicCommandEncoderOverride.java b/src/net/cantab/hayward/george/OCS/BasicCommandEncoderOverride.java
index acb303c..534ef4e 100644
--- a/src/net/cantab/hayward/george/OCS/BasicCommandEncoderOverride.java
+++ b/src/net/cantab/hayward/george/OCS/BasicCommandEncoderOverride.java
@@ -270,6 +270,31 @@ public class BasicCommandEncoderOverride extends BasicCommandEncoder {
 
     private static final char PARAM_SEPARATOR = '/';
 
+ /** Re-entrancy guard for legacy piece conversion */
+ private static int converting = 0;
+
+ /** True only for a plain (non-OCS) decorated BasicPiece */
+ private static boolean isLegacyPiece(GamePiece p) {
+ GamePiece g = p;
+ while (g instanceof Decorator) {
+ if (g instanceof OcsCounter) {
+ return false;
+ }
+ g = ((Decorator) g).getInner();
+ }
+ if (!(g instanceof VASSAL.counters.BasicPiece)) {
+ return false;
+ }
+ // Old OCS counters always have artwork; plain markers such as
+ // "Note" (no image) are current pieces and must not be converted.
+ final SequenceEncoder.Decoder st =
+ new SequenceEncoder.Decoder(g.getType(), ';');
+ st.nextToken();
+ st.nextChar('\0');
+ st.nextChar('\0');
+ return st.nextToken("").length() > 0;
+ }
+
     @Override
     public Command decode(String command) {
         if (command.length() == 0) {
@@ -283,8 +308,23 @@ public class BasicCommandEncoderOverride extends BasicCommandEncoder {
             String type = st.nextToken();
             String state = st.nextToken();
             GamePiece p = createPiece(type);
- if ( !(p instanceof OcsCounter) && !(p instanceof StackOverride)) {
- OcsCounter q = Statics.convertOldPiece(p);
+ /*
+ * Only try to convert genuine old (pre 3.0) pieces coming from a
+ * saved game or log. Since Vassal 3.7, palette slots and prototype
+ * definitions are decoded here too (with a null id); converting
+ * those scans every palette slot, which decodes them again and
+ * recursed until StackOverflowError. Never re-enter a conversion.
+ */
+ if (id != null && converting == 0 && isLegacyPiece(p)) {
+ OcsCounter q = null;
+ converting++;
+ try {
+ q = Statics.convertOldPiece(p);
+ } catch (RuntimeException e) {
+ q = null;
+ } finally {
+ converting--;
+ }
                 if ( q != null ) {
                     p = q;
                     SequenceEncoder sb = new SequenceEncoder('\t');
diff --git a/src/net/cantab/hayward/george/OCS/DragHandlerReplace.java b/src/net/cantab/hayward/george/OCS/DragHandlerReplace.java
index 457b0ee..a6745fd 100644
--- a/src/net/cantab/hayward/george/OCS/DragHandlerReplace.java
+++ b/src/net/cantab/hayward/george/OCS/DragHandlerReplace.java
@@ -270,8 +270,12 @@ public class DragHandlerReplace extends AbstractConfigurable {
 
         }
 
- protected void makeDragCursor(double zoom) {
- super.makeDragCursor(zoom);
+ // Vassal 3.7 only calls the (zoom, os_scale) variant; the old
+ // single-argument override was never called, so the air range
+ // read-out on the drag cursor disappeared.
+ @Override
+ protected void makeDragCursor(double zoom, double os_scale) {
+ super.makeDragCursor(zoom, os_scale);
             dragCursor.setText(null);
             if (isFlight) {
                 dragCursor.setVerticalTextPosition(SwingConstants.BOTTOM);
diff --git a/src/net/cantab/hayward/george/OCS/MapOverride.java b/src/net/cantab/hayward/george/OCS/MapOverride.java
index 6e89a2b..a2410b2 100644
--- a/src/net/cantab/hayward/george/OCS/MapOverride.java
+++ b/src/net/cantab/hayward/george/OCS/MapOverride.java
@@ -23,12 +23,18 @@ import VASSAL.counters.DragBuffer;
 import VASSAL.i18n.Resources;
 import VASSAL.preferences.Prefs;
 import VASSAL.tools.AdjustableSpeedScrollPane;
-import VASSAL.tools.ComponentSplitter;
+import VASSAL.tools.swing.SplitPane;
+import java.awt.Container;
 import VASSAL.tools.KeyStrokeSource;
 import VASSAL.tools.SequenceEncoder;
 import java.awt.Component;
 import java.awt.Dimension;
 import java.awt.Graphics;
+import java.awt.Graphics2D;
+import java.awt.RenderingHints;
+import java.awt.Shape;
+import java.awt.geom.AffineTransform;
+import VASSAL.tools.swing.SwingUtils;
 import java.awt.Point;
 import java.awt.Rectangle;
 import java.awt.Window;
@@ -358,6 +364,9 @@ public class MapOverride extends Map {
      */
     @Override
     public void centerAt(Point p, int dx, int dy) {
+ // Vassal 3.7: remember where the user wants to look, so that
+ // zooming keeps this point centred (Zoomer uses getPreferredCenter)
+ preferredCenter = new Point(p);
         if (scroll != null) {
             p = mapToComponent(p);
 
@@ -918,17 +927,39 @@ public class MapOverride extends Map {
             if (GameModule.getGameModule().getGameState().isUpdating()) {
                 return;
             }
- Rectangle r = getVisibleRect();
- g.setColor(map.bgColor);
- g.fillRect(r.x, r.y, r.width, r.height);
+ final Graphics2D g2d = (Graphics2D) g;
+ g2d.addRenderingHints(SwingUtils.FONT_HINTS);
+ g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
+ RenderingHints.VALUE_ANTIALIAS_ON);
+
+ // HiDPI (Vassal 3.5+): Map.paintRegion() draws at zoom * OS scale
+ // itself, so the OS scale transform must be removed first and the
+ // region converted to drawing coordinates, exactly as
+ // VASSAL.build.module.Map.View.paint() does. Without this the map
+ // is scaled twice on Windows/Mac display scaling > 100% and no
+ // longer lines up with mouse clicks and scrolling.
+ final double os_scale =
+ g2d.getDeviceConfiguration().getDefaultTransform().getScaleX();
+ final AffineTransform orig_t = g2d.getTransform();
+ g2d.setTransform(SwingUtils.descaleTransform(orig_t));
+
+ final Rectangle r = getVisibleRect();
+ final Rectangle full = map.componentToDrawing(r, os_scale);
+ g2d.setColor(map.bgColor);
+ g2d.fillRect(full.x, full.y, full.width, full.height);
             if (map.currentRestriction != null) {
                 if (map.currentRestriction.width < r.width)
                     r.width = map.currentRestriction.width;
                 if (map.currentRestriction.height < r.height)
                     r.height = map.currentRestriction.height;
             }
- g.setClip(r);
- map.paintRegion(g, r);
+ final Rectangle dr = map.componentToDrawing(r, os_scale);
+ final Shape oldClip = g2d.getClip();
+ g2d.clipRect(dr.x, dr.y, dr.width, dr.height);
+ map.paintRegion(g2d, dr);
+ g2d.setClip(oldClip);
+
+ g2d.setTransform(orig_t);
         }
 
 
@@ -953,6 +984,11 @@ public class MapOverride extends Map {
 
             layeredPane.setLayout(new InsetLayout(layeredPane, scroll));
             layeredPane.add(scroll, JLayeredPane.DEFAULT_LAYER);
+
+ // As in Vassal 3.7 Map.setUpView(): track the user's preferred
+ // centre whenever the view scrolls. Without this the preferred
+ // centre stayed at (0,0) and every zoom jumped to the top left.
+ scroll.getViewport().addChangeListener(e -> updateCenter());
         }
         return theMap;
     }
@@ -982,7 +1018,10 @@ public class MapOverride extends Map {
         final DragGestureListener dgl = new DragGestureListener() {
 
             public void dragGestureRecognized(DragGestureEvent dge) {
- if (mouseListenerStack.isEmpty() && dragGestureListener != null) {
+ // As in Vassal 3.7 Map.addTo(): ignore right-button / context
+ // drags so they don't start a piece drag.
+ if (mouseListenerStack.isEmpty() && dragGestureListener != null
+ && SwingUtils.isDragTrigger(dge)) {
                     dragGestureListener.dragGestureRecognized(dge);
                 }
             }
@@ -995,22 +1034,19 @@ public class MapOverride extends Map {
         GameModule.getGameModule().getGameState().addGameComponent(this);
         GameModule.getGameModule().getToolBar().add(launch);
         if (shouldDockIntoMainWindow()) {
- final IntConfigurer config =
- new IntConfigurer(MAIN_WINDOW_HEIGHT, null, -1);
- Prefs.getGlobalPrefs().addOption(null, config);
- final ComponentSplitter splitter = new ComponentSplitter();
-
- /*
- final JXLayer<JComponent> jxl = new JXLayer<JComponent>(layeredPane);
- final DebugPainter<JComponent> dp = new DebugPainter<JComponent>();
- jxl.setPainter(dp);
- mainWindowDock = splitter.splitBottom(splitter.getSplitAncestor(GameModule.getGameModule().getControlPanel(), -1), jxl, true);
- */
-
- mainWindowDock = splitter.splitBottom(
- splitter.getSplitAncestor(
- GameModule.getGameModule().getControlPanel(), -1),
- layeredPane, true);
+ // Vassal 3.7 docking (replaces the removed ComponentSplitter /
+ // mainWindowDock API); same as VASSAL.build.module.Map.addTo()
+ final Component controlPanel =
+ GameModule.getGameModule().getControlPanel();
+ final Container cppar = controlPanel.getParent();
+ final int i = SwingUtils.getIndexInParent(controlPanel, cppar);
+
+ splitPane = new SplitPane(SplitPane.VERTICAL_SPLIT,
+ controlPanel, layeredPane);
+ splitPane.setResizeWeight(0.0);
+ splitPane.hideBottom();
+
+ cppar.add(splitPane, i);
 
             GameModule.getGameModule().addKeyStrokeSource(
                     new KeyStrokeSource(theMap, JComponent.WHEN_FOCUSED));

```

,  
Overall, it is not really that many changes, and they mostly look reasonable. However, I will leave it to others more familiar with the base code to figure out if these changes makes sense. If they do, then the changes should probably be back-ported to the code repository.

However, why I said “work” - in quotation marks - is because some of the intended functionality does not work - for example, only part of the map should be shown in specific set-ups, but that doesn’t work.

The [`github:uckelman/tite-ocs`](https://github.com/uckelman/tite-ocs.git) code was developed for a specific series of modules - one which the module in question is not part of - and it is not clear if the [1914: Offensive à outrance](https://vassalengine.org/library/projects/1914_Offensive_%C3%A0_outrance) module really benefits from the custom code. As such, a more viable and maintainable strategy could be to re-do the module from scratch - _without_ the custom code.

Yours,  
_Christian_
