diff --git a/Control/CalypsoExample/Simulation/scripts/faser_simulate.py b/Control/CalypsoExample/Simulation/scripts/faser_simulate.py
new file mode 100755
index 0000000000000000000000000000000000000000..1c8320245e6ecfa61857a6d60d04d1cf8d538d54
--- /dev/null
+++ b/Control/CalypsoExample/Simulation/scripts/faser_simulate.py
@@ -0,0 +1,281 @@
+#!/usr/bin/env python
+"""
+Produce simulated hits from input 4-vectors
+Derived from G4FaserAlgConfigNew
+
+Usage:
+faser_simulate.py filepath outfile
+
+filepath - full path, including url if needed, to the input 4-vector file
+outfile - output filename, parameters will be inferred from this name
+
+Copyright (C) 2002-2021 CERN for the benefit of the ATLAS and FASER collaborations
+"""
+
+if __name__ == '__main__':
+
+    import sys
+    import time
+    a = time.time()
+#
+# Parse command-line options
+#
+    import argparse
+
+    parser = argparse.ArgumentParser(description="Run FASER simulation")
+
+    parser.add_argument("file_path",
+                        help="Fully qualified path of the raw input file")
+
+    parser.add_argument("output",
+                        help="Output file name")
+
+    #parser.add_argument("--run", type=int, default=123456,
+    #                    help="Specify run number to use in simulated data")
+
+    parser.add_argument("--geom", default="TI12MC",
+                        help="Specify geomtery to simulation (default: TI12MC, alt: TestBeamMC)")
+
+
+    parser.add_argument("--xangle", type=float, default=0.0,
+                        help="Specify H crossing angle (in Radians)")
+    parser.add_argument("--yangle", type=float, default=0.0,
+                        help="Specify V crossing angle (in Radians)")
+    parser.add_argument("--xshift", type=float, default=0.0,
+                        help="Specify H shift of events wrt FASER (in mm)")
+    parser.add_argument("--yshift", type=float, default=0.0,
+                        help="Specify V shift of events wrt FASER (in mm)")
+
+    parser.add_argument("-t", "--tag", default="",
+                        help="Specify sim tag (to append to output filename)")
+    parser.add_argument("-s", "--skip", type=int, default=0,
+                        help="Specify number of events to skip (default: none)")
+    parser.add_argument("-n", "--nevents", type=int, default=-1,
+                        help="Specify number of events to process (default: all)")
+    parser.add_argument("-v", "--verbose", action='store_true', 
+                        help="Turn on DEBUG output")
+
+    args = parser.parse_args()
+
+    from pathlib import Path
+
+    filepath = Path(args.file_path)
+
+#
+# Parse input file
+#
+    print(f"Starting digitization of {filepath.name}")
+
+    filestem = filepath.stem
+    if len(args.tag) > 0:
+        if args.tag in filestem:
+            print(f"Not adding tag {args.tag} to {filestem}")
+        else:
+            filestem += f"-{args.tag}"
+
+    if args.output:
+        # Just directly specify the output filename
+        outfile = args.output
+
+        spl = outfile.split('-')
+        if len(spl) < 4:
+            print(f"Can't get run number from {outfile}!")
+            sys.exit(1)
+
+        runnum = int(spl[2])
+        segnum = int(spl[3])
+
+    else:
+        outfile = f"{filestem}-HITS.root"
+        print(f"Output file name not specified")
+        sys.exit(1)
+
+    print(f"Outfile: {outfile}")
+
+#
+# Figure out events to run
+#
+    if args.skip > 0:
+        print(f"Skipping {args.skip} events by command-line option")
+
+    if args.nevents > 0:
+        print(f"Reconstructing {args.nevents} events by command-line option")
+#
+# Set up logging and config behaviour
+#
+    from AthenaCommon.Logging import log
+    from AthenaCommon.Constants import DEBUG, VERBOSE
+    from AthenaCommon.Configurable import Configurable
+    log.setLevel(DEBUG)
+    Configurable.configurableRun3Behavior = 1
+#
+# Import and set config flags
+#
+    from CalypsoConfiguration.AllConfigFlags import ConfigFlags
+    from AthenaConfiguration.Enums import ProductionStep
+    ConfigFlags.Common.ProductionStep = ProductionStep.Simulation
+#
+# All these must be specified to avoid auto-configuration
+#
+    ConfigFlags.Input.RunNumber = [ runnum ] 
+    ConfigFlags.Input.OverrideRunNumber = True
+    ConfigFlags.Input.LumiBlockNumber = [ (segnum+1) ]
+    ConfigFlags.Input.isMC = True
+#
+# Input file name
+# 
+    # Path mangles // in url, so use direct file_path here
+    ConfigFlags.Input.Files = [ args.file_path ]
+#
+# Skip events
+#
+    ConfigFlags.Exec.SkipEvents = args.skip
+    ConfigFlags.Exec.MaxEvents = args.nevents
+#
+# Output file name
+# 
+    ConfigFlags.Output.HITSFileName = outfile
+#
+# Sim ConfigFlags
+#
+    ConfigFlags.Sim.Layout = "FASER"
+    ConfigFlags.Sim.PhysicsList = "FTFP_BERT"
+    ConfigFlags.Sim.ReleaseGeoModel = False
+    ConfigFlags.Sim.IncludeParentsInG4Event = True # Controls whether BeamTruthEvent is written to output HITS file
+
+#
+# Figure out configuration
+#
+    if args.geom == "TI12MC":
+        # 2022 TI12 geometry
+        ConfigFlags.GeoModel.FaserVersion = "FASERNU-03"  # Geometry set-up
+        ConfigFlags.IOVDb.GlobalTag = "OFLCOND-FASER-03"  # Conditions set-up
+        # TI12 detectors
+        detectors = ['Veto', 'VetoNu', 'Preshower', 'FaserSCT', 'Ecal', 
+                     'Trigger', 'Dipole', 'Emulsion', 'Trench']
+
+    elif args.geom == "TestBeamMC":
+        # Define 2021 test beam geometry
+        ConfigFlags.GeoModel.FaserVersion = "FASER-TB00"   # Geometry set-up
+        ConfigFlags.IOVDb.GlobalTag = "OFLCOND-FASER-TB00" # Conditions set-up
+        # Testbeam detectors (trigger layers are actually veto counters)
+        detectors = ['Veto', 'Preshower', 'FaserSCT', 'Ecal']
+
+    else:
+        print(f"Unknown geometry {args.geom}!")
+        sys.exit(1)
+
+
+    #
+    # Units are radians and mm
+    # Probably should only be allowed in TI12, but leave it here for now
+    ConfigFlags.addFlag("Sim.Beam.xangle", args.xangle)  # Potential beam crossing angles
+    ConfigFlags.addFlag("Sim.Beam.yangle", args.yangle)    
+    ConfigFlags.addFlag("Sim.Beam.xshift", args.xshift)  # Potential beam shift
+    ConfigFlags.addFlag("Sim.Beam.yshift", args.yshift)    
+
+    ConfigFlags.addFlag("Input.InitialTimeStamp", 0)     # To avoid autoconfig 
+    ConfigFlags.GeoModel.Align.Dynamic = False
+
+    # import sys
+    # ConfigFlags.fillFromArgs(sys.argv[1:])
+
+    doShiftLOS = (ConfigFlags.Sim.Beam.xangle or ConfigFlags.Sim.Beam.yangle or
+                  ConfigFlags.Sim.Beam.xshift or ConfigFlags.Sim.Beam.yshift)
+#
+# Setup detector flags
+#
+    from CalypsoConfiguration.DetectorConfigFlags import setupDetectorsFromList
+    setupDetectorsFromList(ConfigFlags, detectors, toggle_geometry=True)
+#
+# Finalize flags
+#
+    ConfigFlags.lock()
+#
+# Initialize a new component accumulator
+#
+    from CalypsoConfiguration.MainServicesConfig import MainServicesCfg
+    cfg = MainServicesCfg(ConfigFlags)
+#
+# Check whether a known input file was specified
+#
+    if ConfigFlags.Input.Files[0].endswith(".events") or ConfigFlags.Input.Files[0].endswith(".hepmc"):
+
+        from HEPMCReader.HepMCReaderConfig import HepMCReaderCfg
+
+        if doShiftLOS:
+            cfg.merge(HepMCReaderCfg(ConfigFlags, McEventKey = "BeamTruthEvent_ATLASCoord"))
+        else:
+            cfg.merge(HepMCReaderCfg(ConfigFlags))
+
+        from McEventSelector.McEventSelectorConfig import McEventSelectorCfg
+        cfg.merge(McEventSelectorCfg(ConfigFlags))
+#
+# Else, set up to read it as a pool.root file
+#
+    else:
+        from AthenaPoolCnvSvc.PoolReadConfig import PoolReadCfg
+        cfg.merge(PoolReadCfg(ConfigFlags))
+
+        if doShiftLOS:
+            from SGComps.AddressRemappingConfig import InputOverwriteCfg
+            # Rename old truth collection to add ATLAS coord to can still use BeamTruthEvent for the one in FASER Coords
+            cfg.merge(InputOverwriteCfg("McEventCollection", "BeamTruthEvent", "McEventCollection", "BeamTruthEvent_ATLASCoord"))
+
+#
+# Output file
+#
+    from AthenaPoolCnvSvc.PoolWriteConfig import PoolWriteCfg
+    cfg.merge(PoolWriteCfg(ConfigFlags))
+
+#
+# Shift LOS
+#
+    if doShiftLOS:
+
+        import McParticleEvent.Pythonizations
+        from GeneratorUtils.ShiftLOSConfig import ShiftLOSCfg
+        cfg.merge(ShiftLOSCfg(ConfigFlags, 
+                              xcross = ConfigFlags.Sim.Beam.xangle, 
+                              ycross = ConfigFlags.Sim.Beam.yangle,
+                              xshift = ConfigFlags.Sim.Beam.xshift,
+                              yshift = ConfigFlags.Sim.Beam.yshift))
+
+
+#
+# Add the G4FaserAlg
+#
+    from G4FaserAlg.G4FaserAlgConfigNew import G4FaserAlgCfg
+    cfg.merge(G4FaserAlgCfg(ConfigFlags))
+#
+# Dump config
+#
+    from AthenaConfiguration.ComponentFactory import CompFactory
+    cfg.addEventAlgo(CompFactory.JobOptsDumperAlg(FileName="G4FaserTestConfig.txt"))
+    cfg.getService("StoreGateSvc").Dump = True
+    cfg.getService("ConditionStore").Dump = True
+    cfg.printConfig(withDetails=True, summariseProps = False)  # gags on ParticleGun if summariseProps = True?
+
+    ConfigFlags.dump()
+    #f = open("test.pkl","wb")
+    #cfg.store(f)
+    #f.close()
+#
+# Execute and finish
+#
+
+    # This fails with ShiftLOSCfg...
+    #if args.verbose:
+    #    cfg.foreach_component("*").OutputLevel = "DEBUG" 
+    #else:
+    #    cfg.foreach_component("*").OutputLevel = "INFO"  
+
+    sc = cfg.run(maxEvents=args.nevents)
+
+    b = time.time()
+    log.info("Finish execution in " + str(b-a) + " seconds")
+#
+# Success should be 0
+#
+    sys.exit(int(sc.isFailure()))
+
diff --git a/Control/CalypsoExample/Simulation/scripts/submit_faser_simulate.sh b/Control/CalypsoExample/Simulation/scripts/submit_faser_simulate.sh
new file mode 100755
index 0000000000000000000000000000000000000000..83a2470f1e93bd9703e0e44c5abca3a9aeebe7c1
--- /dev/null
+++ b/Control/CalypsoExample/Simulation/scripts/submit_faser_simulate.sh
@@ -0,0 +1,221 @@
+#!/bin/bash
+# Used with a condor file to submit to vanilla universe
+#
+# Usage:
+# submit_faserMDC_simluate.sh [--shift] input_file output_file [release_directory] [working_directory] [skip] [nevts]
+#
+# Options:
+# --shift - apply crossing angle (and FASER shift)
+#   --out - specify output location (in EOS) to copy output HITS file
+#   --log - specify output location (in EOS) for log file
+# 
+# input_file - full file name (with path)
+# output_file - full output file name
+# release_directory - optional path to release install directory (default pwd)
+# working_directory - optional path to output directory location (default pwd)
+# skip - events in input file to skip
+# nevts = events in input file to process
+#
+# The release directory must already be set up 
+# (so an unqualified asetup can set up the release properly)
+#
+# Script will use git describe to find the release tag.  
+# If this matches gen/g???? or sim/s???? it will be passed to the job
+#
+#----------------------------------------
+# Keep track of time
+SECONDS=0
+#
+# Parse command-line options
+while [ -n "$1" ]
+do 
+  case "$1" in
+      -s | --shift) 
+	  echo "Applying crossing-angle shift" 
+	  xangle=1 
+	  shift;;  # This 'eats' the argument
+
+      -l | --log)
+	  logdest="$2";
+	  shift;
+	  shift;; # Must eat 2 options here
+
+      -o | --out)
+	  outdest="$2";
+	  shift;
+	  shift;;
+
+      --) # End of options
+	  shift; # Eat this
+	  break;; # And stop parsing
+
+      -*) 
+	  echo "Unknown option $1"
+	  shift;;
+
+      *) break;;  # Not an option, don't shift
+  esac
+done
+
+#
+# Parse command-line arguments
+infile=${1}
+outfile=${2}
+release_directory=${3}
+working_directory=${4}
+skip_events=${5}
+nevts=${6}
+#
+# Set defaults if arguments aren't provided
+if [ -z "$infile" ]
+then
+  echo "No input file specified!"
+  echo "Usage: submit_faser_simulate.sh input_file output_file [release dir] [output dir]"
+  exit 1
+fi
+#
+if [ -z "$outfile" ]
+then
+  outfile="FaserMC-Test-123456-00000-HITS.root"
+  echo "No output file specified, using $outfile !"
+fi
+#
+if [ -z "$release_directory" ]
+then
+  release_directory=`pwd`
+fi
+#
+if [ -z "$working_directory" ]
+then
+  working_directory=`pwd`
+fi
+#
+if [ -z "$skip_events" ]
+then
+  skip_events=0
+fi
+#
+if [ -z "$nevts" ]
+then
+  nevts=-1
+fi
+#
+starting_directory=`pwd`
+#
+# Now extract the file information
+# Here we do this on the output file, as the input files can be non-standard
+#
+# First, get the filename
+outfilename=$(basename "$outfile")
+# 
+# Now split based on '.' to get stem
+defaultIFS=$IFS
+IFS='.'
+read file_stem ext <<< "$outfilename"
+#
+# Try to find the run number
+IFS='-'
+# Read the split words into an array based on delimeter
+read faser short run_number seg <<< "$file_stem"
+#
+# Set the IFS delimeter back or else echo doesn't work...
+IFS=$defaultIFS
+#
+# Check if we found a number, use full input file name if not
+output_directory="$working_directory/${run_number}"
+re='^[0-9]+$'
+if ! [[ $run_number =~ $re ]] ; then
+    # Not a number...
+    output_directory="$working_directory/${file_stem}"
+fi
+#
+# Make output directory if needed
+mkdir -p "$output_directory"
+#
+# This magic redirects everything in this script to our log file
+logfile=${file_stem}.sim.log
+exec >& "$output_directory/$logfile"
+echo `date` - $HOSTNAME
+echo "Input File: $infile"
+echo "Output File: $outfilename"
+echo "Release: $release_directory"
+echo "Output: $output_directory"
+echo "Starting: $starting_directory"
+echo "Skip: $skip_events"
+echo "Nevts: $nevts"
+#
+# Set up the release (do this automatically)?
+export ATLAS_LOCAL_ROOT_BASE=/cvmfs/atlas.cern.ch/repo/ATLASLocalRootBase
+source ${ATLAS_LOCAL_ROOT_BASE}/user/atlasLocalSetup.sh 
+#
+# Try automatic
+# Always go back to the starting directory in case paths are relative
+cd "$starting_directory"
+cd "$release_directory"
+# This doesn't seem to work, as we need the --input argument
+#asetup 
+#source build/x8*/setup.sh
+#
+# Do this by hand
+asetup --input=calypso/asetup.faser Athena,22.0.49
+source build/x86*/setup.sh
+#
+# Move to the run directory
+cd "$starting_directory"
+cd "$output_directory"
+#
+# Remove any previous directory if it exists
+#if [[ -e "$file_stem" ]]; then
+#    echo "Remove previous directory $file_stem"
+#    rm -rf "$file_stem"
+#fi
+#
+# Make run directory
+if [[ -e "${file_stem}" ]]; then
+    echo "Directory ${file_stem} already exists"
+else
+    mkdir "${file_stem}"
+fi
+cd "${file_stem}"
+#
+# Run job
+#if [[ -z "$tag" ]]; then
+#fi
+if [[ -z "$xangle" ]]; then
+    faser_simulate.py  --skip "$skip_events" -n "$nevts" "$infile" "$outfile"
+else
+    faser_simulate.py  --yangle -0.000150 --yshift 12.0 --skip "$skip_events" -n "$nevts" "$infile" "$outfile"
+fi
+#
+# Print out ending time
+date
+echo "Job finished after $SECONDS seconds"
+#
+# Copy output to EOS if desired
+export EOS_MGM_URL=root://eospublic.cern.ch
+#
+if ! [ -z "$outdest" ]
+then
+    ls -l
+    echo "copy *-HITS.root to $outdest"
+    eos mkdir -p $outdest
+    eos cp *-HITS.root ${outdest}/ || true
+fi
+#
+# Also copy log file
+if ! [ -z "$logdest" ]
+then
+    cd ..
+    ls -l
+    echo "copy $logfile to $logdest"
+    eos mkdir -p $logdest
+    eos cp $logfile $logdest/$logfile
+elif ! [ -z "$outdest" ]
+then 
+    cd ..
+    ls -l
+    echo "copy $logfile to $outdest"
+    eos mkdir -p $outdest
+    eos cp $logfile $outdest/$logfile
+fi
+